Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"import": "import { TabIndicator } from '@coinbase/cds-mobile/tabs/TabIndicator'",
"source": "https://github.com/coinbase/cds/blob/master/packages/mobile/src/tabs/TabIndicator.tsx",
"description": "A visual indicator that shows the active tab position.",
"warning": "This component is deprecated along with the TabNavigation component. Please use the Tabs component and DefaultTabsActiveIndicator instead.",
"relatedComponents": [
{
"label": "TabNavigation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"import": "import { TabIndicator } from '@coinbase/cds-web/tabs/TabIndicator'",
"source": "https://github.com/coinbase/cds/blob/master/packages/web/src/tabs/TabIndicator.tsx",
"storybook": "https://cds-storybook.coinbase.com/?path=/story/components-tabs-tabindicator--default",
"warning": "This component is deprecated along with the TabNavigation component. Please use the Tabs component and DefaultTabsActiveIndicator instead.",
"description": "A visual indicator that shows the active tab position.",
"relatedComponents": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"import": "import { TabLabel } from '@coinbase/cds-mobile/tabs/TabLabel'",
"source": "https://github.com/coinbase/cds/blob/master/packages/mobile/src/tabs/TabLabel.tsx",
"description": "A text label component used within tab navigation.",
"warning": "This component is deprecated along with the TabNavigation component. Please use the Tabs component and DefaultTab instead.",
"relatedComponents": [
{
"label": "TabNavigation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"source": "https://github.com/coinbase/cds/blob/master/packages/web/src/tabs/TabLabel.tsx",
"storybook": "https://cds-storybook.coinbase.com/?path=/story/components-tabs-tablabel--default",
"description": "A text label component used within tab navigation.",
"warning": "This component is deprecated along with the TabNavigation component. Please use the Tabs component and DefaultTab instead.",
"relatedComponents": [
{
"label": "TabNavigation",
Expand Down
158 changes: 59 additions & 99 deletions apps/docs/docs/components/navigation/Tabs/_mobileExamples.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
Tabs is a low-level primitive for building custom tab interfaces. It requires a `TabComponent` and `TabsActiveIndicatorComponent` to render. For a ready-to-use tab experience, see [SegmentedTabs](/components/navigation/SegmentedTabs).
Tabs manages which tab is active and positions the animated indicator. For the common **underline** pattern, pass **`TabsActiveIndicatorComponent={DefaultTabsActiveIndicator}`** and rely on the default **`TabComponent` (`DefaultTab`)**. Use a custom **`TabComponent`** when you need layout or content beyond what `DefaultTab` provides. For **pill / segmented** controls, use [SegmentedTabs](/components/navigation/SegmentedTabs/) instead.

## Basics

### Initial Value

Use `useTabsContext` inside your `TabComponent` to access the active tab state. Pair with [TabLabel](/components/navigation/TabLabel) for consistent label styling and [TabsActiveIndicator](/components/navigation/TabIndicator) for the animated indicator.
Out of the box, **`Tabs`** uses **`DefaultTab`** for each row (headline text, optional [DotCount](/components/other/DotCount/) via `count` / `max` on each tab) and **`DefaultTabsActiveIndicator`** for the animated underline. **`activeBackground`** sets the **underline color** (it is forwarded to the indicator as its `background` token).

```jsx
function Example() {
Expand All @@ -13,44 +11,28 @@ function Example() {
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab3', label: 'Tab 3' },
];

const TabComponent = useCallback(({ id, label, disabled }) => {
const { activeTab, updateActiveTab } = useTabsContext();
const isActive = activeTab?.id === id;
return (
<Pressable
onPress={() => updateActiveTab(id)}
disabled={disabled}
accessibilityRole="tab"
accessibilityState={{ selected: isActive, disabled }}
>
<TabLabel id={id} active={isActive}>
{label}
</TabLabel>
</Pressable>
);
}, []);

const ActiveIndicator = useCallback(
(props) => <TabsActiveIndicator {...props} background="bgPrimary" bottom={0} height={2} />,
[],
);

const [activeTab, setActiveTab] = useState(tabs[0]);
return (
<Tabs
gap={4}
tabs={tabs}
accessibilityLabel="Example tabs"
activeBackground="bgPrimary"
activeTab={activeTab}
background="bg"
gap={4}
onChange={setActiveTab}
TabComponent={TabComponent}
TabsActiveIndicatorComponent={ActiveIndicator}
// default value; can be omitted.
TabComponent={DefaultTab}
tabs={tabs}
// default value; can be omitted.
TabsActiveIndicatorComponent={DefaultTabsActiveIndicator}
/>
);
}
```

Tabs can also start with no active selection by passing `null`.
You can omit `TabComponent` explicitly: **`Tabs`** defaults it to **`DefaultTab`**.

### No initial selection

```jsx
function Example() {
Expand All @@ -59,99 +41,77 @@ function Example() {
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab3', label: 'Tab 3' },
];

const TabComponent = useCallback(({ id, label, disabled }) => {
const { activeTab, updateActiveTab } = useTabsContext();
const isActive = activeTab?.id === id;
return (
<Pressable
onPress={() => updateActiveTab(id)}
disabled={disabled}
accessibilityRole="tab"
accessibilityState={{ selected: isActive, disabled }}
>
<TabLabel id={id} active={isActive}>
{label}
</TabLabel>
</Pressable>
);
}, []);

const ActiveIndicator = useCallback(
(props) => <TabsActiveIndicator {...props} background="bgPrimary" bottom={0} height={2} />,
[],
);

const [activeTab, setActiveTab] = useState(null);
return (
<Tabs
accessibilityLabel="Example tabs"
activeBackground="bgPrimary"
activeTab={activeTab}
background="bg"
gap={4}
onChange={setActiveTab}
tabs={tabs}
/>
);
}
```

### Dot counts

```jsx
function Example() {
const tabs = [
{ id: 'inbox', label: 'Inbox', count: 3, max: 99 },
{ id: 'sent', label: 'Sent' },
];
const [activeTab, setActiveTab] = useState(tabs[0]);
return (
<Tabs
accessibilityLabel="Mail folders"
activeBackground="bgPrimary"
activeTab={activeTab}
background="bg"
gap={4}
onChange={setActiveTab}
TabComponent={TabComponent}
TabsActiveIndicatorComponent={ActiveIndicator}
tabs={tabs}
/>
);
}
```

### Disabled

The entire component can be disabled with the `disabled` prop.

```jsx
function Example() {
const tabs = [
{ id: 'tab1', label: 'Tab 1' },
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab2', label: 'Tab 2', disabled: true },
{ id: 'tab3', label: 'Tab 3' },
];

const TabComponent = useCallback(({ id, label, disabled }) => {
const { activeTab, updateActiveTab } = useTabsContext();
const isActive = activeTab?.id === id;
return (
<Pressable
onPress={() => updateActiveTab(id)}
disabled={disabled}
accessibilityRole="tab"
accessibilityState={{ selected: isActive, disabled }}
>
<TabLabel id={id} active={isActive}>
{label}
</TabLabel>
</Pressable>
);
}, []);

const ActiveIndicator = useCallback(
(props) => <TabsActiveIndicator {...props} background="bgPrimary" bottom={0} height={2} />,
[],
);

const [activeTab, setActiveTab] = useState(tabs[0]);
return (
<Tabs
disabled
gap={4}
tabs={tabs}
accessibilityLabel="Example tabs"
activeBackground="bgPrimary"
activeTab={activeTab}
background="bg"
gap={4}
onChange={setActiveTab}
TabComponent={TabComponent}
TabsActiveIndicatorComponent={ActiveIndicator}
tabs={tabs}
/>
);
}
```

Individual tabs can also be disabled while keeping others interactive.
## Custom `TabComponent`

Use **`useTabsContext`** with your own **`Pressable`** and **`Text`** for labels (and a custom **`TabsActiveIndicatorComponent`** if needed) when you need more control than `DefaultTab`.

```jsx
function Example() {
const tabs = [
{ id: 'tab1', label: 'Tab 1' },
{ id: 'tab2', label: 'Tab 2', disabled: true },
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab3', label: 'Tab 3' },
];

Expand All @@ -165,9 +125,9 @@ function Example() {
accessibilityRole="tab"
accessibilityState={{ selected: isActive, disabled }}
>
<TabLabel id={id} active={isActive}>
<Text font="headline" color={isActive ? 'fgPositive' : 'fg'}>
{label}
</TabLabel>
</Text>
</Pressable>
);
}, []);
Expand All @@ -191,11 +151,7 @@ function Example() {
}
```

## Custom Components

### Tab

Pass additional data through the tab definitions and access it in your `TabComponent` to render custom content like icons.
### Custom label content

```jsx
function Example() {
Expand All @@ -217,9 +173,9 @@ function Example() {
>
<HStack gap={1} alignItems="center">
<Icon name={icon} size="s" color={isActive ? 'fgPrimary' : 'fgMuted'} />
<TabLabel id={id} active={isActive}>
<Text font="headline" color={isActive ? 'fgPrimary' : 'fg'}>
{label}
</TabLabel>
</Text>
</HStack>
</Pressable>
);
Expand All @@ -243,3 +199,7 @@ function Example() {
);
}
```

## Accessibility

Set **`accessibilityLabel`** on **`Tabs`**. **`DefaultTab`** wires `accessibilityRole="tab"` and selection state; keep tab panels in sync in your screen content.
Loading
Loading