Skip to content

Commit b6db032

Browse files
authored
feat: controlled embedded tab (#258)
1 parent 8508f08 commit b6db032

File tree

3 files changed

+97
-35
lines changed

3 files changed

+97
-35
lines changed

src/components/embedded-tabs/EmbeddedTabs.tsx

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, useState } from 'react';
1+
import React, { useState } from 'react';
22
import { Box, BoxProps } from 'rebass';
33
import * as S from './styles';
44
import Tab, { Props as EmbeddedTabItem } from './Tab';
@@ -9,13 +9,7 @@ export interface Props extends Omit<BoxProps, 'css'> {
99
onTabChange?: (tabIndex: number) => void;
1010
}
1111

12-
const EmbeddedTabs: FC<Props> = ({
13-
tabs,
14-
onTabChange,
15-
initialTab = 0,
16-
sx = {},
17-
...boxProps
18-
}: Props) => {
12+
const EmbeddedTabs = ({ onTabChange, initialTab = 0, ...restProps }: Props) => {
1913
const [activeIndex, setActiveIndex] = useState(initialTab);
2014

2115
const handleTabClick = (newIndex: number) => {
@@ -26,13 +20,41 @@ const EmbeddedTabs: FC<Props> = ({
2620
}
2721
};
2822

23+
return (
24+
<Controlled
25+
activeTab={activeIndex}
26+
onTabChange={handleTabClick}
27+
{...restProps}
28+
/>
29+
);
30+
};
31+
32+
export default EmbeddedTabs;
33+
34+
export interface ControlledProps extends Omit<Props, 'initialTab'> {
35+
activeTab: number;
36+
}
37+
38+
export const Controlled = ({
39+
tabs,
40+
onTabChange,
41+
sx = {},
42+
activeTab,
43+
...boxProps
44+
}: ControlledProps) => {
45+
const handleTabClick = (newIndex: number) => {
46+
if (onTabChange) {
47+
onTabChange(newIndex);
48+
}
49+
};
50+
2951
return (
3052
<Box sx={{ ...S.tabsList, ...sx }} {...boxProps}>
3153
{tabs.map(({ title, onClick: onTabClick, ...tabProps }, index) => (
3254
<Tab
3355
key={title}
3456
title={title}
35-
active={index === activeIndex}
57+
active={index === activeTab}
3658
onClick={() => {
3759
if (onTabClick) {
3860
onTabClick();
@@ -48,5 +70,3 @@ const EmbeddedTabs: FC<Props> = ({
4870
</Box>
4971
);
5072
};
51-
52-
export default EmbeddedTabs;
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
11
import { Meta, Story } from '@storybook/react/types-6-0';
22
import React, { useState } from 'react';
3-
import { Box } from 'rebass';
3+
import { Box, Flex } from 'rebass';
44
import Labeling from '../typography/labeling';
5-
import EmbeddedTabs, { Props } from './EmbeddedTabs';
5+
import EmbeddedTabs, {
6+
EmbeddedTabsProps,
7+
ControlledEmbeddedTabsProps,
8+
} from '.';
9+
import Button from '../button';
10+
import Subtitle from '../typography/subtitle';
611

712
export default {
813
title: 'Quartz/EmbeddedTabs',
914
component: EmbeddedTabs,
15+
subcomponents: { Controlled: EmbeddedTabs.Controlled },
1016
} as Meta;
1117

12-
const argTypes = {
13-
tabs: {
14-
required: true,
15-
description: 'A list of tab items.',
16-
},
17-
initialTab: {
18-
required: false,
19-
description: 'The initial tab to be selected.',
20-
default: 0,
21-
},
22-
onTabChange: {
23-
description: 'Callback to be called when a tab is selected.',
24-
required: false
25-
},
26-
};
27-
2818
const tabs = [
2919
{
3020
title: 'Validation Reports',
@@ -41,7 +31,7 @@ const tabs = [
4131
},
4232
];
4333

44-
const Template: Story<Props> = (props) => {
34+
export const Uncontrolled: Story<EmbeddedTabsProps> = (props) => {
4535
const [activeTab, setActiveTab] = useState(props.initialTab ?? 0);
4636

4737
return (
@@ -54,10 +44,51 @@ const Template: Story<Props> = (props) => {
5444
);
5545
};
5646

57-
export const Default = Template.bind({});
47+
export const Controlled: Story<ControlledEmbeddedTabsProps> = (props) => {
48+
const [activeTab, setActiveTab] = useState(0);
49+
50+
return (
51+
<Box width="700px">
52+
<EmbeddedTabs.Controlled
53+
{...props}
54+
activeTab={activeTab}
55+
onTabChange={setActiveTab}
56+
/>
57+
<Box mt={3}>
58+
<Labeling bold>
59+
This component is controlled. Active tab: {tabs[activeTab].title}{' '}
60+
</Labeling>
61+
<Flex mt={2} sx={{ gap: '10px' }}>
62+
<Button onClick={() => setActiveTab(2)}>Jump to Statistics</Button>
63+
<Button onClick={() => setActiveTab(1)}>Jump to Results</Button>
64+
</Flex>
65+
</Box>
66+
</Box>
67+
);
68+
};
5869

59-
Default.args = {
70+
const args = {
6071
tabs,
6172
};
6273

63-
Default.argTypes = argTypes;
74+
const argTypes = {
75+
tabs: {
76+
required: true,
77+
description: 'A list of tab items.',
78+
},
79+
initialTab: {
80+
required: false,
81+
description: 'The initial tab to be selected.',
82+
default: 0,
83+
},
84+
onTabChange: {
85+
description: 'Callback to be called when a tab is selected.',
86+
required: false,
87+
},
88+
};
89+
90+
Uncontrolled.args = args;
91+
Uncontrolled.argTypes = argTypes;
92+
93+
Controlled.args = args;
94+
Controlled.argTypes = argTypes;

src/components/embedded-tabs/index.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1-
export { default } from './EmbeddedTabs';
2-
export type { Props as EmbeddedTabsProps } from './EmbeddedTabs';
31
export type { Props as EmbeddedTabsItem } from './Tab';
2+
3+
import EmbeddedTabs, { Controlled } from './EmbeddedTabs';
4+
5+
type IEmbeddedTabs = typeof EmbeddedTabs;
6+
7+
interface EmbeddedTabsComponent extends IEmbeddedTabs {
8+
Controlled: typeof Controlled;
9+
}
10+
11+
(EmbeddedTabs as EmbeddedTabsComponent).Controlled = Controlled;
12+
13+
export type { Props as EmbeddedTabsProps, ControlledProps as ControlledEmbeddedTabsProps } from './EmbeddedTabs';
14+
export default EmbeddedTabs as EmbeddedTabsComponent;

0 commit comments

Comments
 (0)