in src/segmented-control/segmented-control.tsx [86:236]
export function SegmentedControl({
activeKey = '0',
disabled = false,
children,
fill = FILL.intrinsic,
activateOnFocus = true,
onChange,
overrides = {},
// @ts-expect-error todo(ts-migration) TS2322 Type 'null' is not assignable to type 'string'.
uid: customUid = null,
width,
height,
}: SegmentedControlProps) {
// Create unique id prefix for this segments component
const generatedUid = useUID();
const uid = customUid || generatedUid;
// Unpack overrides
const { Root: RootOverrides, Active: ActiveOverrides } = overrides;
const [Root, RootProps] = getOverrides(RootOverrides, StyledRoot);
const [Active, ActiveProps] = getOverrides(ActiveOverrides, StyledActive);
const [SegmentList, SegmentListProps] = getOverrides(overrides.SegmentList, StyledSegmentList);
// Count key updates
// We disable a few things until after first mount:
// - the highlight animation, avoiding an initial slide-in
// - smooth scrolling active segment into view
const [keyUpdated, setKeyUpdated] = React.useState(0);
React.useEffect(() => {
setKeyUpdated(keyUpdated + 1);
}, [activeKey]);
// Positioning the highlight.
const activeSegmentRef = React.useRef<HTMLElement>();
const [highlightLayout, setHighlightLayout] = React.useState({
length: 0,
distance: 0,
});
// Create a shared, memoized callback for segments to call on resize.
const updateHighlight = React.useCallback(() => {
if (activeSegmentRef.current) {
setHighlightLayout(getLayoutParams(activeSegmentRef.current));
}
}, [activeSegmentRef.current]);
// Update highlight on key
React.useEffect(updateHighlight, [activeSegmentRef.current]);
// Scroll active segment into view when the parent has scrollbar on mount and
// on key change (smooth scroll). Note, if the active key changes while
// the segment is not in view, the page will scroll it into view.
// TODO: replace with custom scrolling logic.
React.useEffect(() => {
// Flow needs this condition pulled out.
if (activeSegmentRef.current) {
if (
// @ts-expect-error todo(flow->ts) maybe parentElement?
activeSegmentRef.current.parentNode.scrollWidth >
// @ts-expect-error todo(flow->ts) maybe parentElement?
activeSegmentRef.current.parentNode.clientWidth
) {
if (keyUpdated > 1) {
activeSegmentRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest',
});
} else {
scrollParentToCentreTarget(activeSegmentRef.current);
}
}
}
}, [activeSegmentRef.current]);
// Collect shared styling props
const sharedStylingProps = {
$fill: fill,
};
// Helper for parsing directional keys
// TODO(WPT-6473): move to universal keycode aliases
const [, theme] = useStyletron();
const parseKeyDown = React.useCallback(
(event: { keyCode: number }) => {
if (isRTL(theme.direction)) {
switch (event.keyCode) {
case 39:
return KEYBOARD_ACTION.previous;
case 37:
return KEYBOARD_ACTION.next;
default:
return null;
}
} else {
switch (event.keyCode) {
case 37:
return KEYBOARD_ACTION.previous;
case 39:
return KEYBOARD_ACTION.next;
default:
return null;
}
}
},
[theme.direction]
);
return (
<Root {...sharedStylingProps} {...RootProps} $width={width} $height={height}>
<SegmentList
data-baseweb="segmented-list"
role="listbox"
aria-label="segmented control"
{...SegmentListProps}
>
{React.Children.map(children, (child: React.ReactElement, index) => {
if (!child) return;
return (
<InternalSegment
childKey={child.key}
childIndex={index}
activeKey={activeKey}
activeSegmentRef={activeSegmentRef}
updateHighlight={updateHighlight}
parseKeyDown={parseKeyDown}
activateOnFocus={activateOnFocus}
uid={uid}
disabled={disabled}
sharedStylingProps={sharedStylingProps}
onChange={onChange}
setKeyUpdated={setKeyUpdated}
{...child.props}
/>
);
})}
<Active
data-baseweb="segment-highlight"
$length={highlightLayout.length}
$distance={highlightLayout.distance}
// This avoids the segment sliding in from the side on mount
$animate={keyUpdated > 1}
aria-hidden="true"
role="presentation"
{...sharedStylingProps}
{...ActiveProps}
/>
</SegmentList>
</Root>
);
}