@carbon/react
Version:
React components for the Carbon Design System
152 lines (148 loc) • 5.27 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
import PropTypes from 'prop-types';
import React, { useContext, useState, useRef, Children, useEffect, isValidElement, cloneElement } from 'react';
import cx from 'classnames';
import { deprecate } from '../../prop-types/deprecate.js';
import { LayoutConstraint } from '../Layout/index.js';
import { composeEventHandlers } from '../../tools/events.js';
import { ArrowRight, ArrowLeft } from '../../internal/keyboard/keys.js';
import { matches } from '../../internal/keyboard/match.js';
import { getNextIndex } from '../../internal/keyboard/navigation.js';
import { PrefixContext } from '../../internal/usePrefix.js';
import { noopFn } from '../../internal/noopFn.js';
import '../Switch/Switch.js';
import IconSwitch from '../Switch/IconSwitch.js';
const ContentSwitcher = ({
children,
className,
light,
lowContrast,
selectedIndex: selectedIndexProp = 0,
selectionMode = 'automatic',
size,
onChange = noopFn,
...other
}) => {
const prefix = useContext(PrefixContext);
const [selectedIndex, setSelectedIndex] = useState(selectedIndexProp);
const prevSelectedIndexRef = useRef(selectedIndexProp);
const switchRefs = useRef([]);
const childrenArray = Children.toArray(children);
useEffect(() => {
if (prevSelectedIndexRef.current !== selectedIndexProp) {
setSelectedIndex(selectedIndexProp);
prevSelectedIndexRef.current = selectedIndexProp;
}
}, [selectedIndexProp]);
const handleItemRef = index => ref => {
if (ref) {
switchRefs.current[index] = ref;
}
};
const focusSwitch = index => {
const ref = switchRefs.current[index];
if (ref) {
ref.focus();
}
};
const isKeyboardEvent = event => event && typeof event === 'object' && 'key' in event;
const handleChildChange = event => {
if (typeof event.index === 'undefined') return;
const {
index
} = event;
if (isKeyboardEvent(event) && matches(event, [ArrowRight, ArrowLeft])) {
const nextIndex = getNextIndex(event.key, index, childrenArray.length);
if (typeof nextIndex !== 'number') return;
focusSwitch(nextIndex);
if (selectionMode !== 'manual') {
const child = childrenArray[nextIndex];
setSelectedIndex(nextIndex);
if (/*#__PURE__*/isValidElement(child)) {
onChange({
...event,
index: nextIndex,
name: child.props.name,
text: child.props.text
});
}
}
} else if (selectedIndex !== index) {
setSelectedIndex(index);
focusSwitch(index);
onChange(event);
}
};
const isIconOnly = Children.map(children, child => {
return /*#__PURE__*/isValidElement(child) ? child.type === IconSwitch : null;
})?.every(val => val === true);
const classes = cx(`${prefix}--content-switcher`, className, {
[`${prefix}--content-switcher--light`]: light,
[`${prefix}--content-switcher--${size}`]: size,
// TODO: V12 - Remove this class
[`${prefix}--layout--size-${size}`]: size,
[`${prefix}--content-switcher--icon-only`]: isIconOnly,
[`${prefix}--content-switcher--low-contrast`]: lowContrast
});
return /*#__PURE__*/React.createElement(LayoutConstraint, _extends({
size: {
default: 'md',
min: 'sm',
max: 'lg'
}
}, other, {
className: classes,
role: "tablist",
onChange: undefined
}), children && Children.map(children, (child, index) => /*#__PURE__*/cloneElement(child, {
index,
onClick: composeEventHandlers([handleChildChange, child.props.onClick]),
onKeyDown: composeEventHandlers([handleChildChange, child.props.onKeyDown]),
selected: index === selectedIndex,
ref: handleItemRef(index),
size
})));
};
ContentSwitcher.displayName = 'ContentSwitcher';
ContentSwitcher.propTypes = {
/**
* Pass in Switch components to be rendered in the ContentSwitcher
*/
children: PropTypes.node,
/**
* Specify an optional className to be added to the container node
*/
className: PropTypes.string,
/**
* `true` to use the light variant.
*/
light: deprecate(PropTypes.bool, 'The `light` prop for `ContentSwitcher` is no longer needed and has ' + 'been deprecated. It will be removed in the next major release.'),
/**
* `true` to use the low contrast version.
*/
lowContrast: PropTypes.bool,
/**
* Specify an `onChange` handler that is called whenever the ContentSwitcher
* changes which item is selected
*/
onChange: PropTypes.func.isRequired,
/**
* Specify a selected index for the initially selected content
*/
selectedIndex: PropTypes.number,
/**
* Choose whether or not to automatically change selection on focus when left/right arrow pressed. Defaults to 'automatic'
*/
selectionMode: PropTypes.oneOf(['automatic', 'manual']),
/**
* Specify the size of the Content Switcher. Currently supports either `sm`, `md` (default) or `lg` as an option.
*/
size: PropTypes.oneOf(['sm', 'md', 'lg'])
};
export { ContentSwitcher };