Skip to content

Commit 9e95220

Browse files
committed
chore(components): rename SubTabs component
1 parent 586959a commit 9e95220

File tree

14 files changed

+338
-52
lines changed

14 files changed

+338
-52
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import React, { useState } from 'react';
2+
3+
import { Meta, StoryObj } from '@storybook/react';
4+
5+
import { spacings } from '@trezor/theme';
6+
7+
import { SubTabs as SubTabsComponent, SubTabsProps, allowedSubTabsFrameProps } from './SubTabs';
8+
import { SubTabsSizes } from './types';
9+
import { Column } from '../Flex/Flex';
10+
import { IconName } from '../Icon/Icon';
11+
import { getFramePropsStory } from '../../utils/frameProps';
12+
import { variables } from '../../config';
13+
14+
const meta: Meta = {
15+
title: 'SubTabs',
16+
} as Meta;
17+
export default meta;
18+
19+
const SubTabsApp = (props: Partial<SubTabsProps>) => {
20+
const [selectedTab, setSelectedTab] = useState(0);
21+
22+
const items = ['Lorem', 'Ipsum', 'Dolor Sit', 'Amet'].map((title, index) => ({
23+
title,
24+
id: title.toLowerCase(),
25+
onClick: () => {
26+
setSelectedTab(index);
27+
},
28+
iconName: variables.ICONS[index * 2] as IconName,
29+
'data-testid': title.toLowerCase(),
30+
}));
31+
32+
const getContent = () => {
33+
switch (selectedTab) {
34+
case 0:
35+
return (
36+
<div>
37+
Pariatur magnam esse assumenda et reiciendis et ipsa aspernatur. Aut
38+
deserunt voluptatum id. Consequatur voluptatem nostrum enim facere
39+
accusantium qui provident. Eum at aut consequuntur. Blanditiis nihil impedit
40+
esse fugit iste. Laboriosam voluptas asperiores aut a. Et esse expedita
41+
accusamus. Ratione accusantium ipsam consequatur non in.
42+
</div>
43+
);
44+
case 1:
45+
return (
46+
<div>
47+
Odit velit aliquam explicabo enim autem maiores harum est. Repellat error
48+
rem omnis recusandae cumque veniam qui maiores. Et suscipit consequatur
49+
dolor nesciunt nihil blanditiis reprehenderit facere. Cumque vitae excepturi
50+
dignissimos numquam impedit dolores alias occaecati. Qui similique natus
51+
suscipit minima. Sit voluptatum cum consequatur necessitatibus mollitia vel.
52+
Voluptas cupiditate error aut numquam. Rerum quasi labore est perferendis
53+
est assumenda.
54+
</div>
55+
);
56+
case 2:
57+
return (
58+
<div>
59+
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis
60+
praesentium voluptatum deleniti atque corrupti quos dolores et quas
61+
molestias excepturi sint occaecati cupiditate non provident, similique sunt
62+
in culpa qui officia deserunt mollitia animi, id est laborum et dolorum
63+
fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero
64+
tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus
65+
id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis
66+
dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut
67+
rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et
68+
molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente
69+
delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut
70+
perferendis doloribus asperiores repellat.
71+
</div>
72+
);
73+
case 3:
74+
return (
75+
<div>
76+
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
77+
doloremque laudantium, totam rem aperiam, eaque ipsa quae.
78+
</div>
79+
);
80+
default:
81+
return null;
82+
}
83+
};
84+
85+
return (
86+
<Column gap={spacings.md}>
87+
<SubTabsComponent activeItemId={items[selectedTab].id} {...props}>
88+
{items.map(item => (
89+
<SubTabsComponent.Item key={item.id} {...item}>
90+
{item.title}
91+
</SubTabsComponent.Item>
92+
))}
93+
</SubTabsComponent>
94+
{getContent()}
95+
</Column>
96+
);
97+
};
98+
99+
export const SubTabs: StoryObj = {
100+
render: props => {
101+
return <SubTabsApp {...props} />;
102+
},
103+
args: {
104+
size: 'medium',
105+
...getFramePropsStory(allowedSubTabsFrameProps).args,
106+
},
107+
argTypes: {
108+
size: {
109+
control: {
110+
type: 'select',
111+
},
112+
options: SubTabsSizes,
113+
},
114+
...getFramePropsStory(allowedSubTabsFrameProps).argTypes,
115+
},
116+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { ReactNode } from 'react';
2+
3+
import styled from 'styled-components';
4+
5+
import { spacings } from '@trezor/theme';
6+
import { Row } from '@trezor/components';
7+
8+
import {
9+
FrameProps,
10+
FramePropsKeys,
11+
pickAndPrepareFrameProps,
12+
withFrameProps,
13+
} from '../../utils/frameProps';
14+
import { TransientProps } from '../../utils/transientProps';
15+
import { SubTabsContext } from './SubTabsContext';
16+
import { SubTabsItem } from './SubTabsItem';
17+
import { SubTabsSize } from './types';
18+
19+
export const allowedSubTabsFrameProps = ['margin'] as const satisfies FramePropsKeys[];
20+
type AllowedFrameProps = Pick<FrameProps, (typeof allowedSubTabsFrameProps)[number]>;
21+
22+
const Container = styled.div<TransientProps<AllowedFrameProps>>`
23+
${withFrameProps}
24+
`;
25+
26+
export type SubTabsProps = AllowedFrameProps & {
27+
children: ReactNode;
28+
size?: SubTabsSize;
29+
activeItemId?: string;
30+
};
31+
32+
const SubTabs = ({ activeItemId, size = 'medium', children, ...rest }: SubTabsProps) => {
33+
const frameProps = pickAndPrepareFrameProps(rest, allowedSubTabsFrameProps);
34+
35+
return (
36+
<SubTabsContext.Provider value={{ activeItemId, size }}>
37+
<Container {...frameProps}>
38+
<Row alignItems="stretch" gap={spacings.xxs}>
39+
{children}
40+
</Row>
41+
</Container>
42+
</SubTabsContext.Provider>
43+
);
44+
};
45+
46+
SubTabs.Item = SubTabsItem;
47+
48+
export { SubTabs };
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createContext, useContext } from 'react';
2+
3+
import { SubTabsSize } from './types';
4+
5+
export const SubTabsContext = createContext<{
6+
activeItemId?: string;
7+
size: SubTabsSize;
8+
}>({ size: 'medium' });
9+
10+
export const useSubTabsContext = () => {
11+
const context = useContext(SubTabsContext);
12+
13+
if (!context) {
14+
throw new Error('useSubTabsContext must be used within a SubTabsContext');
15+
}
16+
17+
return context;
18+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import styled, { css } from 'styled-components';
2+
3+
import { borders, spacings, mapElevationToBackground, Elevation } from '@trezor/theme';
4+
5+
import { Row } from '../Flex/Flex';
6+
import { Text } from '../typography/Text/Text';
7+
import { Icon, IconName } from '../Icon/Icon';
8+
import { useSubTabsContext } from './SubTabsContext';
9+
import { mapSizeToTypography, mapSizeToIconSize } from './utils';
10+
import { useElevation } from '../ElevationContext/ElevationContext';
11+
12+
const Item = styled.div<{ $isActive: boolean; $elevation: Elevation }>`
13+
border-radius: ${borders.radii.full};
14+
transition:
15+
color 0.15s,
16+
background 0.15s;
17+
cursor: pointer;
18+
background: ${mapElevationToBackground};
19+
box-shadow: ${({ theme }) => theme.boxShadowBase};
20+
color: ${({ theme }) => theme.textDefault};
21+
22+
&:hover,
23+
&:focus {
24+
color: ${({ theme }) => theme.textDefault};
25+
}
26+
27+
${({ $isActive, theme }) =>
28+
!$isActive &&
29+
css`
30+
background: none;
31+
box-shadow: none;
32+
color: ${theme.textSubdued};
33+
`}
34+
`;
35+
36+
export type SubTabsItemProps = {
37+
id: string;
38+
onClick: () => void;
39+
iconName?: IconName;
40+
count?: number;
41+
children: React.ReactNode;
42+
'data-testid'?: string;
43+
};
44+
45+
export const SubTabsItem = ({
46+
id,
47+
onClick,
48+
iconName,
49+
count = 0,
50+
'data-testid': dataTestId,
51+
children,
52+
}: SubTabsItemProps) => {
53+
const { activeItemId, size } = useSubTabsContext();
54+
const { elevation } = useElevation();
55+
const isActive = id === activeItemId;
56+
57+
return (
58+
<Item
59+
$isActive={isActive}
60+
$elevation={elevation}
61+
onClick={onClick}
62+
data-testid={dataTestId}
63+
>
64+
<Row gap={spacings.xs} padding={{ vertical: spacings.xs, horizontal: spacings.md }}>
65+
{iconName && <Icon name={iconName} size={mapSizeToIconSize(size)} />}
66+
<Text as="div" typographyStyle={mapSizeToTypography(size)}>
67+
{children}
68+
</Text>
69+
{count > 0 && (
70+
<Text typographyStyle={mapSizeToTypography(size)} variant="disabled">
71+
{count}
72+
</Text>
73+
)}
74+
</Row>
75+
</Item>
76+
);
77+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { UISize } from '../../config/types';
2+
3+
export const SubTabsSizes = ['large', 'medium', 'small'] as const;
4+
export type SubTabsSize = Extract<UISize, (typeof SubTabsSizes)[number]>;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { TypographyStyle } from '@trezor/theme';
2+
3+
import { SubTabsSize } from './types';
4+
5+
export const mapSizeToTypography = (size: SubTabsSize): TypographyStyle => {
6+
const typographyStyleMap: Record<SubTabsSize, TypographyStyle> = {
7+
large: 'body',
8+
medium: 'hint',
9+
small: 'label',
10+
};
11+
12+
return typographyStyleMap[size];
13+
};
14+
15+
export const mapSizeToIconSize = (size: SubTabsSize): number => {
16+
const iconSizeMap: Record<SubTabsSize, number> = {
17+
large: 22,
18+
medium: 20,
19+
small: 18,
20+
};
21+
22+
return iconSizeMap[size];
23+
};

packages/components/src/components/Subtabs/Subtabs.stories.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ import { Meta, StoryObj } from '@storybook/react';
44

55
import { spacings } from '@trezor/theme';
66

7-
import { Subtabs as SubtabsComponent, SubtabsProps, allowedSubtabsFrameProps } from './Subtabs';
8-
import { subtabsSizes } from './types';
7+
import { SubTabs as SubTabsComponent, SubTabsProps, allowedSubTabsFrameProps } from './SubTabs';
8+
import { SubTabsSizes } from './types';
99
import { Column } from '../Flex/Flex';
1010
import { IconName } from '../Icon/Icon';
1111
import { getFramePropsStory } from '../../utils/frameProps';
1212
import { variables } from '../../config';
1313

1414
const meta: Meta = {
15-
title: 'Subtabs',
15+
title: 'SubTabs',
1616
} as Meta;
1717
export default meta;
1818

19-
const SubtabsApp = (props: Partial<SubtabsProps>) => {
19+
const SubTabsApp = (props: Partial<SubTabsProps>) => {
2020
const [selectedTab, setSelectedTab] = useState(0);
2121

2222
const items = ['Lorem', 'Ipsum', 'Dolor Sit', 'Amet'].map((title, index) => ({
@@ -84,33 +84,33 @@ const SubtabsApp = (props: Partial<SubtabsProps>) => {
8484

8585
return (
8686
<Column gap={spacings.md}>
87-
<SubtabsComponent activeItemId={items[selectedTab].id} {...props}>
87+
<SubTabsComponent activeItemId={items[selectedTab].id} {...props}>
8888
{items.map(item => (
89-
<SubtabsComponent.Item key={item.id} {...item}>
89+
<SubTabsComponent.Item key={item.id} {...item}>
9090
{item.title}
91-
</SubtabsComponent.Item>
91+
</SubTabsComponent.Item>
9292
))}
93-
</SubtabsComponent>
93+
</SubTabsComponent>
9494
{getContent()}
9595
</Column>
9696
);
9797
};
9898

99-
export const Subtabs: StoryObj = {
99+
export const SubTabs: StoryObj = {
100100
render: props => {
101-
return <SubtabsApp {...props} />;
101+
return <SubTabsApp {...props} />;
102102
},
103103
args: {
104-
...getFramePropsStory(allowedSubtabsFrameProps).args,
105104
size: 'medium',
105+
...getFramePropsStory(allowedSubTabsFrameProps).args,
106106
},
107107
argTypes: {
108-
...getFramePropsStory(allowedSubtabsFrameProps).argTypes,
109108
size: {
110109
control: {
111110
type: 'select',
112111
},
113-
options: subtabsSizes,
112+
options: SubTabsSizes,
114113
},
114+
...getFramePropsStory(allowedSubTabsFrameProps).argTypes,
115115
},
116116
};

0 commit comments

Comments
 (0)