@carbon/react
Version:
React components for the Carbon Design System
118 lines (116 loc) • 4.46 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2026
*
* 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 { PrefixContext } from "../../internal/usePrefix.js";
import { ArrowLeft, ArrowRight, Enter, Space } from "../../internal/keyboard/keys.js";
import { matches } from "../../internal/keyboard/match.js";
import { getNextIndex } from "../../internal/keyboard/navigation.js";
import { deprecate } from "../../prop-types/deprecate.js";
import { isComponentElement } from "../../internal/utils.js";
import { composeEventHandlers } from "../../tools/events.js";
import { LayoutConstraint } from "../Layout/index.js";
import IconSwitch from "../Switch/IconSwitch.js";
import classNames from "classnames";
import { Children, cloneElement, isValidElement, useContext, useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { jsx } from "react/jsx-runtime";
//#region src/components/ContentSwitcher/ContentSwitcher.tsx
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const ContentSwitcher = ({ children, className, light, lowContrast, selectedIndex: selectedIndexProp = 0, selectionMode = "automatic", size, onChange, ...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 (isValidElement(child)) onChange({
...event,
index: nextIndex,
name: child.props.name,
text: child.props.text
});
}
} else if (selectedIndex !== index && (isKeyboardEvent(event) ? matches(event, [Enter, Space]) : true)) {
setSelectedIndex(index);
focusSwitch(index);
onChange(event);
}
};
const isIconOnly = Children.map(children, (child) => {
return isComponentElement(child, IconSwitch);
})?.every((val) => val === true);
const classes = classNames(`${prefix}--content-switcher`, className, {
[`${prefix}--content-switcher--light`]: light,
[`${prefix}--content-switcher--${size}`]: size,
[`${prefix}--layout--size-${size}`]: size,
[`${prefix}--content-switcher--icon-only`]: isIconOnly,
[`${prefix}--content-switcher--low-contrast`]: lowContrast
});
return /* @__PURE__ */ jsx(LayoutConstraint, {
size: {
default: "md",
min: "sm",
max: "lg"
},
...other,
className: classes,
role: "tablist",
onChange: void 0,
children: children && Children.map(children, (child, index) => 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 = {
children: PropTypes.node,
className: PropTypes.string,
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."),
lowContrast: PropTypes.bool,
onChange: PropTypes.func.isRequired,
selectedIndex: PropTypes.number,
selectionMode: PropTypes.oneOf(["automatic", "manual"]),
size: PropTypes.oneOf([
"sm",
"md",
"lg"
])
};
//#endregion
export { ContentSwitcher };