in src/tabs-motion/tabs.tsx [323:506]
function InternalTab({
// @ts-ignore
childKey,
// @ts-ignore
childIndex,
// @ts-ignore
activeKey,
// @ts-ignore
orientation,
// @ts-ignore
activeTabRef,
// @ts-ignore
updateHighlight,
// @ts-ignore
parseKeyDown,
// @ts-ignore
activateOnFocus,
// @ts-ignore
uid,
// @ts-ignore
disabled,
// @ts-ignore
sharedStylingProps,
// @ts-ignore
onChange,
...props
}) {
const key = childKey || String(childIndex);
const isActive = key == activeKey;
const { artwork: Artwork, overrides = {}, tabRef, onClick, title, ...restProps } = props;
// A way to share our internal activeTabRef via the "tabRef" prop.
const ref = React.useRef();
React.useImperativeHandle(tabRef, () => {
return isActive ? activeTabRef.current : ref.current;
});
// Track tab dimensions in a ref after each render
// This is used to compare params when the resize observer fires
const tabLayoutParams = React.useRef({ length: 0, distance: 0 });
React.useEffect(() => {
tabLayoutParams.current = getLayoutParams(
isActive ? activeTabRef.current : ref.current,
orientation
);
});
// We need to potentially update the active tab highlight when the width or
// placement changes for a tab so we listen for resize updates in each tab.
React.useEffect(() => {
if (window.ResizeObserver) {
const observer = new window.ResizeObserver((entries) => {
if (entries[0] && entries[0].target) {
const tabLayoutParamsAfterResize = getLayoutParams(entries[0].target, orientation);
if (
tabLayoutParamsAfterResize.length !== tabLayoutParams.current.length ||
tabLayoutParamsAfterResize.distance !== tabLayoutParams.current.distance
) {
updateHighlight();
}
}
});
observer.observe(isActive ? activeTabRef.current : ref.current);
return () => {
observer.disconnect();
};
}
}, [activeKey, orientation]);
React.useEffect(updateHighlight, [title]);
// Collect overrides
const { Tab: TabOverrides, ArtworkContainer: ArtworkContainerOverrides } = overrides;
const [Tab, TabProps] = getOverrides(TabOverrides, StyledTab);
const [ArtworkContainer, ArtworkContainerProps] = getOverrides(
ArtworkContainerOverrides,
StyledArtworkContainer
);
// Keyboard focus styling
const [focusVisible, setFocusVisible] = React.useState(false);
const handleFocus = React.useCallback((event: SyntheticEvent) => {
if (isFocusVisible(event)) {
setFocusVisible(true);
}
}, []);
const handleBlur = React.useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(event: SyntheticEvent) => {
if (focusVisible !== false) {
setFocusVisible(false);
}
},
[focusVisible]
);
// Keyboard focus management
// @ts-expect-error todo(flow->ts): deps are required
const handleKeyDown = React.useCallback((event) => {
// WAI-ARIA 1.1
// https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel
// We use directional keys to iterate focus through Tabs.
// Find all tabs eligible for focus
const availableTabs = [...event.target.parentNode.childNodes].filter(
(node) => !node.disabled && node.getAttribute('role') === 'tab'
);
// Exit early if there are no other tabs available
if (availableTabs.length === 1) return;
// Find tab to focus, looping to start/end of list if necessary
const currentTabIndex = availableTabs.indexOf(event.target);
const action = parseKeyDown(event);
if (action) {
let nextTab: HTMLButtonElement | undefined | null;
if (action === KEYBOARD_ACTION.previous) {
if (availableTabs[currentTabIndex - 1]) {
nextTab = availableTabs[currentTabIndex - 1];
} else {
nextTab = availableTabs[availableTabs.length - 1];
}
} else if (action === KEYBOARD_ACTION.next) {
if (availableTabs[currentTabIndex + 1]) {
nextTab = availableTabs[currentTabIndex + 1];
} else {
nextTab = availableTabs[0];
}
}
if (nextTab) {
// Focus the tab
nextTab.focus();
// Optionally activate the tab
if (activateOnFocus) {
nextTab.click();
}
}
// Prevent default page scroll when in vertical orientation
if (isVertical(orientation)) {
event.preventDefault();
}
}
});
return (
<Tab
data-baseweb="tab"
key={key}
id={getTabId(uid, key)}
role="tab"
onKeyDown={handleKeyDown}
aria-selected={isActive}
aria-controls={getTabPanelId(uid, key)}
tabIndex={isActive ? '0' : '-1'}
ref={isActive ? activeTabRef : ref}
disabled={!isActive && disabled}
type="button" // so it doesn't trigger a submit when used inside forms
$focusVisible={focusVisible}
$isActive={isActive}
{...sharedStylingProps}
{...restProps}
{...TabProps}
// @ts-ignore
onClick={(event) => {
if (typeof onChange === 'function') onChange({ activeKey: key });
if (typeof onClick === 'function') onClick(event);
}}
onFocus={forkFocus({ ...restProps, ...TabProps }, handleFocus)}
onBlur={forkBlur({ ...restProps, ...TabProps }, handleBlur)}
>
{Artwork ? (
<ArtworkContainer
data-baseweb="artwork-container"
{...sharedStylingProps}
{...ArtworkContainerProps}
>
<Artwork size={20} color="contentPrimary" />
</ArtworkContainer>
) : null}
{title ? title : key}
</Tab>
);
}