diff --git a/docs/data/api/switch-root.json b/docs/data/api/switch-root.json
index b63432c278..ab1c15cd36 100644
--- a/docs/data/api/switch-root.json
+++ b/docs/data/api/switch-root.json
@@ -4,7 +4,6 @@
"className": { "type": { "name": "union", "description": "func
| string" } },
"defaultChecked": { "type": { "name": "bool" } },
"disabled": { "type": { "name": "bool" }, "default": "false" },
- "id": { "type": { "name": "string" } },
"inputRef": { "type": { "name": "custom", "description": "ref" } },
"name": { "type": { "name": "string" } },
"onCheckedChange": {
diff --git a/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.js b/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.js
index ca04551ec5..d48f24dbea 100644
--- a/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.js
+++ b/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.js
@@ -1,148 +1,39 @@
'use client';
import * as React from 'react';
import * as Switch from '@base_ui/react/Switch';
-import { useTheme } from '@mui/system';
+import classes from './styles.module.css';
export default function UnstyledSwitchIntroduction() {
return (
-
+
-
+
-
+
-
+
-
);
}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark modes
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.tsx b/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.tsx
index ca04551ec5..d48f24dbea 100644
--- a/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.tsx
+++ b/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/index.tsx
@@ -1,148 +1,39 @@
'use client';
import * as React from 'react';
import * as Switch from '@base_ui/react/Switch';
-import { useTheme } from '@mui/system';
+import classes from './styles.module.css';
export default function UnstyledSwitchIntroduction() {
return (
-
+
-
+
-
+
-
+
-
);
}
-
-const cyan = {
- 50: '#E9F8FC',
- 100: '#BDEBF4',
- 200: '#99D8E5',
- 300: '#66BACC',
- 400: '#1F94AD',
- 500: '#0D5463',
- 600: '#094855',
- 700: '#063C47',
- 800: '#043039',
- 900: '#022127',
-};
-
-const grey = {
- 50: '#F3F6F9',
- 100: '#E5EAF2',
- 200: '#DAE2ED',
- 300: '#C7D0DD',
- 400: '#B0B8C4',
- 500: '#9DA8B7',
- 600: '#6B7A90',
- 700: '#434D5B',
- 800: '#303740',
- 900: '#1C2025',
-};
-
-function useIsDarkMode() {
- const theme = useTheme();
- return theme.palette.mode === 'dark';
-}
-
-function Styles() {
- // Replace this with your app logic for determining dark modes
- const isDarkMode = useIsDarkMode();
-
- return (
-
- );
-}
diff --git a/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/styles.module.css b/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/styles.module.css
new file mode 100644
index 0000000000..d0b25a5991
--- /dev/null
+++ b/docs/data/components/switch/UnstyledSwitchIntroduction/css-modules/styles.module.css
@@ -0,0 +1,80 @@
+.root {
+ width: 38px;
+ height: 24px;
+ margin: 10px;
+ padding: 0;
+ box-sizing: border-box;
+ background: var(--gray-50);
+ border: 1px solid var(--gray-200);
+ border-radius: 24px;
+ display: inline-block;
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 120ms;
+ box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.05);
+
+ :global(.dark) & {
+ background: var(--gray-900);
+ border-color: var(--gray-800);
+ box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.5);
+ }
+
+ &[data-disabled] {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+
+ &:hover:not([data-disabled]) {
+ background: var(--gray-100);
+ border-color: var(--gray-300);
+
+ :global(.dark) & {
+ background: var(--gray-800);
+ border-color: var(--gray-600);
+ }
+ }
+
+ &:focus-visible {
+ box-shadow: 0 0 0 3px var(--cyan-200);
+
+ :global(.dark) & {
+ box-shadow: 0 0 0 3px var(--cyan-700);
+ }
+ }
+
+ &[data-state='checked'] {
+ border: none;
+ background: var(--cyan-500);
+ }
+
+ &[data-state='checked']:not([data-disabled]):hover {
+ background: var(--cyan-700);
+ }
+}
+
+.thumb {
+ box-sizing: border-box;
+ border: 1px solid var(--gray-200);
+ display: block;
+ width: 16px;
+ height: 16px;
+ left: 4px;
+ border-radius: 16px;
+ background-color: #fff;
+ position: relative;
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 120ms;
+ box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
+
+ :global(.dark) & {
+ border-color: var(--gray-800);
+ box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.25);
+ }
+}
+
+.thumb[data-state='checked'] {
+ left: 18px;
+ background-color: #fff;
+ box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3);
+}
diff --git a/docs/data/components/switch/UnstyledSwitchIntroduction/tailwind/index.tsx b/docs/data/components/switch/UnstyledSwitchIntroduction/tailwind/index.tsx
index f7443c9756..d3c6a6a13f 100644
--- a/docs/data/components/switch/UnstyledSwitchIntroduction/tailwind/index.tsx
+++ b/docs/data/components/switch/UnstyledSwitchIntroduction/tailwind/index.tsx
@@ -30,9 +30,9 @@ export default function UnstyledSwitchIntroduction() {
);
}
-const Switch = React.forwardRef(
+const Switch = React.forwardRef(
function Switch({ className: classNameProp = '', ...props }, ref) {
- const className = ({ checked }: BaseSwitch.OwnerState) =>
+ const className = ({ checked }: BaseSwitch.Root.OwnerState) =>
`group relative inline-block w-[38px] h-[24px] m-2.5 p-0 transition rounded-full
border border-solid outline-none border-slate-300 dark:border-gray-700
focus-visible:shadow-outline-switch
@@ -48,7 +48,7 @@ const Thumb = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes
>(function Thumb({ className: classNameProp = '', ...props }, ref) {
- const className = ({ checked }: BaseSwitch.OwnerState) =>
+ const className = ({ checked }: BaseSwitch.Root.OwnerState) =>
`block w-4 h-4 rounded-2xl border border-solid outline-none border-slate-300 dark:border-gray-700 transition
shadow-[0_1px_2px_rgb(0_0_0_/_0.1)] dark:shadow-[0_1px_2px_rgb(0_0_0_/_0.25)]
relative transition-all
diff --git a/docs/data/components/tabs/UnstyledTabsIntroduction/tailwind/index.tsx b/docs/data/components/tabs/UnstyledTabsIntroduction/tailwind/index.tsx
index 4eb2e98f54..92957ad454 100644
--- a/docs/data/components/tabs/UnstyledTabsIntroduction/tailwind/index.tsx
+++ b/docs/data/components/tabs/UnstyledTabsIntroduction/tailwind/index.tsx
@@ -29,7 +29,7 @@ export default function UnstyledTabsIntroduction() {
);
}
-const TabsList = React.forwardRef((props, ref) => {
+const TabsList = React.forwardRef((props, ref) => {
const { className, ...other } = props;
return (
((props, ref) =
);
});
-const Tab = React.forwardRef((props, ref) => {
+const Tab = React.forwardRef((props, ref) => {
const { className, ...other } = props;
return (
((props, ref) => {
);
});
-const TabPanel = React.forwardRef((props, ref) => {
+const TabPanel = React.forwardRef((props, ref) => {
const { className, ...other } = props;
return (
true, the component is disabled and can't be interacted with."
},
- "id": { "description": "The id of the switch element." },
"inputRef": { "description": "Ref to the underlying input element." },
"name": { "description": "Name of the underlying input element." },
"onCheckedChange": {
diff --git a/docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetRoot.tsx b/docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetRoot.tsx
index eae7177cd5..a9d8e6c234 100644
--- a/docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetRoot.tsx
+++ b/docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetRoot.tsx
@@ -44,7 +44,7 @@ export namespace PackageManagerSnippetRoot {
export type Props = {
children: React.ReactNode;
options: Array<{ value: string; label: string }>;
- renderTab?: Tabs.TabProps['render'];
- renderTabsList?: Tabs.ListProps['render'];
+ renderTab?: Tabs.Tab.Props['render'];
+ renderTabsList?: Tabs.List.Props['render'];
};
}
diff --git a/packages/mui-base/src/Switch/Root/SwitchContext.ts b/packages/mui-base/src/Switch/Root/SwitchContext.ts
deleted file mode 100644
index c2366b6127..0000000000
--- a/packages/mui-base/src/Switch/Root/SwitchContext.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-'use client';
-import * as React from 'react';
-import { type SwitchOwnerState } from './SwitchRoot.types';
-
-export const SwitchContext = React.createContext(null);
diff --git a/packages/mui-base/src/Switch/Root/SwitchRoot.tsx b/packages/mui-base/src/Switch/Root/SwitchRoot.tsx
index f467e55059..0c72e0152d 100644
--- a/packages/mui-base/src/Switch/Root/SwitchRoot.tsx
+++ b/packages/mui-base/src/Switch/Root/SwitchRoot.tsx
@@ -3,17 +3,12 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import refType from '@mui/utils/refType';
import { useSwitchRoot } from './useSwitchRoot';
-import { SwitchContext } from './SwitchContext';
-import type { SwitchRootProps, SwitchOwnerState } from './SwitchRoot.types';
-import { resolveClassName } from '../../utils/resolveClassName';
-import { useSwitchStyleHooks } from './useSwitchStyleHooks';
-import { evaluateRenderProp } from '../../utils/evaluateRenderProp';
-import { useRenderPropForkRef } from '../../utils/useRenderPropForkRef';
+import { SwitchRootContext } from './SwitchRootContext';
+import { styleHookMapping } from '../styleHooks';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useFieldRootContext } from '../../Field/Root/FieldRootContext';
-
-function defaultRender(props: React.ComponentPropsWithRef<'button'>) {
- return ;
-}
+import type { BaseUIComponentProps } from '../../utils/types';
+import type { FieldRootOwnerState } from '../../Field/Root/FieldRoot.types';
/**
* The foundation for building custom-styled switches.
@@ -27,29 +22,28 @@ function defaultRender(props: React.ComponentPropsWithRef<'button'>) {
* - [SwitchRoot API](https://base-ui.netlify.app/components/react-switch/#api-reference-SwitchRoot)
*/
const SwitchRoot = React.forwardRef(function SwitchRoot(
- props: SwitchRootProps,
+ props: SwitchRoot.Props,
forwardedRef: React.ForwardedRef,
) {
const {
checked: checkedProp,
- className: classNameProp,
+ className,
defaultChecked,
inputRef,
onCheckedChange,
readOnly = false,
required = false,
disabled: disabledProp = false,
- render: renderProp,
+ render,
...other
} = props;
- const render = renderProp ?? defaultRender;
const { getInputProps, getButtonProps, checked } = useSwitchRoot(props);
const { ownerState: fieldOwnerState, disabled: fieldDisabled } = useFieldRootContext();
const disabled = fieldDisabled || disabledProp;
- const ownerState: SwitchOwnerState = React.useMemo(
+ const ownerState: SwitchRoot.OwnerState = React.useMemo(
() => ({
...fieldOwnerState,
checked,
@@ -60,26 +54,38 @@ const SwitchRoot = React.forwardRef(function SwitchRoot(
[fieldOwnerState, checked, disabled, readOnly, required],
);
- const className = resolveClassName(classNameProp, ownerState);
- const styleHooks = useSwitchStyleHooks(ownerState);
- const mergedRef = useRenderPropForkRef(render, forwardedRef);
-
- const buttonProps = {
+ const { renderElement } = useComponentRenderer({
+ render: render || 'button',
className,
- ref: mergedRef,
- ...styleHooks,
- ...other,
- };
+ propGetter: getButtonProps,
+ ownerState,
+ extraProps: other,
+ customStyleHookMapping: styleHookMapping,
+ ref: forwardedRef,
+ });
return (
-
- {evaluateRenderProp(render, getButtonProps(buttonProps), ownerState)}
+
+ {renderElement()}
{!checked && props.name && }
-
+
);
});
+namespace SwitchRoot {
+ export interface Props
+ extends useSwitchRoot.Parameters,
+ Omit, 'onChange'> {}
+
+ export interface OwnerState extends FieldRootOwnerState {
+ checked: boolean;
+ disabled: boolean;
+ readOnly: boolean;
+ required: boolean;
+ }
+}
+
SwitchRoot.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
@@ -107,10 +113,6 @@ SwitchRoot.propTypes /* remove-proptypes */ = {
* @default false
*/
disabled: PropTypes.bool,
- /**
- * The id of the switch element.
- */
- id: PropTypes.string,
/**
* Ref to the underlying input element.
*/
diff --git a/packages/mui-base/src/Switch/Root/SwitchRoot.types.ts b/packages/mui-base/src/Switch/Root/SwitchRoot.types.ts
deleted file mode 100644
index 00ac074764..0000000000
--- a/packages/mui-base/src/Switch/Root/SwitchRoot.types.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import type * as React from 'react';
-import type { BaseUIComponentProps } from '../../utils/types';
-import type { FieldRootOwnerState } from '../../Field/Root/FieldRoot.types';
-
-export interface SwitchOwnerState extends FieldRootOwnerState {
- checked: boolean;
- disabled: boolean;
- readOnly: boolean;
- required: boolean;
-}
-
-export interface SwitchRootProps
- extends UseSwitchRootParameters,
- Omit, 'onChange'> {}
-
-export type SwitchContextValue = SwitchOwnerState;
-
-export interface UseSwitchRootParameters {
- /**
- * The id of the switch element.
- */
- id?: string;
- /**
- * If `true`, the switch is checked.
- */
- checked?: boolean;
- /**
- * The default checked state. Use when the component is not controlled.
- */
- defaultChecked?: boolean;
- /**
- * If `true`, the component is disabled and can't be interacted with.
- *
- * @default false
- */
- disabled?: boolean;
- /**
- * Ref to the underlying input element.
- */
- inputRef?: React.Ref;
- /**
- * Name of the underlying input element.
- */
- name?: string;
- /**
- * Callback fired when the checked state is changed.
- *
- * @param {boolean} checked The new checked state.
- * @param {React.ChangeEvent} event The event source of the callback.
- */
- onCheckedChange?: (checked: boolean, event: React.ChangeEvent) => void;
- /**
- * If `true`, the component is read-only.
- * Functionally, this is equivalent to being disabled, but the assistive technologies will announce this differently.
- *
- * @default false
- */
- readOnly?: boolean;
- /**
- * If `true`, the switch must be checked for the browser validation to pass.
- *
- * @default false
- */
- required?: boolean;
-}
-export interface UseSwitchRootReturnValue {
- /**
- * If `true`, the component will be checked.
- */
- checked: boolean;
- /**
- * Resolver for the input element's props.
- * @param externalProps Additional props for the input element
- * @returns Props that should be spread on the input element
- */
- getInputProps: (
- externalProps?: React.ComponentPropsWithRef<'input'>,
- ) => React.ComponentPropsWithRef<'input'>;
- /**
- * Resolver for the button element's props.
- * @param externalProps Additional props for the input element
- * @returns Props that should be spread on the button element
- */
- getButtonProps: (
- externalProps?: React.ComponentPropsWithRef<'button'>,
- ) => React.ComponentPropsWithRef<'button'>;
-}
diff --git a/packages/mui-base/src/Switch/Root/SwitchRootContext.ts b/packages/mui-base/src/Switch/Root/SwitchRootContext.ts
new file mode 100644
index 0000000000..3804b912d4
--- /dev/null
+++ b/packages/mui-base/src/Switch/Root/SwitchRootContext.ts
@@ -0,0 +1,15 @@
+import * as React from 'react';
+import type { SwitchRoot } from './SwitchRoot';
+
+export type SwitchRootContext = SwitchRoot.OwnerState;
+
+export const SwitchRootContext = React.createContext(undefined);
+
+export function useSwitchRootContext() {
+ const context = React.useContext(SwitchRootContext);
+ if (context === undefined) {
+ throw new Error('useSwitchRootContext must be used within a SwitchRootProvider');
+ }
+
+ return context;
+}
diff --git a/packages/mui-base/src/Switch/Root/useSwitchRoot.ts b/packages/mui-base/src/Switch/Root/useSwitchRoot.ts
index 555755f838..6cec0e60d5 100644
--- a/packages/mui-base/src/Switch/Root/useSwitchRoot.ts
+++ b/packages/mui-base/src/Switch/Root/useSwitchRoot.ts
@@ -1,6 +1,5 @@
'use client';
import * as React from 'react';
-import type { UseSwitchRootParameters, UseSwitchRootReturnValue } from './SwitchRoot.types';
import { useControlled } from '../../utils/useControlled';
import { useForkRef } from '../../utils/useForkRef';
import { visuallyHidden } from '../../utils/visuallyHidden';
@@ -12,18 +11,7 @@ import { useEnhancedEffect } from '../../utils/useEnhancedEffect';
import { useFieldControlValidation } from '../../Field/Control/useFieldControlValidation';
import { useField } from '../../Field/useField';
-/**
- * The basic building block for creating custom switches.
- *
- * Demos:
- *
- * - [Switch](https://mui.com/base-ui/react-switch/#hook)
- *
- * API:
- *
- * - [useSwitchRoot API](https://mui.com/base-ui/react-switch/hooks-api/#use-switch-root)
- */
-export function useSwitchRoot(params: UseSwitchRootParameters): UseSwitchRootReturnValue {
+export function useSwitchRoot(params: useSwitchRoot.Parameters): useSwitchRoot.ReturnValue {
const {
id: idProp,
checked: checkedProp,
@@ -154,3 +142,77 @@ export function useSwitchRoot(params: UseSwitchRootParameters): UseSwitchRootRet
[checked, getButtonProps, getInputProps],
);
}
+
+export namespace useSwitchRoot {
+ export interface Parameters {
+ /**
+ * The id of the switch element.
+ */
+ id?: string;
+ /**
+ * If `true`, the switch is checked.
+ */
+ checked?: boolean;
+ /**
+ * The default checked state. Use when the component is not controlled.
+ */
+ defaultChecked?: boolean;
+ /**
+ * If `true`, the component is disabled and can't be interacted with.
+ *
+ * @default false
+ */
+ disabled?: boolean;
+ /**
+ * Ref to the underlying input element.
+ */
+ inputRef?: React.Ref;
+ /**
+ * Name of the underlying input element.
+ */
+ name?: string;
+ /**
+ * Callback fired when the checked state is changed.
+ *
+ * @param {boolean} checked The new checked state.
+ * @param {React.ChangeEvent} event The event source of the callback.
+ */
+ onCheckedChange?: (checked: boolean, event: React.ChangeEvent) => void;
+ /**
+ * If `true`, the component is read-only.
+ * Functionally, this is equivalent to being disabled, but the assistive technologies will announce this differently.
+ *
+ * @default false
+ */
+ readOnly?: boolean;
+ /**
+ * If `true`, the switch must be checked for the browser validation to pass.
+ *
+ * @default false
+ */
+ required?: boolean;
+ }
+
+ export interface ReturnValue {
+ /**
+ * If `true`, the component will be checked.
+ */
+ checked: boolean;
+ /**
+ * Resolver for the input element's props.
+ * @param externalProps Additional props for the input element
+ * @returns Props that should be spread on the input element
+ */
+ getInputProps: (
+ externalProps?: React.ComponentPropsWithRef<'input'>,
+ ) => React.ComponentPropsWithRef<'input'>;
+ /**
+ * Resolver for the button element's props.
+ * @param externalProps Additional props for the input element
+ * @returns Props that should be spread on the button element
+ */
+ getButtonProps: (
+ externalProps?: React.ComponentPropsWithRef<'button'>,
+ ) => React.ComponentPropsWithRef<'button'>;
+ }
+}
diff --git a/packages/mui-base/src/Switch/Root/useSwitchStyleHooks.ts b/packages/mui-base/src/Switch/Root/useSwitchStyleHooks.ts
deleted file mode 100644
index 885b5c9cb4..0000000000
--- a/packages/mui-base/src/Switch/Root/useSwitchStyleHooks.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as React from 'react';
-import { SwitchOwnerState } from './SwitchRoot.types';
-import { getStyleHookProps } from '../../utils/getStyleHookProps';
-
-/**
- * @ignore - internal hook.
- */
-export function useSwitchStyleHooks(ownerState: SwitchOwnerState) {
- return React.useMemo(() => {
- return getStyleHookProps(ownerState, {
- checked: (value) => ({ 'data-state': value ? 'checked' : 'unchecked' }),
- });
- }, [ownerState]);
-}
diff --git a/packages/mui-base/src/Switch/Thumb/SwitchThumb.test.tsx b/packages/mui-base/src/Switch/Thumb/SwitchThumb.test.tsx
index 4db313f8f2..9e49e094b7 100644
--- a/packages/mui-base/src/Switch/Thumb/SwitchThumb.test.tsx
+++ b/packages/mui-base/src/Switch/Thumb/SwitchThumb.test.tsx
@@ -1,9 +1,9 @@
import * as React from 'react';
import * as Switch from '@base_ui/react/Switch';
import { createRenderer, describeConformance } from '#test-utils';
-import { SwitchContext } from '../Root/SwitchContext';
+import { SwitchRootContext } from '../Root/SwitchRootContext';
-const testContext = {
+const testContext: SwitchRootContext = {
checked: false,
disabled: false,
readOnly: false,
@@ -19,7 +19,9 @@ describe('', () => {
describeConformance(, () => ({
refInstanceof: window.HTMLSpanElement,
render: (node) => {
- return render({node});
+ return render(
+ {node},
+ );
},
}));
});
diff --git a/packages/mui-base/src/Switch/Thumb/SwitchThumb.tsx b/packages/mui-base/src/Switch/Thumb/SwitchThumb.tsx
index 078b49daee..831ee3c0e2 100644
--- a/packages/mui-base/src/Switch/Thumb/SwitchThumb.tsx
+++ b/packages/mui-base/src/Switch/Thumb/SwitchThumb.tsx
@@ -1,17 +1,12 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
-import { SwitchThumbProps } from './SwitchThumb.types';
-import { SwitchContext } from '../Root/SwitchContext';
-import { useSwitchStyleHooks } from '../Root/useSwitchStyleHooks';
-import { resolveClassName } from '../../utils/resolveClassName';
-import { evaluateRenderProp } from '../../utils/evaluateRenderProp';
-import { useRenderPropForkRef } from '../../utils/useRenderPropForkRef';
+import type { SwitchRoot } from '../Root/SwitchRoot';
+import { useSwitchRootContext } from '../Root/SwitchRootContext';
+import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useFieldRootContext } from '../../Field/Root/FieldRootContext';
-
-function defaultRender(props: React.ComponentPropsWithRef<'span'>) {
- return ;
-}
+import type { BaseUIComponentProps } from '../../utils/types';
+import { styleHookMapping } from '../styleHooks';
/**
*
@@ -24,35 +19,34 @@ function defaultRender(props: React.ComponentPropsWithRef<'span'>) {
* - [SwitchThumb API](https://base-ui.netlify.app/components/react-switch/#api-reference-SwitchThumb)
*/
const SwitchThumb = React.forwardRef(function SwitchThumb(
- props: SwitchThumbProps,
+ props: SwitchThumb.Props,
forwardedRef: React.ForwardedRef,
) {
- const { render: renderProp, className: classNameProp, ...other } = props;
- const render = renderProp ?? defaultRender;
+ const { render, className, ...other } = props;
const { ownerState: fieldOwnerState } = useFieldRootContext();
- const ownerState = React.useContext(SwitchContext);
- if (ownerState === null) {
- throw new Error('Base UI: Switch.Thumb is not placed inside the Switch component.');
- }
-
+ const ownerState = useSwitchRootContext();
const extendedOwnerState = { ...fieldOwnerState, ...ownerState };
- const className = resolveClassName(classNameProp, extendedOwnerState);
- const styleHooks = useSwitchStyleHooks(extendedOwnerState);
- const mergedRef = useRenderPropForkRef(render, forwardedRef);
-
- const elementProps = {
+ const { renderElement } = useComponentRenderer({
+ render: render || 'span',
className,
- ref: mergedRef,
- ...styleHooks,
- ...other,
- };
+ ownerState: extendedOwnerState,
+ extraProps: other,
+ customStyleHookMapping: styleHookMapping,
+ ref: forwardedRef,
+ });
- return evaluateRenderProp(render, elementProps, ownerState);
+ return renderElement();
});
+namespace SwitchThumb {
+ export interface Props extends BaseUIComponentProps<'span', OwnerState> {}
+
+ export interface OwnerState extends SwitchRoot.OwnerState {}
+}
+
SwitchThumb.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
diff --git a/packages/mui-base/src/Switch/Thumb/SwitchThumb.types.ts b/packages/mui-base/src/Switch/Thumb/SwitchThumb.types.ts
deleted file mode 100644
index 51d55535b3..0000000000
--- a/packages/mui-base/src/Switch/Thumb/SwitchThumb.types.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import type { SwitchOwnerState } from '../Root/SwitchRoot.types';
-import type { BaseUIComponentProps } from '../../utils/types';
-
-export interface SwitchThumbProps extends BaseUIComponentProps<'span', SwitchOwnerState> {}
diff --git a/packages/mui-base/src/Switch/index.barrel.ts b/packages/mui-base/src/Switch/index.barrel.ts
index f4f136cf88..f717fab5ad 100644
--- a/packages/mui-base/src/Switch/index.barrel.ts
+++ b/packages/mui-base/src/Switch/index.barrel.ts
@@ -1,13 +1,2 @@
export { SwitchRoot } from './Root/SwitchRoot';
-export { useSwitchRoot } from './Root/useSwitchRoot';
-export { SwitchContext } from './Root/SwitchContext';
-export type {
- SwitchRootProps,
- SwitchOwnerState,
- SwitchContextValue,
- UseSwitchRootParameters,
- UseSwitchRootReturnValue,
-} from './Root/SwitchRoot.types';
-
export { SwitchThumb } from './Thumb/SwitchThumb';
-export type { SwitchThumbProps } from './Thumb/SwitchThumb.types';
diff --git a/packages/mui-base/src/Switch/index.ts b/packages/mui-base/src/Switch/index.ts
index d033156fdf..7edb4a4336 100644
--- a/packages/mui-base/src/Switch/index.ts
+++ b/packages/mui-base/src/Switch/index.ts
@@ -1,13 +1,2 @@
export { SwitchRoot as Root } from './Root/SwitchRoot';
-export { useSwitchRoot } from './Root/useSwitchRoot';
-export { SwitchContext } from './Root/SwitchContext';
-export type {
- SwitchRootProps as RootProps,
- SwitchOwnerState as OwnerState,
- SwitchContextValue,
- UseSwitchRootParameters,
- UseSwitchRootReturnValue,
-} from './Root/SwitchRoot.types';
-
export { SwitchThumb as Thumb } from './Thumb/SwitchThumb';
-export type { SwitchThumbProps as ThumbProps } from './Thumb/SwitchThumb.types';
diff --git a/packages/mui-base/src/Switch/styleHooks.ts b/packages/mui-base/src/Switch/styleHooks.ts
new file mode 100644
index 0000000000..0c8fd40aba
--- /dev/null
+++ b/packages/mui-base/src/Switch/styleHooks.ts
@@ -0,0 +1,6 @@
+import type { SwitchRoot } from './Root/SwitchRoot';
+import type { CustomStyleHookMapping } from '../utils/getStyleHookProps';
+
+export const styleHookMapping: CustomStyleHookMapping = {
+ checked: (value) => ({ 'data-state': value ? 'checked' : 'unchecked' }),
+};
diff --git a/packages/mui-base/src/Tabs/Root/TabsProvider.tsx b/packages/mui-base/src/Tabs/Root/TabsProvider.tsx
index fdef97885f..2a6670b7ad 100644
--- a/packages/mui-base/src/Tabs/Root/TabsProvider.tsx
+++ b/packages/mui-base/src/Tabs/Root/TabsProvider.tsx
@@ -1,7 +1,7 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
-import { TabsContext, TabsContextValue } from './TabsContext';
+import { TabsRootContext } from './TabsRootContext';
import { CompoundComponentContext, CompoundComponentContextValue } from '../../useCompound';
export type TabPanelMetadata = {
@@ -10,7 +10,7 @@ export type TabPanelMetadata = {
};
export type TabsProviderValue = CompoundComponentContextValue &
- TabsContextValue;
+ TabsRootContext;
export interface TabsProviderProps {
value: TabsProviderValue;
@@ -48,7 +48,7 @@ function TabsProvider(props: TabsProviderProps) {
[registerItem, getItemIndex, totalSubitemCount],
);
- const tabsContextValue: TabsContextValue = React.useMemo(
+ const tabsContextValue: TabsRootContext = React.useMemo(
() => ({
direction,
getTabId,
@@ -73,7 +73,7 @@ function TabsProvider(props: TabsProviderProps) {
return (
- {children}
+ {children}
);
}
diff --git a/packages/mui-base/src/Tabs/Root/TabsRoot.test.tsx b/packages/mui-base/src/Tabs/Root/TabsRoot.test.tsx
index 18c46cc3fa..00e145932b 100644
--- a/packages/mui-base/src/Tabs/Root/TabsRoot.test.tsx
+++ b/packages/mui-base/src/Tabs/Root/TabsRoot.test.tsx
@@ -280,10 +280,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -311,10 +311,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -344,10 +344,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -376,10 +376,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -408,9 +408,9 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -440,10 +440,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -471,10 +471,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -504,10 +504,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -536,10 +536,10 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
@@ -568,9 +568,9 @@ describe('', () => {
const handleKeyDown = spy();
const { getAllByRole } = await render(
diff --git a/packages/mui-base/src/Tabs/Root/TabsRoot.tsx b/packages/mui-base/src/Tabs/Root/TabsRoot.tsx
index 48ac80b66a..7660a562d1 100644
--- a/packages/mui-base/src/Tabs/Root/TabsRoot.tsx
+++ b/packages/mui-base/src/Tabs/Root/TabsRoot.tsx
@@ -1,11 +1,11 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
-import { TabsRootOwnerState, TabsRootProps } from './TabsRoot.types';
import { useTabsRoot } from './useTabsRoot';
import { tabsStyleHookMapping } from './styleHooks';
import { TabsProvider } from './TabsProvider';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import type { BaseUIComponentProps } from '../../utils/types';
/**
*
@@ -18,7 +18,7 @@ import { useComponentRenderer } from '../../utils/useComponentRenderer';
* - [TabsRoot API](https://base-ui.netlify.app/components/react-tabs/#api-reference-TabsRoot)
*/
const TabsRoot = React.forwardRef(function TabsRoot(
- props: TabsRootProps,
+ props: TabsRoot.Props,
forwardedRef: React.ForwardedRef,
) {
const {
@@ -40,7 +40,7 @@ const TabsRoot = React.forwardRef(function TabsRoot(
direction,
});
- const ownerState: TabsRootOwnerState = {
+ const ownerState: TabsRoot.OwnerState = {
orientation,
direction,
tabActivationDirection,
@@ -59,6 +59,44 @@ const TabsRoot = React.forwardRef(function TabsRoot(
return {renderElement()};
});
+export type TabsOrientation = 'horizontal' | 'vertical';
+export type TabsDirection = 'ltr' | 'rtl';
+export type TabActivationDirection = 'left' | 'right' | 'up' | 'down' | 'none';
+
+namespace TabsRoot {
+ export type OwnerState = {
+ orientation: TabsOrientation;
+ direction: TabsDirection;
+ tabActivationDirection: TabActivationDirection;
+ };
+
+ export interface Props extends BaseUIComponentProps<'div', OwnerState> {
+ /**
+ * The value of the currently selected `Tab`.
+ * If you don't want any selected `Tab`, you can set this prop to `null`.
+ */
+ value?: any | null;
+ /**
+ * The default value. Use when the component is not controlled.
+ */
+ defaultValue?: any | null;
+ /**
+ * The component orientation (layout flow direction).
+ * @default 'horizontal'
+ */
+ orientation?: TabsOrientation;
+ /**
+ * The direction of the text.
+ * @default 'ltr'
+ */
+ direction?: TabsDirection;
+ /**
+ * Callback invoked when new value is being set.
+ */
+ onValueChange?: (value: any | null, event: React.SyntheticEvent | null) => void;
+ }
+}
+
TabsRoot.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
diff --git a/packages/mui-base/src/Tabs/Root/TabsRoot.types.ts b/packages/mui-base/src/Tabs/Root/TabsRoot.types.ts
deleted file mode 100644
index cfb8dfa1e1..0000000000
--- a/packages/mui-base/src/Tabs/Root/TabsRoot.types.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import * as React from 'react';
-import { TabsProviderValue } from './TabsProvider';
-import { BaseUIComponentProps } from '../../utils/types';
-
-export type TabsRootOwnerState = {
- orientation: TabsOrientation;
- direction: TabsDirection;
- tabActivationDirection: TabActivationDirection;
-};
-
-export interface TabsRootProps extends BaseUIComponentProps<'div', TabsRootOwnerState> {
- /**
- * The value of the currently selected `Tab`.
- * If you don't want any selected `Tab`, you can set this prop to `null`.
- */
- value?: any | null;
- /**
- * The default value. Use when the component is not controlled.
- */
- defaultValue?: any | null;
- /**
- * The component orientation (layout flow direction).
- * @default 'horizontal'
- */
- orientation?: TabsOrientation;
- /**
- * The direction of the text.
- * @default 'ltr'
- */
- direction?: TabsDirection;
- /**
- * Callback invoked when new value is being set.
- */
- onValueChange?: (value: any | null, event: React.SyntheticEvent | null) => void;
-}
-
-export type TabActivationDirection = 'left' | 'right' | 'up' | 'down' | 'none';
-
-export type TabsOrientation = 'horizontal' | 'vertical';
-
-export type TabsDirection = 'ltr' | 'rtl';
-
-export interface UseTabsParameters {
- /**
- * The value of the currently selected `Tab`.
- * If you don't want any selected `Tab`, you can set this prop to `false`.
- */
- value?: any | null;
- /**
- * The default value. Use when the component is not controlled.
- */
- defaultValue?: any | null;
- /**
- * The component orientation (layout flow direction).
- * @default 'horizontal'
- */
- orientation?: 'horizontal' | 'vertical';
- /**
- * The direction of the text.
- * @default 'ltr'
- */
- direction?: 'ltr' | 'rtl';
- /**
- * Callback invoked when new value is being set.
- */
- onValueChange?: (value: any | null, event: React.SyntheticEvent | null) => void;
-}
-
-export interface UseTabsReturnValue {
- /**
- * Returns the values to be passed to the tabs provider.
- */
- contextValue: TabsProviderValue;
- getRootProps: (
- externalProps?: React.ComponentPropsWithRef<'div'>,
- ) => React.ComponentPropsWithRef<'div'>;
- tabActivationDirection: TabActivationDirection;
-}
diff --git a/packages/mui-base/src/Tabs/Root/TabsContext.ts b/packages/mui-base/src/Tabs/Root/TabsRootContext.ts
similarity index 76%
rename from packages/mui-base/src/Tabs/Root/TabsContext.ts
rename to packages/mui-base/src/Tabs/Root/TabsRootContext.ts
index be0e02bc49..ea4d9101c9 100644
--- a/packages/mui-base/src/Tabs/Root/TabsContext.ts
+++ b/packages/mui-base/src/Tabs/Root/TabsRootContext.ts
@@ -1,8 +1,8 @@
'use client';
import * as React from 'react';
-import { type TabActivationDirection } from './TabsRoot.types';
+import { type TabActivationDirection } from './TabsRoot';
-export interface TabsContextValue {
+export interface TabsRootContext {
/**
* The currently selected tab's value.
*/
@@ -46,19 +46,19 @@ export interface TabsContextValue {
/**
* @ignore - internal component.
*/
-const TabsContext = React.createContext(null);
+const TabsRootContext = React.createContext(null);
if (process.env.NODE_ENV !== 'production') {
- TabsContext.displayName = 'TabsContext';
+ TabsRootContext.displayName = 'TabsRootContext';
}
-export function useTabsContext() {
- const context = React.useContext(TabsContext);
+export function useTabsRootContext() {
+ const context = React.useContext(TabsRootContext);
if (context == null) {
- throw new Error('No TabsContext provided');
+ throw new Error('Base UI: No TabsRootContext provided');
}
return context;
}
-export { TabsContext };
+export { TabsRootContext };
diff --git a/packages/mui-base/src/Tabs/Root/styleHooks.ts b/packages/mui-base/src/Tabs/Root/styleHooks.ts
index 56883ad061..0e9a3c4fb3 100644
--- a/packages/mui-base/src/Tabs/Root/styleHooks.ts
+++ b/packages/mui-base/src/Tabs/Root/styleHooks.ts
@@ -1,7 +1,7 @@
-import type { TabsRootOwnerState } from './TabsRoot.types';
+import type { TabsRoot } from './TabsRoot';
import type { CustomStyleHookMapping } from '../../utils/getStyleHookProps';
-export const tabsStyleHookMapping: CustomStyleHookMapping = {
+export const tabsStyleHookMapping: CustomStyleHookMapping = {
direction: () => null,
tabActivationDirection: (dir) => ({
'data-activation-direction': dir,
diff --git a/packages/mui-base/src/Tabs/Root/useTabsRoot.ts b/packages/mui-base/src/Tabs/Root/useTabsRoot.ts
index 36d867a4a1..8c9e49354d 100644
--- a/packages/mui-base/src/Tabs/Root/useTabsRoot.ts
+++ b/packages/mui-base/src/Tabs/Root/useTabsRoot.ts
@@ -1,11 +1,7 @@
'use client';
import * as React from 'react';
-import type {
- TabActivationDirection,
- UseTabsParameters,
- UseTabsReturnValue,
-} from './TabsRoot.types';
-import type { TabPanelMetadata } from './TabsProvider';
+import type { TabActivationDirection } from './TabsRoot';
+import type { TabsProviderValue, TabPanelMetadata } from './TabsProvider';
import { useCompoundParent } from '../../useCompound';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useControlled } from '../../utils/useControlled';
@@ -18,7 +14,7 @@ export interface TabMetadata {
type IdLookupFunction = (id: any) => string | undefined;
-function useTabsRoot(parameters: UseTabsParameters): UseTabsReturnValue {
+function useTabsRoot(parameters: useTabsRoot.Parameters): useTabsRoot.ReturnValue {
const {
value: valueProp,
defaultValue,
@@ -72,7 +68,7 @@ function useTabsRoot(parameters: UseTabsParameters): UseTabsReturnValue {
tabIdLookup.current = lookupFunction;
}, []);
- const getRootProps: UseTabsReturnValue['getRootProps'] = React.useCallback(
+ const getRootProps: useTabsRoot.ReturnValue['getRootProps'] = React.useCallback(
(otherProps = {}) =>
mergeReactProps<'div'>(otherProps, {
dir: direction,
@@ -97,4 +93,43 @@ function useTabsRoot(parameters: UseTabsParameters): UseTabsReturnValue {
};
}
+namespace useTabsRoot {
+ export interface Parameters {
+ /**
+ * The value of the currently selected `Tab`.
+ * If you don't want any selected `Tab`, you can set this prop to `false`.
+ */
+ value?: any | null;
+ /**
+ * The default value. Use when the component is not controlled.
+ */
+ defaultValue?: any | null;
+ /**
+ * The component orientation (layout flow direction).
+ * @default 'horizontal'
+ */
+ orientation?: 'horizontal' | 'vertical';
+ /**
+ * The direction of the text.
+ * @default 'ltr'
+ */
+ direction?: 'ltr' | 'rtl';
+ /**
+ * Callback invoked when new value is being set.
+ */
+ onValueChange?: (value: any | null, event: React.SyntheticEvent | null) => void;
+ }
+
+ export interface ReturnValue {
+ /**
+ * Returns the values to be passed to the tabs provider.
+ */
+ contextValue: TabsProviderValue;
+ getRootProps: (
+ externalProps?: React.ComponentPropsWithRef<'div'>,
+ ) => React.ComponentPropsWithRef<'div'>;
+ tabActivationDirection: TabActivationDirection;
+ }
+}
+
export { useTabsRoot };
diff --git a/packages/mui-base/src/Tabs/Tab/Tab.test.tsx b/packages/mui-base/src/Tabs/Tab/Tab.test.tsx
index c934ac3ed0..328052085d 100644
--- a/packages/mui-base/src/Tabs/Tab/Tab.test.tsx
+++ b/packages/mui-base/src/Tabs/Tab/Tab.test.tsx
@@ -1,12 +1,8 @@
import * as React from 'react';
import * as Tabs from '@base_ui/react/Tabs';
-import {
- TabsListProvider,
- TabsListProviderValue,
- TabsContext,
- TabsContextValue,
-} from '@base_ui/react/Tabs';
import { createRenderer, describeConformance } from '#test-utils';
+import { TabsListProviderValue, TabsListProvider } from '../TabsList/TabsListProvider';
+import { TabsRootContext } from '../Root/TabsRootContext';
describe('', () => {
const { render } = createRenderer();
@@ -26,7 +22,7 @@ describe('', () => {
},
};
- const testTabsContext: TabsContextValue = {
+ const testTabsContext: TabsRootContext = {
value: 0,
onSelected() {},
registerTabIdLookup() {},
@@ -40,9 +36,9 @@ describe('', () => {
describeConformance(, () => ({
render: (node) => {
return render(
-
+
{node}
- ,
+ ,
);
},
refInstanceof: window.HTMLButtonElement,
diff --git a/packages/mui-base/src/Tabs/Tab/Tab.tsx b/packages/mui-base/src/Tabs/Tab/Tab.tsx
index 7cef93ed05..217fef7cbb 100644
--- a/packages/mui-base/src/Tabs/Tab/Tab.tsx
+++ b/packages/mui-base/src/Tabs/Tab/Tab.tsx
@@ -1,9 +1,10 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
-import { TabProps, TabOwnerState } from './Tab.types';
import { useTab } from './useTab';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import type { BaseUIComponentProps } from '../../utils/types';
+import { TabsOrientation } from '../Root/TabsRoot';
/**
*
@@ -16,7 +17,7 @@ import { useComponentRenderer } from '../../utils/useComponentRenderer';
* - [Tab API](https://base-ui.netlify.app/components/react-tabs/#api-reference-Tab)
*/
const Tab = React.forwardRef(function Tab(
- props: TabProps,
+ props: Tab.Props,
forwardedRef: React.ForwardedRef,
) {
const { className, disabled = false, render, value, ...other } = props;
@@ -26,7 +27,7 @@ const Tab = React.forwardRef(function Tab(
rootRef: forwardedRef,
});
- const ownerState: TabOwnerState = {
+ const ownerState: Tab.OwnerState = {
disabled,
selected,
orientation,
@@ -43,6 +44,21 @@ const Tab = React.forwardRef(function Tab(
return renderElement();
});
+namespace Tab {
+ export interface Props extends BaseUIComponentProps<'button', Tab.OwnerState> {
+ /**
+ * You can provide your own value. Otherwise, it falls back to the child position index.
+ */
+ value?: any;
+ }
+
+ export interface OwnerState {
+ disabled: boolean;
+ selected: boolean;
+ orientation: TabsOrientation;
+ }
+}
+
Tab.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
diff --git a/packages/mui-base/src/Tabs/Tab/Tab.types.ts b/packages/mui-base/src/Tabs/Tab/Tab.types.ts
deleted file mode 100644
index 23a967a844..0000000000
--- a/packages/mui-base/src/Tabs/Tab/Tab.types.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as React from 'react';
-import { TabsOrientation } from '../Root/TabsRoot.types';
-import { BaseUIComponentProps } from '../../utils/types';
-
-export type TabOwnerState = {
- disabled: boolean;
- selected: boolean;
- orientation: TabsOrientation;
-};
-
-export interface TabProps extends BaseUIComponentProps<'button', TabOwnerState> {
- /**
- * You can provide your own value. Otherwise, it falls back to the child position index.
- */
- value?: any;
-}
-
-export interface UseTabParameters {
- /**
- * The value of the tab.
- * It's used to associate the tab with a tab panel(s) with the same value.
- * If the value is not provided, it falls back to the position index.
- */
- value?: any;
- /**
- * Callback fired when the tab is clicked.
- */
- onClick?: React.MouseEventHandler;
- /**
- * If `true`, the tab will be disabled.
- */
- disabled?: boolean;
- /**
- * The id of the tab.
- * If not provided, it will be automatically generated.
- */
- id?: string;
- /**
- * Ref to the root slot's DOM element.
- */
- rootRef?: React.Ref;
-}
-
-export interface UseTabReturnValue {
- /**
- * Resolver for the root slot's props.
- * @param externalProps props for the root slot
- * @returns props that should be spread on the root slot
- */
- getRootProps: (
- externalProps?: React.ComponentPropsWithRef<'button'>,
- ) => React.ComponentPropsWithRef<'button'>;
- /**
- * 0-based index of the tab in the list of tabs.
- */
- index: number;
- orientation: TabsOrientation;
- /**
- * Ref to the root slot's DOM element.
- */
- rootRef: React.RefCallback | null;
- /**
- * If `true`, the tab is selected.
- */
- selected: boolean;
- /**
- * Total number of tabs in the nearest parent TabsList.
- * This can be used to determine if the tab is the last one to style it accordingly.
- */
- totalTabsCount: number;
-}
diff --git a/packages/mui-base/src/Tabs/Tab/useTab.ts b/packages/mui-base/src/Tabs/Tab/useTab.ts
index f04b4d7cf5..204ba177c2 100644
--- a/packages/mui-base/src/Tabs/Tab/useTab.ts
+++ b/packages/mui-base/src/Tabs/Tab/useTab.ts
@@ -1,7 +1,6 @@
'use client';
import * as React from 'react';
-import { UseTabParameters, UseTabReturnValue } from './Tab.types';
-import { useTabsContext } from '../Root/TabsContext';
+import { useTabsRootContext } from '../Root/TabsRootContext';
import { TabMetadata } from '../Root/useTabsRoot';
import { useCompoundItem } from '../../useCompound';
import { useListItem } from '../../useList';
@@ -9,18 +8,19 @@ import { useButton } from '../../useButton';
import { useId } from '../../utils/useId';
import { useForkRef } from '../../utils/useForkRef';
import { mergeReactProps } from '../../utils/mergeReactProps';
+import { TabsOrientation } from '../Root/TabsRoot';
function tabValueGenerator(otherTabValues: Set) {
return otherTabValues.size;
}
-function useTab(parameters: UseTabParameters): UseTabReturnValue {
+function useTab(parameters: useTab.Parameters): useTab.ReturnValue {
const { value: valueParam, rootRef: externalRef, disabled = false, id: idParam } = parameters;
const tabRef = React.useRef(null);
const id = useId(idParam);
- const { value: selectedValue, getTabPanelId, orientation } = useTabsContext();
+ const { value: selectedValue, getTabPanelId, orientation } = useTabsRootContext();
const tabMetadata = React.useMemo(() => ({ disabled, ref: tabRef, id }), [disabled, tabRef, id]);
@@ -76,4 +76,61 @@ function useTab(parameters: UseTabParameters): UseTabReturnValue {
};
}
+namespace useTab {
+ export interface Parameters {
+ /**
+ * The value of the tab.
+ * It's used to associate the tab with a tab panel(s) with the same value.
+ * If the value is not provided, it falls back to the position index.
+ */
+ value?: any;
+ /**
+ * Callback fired when the tab is clicked.
+ */
+ onClick?: React.MouseEventHandler;
+ /**
+ * If `true`, the tab will be disabled.
+ */
+ disabled?: boolean;
+ /**
+ * The id of the tab.
+ * If not provided, it will be automatically generated.
+ */
+ id?: string;
+ /**
+ * Ref to the root slot's DOM element.
+ */
+ rootRef?: React.Ref;
+ }
+
+ export interface ReturnValue {
+ /**
+ * Resolver for the root slot's props.
+ * @param externalProps props for the root slot
+ * @returns props that should be spread on the root slot
+ */
+ getRootProps: (
+ externalProps?: React.ComponentPropsWithRef<'button'>,
+ ) => React.ComponentPropsWithRef<'button'>;
+ /**
+ * 0-based index of the tab in the list of tabs.
+ */
+ index: number;
+ orientation: TabsOrientation;
+ /**
+ * Ref to the root slot's DOM element.
+ */
+ rootRef: React.RefCallback | null;
+ /**
+ * If `true`, the tab is selected.
+ */
+ selected: boolean;
+ /**
+ * Total number of tabs in the nearest parent TabsList.
+ * This can be used to determine if the tab is the last one to style it accordingly.
+ */
+ totalTabsCount: number;
+ }
+}
+
export { useTab };
diff --git a/packages/mui-base/src/Tabs/TabIndicator/TabIndicator.tsx b/packages/mui-base/src/Tabs/TabIndicator/TabIndicator.tsx
index 1ad3466f81..49d2594124 100644
--- a/packages/mui-base/src/Tabs/TabIndicator/TabIndicator.tsx
+++ b/packages/mui-base/src/Tabs/TabIndicator/TabIndicator.tsx
@@ -1,12 +1,13 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
-import { useTabIndicator } from './useTabIndicator';
-import { TabIndicatorOwnerState, TabIndicatorProps } from './TabIndicator.types';
+import { ActiveTabPosition, useTabIndicator } from './useTabIndicator';
import { script as prehydrationScript } from './prehydrationScript.min';
-import { useTabsContext } from '../Root/TabsContext';
+import type { TabsDirection, TabsOrientation, TabsRoot } from '../Root/TabsRoot';
+import { useTabsRootContext } from '../Root/TabsRootContext';
import { tabsStyleHookMapping } from '../Root/styleHooks';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import type { BaseUIComponentProps } from '../../utils/types';
const noop = () => null;
@@ -20,13 +21,13 @@ const noop = () => null;
*
* - [TabIndicator API](https://base-ui.netlify.app/components/react-tabs/#api-reference-TabIndicator)
*/
-const TabIndicator = React.forwardRef(
+const TabIndicator = React.forwardRef(
function TabIndicator(props, forwardedRef) {
const { className, render, renderBeforeHydration = false, ...other } = props;
const [instanceId] = React.useState(() => Math.random().toString(36).slice(2));
const [isMounted, setIsMounted] = React.useState(false);
- const { value: activeTabValue } = useTabsContext();
+ const { value: activeTabValue } = useTabsRootContext();
React.useEffect(() => {
setIsMounted(true);
@@ -40,7 +41,7 @@ const TabIndicator = React.forwardRef(
tabActivationDirection,
} = useTabIndicator();
- const ownerState: TabIndicatorOwnerState = {
+ const ownerState: TabIndicator.OwnerState = {
selectedTabPosition,
orientation,
direction,
@@ -83,6 +84,24 @@ const TabIndicator = React.forwardRef(
},
);
+namespace TabIndicator {
+ export interface OwnerState extends TabsRoot.OwnerState {
+ selectedTabPosition: ActiveTabPosition | null;
+ orientation: TabsOrientation;
+ direction: TabsDirection;
+ }
+
+ export interface Props extends BaseUIComponentProps<'span', TabIndicator.OwnerState> {
+ /**
+ * If `true`, the indicator will include code to render itself before React hydrates.
+ * This will minimize the time the indicator is not visible after the SSR-generated content is downloaded.
+ *
+ * @default false
+ */
+ renderBeforeHydration?: boolean;
+ }
+}
+
TabIndicator.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
diff --git a/packages/mui-base/src/Tabs/TabIndicator/TabIndicator.types.ts b/packages/mui-base/src/Tabs/TabIndicator/TabIndicator.types.ts
deleted file mode 100644
index 556c5af33b..0000000000
--- a/packages/mui-base/src/Tabs/TabIndicator/TabIndicator.types.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { BaseUIComponentProps } from '../../utils/types';
-import type {
- TabActivationDirection,
- TabsDirection,
- TabsOrientation,
- TabsRootOwnerState,
-} from '../Root/TabsRoot.types';
-
-export type TabIndicatorOwnerState = TabsRootOwnerState & {
- selectedTabPosition: ActiveTabPosition | null;
- orientation: TabsOrientation;
- direction: TabsDirection;
-};
-
-export interface TabIndicatorProps extends BaseUIComponentProps<'span', TabIndicatorOwnerState> {
- /**
- * If `true`, the indicator will include code to render itself before React hydrates.
- * This will minimize the time the indicator is not visible after the SSR-generated content is downloaded.
- *
- * @default false
- */
- renderBeforeHydration?: boolean;
-}
-
-export interface ActiveTabPosition {
- left: number;
- right: number;
- top: number;
- bottom: number;
-}
-
-export type UseTabIndicatorReturnValue = {
- getRootProps: (
- otherProps?: React.ComponentPropsWithRef<'span'>,
- ) => React.ComponentPropsWithRef<'span'>;
- activeTabPosition: ActiveTabPosition | null;
- direction: TabsDirection;
- orientation: TabsOrientation;
- tabActivationDirection: TabActivationDirection;
-};
diff --git a/packages/mui-base/src/Tabs/TabIndicator/useTabIndicator.ts b/packages/mui-base/src/Tabs/TabIndicator/useTabIndicator.ts
index 6991388e36..8571028952 100644
--- a/packages/mui-base/src/Tabs/TabIndicator/useTabIndicator.ts
+++ b/packages/mui-base/src/Tabs/TabIndicator/useTabIndicator.ts
@@ -1,18 +1,18 @@
'use client';
import * as React from 'react';
-import { UseTabIndicatorReturnValue } from './TabIndicator.types';
import { useTabsListContext } from '../TabsList/TabsListContext';
-import { useTabsContext } from '../Root/TabsContext';
+import { useTabsRootContext } from '../Root/TabsRootContext';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useForcedRerendering } from '../../utils/useForcedRerendering';
+import type { TabsDirection, TabsOrientation, TabActivationDirection } from '../Root/TabsRoot';
function round(value: number) {
return Math.round(value * 100) * 0.01;
}
-export function useTabIndicator(): UseTabIndicatorReturnValue {
+export function useTabIndicator(): useTabIndicator.ReturnValue {
const { tabsListRef, getTabElement } = useTabsListContext();
- const { orientation, direction, value, tabActivationDirection } = useTabsContext();
+ const { orientation, direction, value, tabActivationDirection } = useTabsRootContext();
const rerender = useForcedRerendering();
React.useEffect(() => {
@@ -114,3 +114,22 @@ export function useTabIndicator(): UseTabIndicatorReturnValue {
tabActivationDirection,
};
}
+
+export interface ActiveTabPosition {
+ left: number;
+ right: number;
+ top: number;
+ bottom: number;
+}
+
+export namespace useTabIndicator {
+ export interface ReturnValue {
+ getRootProps: (
+ otherProps?: React.ComponentPropsWithRef<'span'>,
+ ) => React.ComponentPropsWithRef<'span'>;
+ activeTabPosition: ActiveTabPosition | null;
+ direction: TabsDirection;
+ orientation: TabsOrientation;
+ tabActivationDirection: TabActivationDirection;
+ }
+}
diff --git a/packages/mui-base/src/Tabs/TabPanel/TabPanel.test.tsx b/packages/mui-base/src/Tabs/TabPanel/TabPanel.test.tsx
index bda8b77f36..b17e0b994b 100644
--- a/packages/mui-base/src/Tabs/TabPanel/TabPanel.test.tsx
+++ b/packages/mui-base/src/Tabs/TabPanel/TabPanel.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as Tabs from '@base_ui/react/Tabs';
-import { TabsProvider, TabsProviderValue } from '@base_ui/react/Tabs';
import { createRenderer, describeConformance } from '#test-utils';
+import { TabsProvider, TabsProviderValue } from '../Root/TabsProvider';
describe('', () => {
const { render } = createRenderer();
diff --git a/packages/mui-base/src/Tabs/TabPanel/TabPanel.tsx b/packages/mui-base/src/Tabs/TabPanel/TabPanel.tsx
index 4d26218618..d01692b243 100644
--- a/packages/mui-base/src/Tabs/TabPanel/TabPanel.tsx
+++ b/packages/mui-base/src/Tabs/TabPanel/TabPanel.tsx
@@ -2,9 +2,10 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useTabPanel } from './useTabPanel';
-import { TabPanelOwnerState, TabPanelProps } from './TabPanel.types';
import { tabsStyleHookMapping } from '../Root/styleHooks';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { TabsRoot } from '../Root/TabsRoot';
+import type { BaseUIComponentProps } from '../../utils/types';
/**
*
@@ -17,7 +18,7 @@ import { useComponentRenderer } from '../../utils/useComponentRenderer';
* - [TabPanel API](https://base-ui.netlify.app/components/react-tabs/#api-reference-TabPanel)
*/
const TabPanel = React.forwardRef(function TabPanel(
- props: TabPanelProps,
+ props: TabPanel.Props,
forwardedRef: React.ForwardedRef,
) {
const { children, className, value, render, keepMounted = false, ...other } = props;
@@ -27,7 +28,7 @@ const TabPanel = React.forwardRef(function TabPanel(
rootRef: forwardedRef,
});
- const ownerState: TabPanelOwnerState = {
+ const ownerState: TabPanel.OwnerState = {
hidden,
orientation,
direction,
@@ -46,6 +47,26 @@ const TabPanel = React.forwardRef(function TabPanel(
return renderElement();
});
+namespace TabPanel {
+ export interface OwnerState extends TabsRoot.OwnerState {
+ hidden: boolean;
+ }
+
+ export interface Props extends BaseUIComponentProps<'div', OwnerState> {
+ /**
+ * The value of the TabPanel. It will be shown when the Tab with the corresponding value is selected.
+ * If not provided, it will fall back to the index of the panel.
+ * It is recommended to explicitly provide it, as it's required for the tab panel to be rendered on the server.
+ */
+ value?: any;
+ /**
+ * If `true`, keeps the contents of the hidden TabPanel in the DOM.
+ * @default false
+ */
+ keepMounted?: boolean;
+ }
+}
+
TabPanel.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
diff --git a/packages/mui-base/src/Tabs/TabPanel/TabPanel.types.ts b/packages/mui-base/src/Tabs/TabPanel/TabPanel.types.ts
deleted file mode 100644
index edeeb851ed..0000000000
--- a/packages/mui-base/src/Tabs/TabPanel/TabPanel.types.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type {
- TabActivationDirection,
- TabsDirection,
- TabsOrientation,
- TabsRootOwnerState,
-} from '../Root/TabsRoot.types';
-import type { BaseUIComponentProps } from '../../utils/types';
-
-export type TabPanelOwnerState = TabsRootOwnerState & {
- hidden: boolean;
-};
-
-export interface TabPanelProps extends BaseUIComponentProps<'div', TabPanelOwnerState> {
- /**
- * The value of the TabPanel. It will be shown when the Tab with the corresponding value is selected.
- * If not provided, it will fall back to the index of the panel.
- * It is recommended to explicitly provide it, as it's required for the tab panel to be rendered on the server.
- */
- value?: any;
- /**
- * If `true`, keeps the contents of the hidden TabPanel in the DOM.
- * @default false
- */
- keepMounted?: boolean;
-}
-
-export interface UseTabPanelParameters {
- /**
- * The id of the TabPanel.
- */
- id?: string;
- /**
- * The ref of the TabPanel.
- */
- rootRef?: React.Ref;
- /**
- * The value of the TabPanel. It will be shown when the Tab with the corresponding value is selected.
- */
- value?: any;
-}
-
-export interface UseTabPanelReturnValue {
- /**
- * If `true`, it indicates that the tab panel will be hidden.
- */
- hidden: boolean;
- /**
- * Resolver for the root slot's props.
- * @param externalProps additional props for the root slot
- * @returns props that should be spread on the root slot
- */
- getRootProps: (
- externalProps?: React.ComponentPropsWithRef<'div'>,
- ) => React.ComponentPropsWithRef<'div'>;
- rootRef: React.RefCallback | null;
- orientation: TabsOrientation;
- direction: TabsDirection;
- tabActivationDirection: TabActivationDirection;
-}
diff --git a/packages/mui-base/src/Tabs/TabPanel/useTabPanel.test.tsx b/packages/mui-base/src/Tabs/TabPanel/useTabPanel.test.tsx
index 48fda35afe..3e9b644a92 100644
--- a/packages/mui-base/src/Tabs/TabPanel/useTabPanel.test.tsx
+++ b/packages/mui-base/src/Tabs/TabPanel/useTabPanel.test.tsx
@@ -3,6 +3,7 @@ import { expect } from 'chai';
import { spy } from 'sinon';
import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils';
import * as Tabs from '@base_ui/react/Tabs';
+import { useTabPanel } from './useTabPanel';
describe('useTabPanel', () => {
const { render } = createRenderer();
@@ -11,7 +12,7 @@ describe('useTabPanel', () => {
it('returns props for root slot', () => {
const rootRef = React.createRef();
function TestTabPanel() {
- const { getRootProps } = Tabs.useTabPanel({ rootRef, id: 'test-tabpanel', value: 0 });
+ const { getRootProps } = useTabPanel({ rootRef, id: 'test-tabpanel', value: 0 });
return ;
}
@@ -34,7 +35,7 @@ describe('useTabPanel', () => {
const rootRef = React.createRef();
function TestTabPanel() {
- const { getRootProps } = Tabs.useTabPanel({ rootRef, value: 0 });
+ const { getRootProps } = useTabPanel({ rootRef, value: 0 });
return (
) {
return otherTabPanelValues.size;
}
-function useTabPanel(parameters: UseTabPanelParameters): UseTabPanelReturnValue {
+function useTabPanel(parameters: useTabPanel.Parameters): useTabPanel.ReturnValue {
const { value: valueParam, id: idParam, rootRef: externalRef } = parameters;
const {
value: selectedTabValue,
@@ -19,7 +19,7 @@ function useTabPanel(parameters: UseTabPanelParameters): UseTabPanelReturnValue
orientation,
direction,
tabActivationDirection,
- } = useTabsContext();
+ } = useTabsRootContext();
const id = useId(idParam);
const ref = React.useRef
(null);
@@ -58,4 +58,40 @@ function useTabPanel(parameters: UseTabPanelParameters): UseTabPanelReturnValue
};
}
+namespace useTabPanel {
+ export interface Parameters {
+ /**
+ * The id of the TabPanel.
+ */
+ id?: string;
+ /**
+ * The ref of the TabPanel.
+ */
+ rootRef?: React.Ref;
+ /**
+ * The value of the TabPanel. It will be shown when the Tab with the corresponding value is selected.
+ */
+ value?: any;
+ }
+
+ export interface ReturnValue {
+ /**
+ * If `true`, it indicates that the tab panel will be hidden.
+ */
+ hidden: boolean;
+ /**
+ * Resolver for the root slot's props.
+ * @param externalProps additional props for the root slot
+ * @returns props that should be spread on the root slot
+ */
+ getRootProps: (
+ externalProps?: React.ComponentPropsWithRef<'div'>,
+ ) => React.ComponentPropsWithRef<'div'>;
+ rootRef: React.RefCallback | null;
+ orientation: TabsOrientation;
+ direction: TabsDirection;
+ tabActivationDirection: TabActivationDirection;
+ }
+}
+
export { useTabPanel };
diff --git a/packages/mui-base/src/Tabs/TabsList/TabsList.test.tsx b/packages/mui-base/src/Tabs/TabsList/TabsList.test.tsx
index 1979841892..3c38e7ff34 100644
--- a/packages/mui-base/src/Tabs/TabsList/TabsList.test.tsx
+++ b/packages/mui-base/src/Tabs/TabsList/TabsList.test.tsx
@@ -2,8 +2,8 @@ import * as React from 'react';
import { expect } from 'chai';
import { act } from '@mui/internal-test-utils';
import * as Tabs from '@base_ui/react/Tabs';
-import { TabsContext } from '@base_ui/react/Tabs';
import { createRenderer, describeConformance } from '#test-utils';
+import { TabsRootContext } from '../Root/TabsRootContext';
describe('', () => {
const { render } = createRenderer();
@@ -11,7 +11,7 @@ describe('', () => {
describeConformance(, () => ({
render: (node) => {
return render(
- {},
@@ -24,7 +24,7 @@ describe('', () => {
}}
>
{node}
- ,
+ ,
);
},
refInstanceof: window.HTMLDivElement,
diff --git a/packages/mui-base/src/Tabs/TabsList/TabsList.tsx b/packages/mui-base/src/Tabs/TabsList/TabsList.tsx
index 7cc96d2771..140f0a8d9d 100644
--- a/packages/mui-base/src/Tabs/TabsList/TabsList.tsx
+++ b/packages/mui-base/src/Tabs/TabsList/TabsList.tsx
@@ -1,11 +1,12 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
-import { TabsListOwnerState, TabsListProps } from './TabsList.types';
import { useTabsList } from './useTabsList';
import { TabsListProvider } from './TabsListProvider';
import { tabsStyleHookMapping } from '../Root/styleHooks';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
+import { TabsRoot } from '../Root/TabsRoot';
+import { BaseUIComponentProps } from '../../utils/types';
/**
*
@@ -18,7 +19,7 @@ import { useComponentRenderer } from '../../utils/useComponentRenderer';
* - [TabsList API](https://base-ui.netlify.app/components/react-tabs/#api-reference-TabsList)
*/
const TabsList = React.forwardRef(function TabsList(
- props: TabsListProps,
+ props: TabsList.Props,
forwardedRef: React.ForwardedRef,
) {
const { activateOnFocus = true, className, loop = true, render, ...other } = props;
@@ -30,7 +31,7 @@ const TabsList = React.forwardRef(function TabsList(
activateOnFocus,
});
- const ownerState: TabsListOwnerState = React.useMemo(
+ const ownerState: TabsList.OwnerState = React.useMemo(
() => ({
direction,
orientation,
@@ -51,6 +52,26 @@ const TabsList = React.forwardRef(function TabsList(
return {renderElement()};
});
+namespace TabsList {
+ export type OwnerState = TabsRoot.OwnerState;
+
+ export interface Props extends BaseUIComponentProps<'div', TabsList.OwnerState> {
+ /**
+ * If `true`, the tab will be activated whenever it is focused.
+ * Otherwise, it has to be activated by clicking or pressing the Enter or Space key.
+ *
+ * @default true
+ */
+ activateOnFocus?: boolean;
+ /**
+ * If `true`, using keyboard navigation will wrap focus to the other end of the list once the end is reached.
+ *
+ * @default true
+ */
+ loop?: boolean;
+ }
+}
+
TabsList.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
diff --git a/packages/mui-base/src/Tabs/TabsList/TabsList.types.ts b/packages/mui-base/src/Tabs/TabsList/TabsList.types.ts
deleted file mode 100644
index 22d8c8bc3f..0000000000
--- a/packages/mui-base/src/Tabs/TabsList/TabsList.types.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { TabsListProviderValue } from './TabsListProvider';
-import {
- TabActivationDirection,
- TabsDirection,
- TabsOrientation,
- TabsRootOwnerState,
-} from '../Root/TabsRoot.types';
-import { ListAction } from '../../useList';
-import { BaseUIComponentProps } from '../../utils/types';
-
-export type TabsListOwnerState = TabsRootOwnerState;
-
-export interface TabsListProps extends BaseUIComponentProps<'div', TabsListOwnerState> {
- /**
- * If `true`, the tab will be activated whenever it is focused.
- * Otherwise, it has to be activated by clicking or pressing the Enter or Space key.
- *
- * @default true
- */
- activateOnFocus?: boolean;
- /**
- * If `true`, using keyboard navigation will wrap focus to the other end of the list once the end is reached.
- *
- * @default true
- */
- loop?: boolean;
-}
-
-export interface UseTabsListParameters {
- /**
- * If `true`, the tab will be activated whenever it is focused.
- * Otherwise, it has to be activated by clicking or pressing the Enter or Space key.
- */
- activateOnFocus: boolean;
- /**
- * If `true`, using keyboard navigation will wrap focus to the other end of the list once the end is reached.
- */
- loop: boolean;
- /**
- * Ref to the root element.
- */
- rootRef: React.Ref;
-}
-
-export interface UseTabsListReturnValue {
- /**
- * The value to be passed to the TabListProvider above all the tabs.
- */
- contextValue: TabsListProviderValue;
- /**
- * Action dispatcher for the tabs list component.
- * Allows to programmatically control the tabs list.
- */
- dispatch: (action: ListAction) => void;
- /**
- * Resolver for the root slot's props.
- * @param externalProps props for the root slot
- * @returns props that should be spread on the root slot
- */
- getRootProps: (
- externalProps?: React.ComponentPropsWithRef<'div'>,
- ) => React.ComponentPropsWithRef<'div'>;
- /**
- * The value of the currently highlighted tab.
- */
- highlightedValue: any | null;
- /**
- * If `true`, it will indicate that the text's direction in right-to-left.
- */
- direction: TabsDirection;
- /**
- * The component orientation (layout flow direction).
- */
- orientation: TabsOrientation;
- rootRef: React.RefCallback | null;
- /**
- * The value of the currently selected tab.
- */
- selectedValue: any | null;
- tabActivationDirection: TabActivationDirection;
-}
-
-export const TabsListActionTypes = {
- valueChange: 'valueChange',
-} as const;
-
-export interface ValueChangeAction {
- type: typeof TabsListActionTypes.valueChange;
- value: any | null;
-}
diff --git a/packages/mui-base/src/Tabs/TabsList/TabsListContext.ts b/packages/mui-base/src/Tabs/TabsList/TabsListContext.ts
index c8d8a6ddf1..9b2833529d 100644
--- a/packages/mui-base/src/Tabs/TabsList/TabsListContext.ts
+++ b/packages/mui-base/src/Tabs/TabsList/TabsListContext.ts
@@ -1,13 +1,13 @@
'use client';
import * as React from 'react';
-export type TabsListContextValue = {
+export interface TabsListContext {
activateOnFocus: boolean;
getTabElement: (value: any) => HTMLElement | null;
tabsListRef: React.RefObject;
-};
+}
-export const TabsListContext = React.createContext(undefined);
+export const TabsListContext = React.createContext(undefined);
export function useTabsListContext() {
const context = React.useContext(TabsListContext);
diff --git a/packages/mui-base/src/Tabs/TabsList/TabsListProvider.tsx b/packages/mui-base/src/Tabs/TabsList/TabsListProvider.tsx
index a7e042824f..1e2a319e92 100644
--- a/packages/mui-base/src/Tabs/TabsList/TabsListProvider.tsx
+++ b/packages/mui-base/src/Tabs/TabsList/TabsListProvider.tsx
@@ -1,7 +1,7 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
-import { TabsListContext, TabsListContextValue } from './TabsListContext';
+import { TabsListContext } from './TabsListContext';
import { TabMetadata } from '../Root/useTabsRoot';
import { ListContext, ListContextValue } from '../../useList/ListContext';
import { CompoundComponentContext, CompoundComponentContextValue } from '../../useCompound';
@@ -9,7 +9,7 @@ import { refType } from '../../utils/proptypes';
export type TabsListProviderValue = CompoundComponentContextValue &
ListContextValue &
- TabsListContextValue;
+ TabsListContext;
export interface TabsListProviderProps {
value: TabsListProviderValue;
@@ -53,7 +53,7 @@ function TabsListProvider(props: TabsListProviderProps) {
[registerItem, getItemIndex, totalSubitemCount],
);
- const tabsListContextValue: TabsListContextValue = React.useMemo(
+ const tabsListContextValue: TabsListContext = React.useMemo(
() => ({ activateOnFocus, getTabElement, tabsListRef }),
[activateOnFocus, getTabElement, tabsListRef],
);
diff --git a/packages/mui-base/src/Tabs/TabsList/tabsListReducer.ts b/packages/mui-base/src/Tabs/TabsList/tabsListReducer.ts
index e337803e9c..903bed29a6 100644
--- a/packages/mui-base/src/Tabs/TabsList/tabsListReducer.ts
+++ b/packages/mui-base/src/Tabs/TabsList/tabsListReducer.ts
@@ -1,4 +1,3 @@
-import { TabsListActionTypes, ValueChangeAction } from './TabsList.types';
import {
ListState,
ListAction,
@@ -13,6 +12,15 @@ export type TabsListActionContext = ListActionContext & {
activateOnFocus: boolean;
};
+export const TabsListActionTypes = {
+ valueChange: 'valueChange',
+} as const;
+
+export interface ValueChangeAction {
+ type: typeof TabsListActionTypes.valueChange;
+ value: any | null;
+}
+
export function tabsListReducer(
state: ListState,
action: ActionWithContext | ValueChangeAction, TabsListActionContext>,
diff --git a/packages/mui-base/src/Tabs/TabsList/useTabsList.ts b/packages/mui-base/src/Tabs/TabsList/useTabsList.ts
index 07fe4272f6..5ecbbbb81f 100644
--- a/packages/mui-base/src/Tabs/TabsList/useTabsList.ts
+++ b/packages/mui-base/src/Tabs/TabsList/useTabsList.ts
@@ -1,22 +1,18 @@
'use client';
import * as React from 'react';
-import {
- TabsListActionTypes,
- type UseTabsListParameters,
- type UseTabsListReturnValue,
- type ValueChangeAction,
-} from './TabsList.types';
-import { tabsListReducer } from './tabsListReducer';
-import { useTabsContext } from '../Root/TabsContext';
+import { TabsListActionTypes, tabsListReducer, ValueChangeAction } from './tabsListReducer';
+import { useTabsRootContext } from '../Root/TabsRootContext';
import { type TabMetadata } from '../Root/useTabsRoot';
-import { type TabsOrientation, type TabActivationDirection } from '../Root/TabsRoot.types';
+import { type TabsOrientation, type TabActivationDirection } from '../Root/TabsRoot';
import { useCompoundParent } from '../../useCompound';
-import { useList, ListState, UseListParameters } from '../../useList';
+import { useList, ListState, UseListParameters, ListAction } from '../../useList';
import { useForkRef } from '../../utils/useForkRef';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useEnhancedEffect } from '../../utils/useEnhancedEffect';
+import { TabsDirection } from '../Root/TabsRoot';
+import { TabsListProviderValue } from './TabsListProvider';
-function useTabsList(parameters: UseTabsListParameters): UseTabsListReturnValue {
+function useTabsList(parameters: useTabsList.Parameters): useTabsList.ReturnValue {
const { rootRef: externalRef, loop, activateOnFocus } = parameters;
const {
@@ -26,7 +22,7 @@ function useTabsList(parameters: UseTabsListParameters): UseTabsListReturnValue
value,
registerTabIdLookup,
tabActivationDirection,
- } = useTabsContext();
+ } = useTabsRootContext();
const { subitems, contextValue: compoundComponentContextValue } = useCompoundParent<
any,
@@ -263,4 +259,60 @@ function useActivationDirectionDetector(
);
}
+namespace useTabsList {
+ export interface Parameters {
+ /**
+ * If `true`, the tab will be activated whenever it is focused.
+ * Otherwise, it has to be activated by clicking or pressing the Enter or Space key.
+ */
+ activateOnFocus: boolean;
+ /**
+ * If `true`, using keyboard navigation will wrap focus to the other end of the list once the end is reached.
+ */
+ loop: boolean;
+ /**
+ * Ref to the root element.
+ */
+ rootRef: React.Ref;
+ }
+
+ export interface ReturnValue {
+ /**
+ * The value to be passed to the TabListProvider above all the tabs.
+ */
+ contextValue: TabsListProviderValue;
+ /**
+ * Action dispatcher for the tabs list component.
+ * Allows to programmatically control the tabs list.
+ */
+ dispatch: (action: ListAction) => void;
+ /**
+ * Resolver for the root slot's props.
+ * @param externalProps props for the root slot
+ * @returns props that should be spread on the root slot
+ */
+ getRootProps: (
+ externalProps?: React.ComponentPropsWithRef<'div'>,
+ ) => React.ComponentPropsWithRef<'div'>;
+ /**
+ * The value of the currently highlighted tab.
+ */
+ highlightedValue: any | null;
+ /**
+ * If `true`, it will indicate that the text's direction in right-to-left.
+ */
+ direction: TabsDirection;
+ /**
+ * The component orientation (layout flow direction).
+ */
+ orientation: TabsOrientation;
+ rootRef: React.RefCallback | null;
+ /**
+ * The value of the currently selected tab.
+ */
+ selectedValue: any | null;
+ tabActivationDirection: TabActivationDirection;
+ }
+}
+
export { useTabsList };
diff --git a/packages/mui-base/src/Tabs/index.barrel.ts b/packages/mui-base/src/Tabs/index.barrel.ts
index 04656786ba..d8c499e9e6 100644
--- a/packages/mui-base/src/Tabs/index.barrel.ts
+++ b/packages/mui-base/src/Tabs/index.barrel.ts
@@ -1,43 +1,5 @@
export { TabsRoot } from './Root/TabsRoot';
-export type {
- TabsRootOwnerState,
- TabsRootProps,
- TabsDirection,
- TabsOrientation,
- UseTabsParameters,
- UseTabsReturnValue,
-} from './Root/TabsRoot.types';
-export { useTabsRoot } from './Root/useTabsRoot';
-export { TabsContext, type TabsContextValue, useTabsContext } from './Root/TabsContext';
-export * from './Root/TabsProvider';
-
export { Tab } from './Tab/Tab';
-export type { TabOwnerState, TabProps, UseTabParameters, UseTabReturnValue } from './Tab/Tab.types';
-export { useTab } from './Tab/useTab';
-
export { TabIndicator } from './TabIndicator/TabIndicator';
-export type {
- TabIndicatorOwnerState,
- TabIndicatorProps,
- UseTabIndicatorReturnValue,
-} from './TabIndicator/TabIndicator.types';
-export { useTabIndicator } from './TabIndicator/useTabIndicator';
-
export { TabPanel } from './TabPanel/TabPanel';
-export type {
- TabPanelOwnerState,
- TabPanelProps,
- UseTabPanelParameters,
- UseTabPanelReturnValue,
-} from './TabPanel/TabPanel.types';
-export { useTabPanel } from './TabPanel/useTabPanel';
-
export { TabsList } from './TabsList/TabsList';
-export type {
- TabsListOwnerState,
- TabsListProps,
- UseTabsListParameters,
- UseTabsListReturnValue,
-} from './TabsList/TabsList.types';
-export * from './TabsList/TabsListProvider';
-export { useTabsList } from './TabsList/useTabsList';
diff --git a/packages/mui-base/src/Tabs/index.ts b/packages/mui-base/src/Tabs/index.ts
index a204765d0d..acb9d122d3 100644
--- a/packages/mui-base/src/Tabs/index.ts
+++ b/packages/mui-base/src/Tabs/index.ts
@@ -1,43 +1,5 @@
export { TabsRoot as Root } from './Root/TabsRoot';
-export type {
- TabsRootOwnerState as RootOwnerState,
- TabsRootProps as RootProps,
- TabsDirection as Direction,
- TabsOrientation as Orientation,
- UseTabsParameters,
- UseTabsReturnValue,
-} from './Root/TabsRoot.types';
-export { useTabsRoot } from './Root/useTabsRoot';
-export { TabsContext, type TabsContextValue, useTabsContext } from './Root/TabsContext';
-export * from './Root/TabsProvider';
-
export { Tab } from './Tab/Tab';
-export type { TabOwnerState, TabProps, UseTabParameters, UseTabReturnValue } from './Tab/Tab.types';
-export { useTab } from './Tab/useTab';
-
export { TabIndicator as Indicator } from './TabIndicator/TabIndicator';
-export type {
- TabIndicatorOwnerState as IndicatorOwnerState,
- TabIndicatorProps as IndicatorProps,
- UseTabIndicatorReturnValue,
-} from './TabIndicator/TabIndicator.types';
-export { useTabIndicator } from './TabIndicator/useTabIndicator';
-
export { TabPanel as Panel } from './TabPanel/TabPanel';
-export type {
- TabPanelOwnerState as PanelOwnerState,
- TabPanelProps as PanelProps,
- UseTabPanelParameters,
- UseTabPanelReturnValue,
-} from './TabPanel/TabPanel.types';
-export { useTabPanel } from './TabPanel/useTabPanel';
-
export { TabsList as List } from './TabsList/TabsList';
-export type {
- TabsListOwnerState as ListOwnerState,
- TabsListProps as ListProps,
- UseTabsListParameters,
- UseTabsListReturnValue,
-} from './TabsList/TabsList.types';
-export * from './TabsList/TabsListProvider';
-export { useTabsList } from './TabsList/useTabsList';