diff --git a/packages/console/src/mdx-components/Tabs/index.module.scss b/packages/console/src/mdx-components/Tabs/index.module.scss new file mode 100644 index 000000000..9b61947a5 --- /dev/null +++ b/packages/console/src/mdx-components/Tabs/index.module.scss @@ -0,0 +1,32 @@ +@use '@/scss/underscore' as _; + +.container { + width: 100%; + + ul { + border-bottom: 1px solid var(--color-divider); + display: flex; + margin: _.unit(1) 0; + padding: 0; + + li { + list-style: none; + margin-right: _.unit(6); + padding-bottom: _.unit(1); + font: var(--font-subhead-2); + color: var(--color-caption); + cursor: pointer; + } + + li[aria-selected='true'] { + color: var(--color-primary); + border-bottom: 2px solid var(--color-primary); + margin-bottom: -1px; + outline: none; + } + } + + .hidden { + display: none; + } +} diff --git a/packages/console/src/mdx-components/Tabs/index.tsx b/packages/console/src/mdx-components/Tabs/index.tsx index 1f78b967e..d01abdf2c 100644 --- a/packages/console/src/mdx-components/Tabs/index.tsx +++ b/packages/console/src/mdx-components/Tabs/index.tsx @@ -6,9 +6,10 @@ */ import { Nullable } from '@silverhand/essentials'; -import React, { useState, isValidElement, type ReactElement, cloneElement } from 'react'; +import React, { useState, isValidElement, type ReactElement, cloneElement, useRef } from 'react'; import type { Props as TabItemProps } from '../TabItem'; +import * as styles from './index.module.scss'; type Props = { className?: string; @@ -35,20 +36,11 @@ const Tabs = ({ className, children }: Props): JSX.Element => { label, })); - const [selectedValue, setSelectedValue] = useState(); - const tabReferences: Array> = []; - - const handleTabChange = ( - event: React.FocusEvent | React.MouseEvent - ) => { - const newTab = event.currentTarget; - const newTabIndex = tabReferences.indexOf(newTab); - const newTabValue = values[newTabIndex]?.value; - - if (newTabValue !== selectedValue) { - setSelectedValue(newTabValue); - } - }; + const [selectedIndex, setSelectedIndex] = useState(0); + const tabReferences = useRef>>( + // eslint-disable-next-line @typescript-eslint/ban-types + Array.from({ length }).fill(null) + ); const handleKeydown = (event: React.KeyboardEvent) => { // eslint-disable-next-line @silverhand/fp/no-let @@ -56,17 +48,19 @@ const Tabs = ({ className, children }: Props): JSX.Element => { switch (event.key) { case 'ArrowRight': { - const nextTab = tabReferences.indexOf(event.currentTarget) + 1; + const nextTab = tabReferences.current.indexOf(event.currentTarget) + 1; // eslint-disable-next-line @silverhand/fp/no-mutation - focusElement = tabReferences[nextTab] ?? tabReferences[0] ?? null; + focusElement = tabReferences.current[nextTab] ?? tabReferences.current[0] ?? null; break; } case 'ArrowLeft': { - const previousTab = tabReferences.indexOf(event.currentTarget) - 1; + const previousTab = tabReferences.current.indexOf(event.currentTarget) - 1; // eslint-disable-next-line @silverhand/fp/no-mutation focusElement = - tabReferences[previousTab] ?? tabReferences[tabReferences.length - 1] ?? null; + tabReferences.current[previousTab] ?? + tabReferences.current[tabReferences.current.length - 1] ?? + null; break; } default: @@ -77,27 +71,35 @@ const Tabs = ({ className, children }: Props): JSX.Element => { }; return ( -
+
    - {values.map(({ value, label }) => ( + {values.map(({ value, label }, index) => (
  • tabReferences.concat(tabControl)} + ref={(element) => { + // eslint-disable-next-line @silverhand/fp/no-mutation + tabReferences.current[index] = element; + }} role="tab" - tabIndex={selectedValue === value ? 0 : -1} - aria-selected={selectedValue === value} + tabIndex={selectedIndex === index ? 0 : -1} + aria-selected={selectedIndex === index} onKeyDown={handleKeydown} - onFocus={handleTabChange} - onClick={handleTabChange} + onFocus={() => { + setSelectedIndex(index); + }} + onClick={() => { + setSelectedIndex(index); + }} > {label ?? value}
  • ))}
- {verifiedChildren.map((tabItem) => + {verifiedChildren.map((tabItem, index) => cloneElement(tabItem, { key: tabItem.props.value, + className: index === selectedIndex ? undefined : styles.hidden, }) )}