Skip to content

Commit 95ed65d

Browse files
authored
refactor: user component and setup automatic docs (#270)
* refactor: user component and setup automatic docs - set up automatic docs generation from typescript - refactor `User` component - move out `User` out of badges - change default SB view from canvas to docs - add some more stories for User/Alternative header - rename `User.stories.tsx` to `stories.tsx` as they're in a folder
1 parent 4b9dc86 commit 95ed65d

16 files changed

+160
-196
lines changed

.storybook/main.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
module.exports = {
22
"stories": [
3-
"../src/**/*.stories.mdx",
4-
"../src/**/*.stories.@(js|jsx|ts|tsx)"
3+
"../src/**/*/stories.@(ts|tsx)",
4+
"../src/**/*.stories.@(js|jsx|ts|tsx)",
55
],
66
"addons": [
77
"@storybook/addon-links",
8-
"@storybook/addon-essentials"
8+
"@storybook/addon-essentials",
9+
"@storybook/addon-docs",
910
]
10-
}
11+
}

.storybook/preview.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const parameters = {
66
actions: { argTypesRegex: '^on[A-Z].*' },
77
layout: 'centered',
88
controls: { expanded: true },
9+
viewMode: 'docs' // `docs` is default tab, instead of canvas
910
};
1011

1112
export const decorators = [

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@semantic-release/npm": "^9.0.1",
4646
"@semantic-release/release-notes-generator": "^10.0.3",
4747
"@storybook/addon-actions": "^6.5.9",
48+
"@storybook/addon-docs": "^6.5.9",
4849
"@storybook/addon-essentials": "^6.5.9",
4950
"@storybook/addon-links": "^6.5.9",
5051
"@storybook/react": "^6.5.9",

src/components/alternative-header/AlternativeHeader.tsx

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,31 @@ import Value from '../typography/value';
44
import * as S from './styles';
55

66
export interface Tab {
7+
/** Title for the tab */
78
title: string;
9+
/** Additional content, e.g. counter */
810
altContent?: React.ReactElement | null;
11+
/** Whether tab is active or not */
912
isActive: boolean;
13+
/** Handler called when tab is clicked */
1014
onClick: () => void;
1115
}
1216

13-
export interface AlternativeHeaderProps extends Omit<BoxProps, 'css'> {
17+
export interface Props extends Omit<BoxProps, 'css'> {
18+
/** Title for the tabs bar */
1419
title?: string;
20+
/** A list of tabs, defined by `ALternativeHeaderTab` interface */
1521
tabs: Tab[];
22+
/** Whether to show a line under the tabs menu */
1623
withBase?: boolean;
1724
}
1825

19-
export const AlternativeHeader: FC<AlternativeHeaderProps> = ({
26+
export const AlternativeHeader: FC<Props> = ({
2027
title,
2128
tabs,
22-
withBase,
29+
withBase = false,
2330
...props
24-
}: AlternativeHeaderProps) => {
31+
}: Props) => {
2532
const activeTabIndex = useMemo(() => {
2633
const index = tabs.findIndex(({ isActive }) => isActive);
2734

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export { AlternativeHeader } from './AlternativeHeader';
22

3-
export type { AlternativeHeaderProps } from './AlternativeHeader';
3+
export type {
4+
Props as AlternativeHeaderProps,
5+
Tab as AlternativeHeaderTab,
6+
} from './AlternativeHeader';

src/components/alternative-header/AlternativeHeader.stories.tsx src/components/alternative-header/stories.tsx

+11-26
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import { Meta, Story } from '@storybook/react/types-6-0';
22
import React, { useMemo, useState } from 'react';
33
import { Box } from 'rebass';
44
import Value from '../typography/value';
5-
import { AlternativeHeader, AlternativeHeaderProps } from './AlternativeHeader';
5+
import { AlternativeHeader, Props } from './AlternativeHeader';
66

77
export default {
88
title: 'Quartz/AlternativeHeader',
99
component: AlternativeHeader,
10+
args: {
11+
title: 'Cluster settings',
12+
},
1013
} as Meta;
1114

12-
const Template: Story<AlternativeHeaderProps> = (props) => {
15+
const Template: Story<Props> = (props) => {
1316
const [active, setActive] = useState('tab');
1417

1518
const tabs = useMemo(
@@ -29,12 +32,13 @@ const Template: Story<AlternativeHeaderProps> = (props) => {
2932
isActive: active === 'one more tab',
3033
onClick: () => setActive('one more tab'),
3134
},
35+
// if the storybook page hangs, comment the tab below. There is an unknown memory leak, which breaks the page.
3236
{
3337
title: 'last tab',
3438
isActive: active === 'last tab',
3539
altContent: (
3640
<Value as="span" lineHeight="13px">
37-
altContent
41+
38
3842
</Value>
3943
),
4044
onClick: () => setActive('last tab'),
@@ -52,27 +56,8 @@ const Template: Story<AlternativeHeaderProps> = (props) => {
5256

5357
export const Default = Template.bind({});
5458

55-
Default.args = {
56-
title: 'Cluster settings',
57-
};
58-
59-
Default.argTypes = {
60-
title: {
61-
summary: 'Title',
62-
control: {
63-
type: 'text',
64-
},
65-
},
66-
tabs: {
67-
summary: 'Tabs of header',
68-
control: {
69-
type: 'object',
70-
},
71-
},
72-
withBase: {
73-
summary: 'Adds a line under the menu',
74-
control: {
75-
type: 'boolean',
76-
},
77-
},
59+
export const WithBase = Template.bind({});
60+
WithBase.args = {
61+
title: 'An example with base',
62+
withBase: true,
7863
};

src/components/avatar/avatar.styles.ts

-7
This file was deleted.

src/components/avatar/default.svg

-5
This file was deleted.

src/components/header/header.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Subtitle from '../typography/subtitle';
88
import Header, { HeaderProps } from './index';
99
// Types
1010
import MenuButton from './menu-button';
11-
import User from '../user';
11+
import { User } from '../user';
1212
import { GetIcon, IconName } from '../icon';
1313

1414
import { Default as NavigationStory } from '../navigation/navigation.stories';

src/components/user/User.tsx

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as R from 'ramda';
2+
import React from 'react';
3+
import { Flex } from 'rebass';
4+
import Label from '../label';
5+
import Tooltip from '../tooltip';
6+
import * as S from './styles';
7+
8+
export interface Props {
9+
/** User's first name */
10+
firstName: string;
11+
/** User's last name */
12+
lastName?: string;
13+
/** Whether tooltip should be shown */
14+
isTooltipActive?: boolean;
15+
/** Appears in the tooltip */
16+
title?: string;
17+
/** Allows including text into user badge */
18+
secondaryText?: string;
19+
}
20+
21+
export const User = ({
22+
firstName,
23+
lastName,
24+
title,
25+
secondaryText,
26+
isTooltipActive = true,
27+
}: Props) => {
28+
const initials = `${R.head(firstName)} ${R.head(lastName ?? '')}`;
29+
30+
return (
31+
<Tooltip
32+
disabled={!isTooltipActive}
33+
mainText={firstName && lastName ? `${firstName} ${lastName}` : firstName}
34+
secondaryText={title}
35+
>
36+
{!secondaryText ? (
37+
<ImageBadge initials={initials} />
38+
) : (
39+
<Flex alignItems="center" sx={S.secondaryTextContainer}>
40+
<Flex sx={S.userBadgeContainer}>
41+
<ImageBadge initials={initials} />
42+
</Flex>
43+
<Label ml="12px" mr="20px">
44+
{secondaryText}
45+
</Label>
46+
</Flex>
47+
)}
48+
</Tooltip>
49+
);
50+
};
51+
52+
const ImageBadge = ({ initials }: { initials: string }) => {
53+
const hue = Math.round(((initials.charCodeAt(0) - 64) * 360) / 26);
54+
55+
return (
56+
<Flex sx={S.avatar(hue)} minWidth="32px" height="32px" alt="User avatar">
57+
{initials}
58+
</Flex>
59+
);
60+
};

src/components/user/index.tsx

+2-82
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,3 @@
1-
import React, { FC } from 'react';
2-
import { Flex } from 'rebass';
3-
import Tooltip from '../tooltip';
4-
import styles from '../avatar/avatar.styles';
5-
import Label from '../label';
1+
export { User } from './User';
62

7-
export interface UserProps {
8-
firstName: string;
9-
lastName?: string;
10-
title?: string;
11-
secondaryText?: string;
12-
isTooltipActive?: boolean;
13-
}
14-
15-
const User: FC<UserProps> = ({
16-
firstName,
17-
lastName,
18-
title,
19-
secondaryText,
20-
isTooltipActive = true,
21-
}: UserProps) => {
22-
// eslint-disable-next-line arrow-body-style
23-
const getInitial = (word?: string) => {
24-
return word ? word.charAt(0).toLocaleUpperCase() : '';
25-
};
26-
27-
const capitalisedName = getInitial(firstName) + getInitial(lastName);
28-
const hue = Math.round(((capitalisedName.charCodeAt(0) - 64) * 360) / 26);
29-
const color = (lightness: number) => `hsl(${hue}, 75%, ${lightness}%)`;
30-
31-
const ImgBadge = () => (
32-
<Flex
33-
sx={{
34-
...styles,
35-
borderColor: color(55),
36-
color: color(55),
37-
fontWeight: 'bold',
38-
fontFamily: 'Inter',
39-
backgroundColor: color(90),
40-
justifyContent: 'center',
41-
alignItems: 'center',
42-
fontSize: '12px',
43-
}}
44-
minWidth="32px"
45-
height="32px"
46-
alt="User avatar"
47-
>
48-
{capitalisedName}
49-
</Flex>
50-
);
51-
52-
return (
53-
<Tooltip
54-
disabled={!isTooltipActive}
55-
mainText={firstName && lastName ? `${firstName} ${lastName}` : firstName}
56-
secondaryText={title}
57-
>
58-
{secondaryText ? (
59-
<Flex
60-
alignItems="center"
61-
sx={{ backgroundColor: 'grayShade2', borderRadius: '20px' }}
62-
>
63-
<Flex
64-
sx={{
65-
border: '1px solid',
66-
borderColor: 'white',
67-
borderRadius: '20px',
68-
}}
69-
>
70-
<ImgBadge />
71-
</Flex>
72-
<Label ml="12px" mr="20px">
73-
{secondaryText}
74-
</Label>
75-
</Flex>
76-
) : (
77-
<ImgBadge />
78-
)}
79-
</Tooltip>
80-
);
81-
};
82-
83-
export default User;
3+
export type { Props as UserProps } from './User';

src/components/user/stories.tsx

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Story } from '@storybook/react/types-6-0';
2+
import React from 'react';
3+
import { Flex } from 'rebass';
4+
import Labeling from '../typography/labeling';
5+
import { User, UserProps } from './index';
6+
7+
export default {
8+
title: 'Quartz/User',
9+
component: User,
10+
args: {
11+
firstName: 'Martin',
12+
lastName: 'Heidegger',
13+
},
14+
};
15+
16+
const Template: Story<UserProps> = (props) => <User {...props} />;
17+
18+
export const Default = Template.bind({});
19+
20+
export const WithSecondaryText = Template.bind({});
21+
WithSecondaryText.args = {
22+
secondaryText: 'you can add some stuff here',
23+
};
24+
25+
export const WithTitle = () => (
26+
<Flex alignItems="center">
27+
<Labeling mr={2}>hover your mouse on the user to see the title</Labeling>
28+
29+
<User firstName="Martin" lastName="Heidegger" title="Philosopher" />
30+
</Flex>
31+
);

src/components/user/styles.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const color = (hue: number, lightness: number) =>
2+
`hsl(${hue}, 75%, ${lightness}%)`;
3+
4+
export const avatar = (hue: number) => ({
5+
justifyContent: 'center',
6+
alignItems: 'center',
7+
8+
borderRadius: '50%',
9+
borderWidth: '2px',
10+
borderStyle: 'solid',
11+
borderColor: color(hue, 55),
12+
13+
color: color(hue, 55),
14+
backgroundColor: color(hue, 90),
15+
16+
fontSize: '12px',
17+
fontWeight: 'bold',
18+
fontFamily: 'Inter',
19+
});
20+
21+
export const secondaryTextContainer = {
22+
backgroundColor: 'grayShade2',
23+
borderRadius: '20px',
24+
};
25+
26+
export const userBadgeContainer = {
27+
border: '1px solid',
28+
borderColor: 'white',
29+
borderRadius: '20px',
30+
};

0 commit comments

Comments
 (0)