Migrate Tabs component from useStyles to useDefinition

Split monolithic TabsDefinition into 5 per-sub-component definitions
(Tabs, TabList, Tab, TabPanel, TabsIndicators). Each sub-component now
uses useDefinition with its own OwnProps type. Removed clsx and direct
CSS module imports from component files.

Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
Johan Persson
2026-02-27 10:54:08 +01:00
parent 4742c3f2e0
commit 66dc56c7f9
6 changed files with 220 additions and 93 deletions
+45 -13
View File
@@ -2355,40 +2355,72 @@ export interface TableSelection {
export const TabList: (props: TabListProps) => JSX_2.Element;
// @public
export interface TabListProps extends Omit<TabListProps_2<object>, 'items'> {}
export type TabListOwnProps = {
className?: string;
children?: TabListProps_2<object>['children'];
};
// @public
export interface TabListProps
extends TabListOwnProps,
Omit<TabListProps_2<object>, 'items' | keyof TabListOwnProps> {}
// @public
export type TabMatchStrategy = 'exact' | 'prefix';
// @public
export type TabOwnProps = {
className?: string;
matchStrategy?: TabMatchStrategy;
href?: TabProps_2['href'];
id?: TabProps_2['id'];
};
// @public
export const TabPanel: (props: TabPanelProps) => JSX_2.Element;
// @public
export interface TabPanelProps extends TabPanelProps_2 {}
export type TabPanelOwnProps = {
className?: string;
};
// @public
export interface TabProps extends TabProps_2 {
matchStrategy?: 'exact' | 'prefix';
}
export interface TabPanelProps
extends TabPanelOwnProps,
Omit<TabPanelProps_2, keyof TabPanelOwnProps> {}
// @public
export interface TabProps
extends TabOwnProps,
Omit<TabProps_2, keyof TabOwnProps> {}
// @public
export const Tabs: (props: TabsProps) => JSX_2.Element | null;
// @public
export const TabsDefinition: {
readonly styles: {
readonly [key: string]: string;
};
readonly classNames: {
readonly tabs: 'bui-Tabs';
readonly tabList: 'bui-TabList';
readonly tabListWrapper: 'bui-TabListWrapper';
readonly tab: 'bui-Tab';
readonly tabActive: 'bui-TabActive';
readonly tabHovered: 'bui-TabHovered';
readonly panel: 'bui-TabPanel';
readonly root: 'bui-Tabs';
};
readonly propDefs: {
readonly className: {};
readonly children: {};
};
};
// @public
export interface TabsProps extends TabsProps_2 {}
export type TabsOwnProps = {
className?: string;
children?: TabsProps_2['children'];
};
// @public
export interface TabsProps
extends TabsOwnProps,
Omit<TabsProps_2, keyof TabsOwnProps> {}
// @public
export const Tag: ForwardRefExoticComponent<
+23 -41
View File
@@ -43,14 +43,17 @@ import {
TabPanel as AriaTabPanel,
TabProps as AriaTabProps,
} from 'react-aria-components';
import { useStyles } from '../../hooks/useStyles';
import { TabsDefinition } from './definition';
import { useDefinition } from '../../hooks/useDefinition';
import {
TabsDefinition,
TabListDefinition,
TabDefinition,
TabPanelDefinition,
} from './definition';
import {
isInternalLink,
createRoutingRegistration,
} from '../InternalLinkProvider';
import styles from './Tabs.module.css';
import clsx from 'clsx';
const { RoutingProvider, useRoutingRegistrationEffect } =
createRoutingRegistration();
@@ -105,8 +108,8 @@ const isTabActive = (
* @public
*/
export const Tabs = (props: TabsProps) => {
const { classNames, cleanedProps } = useStyles(TabsDefinition, props);
const { className, children, ...rest } = cleanedProps;
const { ownProps, restProps } = useDefinition(TabsDefinition, props);
const { classes, children } = ownProps;
const tabsRef = useRef<HTMLDivElement>(null);
const tabRefs = useRef<Map<string, HTMLDivElement>>(new Map());
const [hoveredKey, setHoveredKey] = useState<string | null>(null);
@@ -210,15 +213,11 @@ export const Tabs = (props: TabsProps) => {
<TabsContext.Provider value={tabsContextValue}>
<TabSelectionContext.Provider value={selectionContextValue}>
<AriaTabs
className={clsx(
classNames.tabs,
styles[classNames.tabs],
className,
)}
className={classes.root}
keyboardActivation="manual"
selectedKey={selectedTabId}
ref={tabsRef}
{...rest}
{...restProps}
>
{children as ReactNode}
</AriaTabs>
@@ -234,8 +233,8 @@ export const Tabs = (props: TabsProps) => {
* @public
*/
export const TabList = (props: TabListProps) => {
const { classNames, cleanedProps } = useStyles(TabsDefinition, props);
const { className, children, ...rest } = cleanedProps;
const { ownProps, restProps } = useDefinition(TabListDefinition, props);
const { classes, children } = ownProps;
const { setHoveredKey, tabRefs, tabsRef, hoveredKey, prevHoveredKey } =
useTabsContext();
@@ -255,17 +254,11 @@ export const TabList = (props: TabListProps) => {
});
return (
<div
className={clsx(
classNames.tabListWrapper,
styles[classNames.tabListWrapper],
className,
)}
>
<div className={classes.root}>
<AriaTabList
className={clsx(classNames.tabList, styles[classNames.tabList])}
className={classes.tabList}
aria-label="Toolbar tabs"
{...rest}
{...restProps}
>
{enhancedChildren}
</AriaTabList>
@@ -330,9 +323,8 @@ function RoutedTabEffects({
* @public
*/
export const Tab = (props: TabProps) => {
const { classNames, cleanedProps } = useStyles(TabsDefinition, props);
const { className, href, children, id, matchStrategy, ...rest } =
cleanedProps;
const { ownProps, restProps } = useDefinition(TabDefinition, props);
const { classes, matchStrategy, href, id } = ownProps;
const { setTabRef } = useTabsContext();
return (
@@ -346,13 +338,11 @@ export const Tab = (props: TabProps) => {
)}
<AriaTab
id={id}
className={clsx(classNames.tab, styles[classNames.tab], className)}
className={classes.root}
ref={el => setTabRef(id as string, el as HTMLDivElement)}
href={href}
{...rest}
>
{children}
</AriaTab>
{...restProps}
/>
</>
);
};
@@ -363,15 +353,7 @@ export const Tab = (props: TabProps) => {
* @public
*/
export const TabPanel = (props: TabPanelProps) => {
const { classNames, cleanedProps } = useStyles(TabsDefinition, props);
const { className, children, ...rest } = cleanedProps;
const { ownProps, restProps } = useDefinition(TabPanelDefinition, props);
return (
<AriaTabPanel
className={clsx(classNames.panel, styles[classNames.panel], className)}
{...rest}
>
{children}
</AriaTabPanel>
);
return <AriaTabPanel className={ownProps.classes.root} {...restProps} />;
};
@@ -15,12 +15,10 @@
*/
import { TabListStateContext } from 'react-aria-components';
import { useStyles } from '../../hooks/useStyles';
import { TabsDefinition } from './definition';
import { useDefinition } from '../../hooks/useDefinition';
import { TabsIndicatorsDefinition } from './definition';
import { useContext, useEffect, useCallback, useRef } from 'react';
import type { TabsIndicatorsProps } from './types';
import styles from './Tabs.module.css';
import clsx from 'clsx';
/**
* A component that renders the indicators for the toolbar.
@@ -28,8 +26,8 @@ import clsx from 'clsx';
* @internal
*/
export const TabsIndicators = (props: TabsIndicatorsProps) => {
const { tabRefs, tabsRef, hoveredKey, prevHoveredKey } = props;
const { classNames } = useStyles(TabsDefinition);
const { ownProps } = useDefinition(TabsIndicatorsDefinition, props);
const { classes, tabRefs, tabsRef, hoveredKey, prevHoveredKey } = ownProps;
const state = useContext(TabListStateContext);
const prevSelectedKey = useRef<string | null>(null);
@@ -187,12 +185,8 @@ export const TabsIndicators = (props: TabsIndicatorsProps) => {
return (
<>
<div
className={clsx(classNames.tabActive, styles[classNames.tabActive])}
/>
<div
className={clsx(classNames.tabHovered, styles[classNames.tabHovered])}
/>
<div className={classes.root} />
<div className={classes.hovered} />
</>
);
};
+71 -10
View File
@@ -14,20 +14,81 @@
* limitations under the License.
*/
import type { ComponentDefinition } from '../../types';
import { defineComponent } from '../../hooks/useDefinition';
import type {
TabsOwnProps,
TabListOwnProps,
TabOwnProps,
TabPanelOwnProps,
TabsIndicatorsOwnProps,
} from './types';
import styles from './Tabs.module.css';
/**
* Component definition for Tabs
* @public
*/
export const TabsDefinition = {
export const TabsDefinition = defineComponent<TabsOwnProps>()({
styles,
classNames: {
tabs: 'bui-Tabs',
tabList: 'bui-TabList',
tabListWrapper: 'bui-TabListWrapper',
tab: 'bui-Tab',
tabActive: 'bui-TabActive',
tabHovered: 'bui-TabHovered',
panel: 'bui-TabPanel',
root: 'bui-Tabs',
},
} as const satisfies ComponentDefinition;
propDefs: {
className: {},
children: {},
},
});
/** @internal */
export const TabListDefinition = defineComponent<TabListOwnProps>()({
styles,
classNames: {
root: 'bui-TabListWrapper',
tabList: 'bui-TabList',
},
propDefs: {
className: {},
children: {},
},
});
/** @internal */
export const TabDefinition = defineComponent<TabOwnProps>()({
styles,
classNames: {
root: 'bui-Tab',
},
propDefs: {
className: {},
matchStrategy: {},
href: {},
id: {},
},
});
/** @internal */
export const TabPanelDefinition = defineComponent<TabPanelOwnProps>()({
styles,
classNames: {
root: 'bui-TabPanel',
},
propDefs: {
className: {},
},
});
/** @internal */
export const TabsIndicatorsDefinition =
defineComponent<TabsIndicatorsOwnProps>()({
styles,
classNames: {
root: 'bui-TabActive',
hovered: 'bui-TabHovered',
},
propDefs: {
tabRefs: {},
tabsRef: {},
hoveredKey: {},
prevHoveredKey: {},
},
});
+4
View File
@@ -17,9 +17,13 @@
export { Tabs, TabList, Tab, TabPanel } from './Tabs';
export type {
TabsProps,
TabsOwnProps,
TabListProps,
TabListOwnProps,
TabPanelProps,
TabPanelOwnProps,
TabProps,
TabOwnProps,
TabMatchStrategy,
} from './types';
export { TabsDefinition } from './definition';
+71 -17
View File
@@ -29,33 +29,69 @@ import { MutableRefObject } from 'react';
*/
export type TabMatchStrategy = 'exact' | 'prefix';
/**
* Own props for the Tabs component.
*
* @public
*/
export type TabsOwnProps = {
className?: string;
children?: AriaTabsProps['children'];
};
/**
* Props for the Tabs component.
*
* @public
*/
export interface TabsProps extends AriaTabsProps {}
export interface TabsProps
extends TabsOwnProps,
Omit<AriaTabsProps, keyof TabsOwnProps> {}
/**
* Own props for the TabList component.
*
* @public
*/
export type TabListOwnProps = {
className?: string;
children?: AriaTabListProps<object>['children'];
};
/**
* Props for the TabList component.
*
* @public
*/
export interface TabListProps extends Omit<AriaTabListProps<object>, 'items'> {}
export interface TabListProps
extends TabListOwnProps,
Omit<AriaTabListProps<object>, 'items' | keyof TabListOwnProps> {}
/**
* Own props for the Tab component.
*
* @public
*/
export type TabOwnProps = {
className?: string;
/**
* Strategy for matching the current route to determine if this tab should be active.
* - 'exact': Tab href must exactly match the current pathname (default)
* - 'prefix': Tab is active if current pathname starts with tab href
*/
matchStrategy?: TabMatchStrategy;
href?: AriaTabProps['href'];
id?: AriaTabProps['id'];
};
/**
* Props for the Tab component.
*
* @public
*/
export interface TabProps extends AriaTabProps {
/**
* Strategy for matching the current route to determine if this tab should be active.
* - 'exact': Tab href must exactly match the current pathname (default)
* - 'prefix': Tab is active if current pathname starts with tab href
*/
matchStrategy?: 'exact' | 'prefix';
}
export interface TabProps
extends TabOwnProps,
Omit<AriaTabProps, keyof TabOwnProps> {}
/** Context for sharing refs between Tabs and TabList
*
@@ -70,21 +106,39 @@ export interface TabsContextValue {
setTabRef: (key: string, element: HTMLDivElement | null) => void;
}
/**
* Own props for the TabPanel component.
*
* @public
*/
export type TabPanelOwnProps = {
className?: string;
};
/**
* Props for the TabPanel component.
*
* @public
*/
export interface TabPanelProps extends AriaTabPanelProps {}
export interface TabPanelProps
extends TabPanelOwnProps,
Omit<AriaTabPanelProps, keyof TabPanelOwnProps> {}
/**
* Own props for the TabsIndicators component.
*
* @internal
*/
export type TabsIndicatorsOwnProps = {
tabRefs: MutableRefObject<Map<string, HTMLDivElement>>;
tabsRef: MutableRefObject<HTMLDivElement | null>;
hoveredKey: string | null;
prevHoveredKey: MutableRefObject<string | null>;
};
/**
* Props for the TabsIndicators component.
*
* @internal
*/
export interface TabsIndicatorsProps {
tabRefs: MutableRefObject<Map<string, HTMLDivElement>>;
tabsRef: MutableRefObject<HTMLDivElement | null>;
hoveredKey: string | null;
prevHoveredKey: MutableRefObject<string | null>;
}
export interface TabsIndicatorsProps extends TabsIndicatorsOwnProps {}