Add docs for tabs
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { TabsSnippet } from '@/snippets/stories-snippets';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Tabs } from '@/components/Tabs';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
tabsRootPropDefs,
|
||||
tabsListPropDefs,
|
||||
tabsTabPropDefs,
|
||||
tabsPanelPropDefs,
|
||||
} from './props';
|
||||
import { BaseUI } from '@/components/HeadlessBanners/BaseUI';
|
||||
|
||||
# Tabs
|
||||
|
||||
A component for toggling between related panels on the same page.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<TabsSnippet story="Default" />}
|
||||
code={`<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Tab 1</Tabs.Tab>
|
||||
<Tabs.Tab>Tab 2</Tabs.Tab>
|
||||
<Tabs.Tab>Tab 3 With long title</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>Content for Tab 1</Tabs.Panel>
|
||||
<Tabs.Panel>Content for Tab 2</Tabs.Panel>
|
||||
<Tabs.Panel>Content for Tab 3</Tabs.Panel>
|
||||
</Tabs.Root>`}
|
||||
/>
|
||||
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Usage</Tabs.Tab>
|
||||
<Tabs.Tab>Theming</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>
|
||||
<CodeBlock
|
||||
code={`import { Tabs } from '@backstage/canon';
|
||||
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Tab 1</Tabs.Tab>
|
||||
<Tabs.Tab>Tab 2</Tabs.Tab>
|
||||
<Tabs.Tab>Tab 3</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>Content for Tab 1</Tabs.Panel>
|
||||
<Tabs.Panel>Content for Tab 2</Tabs.Panel>
|
||||
<Tabs.Panel>Content for Tab 3</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
`}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel>
|
||||
We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with
|
||||
your brand. For additional flexibility, you can use the provided class names for each element listed below.
|
||||
- `canon-TabsRoot`
|
||||
- `canon-TabsList`
|
||||
- `canon-TabsTab`
|
||||
- `canon-TabsPanel`
|
||||
- `canon-TabsIndicator`
|
||||
</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
|
||||
## API reference
|
||||
|
||||
<BaseUI href="https://base-ui.com/react/components/menu" />
|
||||
|
||||
### Tabs.Root
|
||||
|
||||
Groups the tabs and the corresponding panels. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={tabsRootPropDefs} />
|
||||
|
||||
### Tabs.List
|
||||
|
||||
Groups the individual tab buttons. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={tabsListPropDefs} />
|
||||
|
||||
### Tabs.Tab
|
||||
|
||||
An individual interactive tab button that toggles the corresponding panel. Renders a `<button>` element.
|
||||
|
||||
<PropsTable data={tabsTabPropDefs} />
|
||||
|
||||
### Tabs.Panel
|
||||
|
||||
A panel displayed when the corresponding tab is active. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={tabsPanelPropDefs} />
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
} from '../../../../utils/propDefs';
|
||||
import type { PropDef } from '../../../../utils/propDefs';
|
||||
|
||||
export const tabsRootPropDefs: Record<string, PropDef> = {
|
||||
defaultValue: {
|
||||
type: 'enum',
|
||||
values: ['any'],
|
||||
default: '0',
|
||||
},
|
||||
value: {
|
||||
type: 'enum',
|
||||
values: ['any'],
|
||||
},
|
||||
onValueChange: {
|
||||
type: 'enum',
|
||||
values: [`((value) => void)`],
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tabsListPropDefs: Record<string, PropDef> = {
|
||||
activateOnFocus: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
loop: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tabsTabPropDefs: Record<string, PropDef> = {
|
||||
value: {
|
||||
type: 'string',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tabsPanelPropDefs: Record<string, PropDef> = {
|
||||
value: {
|
||||
type: 'string',
|
||||
},
|
||||
keepMounted: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -17,6 +17,7 @@ import * as MenuStories from '../../../packages/canon/src/components/Menu/Menu.s
|
||||
import * as LinkStories from '../../../packages/canon/src/components/Link/Link.stories';
|
||||
import * as AvatarStories from '../../../packages/canon/src/components/Avatar/Avatar.stories';
|
||||
import * as CollapsibleStories from '../../../packages/canon/src/components/Collapsible/Collapsible.stories';
|
||||
import * as TabsStories from '../../../packages/canon/src/components/Tabs/Tabs.stories';
|
||||
|
||||
export const BoxSnippet = ({ story }: { story: keyof typeof BoxStories }) => {
|
||||
const stories = composeStories(BoxStories);
|
||||
@@ -165,3 +166,10 @@ export const CollapsibleSnippet = ({
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
export const TabsSnippet = ({ story }: { story: keyof typeof TabsStories }) => {
|
||||
const stories = composeStories(TabsStories);
|
||||
const StoryComponent = stories[story as keyof typeof stories];
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
@@ -121,6 +121,11 @@ export const components: Page[] = [
|
||||
slug: 'table',
|
||||
status: 'inProgress',
|
||||
},
|
||||
{
|
||||
title: 'Tabs',
|
||||
slug: 'tabs',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Text',
|
||||
slug: 'text',
|
||||
|
||||
@@ -1235,8 +1235,7 @@ export interface TableCellTextProps
|
||||
// @public (undocumented)
|
||||
export const Tabs: {
|
||||
Root: ForwardRefExoticComponent<
|
||||
Omit<Tabs_2.Root.Props & RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
RefAttributes<HTMLDivElement>
|
||||
TabsRootWithoutOrientation & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
List: ForwardRefExoticComponent<
|
||||
Omit<Tabs_2.List.Props & RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
@@ -1252,6 +1251,13 @@ export const Tabs: {
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TabsRootWithoutOrientation
|
||||
extends Omit<
|
||||
React.ComponentPropsWithoutRef<typeof Tabs_2.Root>,
|
||||
'orientation'
|
||||
> {}
|
||||
|
||||
// @public (undocumented)
|
||||
const Text_2: ForwardRefExoticComponent<
|
||||
TextProps & RefAttributes<HTMLParagraphElement>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Tabs } from './Tabs';
|
||||
import { Text } from '../../index';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Tabs',
|
||||
@@ -26,6 +25,11 @@ const meta = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const panelStyles = {
|
||||
padding: 'var(--canon-space-3)',
|
||||
fontSize: 'var(--canon-font-size-2)',
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
@@ -35,15 +39,9 @@ export const Default: Story = {
|
||||
<Tabs.Tab>Tab 2</Tabs.Tab>
|
||||
<Tabs.Tab>Tab 3 With long title</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel style={{ padding: 'var(--canon-space-3' }}>
|
||||
<Text>Content for Tab 1</Text>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel style={{ padding: 'var(--canon-space-3' }}>
|
||||
<Text>Content for Tab 2</Text>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel style={{ padding: 'var(--canon-space-3' }}>
|
||||
<Text>Content for Tab 3</Text>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel style={panelStyles}>Content for Tab 1</Tabs.Panel>
|
||||
<Tabs.Panel style={panelStyles}>Content for Tab 2</Tabs.Panel>
|
||||
<Tabs.Panel style={panelStyles}>Content for Tab 3</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { Tabs as TabsPrimitive } from '@base-ui-components/react/tabs';
|
||||
import type { TabsRootWithoutOrientation } from './types';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const TabsRoot = forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
|
||||
TabsRootWithoutOrientation
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Root
|
||||
ref={ref}
|
||||
|
||||
@@ -13,4 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Tabs } from './Tabs';
|
||||
export type { TabsRootWithoutOrientation } from './types';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Tabs } from '@base-ui-components/react/tabs';
|
||||
|
||||
// We are not supporting the orientation prop, so we need to omit it
|
||||
|
||||
/** @public */
|
||||
export interface TabsRootWithoutOrientation
|
||||
extends Omit<
|
||||
React.ComponentPropsWithoutRef<typeof Tabs.Root>,
|
||||
'orientation'
|
||||
> {}
|
||||
Reference in New Issue
Block a user