docs: use ComponentDecorator (#800)

Related to #702
This commit is contained in:
Thaïs
2023-07-21 21:02:21 +02:00
committed by GitHub
parent 79fccb0404
commit 56cff63c4b
36 changed files with 777 additions and 910 deletions

View File

@ -1,7 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { CommentThreadActionBar } from '../../right-drawer/components/CommentThreadActionBar';
import { Comment } from '../Comment';
import { mockComment, mockCommentWithLongValues } from './mock-comment';
@ -9,17 +10,24 @@ import { mockComment, mockCommentWithLongValues } from './mock-comment';
const meta: Meta<typeof Comment> = {
title: 'Modules/Activity/Comment/Comment',
component: Comment,
decorators: [ComponentDecorator],
argTypes: {
actionBar: {
type: 'boolean',
mapping: {
true: <CommentThreadActionBar commentThreadId="test-id" />,
false: undefined,
},
},
},
args: { comment: mockComment },
};
export default meta;
type Story = StoryObj<typeof Comment>;
export const Default: Story = {
render: getRenderWrapperForComponent(<Comment comment={mockComment} />),
};
export const Default: Story = {};
export const WithLongValues: Story = {
render: getRenderWrapperForComponent(
<Comment comment={mockCommentWithLongValues} />,
),
args: { comment: mockCommentWithLongValues },
};

View File

@ -1,7 +1,9 @@
import type { Meta, StoryObj } from '@storybook/react';
import { DateTime } from 'luxon';
import { CommentThreadActionBar } from '@/activities/right-drawer/components/CommentThreadActionBar';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { avatarUrl } from '~/testing/mock-data/users';
import { CommentHeader } from '../CommentHeader';
@ -10,60 +12,76 @@ import { mockComment, mockCommentWithLongValues } from './mock-comment';
const meta: Meta<typeof CommentHeader> = {
title: 'Modules/Activity/Comment/CommentHeader',
component: CommentHeader,
decorators: [ComponentDecorator],
argTypes: {
actionBar: {
type: 'boolean',
mapping: {
true: <CommentThreadActionBar commentThreadId="test-id" />,
false: undefined,
},
},
},
args: { comment: mockComment },
};
export default meta;
type Story = StoryObj<typeof CommentHeader>;
export const Default: Story = {
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
export const Default: Story = {};
export const FewHoursAgo: Story = {
args: {
comment: {
...mockComment,
createdAt: DateTime.now().minus({ hours: 2 }).toISO() ?? '',
},
},
};
export const FewDaysAgo: Story = {
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
args: {
comment: {
...mockComment,
createdAt: DateTime.now().minus({ days: 2 }).toISO() ?? '',
},
},
};
export const FewMonthsAgo: Story = {
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
args: {
comment: {
...mockComment,
createdAt: DateTime.now().minus({ months: 2 }).toISO() ?? '',
},
},
};
export const FewYearsAgo: Story = {
render: getRenderWrapperForComponent(<CommentHeader comment={mockComment} />),
args: {
comment: {
...mockComment,
createdAt: DateTime.now().minus({ years: 2 }).toISO() ?? '',
},
},
};
export const WithoutAvatar: Story = {
render: getRenderWrapperForComponent(
<CommentHeader
comment={{
...mockComment,
author: {
...mockComment.author,
avatarUrl: '',
},
}}
/>,
),
export const WithAvatar: Story = {
args: {
comment: {
...mockComment,
author: {
...mockComment.author,
avatarUrl,
},
},
},
};
export const WithLongUserName: Story = {
render: getRenderWrapperForComponent(
<CommentHeader
comment={{
...mockCommentWithLongValues,
author: {
...mockCommentWithLongValues.author,
avatarUrl: '',
},
}}
/>,
),
args: { comment: mockCommentWithLongValues },
};
export const WithActionBar: Story = {
render: getRenderWrapperForComponent(
<CommentHeader
comment={mockComment}
actionBar={<CommentThreadActionBar commentThreadId="test-id" />}
/>,
),
args: { actionBar: true },
};

View File

@ -2,33 +2,36 @@ import { MemoryRouter } from 'react-router-dom';
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { CommentThreadRelationPicker } from '../CommentThreadRelationPicker';
const meta: Meta<typeof CommentThreadRelationPicker> = {
title: 'Modules/Comments/CommentThreadRelationPicker',
component: CommentThreadRelationPicker,
parameters: {
msw: graphqlMocks,
},
};
const StyledContainer = styled.div`
width: 400px;
`;
const meta: Meta<typeof CommentThreadRelationPicker> = {
title: 'Modules/Comments/CommentThreadRelationPicker',
component: CommentThreadRelationPicker,
decorators: [
(Story) => (
<MemoryRouter>
<StyledContainer>
<Story />
</StyledContainer>
</MemoryRouter>
),
ComponentDecorator,
],
args: { commentThread: mockedCommentThreads[0] },
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof CommentThreadRelationPicker>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<MemoryRouter>
<StyledContainer>
<CommentThreadRelationPicker commentThread={mockedCommentThreads[0]} />
</StyledContainer>
</MemoryRouter>,
),
};
export const Default: Story = {};

View File

@ -1,18 +1,19 @@
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { CellCommentChip } from '../CellCommentChip';
import { CommentChip } from '../CommentChip';
const meta: Meta<typeof CellCommentChip> = {
title: 'Modules/Comments/CellCommentChip',
component: CellCommentChip,
const meta: Meta<typeof CommentChip> = {
title: 'Modules/Comments/CommentChip',
component: CommentChip,
decorators: [ComponentDecorator],
args: { count: 1 },
};
export default meta;
type Story = StoryObj<typeof CellCommentChip>;
type Story = StoryObj<typeof CommentChip>;
const TestCellContainer = styled.div`
align-items: center;
@ -36,34 +37,38 @@ const StyledFakeCellText = styled.div`
white-space: nowrap;
`;
export const OneComment: Story = {
render: getRenderWrapperForComponent(<CommentChip count={1} />),
};
export const OneComment: Story = {};
export const TenComments: Story = {
render: getRenderWrapperForComponent(<CommentChip count={10} />),
args: { count: 10 },
};
export const TooManyComments: Story = {
render: getRenderWrapperForComponent(<CommentChip count={1000} />),
args: { count: 1000 },
};
export const InCellDefault: Story = {
render: getRenderWrapperForComponent(
<TestCellContainer>
<StyledFakeCellText>Fake short text</StyledFakeCellText>
<CellCommentChip count={12} />
</TestCellContainer>,
),
args: { count: 12 },
decorators: [
(Story) => (
<TestCellContainer>
<StyledFakeCellText>Fake short text</StyledFakeCellText>
<Story />
</TestCellContainer>
),
],
};
export const InCellOverlappingBlur: Story = {
render: getRenderWrapperForComponent(
<TestCellContainer>
<StyledFakeCellText>
Fake long text to demonstrate ellipsis
</StyledFakeCellText>
<CellCommentChip count={12} />
</TestCellContainer>,
),
...InCellDefault,
decorators: [
(Story) => (
<TestCellContainer>
<StyledFakeCellText>
Fake long text to demonstrate ellipsis
</StyledFakeCellText>
<Story />
</TestCellContainer>
),
],
};

View File

@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react';
import { fireEvent, userEvent, within } from '@storybook/testing-library';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { sleep } from '~/testing/sleep';
import { CommandMenu } from '../CommandMenu';
@ -10,25 +10,22 @@ import { CommandMenu } from '../CommandMenu';
const meta: Meta<typeof CommandMenu> = {
title: 'Modules/CommandMenu/CommandMenu',
component: CommandMenu,
decorators: [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
),
ComponentDecorator,
],
};
export default meta;
type Story = StoryObj<typeof CommandMenu>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<MemoryRouter>
<CommandMenu />
</MemoryRouter>,
),
};
export const Default: Story = {};
export const CmdK: Story = {
render: getRenderWrapperForComponent(
<MemoryRouter>
<CommandMenu />
</MemoryRouter>,
),
play: async ({ canvasElement }) => {
fireEvent.keyDown(canvasElement, {
key: 'k',

View File

@ -3,32 +3,41 @@ import { Meta, StoryObj } from '@storybook/react';
import { EntityBoard } from '@/pipeline/components/EntityBoard';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { BoardDecorator } from '~/testing/decorators';
import { ComponentDecorator } from '~/testing/decorators';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { defaultPipelineProgressOrderBy } from '../../pipeline/queries';
import { RecoilScope } from '../../ui/recoil-scope/components/RecoilScope';
import { HooksCompanyBoard } from '../components/HooksCompanyBoard';
import { CompanyBoardContext } from '../states/CompanyBoardContext';
const meta: Meta<typeof EntityBoard> = {
title: 'Modules/Companies/Board',
component: EntityBoard,
decorators: [BoardDecorator],
decorators: [
(Story) => (
<RecoilScope SpecificContext={CompanyBoardContext}>
<HooksCompanyBoard
availableFilters={[]}
orderBy={defaultPipelineProgressOrderBy}
/>
<MemoryRouter>
<Story />
</MemoryRouter>
</RecoilScope>
),
ComponentDecorator,
],
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof EntityBoard>;
export const OneColumnBoard: Story = {
render: getRenderWrapperForComponent(
<MemoryRouter>
<EntityBoard
boardOptions={opportunitiesBoardOptions}
updateSorts={() => {
return;
}}
/>
,
</MemoryRouter>,
render: (args) => (
<EntityBoard {...args} boardOptions={opportunitiesBoardOptions} />
),
parameters: {
msw: graphqlMocks,
},
};

View File

@ -1,31 +1,61 @@
import { useEffect } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Meta, StoryObj } from '@storybook/react';
import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard';
import { BoardCardDecorator } from '~/testing/decorators';
import { ComponentDecorator } from '~/testing/decorators';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { mockedPipelineProgressData } from '~/testing/mock-data/pipeline-progress';
import { defaultPipelineProgressOrderBy } from '../../pipeline/queries';
import { BoardCardContext } from '../../pipeline/states/BoardCardContext';
import { BoardColumnContext } from '../../pipeline/states/BoardColumnContext';
import { pipelineProgressIdScopedState } from '../../pipeline/states/pipelineProgressIdScopedState';
import { RecoilScope } from '../../ui/recoil-scope/components/RecoilScope';
import { useRecoilScopedState } from '../../ui/recoil-scope/hooks/useRecoilScopedState';
import { HooksCompanyBoard } from '../components/HooksCompanyBoard';
import { CompanyBoardContext } from '../states/CompanyBoardContext';
function HookLoadFakeBoardContextState() {
const [, setPipelineProgressId] = useRecoilScopedState(
pipelineProgressIdScopedState,
BoardCardContext,
);
const pipelineProgress = mockedPipelineProgressData[1];
useEffect(() => {
setPipelineProgressId(pipelineProgress?.id || '');
}, [pipelineProgress?.id, setPipelineProgressId]);
return <></>;
}
const meta: Meta<typeof CompanyBoardCard> = {
title: 'Modules/Companies/CompanyBoardCard',
component: CompanyBoardCard,
decorators: [BoardCardDecorator],
decorators: [
(Story) => (
<RecoilScope SpecificContext={CompanyBoardContext}>
<HooksCompanyBoard
availableFilters={[]}
orderBy={defaultPipelineProgressOrderBy}
/>
<RecoilScope SpecificContext={BoardColumnContext}>
<RecoilScope SpecificContext={BoardCardContext}>
<HookLoadFakeBoardContextState />
<MemoryRouter>
<Story />
</MemoryRouter>
</RecoilScope>
</RecoilScope>
</RecoilScope>
),
ComponentDecorator,
],
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof CompanyBoardCard>;
const FakeSelectableCompanyBoardCard = () => {
return (
<MemoryRouter>
<CompanyBoardCard />
</MemoryRouter>
);
};
export const CompanyCompanyBoardCard: Story = {
render: getRenderWrapperForComponent(<FakeSelectableCompanyBoardCard />),
parameters: {
msw: graphqlMocks,
},
};
export const CompanyCompanyBoardCard: Story = {};

View File

@ -2,13 +2,23 @@ import { BrowserRouter } from 'react-router-dom';
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { CompanyChip } from '../components/CompanyChip';
const meta: Meta<typeof CompanyChip> = {
title: 'Modules/Companies/CompanyChip',
component: CompanyChip,
decorators: [
(Story) => (
<TestCellContainer>
<BrowserRouter>
<Story />
</BrowserRouter>
</TestCellContainer>
),
ComponentDecorator,
],
};
export default meta;
@ -31,29 +41,21 @@ const TestCellContainer = styled.div`
`;
export const SmallName: Story = {
render: getRenderWrapperForComponent(
<TestCellContainer>
<BrowserRouter>
<CompanyChip
id="airbnb"
name="Airbnb"
picture="https://api.faviconkit.com/airbnb.com/144"
/>
</BrowserRouter>
</TestCellContainer>,
),
args: {
id: 'airbnb',
name: 'Airbnb',
picture: 'https://api.faviconkit.com/airbnb.com/144',
},
};
export const Clickable: Story = {
args: { ...SmallName.args, clickable: true },
};
export const BigName: Story = {
render: getRenderWrapperForComponent(
<TestCellContainer>
<BrowserRouter>
<CompanyChip
id="google"
name="Google with a real big name to overflow the cell"
picture="https://api.faviconkit.com/google.com/144"
/>
</BrowserRouter>
</TestCellContainer>,
),
args: {
id: 'google',
name: 'Google with a real big name to overflow the cell',
picture: 'https://api.faviconkit.com/google.com/144',
},
};

View File

@ -2,18 +2,10 @@ import { BrowserRouter } from 'react-router-dom';
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { PersonChip } from '../PersonChip';
const meta: Meta<typeof PersonChip> = {
title: 'Modules/People/PersonChip',
component: PersonChip,
};
export default meta;
type Story = StoryObj<typeof PersonChip>;
const TestCellContainer = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.primary};
@ -26,22 +18,28 @@ const TestCellContainer = styled.div`
text-wrap: nowrap;
`;
const meta: Meta<typeof PersonChip> = {
title: 'Modules/People/PersonChip',
component: PersonChip,
decorators: [
(Story) => (
<TestCellContainer>
<BrowserRouter>
<Story />
</BrowserRouter>
</TestCellContainer>
),
ComponentDecorator,
],
};
export default meta;
type Story = StoryObj<typeof PersonChip>;
export const SmallName: Story = {
render: getRenderWrapperForComponent(
<TestCellContainer>
<BrowserRouter>
<PersonChip id="tim_fake_id" name="Tim C." />
</BrowserRouter>
</TestCellContainer>,
),
args: { id: 'tim_fake_id', name: 'Tim C.' },
};
export const BigName: Story = {
render: getRenderWrapperForComponent(
<TestCellContainer>
<BrowserRouter>
<PersonChip id="steve_fake_id" name="Steve J." />
</BrowserRouter>
</TestCellContainer>,
),
args: { id: 'steve_fake_id', name: 'Steve LoremIpsumLoremIpsumLoremIpsum' },
};

View File

@ -1,19 +1,17 @@
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { ActionBar } from '../ActionBar';
const meta: Meta<typeof ActionBar> = {
title: 'UI/ActionBar/ActionBar',
component: ActionBar,
decorators: [ComponentDecorator],
args: { children: 'Lorem ipsum', selectedIds: [] },
};
export default meta;
type Story = StoryObj<typeof ActionBar>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<ActionBar children={<div />} selectedIds={[]} />,
),
};
export const Default: Story = {};

View File

@ -45,7 +45,7 @@ const StyledColorSample = styled.div<{ colorName: string }>`
width: 12px;
`;
const COLOR_OPTIONS = [
export const COLOR_OPTIONS = [
{ name: 'Green', id: 'green' },
{ name: 'Turquoise', id: 'turquoise' },
{ name: 'Sky', id: 'sky' },

View File

@ -0,0 +1,26 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators';
import {
BoardColumnEditTitleMenu,
COLOR_OPTIONS,
} from '../BoardColumnEditTitleMenu';
const meta: Meta<typeof BoardColumnEditTitleMenu> = {
title: 'UI/Board/BoardColumnMenu',
component: BoardColumnEditTitleMenu,
decorators: [ComponentDecorator],
argTypes: {
color: {
control: 'select',
options: COLOR_OPTIONS.map(({ id }) => id),
},
},
args: { color: 'green', title: 'Column title' },
};
export default meta;
type Story = StoryObj<typeof BoardColumnEditTitleMenu>;
export const AllTags: Story = {};

View File

@ -1,28 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { BoardColumnEditTitleMenu } from '../BoardColumnEditTitleMenu';
const meta: Meta<typeof BoardColumnEditTitleMenu> = {
title: 'UI/Board/BoardColumnMenu',
component: BoardColumnEditTitleMenu,
};
export default meta;
type Story = StoryObj<typeof BoardColumnEditTitleMenu>;
export const AllTags: Story = {
render: getRenderWrapperForComponent(
<BoardColumnEditTitleMenu
color="green"
title={'Column title'}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClose={() => {}}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onTitleEdit={() => {}}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onColumnColorEdit={() => {}}
/>,
),
};

View File

@ -1,12 +1,9 @@
import React from 'react';
import styled from '@emotion/styled';
import { text, withKnobs } from '@storybook/addon-knobs';
import { expect, jest } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { IconSearch } from '@/ui/icon';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { Button } from '../Button';
import { ButtonGroup } from '../ButtonGroup';
@ -15,8 +12,8 @@ type ButtonProps = React.ComponentProps<typeof Button>;
const StyledContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
padding: 20px;
width: 800px;
> * + * {
margin-top: ${({ theme }) => theme.spacing(4)};
@ -55,15 +52,6 @@ const StyledButtonContainer = styled.div`
padding: ${({ theme }) => theme.spacing(2)};
`;
const meta: Meta<typeof Button> = {
title: 'UI/Button/Button',
component: Button,
decorators: [withKnobs],
};
export default meta;
type Story = StoryObj<typeof Button>;
const variants: ButtonProps['variant'][] = [
'primary',
'secondary',
@ -73,8 +61,6 @@ const variants: ButtonProps['variant'][] = [
'danger',
];
const clickJestFn = jest.fn();
const states = {
'with-icon': {
description: 'With icon',
@ -127,81 +113,16 @@ const states = {
},
};
const ButtonLine: React.FC<ButtonProps> = ({ variant, ...props }) => (
<>
{Object.entries(states).map(([state, { description, extraProps }]) => (
<StyledButtonContainer key={`${variant}-container-${state}`}>
<StyledDescription>{description}</StyledDescription>
<Button {...props} {...extraProps(variant ?? '')} variant={variant} />
</StyledButtonContainer>
))}
</>
);
const ButtonGroupLine: React.FC<ButtonProps> = ({ variant, ...props }) => (
<>
{Object.entries(states).map(([state, { description, extraProps }]) => (
<StyledButtonContainer key={`${variant}-group-container-${state}`}>
<StyledDescription>{description}</StyledDescription>
<ButtonGroup>
<Button
{...props}
{...extraProps(`${variant}-left`)}
variant={variant}
title="Left"
/>
<Button
{...props}
{...extraProps(`${variant}-center`)}
variant={variant}
title="Center"
/>
<Button
{...props}
{...extraProps(`${variant}-right`)}
variant={variant}
title="Right"
/>
</ButtonGroup>
</StyledButtonContainer>
))}
</>
);
const generateStory = (
size: ButtonProps['size'],
type: 'button' | 'group',
LineComponent: React.ComponentType<ButtonProps>,
): Story => ({
render: getRenderWrapperForComponent(
<StyledContainer>
{variants.map((variant) => (
<div key={variant}>
<StyledTitle>{variant}</StyledTitle>
<StyledLine>
<LineComponent
size={size}
variant={variant}
title={text('Text', 'A button title')}
/>
</StyledLine>
</div>
))}
</StyledContainer>,
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
let button;
if (type === 'group') {
button = canvas.getByTestId(`primary-left-button-default`);
} else {
button = canvas.getByTestId(`primary-button-default`);
}
const numberOfClicks = clickJestFn.mock.calls.length;
await userEvent.click(button);
expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
},
const meta: Meta<typeof Button> = {
title: 'UI/Button/Button',
component: Button,
decorators: [
(Story) => (
<StyledContainer>
<Story />
</StyledContainer>
),
],
parameters: {
pseudo: Object.keys(states).reduce(
(acc, state) => ({
@ -210,20 +131,105 @@ const generateStory = (
(variant) =>
variant &&
['#left', '#center', '#right'].map(
(pos) => `${pos}-${variant}-${type}-${state}`,
(pos) => `${pos}-${variant}-button-${state}`,
),
),
}),
{},
),
},
});
argTypes: { icon: { control: false }, variant: { control: false } },
args: { title: 'A button title' },
};
export const MediumSize = generateStory('medium', 'button', ButtonLine);
export const SmallSize = generateStory('small', 'button', ButtonLine);
export const MediumSizeGroup = generateStory(
'medium',
'group',
ButtonGroupLine,
);
export const SmallSizeGroup = generateStory('small', 'group', ButtonGroupLine);
export default meta;
type Story = StoryObj<typeof Button>;
const clickJestFn = jest.fn();
export const MediumSize: Story = {
args: { size: 'medium' },
render: (args) => (
<>
{variants.map((variant) => (
<div key={variant}>
<StyledTitle>{variant}</StyledTitle>
<StyledLine>
{Object.entries(states).map(
([state, { description, extraProps }]) => (
<StyledButtonContainer key={`${variant}-container-${state}`}>
<StyledDescription>{description}</StyledDescription>
<Button
{...args}
{...extraProps(variant ?? '')}
variant={variant}
/>
</StyledButtonContainer>
),
)}
</StyledLine>
</div>
))}
</>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByTestId('primary-button-default');
const numberOfClicks = clickJestFn.mock.calls.length;
await userEvent.click(button);
expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
},
};
export const SmallSize: Story = {
...MediumSize,
args: { size: 'small' },
};
export const MediumSizeGroup: Story = {
args: { size: 'medium' },
render: (args) => (
<>
{variants.map((variant) => (
<div key={variant}>
<StyledTitle>{variant}</StyledTitle>
<StyledLine>
{Object.entries(states).map(
([state, { description, extraProps }]) => (
<StyledButtonContainer
key={`${variant}-group-container-${state}`}
>
<StyledDescription>{description}</StyledDescription>
<ButtonGroup>
{['Left', 'Center', 'Right'].map((position) => (
<Button
{...args}
{...extraProps(`${variant}-${position.toLowerCase()}`)}
variant={variant}
title={position}
/>
))}
</ButtonGroup>
</StyledButtonContainer>
),
)}
</StyledLine>
</div>
))}
</>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByTestId('primary-left-button-default');
const numberOfClicks = clickJestFn.mock.calls.length;
await userEvent.click(button);
expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
},
};
export const SmallSizeGroup: Story = {
...MediumSizeGroup,
args: { size: 'small' },
};

View File

@ -1,12 +1,9 @@
import React from 'react';
import styled from '@emotion/styled';
import { withKnobs } from '@storybook/addon-knobs';
import { expect, jest } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { IconUser } from '@/ui/icon';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { IconButton } from '../IconButton';
@ -16,6 +13,7 @@ const StyledContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
padding: 20px;
width: 800px;
> * + * {
margin-top: ${({ theme }) => theme.spacing(4)};
@ -55,7 +53,14 @@ const StyledIconButtonContainer = styled.div`
const meta: Meta<typeof IconButton> = {
title: 'UI/Button/IconButton',
component: IconButton,
decorators: [withKnobs],
decorators: [
(Story) => (
<StyledContainer>
<Story />
</StyledContainer>
),
],
argTypes: { icon: { control: false }, variant: { control: false } },
};
export default meta;
@ -101,53 +106,51 @@ const states = {
},
};
function IconButtonRow({ variant, size, ...props }: IconButtonProps) {
const iconSize = size === 'small' ? 14 : 16;
return (
export const LargeSize: Story = {
args: { size: 'large' },
render: (args) => (
<>
{Object.entries(states).map(([state, { description, extraProps }]) => (
<StyledIconButtonContainer key={`${variant}-container-${state}`}>
<StyledDescription>{description}</StyledDescription>
<IconButton
{...props}
{...extraProps(variant ?? '')}
variant={variant}
size={size}
icon={<IconUser size={iconSize} />}
/>
</StyledIconButtonContainer>
))}
</>
);
}
const generateStory = (
size: IconButtonProps['size'],
LineComponent: React.ComponentType<IconButtonProps>,
): Story => ({
render: getRenderWrapperForComponent(
<StyledContainer>
{variants.map((variant) => (
<div key={variant}>
<StyledTitle>{variant}</StyledTitle>
<StyledLine>
<LineComponent size={size} variant={variant} />
{Object.entries(states).map(
([state, { description, extraProps }]) => (
<StyledIconButtonContainer
key={`${variant}-container-${state}`}
>
<StyledDescription>{description}</StyledDescription>
<IconButton
{...args}
{...extraProps(variant ?? '')}
variant={variant}
icon={<IconUser size={args.size === 'small' ? 14 : 16} />}
/>
</StyledIconButtonContainer>
),
)}
</StyledLine>
</div>
))}
</StyledContainer>,
</>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByTestId(`transparent-button-default`);
const button = canvas.getByTestId('transparent-button-default');
const numberOfClicks = clickJestFn.mock.calls.length;
await userEvent.click(button);
expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
},
});
};
export const LargeSize = generateStory('large', IconButtonRow);
export const MediumSize = generateStory('medium', IconButtonRow);
export const SmallSize = generateStory('small', IconButtonRow);
export const MediumSize: Story = {
...LargeSize,
args: { size: 'medium' },
};
export const SmallSize: Story = {
...LargeSize,
args: { size: 'small' },
};

View File

@ -3,24 +3,32 @@ import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { IconBrandGoogle } from '@/ui/icon';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { MainButton } from '../MainButton';
const clickJestFn = jest.fn();
const meta: Meta<typeof MainButton> = {
title: 'UI/Button/MainButton',
component: MainButton,
decorators: [ComponentDecorator],
argTypes: {
icon: {
type: 'boolean',
mapping: {
true: <IconBrandGoogle size={16} stroke={4} />,
false: undefined,
},
},
},
args: { title: 'A primary Button', onClick: clickJestFn },
};
export default meta;
type Story = StoryObj<typeof MainButton>;
const clickJestFn = jest.fn();
export const DefaultPrimary: Story = {
render: getRenderWrapperForComponent(
<MainButton title="A primary Button" onClick={clickJestFn} />,
),
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@ -32,64 +40,30 @@ export const DefaultPrimary: Story = {
},
};
export const WithIconPrimary: Story = {
render: getRenderWrapperForComponent(
<MainButton
icon={<IconBrandGoogle size={16} stroke={4} />}
title="A primary Button"
/>,
),
export const WithIcon: Story = {
args: { icon: true },
};
export const WithIconPrimaryDisabled: Story = {
render: getRenderWrapperForComponent(
<MainButton
icon={<IconBrandGoogle size={16} stroke={4} />}
title="A primary Button"
disabled
/>,
),
export const DisabledWithIcon: Story = {
args: { ...WithIcon.args, disabled: true },
};
export const FullWidthPrimary: Story = {
render: getRenderWrapperForComponent(
<MainButton title="A primary Button" fullWidth />,
),
export const FullWidth: Story = {
args: { fullWidth: true },
};
export const DefaultSecondary: Story = {
render: getRenderWrapperForComponent(
<MainButton
title="A secondary Button"
onClick={clickJestFn}
variant="secondary"
/>,
),
export const Secondary: Story = {
args: { title: 'A secondary Button', variant: 'secondary' },
};
export const WithIconSecondary: Story = {
render: getRenderWrapperForComponent(
<MainButton
icon={<IconBrandGoogle size={16} stroke={4} />}
title="A secondary Button"
variant="secondary"
/>,
),
export const SecondaryWithIcon: Story = {
args: { ...Secondary.args, ...WithIcon.args },
};
export const WithIconSecondaryDisabled: Story = {
render: getRenderWrapperForComponent(
<MainButton
icon={<IconBrandGoogle size={16} stroke={4} />}
title="A secondary Button"
variant="secondary"
disabled
/>,
),
export const SecondaryDisabledWithIcon: Story = {
args: { ...SecondaryWithIcon.args, disabled: true },
};
export const FullWidthSecondary: Story = {
render: getRenderWrapperForComponent(
<MainButton title="A secondary Button" variant="secondary" fullWidth />,
),
export const SecondaryFullWidth: Story = {
args: { ...Secondary.args, ...FullWidth.args },
};

View File

@ -3,27 +3,23 @@ import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { IconArrowRight } from '@/ui/icon';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { RoundedIconButton } from '../RoundedIconButton';
const clickJestFn = jest.fn();
const meta: Meta<typeof RoundedIconButton> = {
title: 'UI/Button/RoundedIconButton',
component: RoundedIconButton,
decorators: [ComponentDecorator],
args: { onClick: clickJestFn, icon: <IconArrowRight size={15} /> },
};
export default meta;
type Story = StoryObj<typeof RoundedIconButton>;
const clickJestFn = jest.fn();
export const Default: Story = {
render: getRenderWrapperForComponent(
<RoundedIconButton
onClick={clickJestFn}
icon={<IconArrowRight size={15} />}
/>,
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { ColorSchemeCard } from '../ColorSchemeCard';
@ -16,27 +16,34 @@ const Container = styled.div`
const meta: Meta<typeof ColorSchemeCard> = {
title: 'UI/ColorScheme/ColorSchemeCard',
component: ColorSchemeCard,
decorators: [
(Story) => (
<Container>
<Story />
</Container>
),
ComponentDecorator,
],
argTypes: {
variant: { control: false },
},
args: { selected: false },
};
export default meta;
type Story = StoryObj<typeof ColorSchemeCard>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<Container>
<ColorSchemeCard variant="light" selected={false} />
<ColorSchemeCard variant="dark" selected={false} />
<ColorSchemeCard variant="system" selected={false} />
</Container>,
render: (args) => (
<>
<ColorSchemeCard variant="light" selected={args.selected} />
<ColorSchemeCard variant="dark" selected={args.selected} />
<ColorSchemeCard variant="system" selected={args.selected} />
</>
),
};
export const Selected: Story = {
render: getRenderWrapperForComponent(
<Container>
<ColorSchemeCard variant="light" selected={true} />
<ColorSchemeCard variant="dark" selected={true} />
<ColorSchemeCard variant="system" selected={true} />
</Container>,
),
...Default,
args: { selected: true },
};

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { useState } from 'react';
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { IconPlus } from '@/ui/icon/index';
import { Avatar } from '@/users/components/Avatar';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { DropdownMenu } from '../DropdownMenu';
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
@ -17,6 +17,11 @@ import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
const meta: Meta<typeof DropdownMenu> = {
title: 'UI/Dropdown/DropdownMenu',
component: DropdownMenu,
decorators: [ComponentDecorator],
argTypes: {
as: { table: { disable: true } },
theme: { table: { disable: true } },
},
};
export default meta;
@ -97,236 +102,169 @@ const mockSelectArray = [
},
];
const FakeSelectableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
const [selectedItem, setSelectedItem] = useState<string | null>(null);
return (
<>
{mockSelectArray.map((item) => (
<DropdownMenuSelectableItem
key={item.id}
selected={selectedItem === item.id}
onClick={() => setSelectedItem(item.id)}
>
{hasAvatar && (
<Avatar
placeholder="A"
avatarUrl={item.avatarUrl}
size={16}
type="squared"
/>
)}
{item.name}
</DropdownMenuSelectableItem>
))}
</>
);
};
const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
const [selectedItems, setSelectedItems] = useState<string[]>([]);
return (
<>
{mockSelectArray.map((item) => (
<DropdownMenuCheckableItem
key={item.id}
id={item.id}
checked={selectedItems.includes(item.id)}
onChange={(checked) => {
if (checked) {
setSelectedItems([...selectedItems, item.id]);
} else {
setSelectedItems(selectedItems.filter((id) => id !== item.id));
}
}}
>
{hasAvatar && (
<Avatar
placeholder="A"
avatarUrl={item.avatarUrl}
size={16}
type="squared"
/>
)}
{item.name}
</DropdownMenuCheckableItem>
))}
</>
);
};
export const Empty: Story = {
render: getRenderWrapperForComponent(
<DropdownMenu>
render: (args) => (
<DropdownMenu {...args}>
<FakeMenuContent />
</DropdownMenu>,
</DropdownMenu>
),
};
const DropdownMenuStoryWrapper = ({
children,
}: React.PropsWithChildren<unknown>) => (
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<DropdownMenu>{children}</DropdownMenu>
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>
);
export const EmptyWithContentBelow: Story = {
render: getRenderWrapperForComponent(
<DropdownMenuStoryWrapper>
<FakeMenuContent />
</DropdownMenuStoryWrapper>,
),
export const WithContentBelow: Story = {
...Empty,
decorators: [
(Story) => (
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<Story />
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>
),
],
};
export const SimpleMenuItem: Story = {
render: getRenderWrapperForComponent(
<DropdownMenuStoryWrapper>
...WithContentBelow,
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenuStoryWrapper>,
</DropdownMenu>
),
};
export const Search: Story = {
render: getRenderWrapperForComponent(
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<DropdownMenu>
<DropdownMenuSearch />
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<FakeMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>,
...WithContentBelow,
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuSearch />
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<FakeMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
),
};
const FakeSelectableMenuItemList = () => {
const [selectedItem, setSelectedItem] = useState<string | null>(null);
return (
<>
{mockSelectArray.map((item) => (
<DropdownMenuSelectableItem
key={item.id}
selected={selectedItem === item.id}
onClick={() => setSelectedItem(item.id)}
>
{item.name}
</DropdownMenuSelectableItem>
))}
</>
);
};
export const Button: Story = {
render: getRenderWrapperForComponent(
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<DropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<DropdownMenuItem>
<IconPlus size={16} />
<div>Create new</div>
</DropdownMenuItem>
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>,
...WithContentBelow,
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItemsContainer hasMaxHeight>
<DropdownMenuItem>
<IconPlus size={16} />
<div>Create new</div>
</DropdownMenuItem>
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
),
};
export const SelectableMenuItem: Story = {
render: getRenderWrapperForComponent(
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<DropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>,
...WithContentBelow,
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
),
};
const FakeSelectableMenuItemWithAvatarList = () => {
const [selectedItem, setSelectedItem] = useState<string | null>(null);
return (
<>
{mockSelectArray.map((item) => (
<DropdownMenuSelectableItem
key={item.id}
selected={selectedItem === item.id}
onClick={() => setSelectedItem(item.id)}
>
<Avatar
placeholder="A"
avatarUrl={item.avatarUrl}
size={16}
type="squared"
/>
{item.name}
</DropdownMenuSelectableItem>
))}
</>
);
};
export const SelectableMenuItemWithAvatar: Story = {
render: getRenderWrapperForComponent(
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<DropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemWithAvatarList />
</DropdownMenuItemsContainer>
</DropdownMenu>
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>,
...WithContentBelow,
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeSelectableMenuItemList hasAvatar />
</DropdownMenuItemsContainer>
</DropdownMenu>
),
};
const FakeCheckableMenuItemList = () => {
const [selectedItems, setSelectedItems] = useState<string[]>([]);
return (
<>
{mockSelectArray.map((item) => (
<DropdownMenuCheckableItem
key={item.id}
id={item.id}
checked={selectedItems.includes(item.id)}
onChange={(checked) => {
if (checked) {
setSelectedItems([...selectedItems, item.id]);
} else {
setSelectedItems(selectedItems.filter((id) => id !== item.id));
}
}}
>
{item.name}
</DropdownMenuCheckableItem>
))}
</>
);
};
export const CheckableMenuItem: Story = {
render: getRenderWrapperForComponent(
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<DropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeCheckableMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>,
...WithContentBelow,
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeCheckableMenuItemList />
</DropdownMenuItemsContainer>
</DropdownMenu>
),
};
const FakeCheckableMenuItemWithAvatarList = () => {
const [selectedItems, setSelectedItems] = useState<string[]>([]);
return (
<>
{mockSelectArray.map((item) => (
<DropdownMenuCheckableItem
key={item.id}
id={item.id}
checked={selectedItems.includes(item.id)}
onChange={(checked) => {
if (checked) {
setSelectedItems([...selectedItems, item.id]);
} else {
setSelectedItems(selectedItems.filter((id) => id !== item.id));
}
}}
>
<Avatar
placeholder="A"
avatarUrl={item.avatarUrl}
size={16}
type="squared"
/>
{item.name}
</DropdownMenuCheckableItem>
))}
</>
);
};
export const CheckableMenuItemWithAvatar: Story = {
render: getRenderWrapperForComponent(
<FakeBelowContainer>
<FakeContentBelow />
<MenuAbsolutePositionWrapper>
<DropdownMenu>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeCheckableMenuItemWithAvatarList />
</DropdownMenuItemsContainer>
</DropdownMenu>
</MenuAbsolutePositionWrapper>
</FakeBelowContainer>,
...WithContentBelow,
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeCheckableMenuItemList hasAvatar />
</DropdownMenuItemsContainer>
</DropdownMenu>
),
};

View File

@ -1,23 +1,31 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconCalendar } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { DateEditableField } from '../DateEditableField';
const meta: Meta<typeof DateEditableField> = {
title: 'UI/EditableField/DateEditableField',
component: DateEditableField,
decorators: [ComponentDecorator],
argTypes: {
icon: {
type: 'boolean',
mapping: {
true: <IconCalendar />,
false: undefined,
},
},
value: { control: { type: 'date' } },
},
args: {
value: new Date().toISOString(),
icon: true,
},
};
export default meta;
type Story = StoryObj<typeof DateEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<DateEditableField
value={new Date().toISOString()}
icon={<IconCalendar />}
/>,
),
};
export const Default: Story = {};

View File

@ -1,24 +1,32 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconCurrencyDollar } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { NumberEditableField } from '../NumberEditableField';
const meta: Meta<typeof NumberEditableField> = {
title: 'UI/EditableField/NumberEditableField',
component: NumberEditableField,
decorators: [ComponentDecorator],
argTypes: {
icon: {
type: 'boolean',
mapping: {
true: <IconCurrencyDollar />,
false: undefined,
},
},
value: { control: { type: 'number' } },
},
args: {
value: 10,
icon: true,
placeholder: 'Number',
},
};
export default meta;
type Story = StoryObj<typeof NumberEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<NumberEditableField
value={10}
icon={<IconCurrencyDollar />}
placeholder="Number"
/>,
),
};
export const Default: Story = {};

View File

@ -2,26 +2,38 @@ import { BrowserRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react';
import { IconPhone } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { PhoneEditableField } from '../PhoneEditableField';
const meta: Meta<typeof PhoneEditableField> = {
title: 'UI/EditableField/PhoneEditableField',
component: PhoneEditableField,
decorators: [
(Story) => (
<BrowserRouter>
<Story />
</BrowserRouter>
),
ComponentDecorator,
],
argTypes: {
icon: {
type: 'boolean',
mapping: {
true: <IconPhone />,
false: undefined,
},
},
},
args: {
value: '+33714446494',
icon: true,
placeholder: 'Phone',
},
};
export default meta;
type Story = StoryObj<typeof PhoneEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<BrowserRouter>
<PhoneEditableField
value={'+33714446494'}
icon={<IconPhone />}
placeholder="Phone"
/>
</BrowserRouter>,
),
};
export const Default: Story = {};

View File

@ -1,24 +1,31 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconUser } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { TextEditableField } from '../TextEditableField';
const meta: Meta<typeof TextEditableField> = {
title: 'UI/EditableField/TextEditableField',
component: TextEditableField,
decorators: [ComponentDecorator],
argTypes: {
icon: {
type: 'boolean',
mapping: {
true: <IconUser />,
false: undefined,
},
},
},
args: {
value: 'John Doe',
icon: true,
placeholder: 'Name',
},
};
export default meta;
type Story = StoryObj<typeof TextEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<TextEditableField
value={'John Doe'}
icon={<IconUser />}
placeholder="Name"
/>,
),
};
export const Default: Story = {};

View File

@ -1,17 +1,16 @@
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { AutosizeTextInput } from '../AutosizeTextInput';
const meta: Meta<typeof AutosizeTextInput> = {
title: 'UI/Inputs/AutosizeTextInput',
component: AutosizeTextInput,
decorators: [ComponentDecorator],
};
export default meta;
type Story = StoryObj<typeof AutosizeTextInput>;
export const Default: Story = {
render: getRenderWrapperForComponent(<AutosizeTextInput />),
};
export const Default: Story = {};

View File

@ -4,37 +4,44 @@ import { jest } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { TextInput } from '../TextInput';
const changeJestFn = jest.fn();
const meta: Meta<typeof TextInput> = {
title: 'UI/Inputs/TextInput',
component: TextInput,
decorators: [ComponentDecorator],
args: { value: '', onChange: changeJestFn, placeholder: 'Placeholder' },
};
export default meta;
type Story = StoryObj<typeof TextInput>;
const changeJestFn = jest.fn();
function FakeTextInput({ onChange }: any) {
const [value, setValue] = useState<string>('A good value ');
function FakeTextInput({
onChange,
value: initialValue,
...props
}: React.ComponentProps<typeof TextInput>) {
const [value, setValue] = useState(initialValue);
return (
<TextInput
{...props}
value={value}
onChange={(text) => {
setValue(text);
onChange(text);
onChange?.(text);
}}
/>
);
}
export const Default: Story = {
render: getRenderWrapperForComponent(
<FakeTextInput onChange={changeJestFn} />,
),
argTypes: { value: { control: false } },
args: { value: 'A good value ' },
render: (args) => <FakeTextInput {...args} />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@ -47,31 +54,22 @@ export const Default: Story = {
},
};
export const Placeholder: Story = {
render: getRenderWrapperForComponent(
<TextInput value="" onChange={changeJestFn} placeholder="Placeholder" />,
),
};
export const Placeholder: Story = {};
export const FullWidth: Story = {
render: getRenderWrapperForComponent(
<TextInput
value="A good value"
onChange={changeJestFn}
placeholder="Placeholder"
fullWidth
/>,
),
args: { value: 'A good value', fullWidth: true },
};
export const WithLabel: Story = {
args: { label: 'Lorem ipsum' },
};
export const WithError: Story = {
args: { error: 'Lorem ipsum' },
};
export const PasswordInput: Story = {
render: getRenderWrapperForComponent(
<TextInput
onChange={changeJestFn}
type="password"
placeholder="Password"
/>,
),
args: { type: 'password', placeholder: 'Password' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@ -3,13 +3,21 @@ import { expect, jest } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { PrimaryLink } from '../PrimaryLink';
const meta: Meta<typeof PrimaryLink> = {
title: 'UI/Links/PrimaryLink',
component: PrimaryLink,
decorators: [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
),
ComponentDecorator,
],
};
export default meta;
@ -18,13 +26,7 @@ type Story = StoryObj<typeof PrimaryLink>;
const clickJestFn = jest.fn();
export const Default: Story = {
render: getRenderWrapperForComponent(
<MemoryRouter>
<PrimaryLink href="/test" onClick={clickJestFn}>
A primary link
</PrimaryLink>
</MemoryRouter>,
),
args: { href: '/test', onClick: clickJestFn, children: 'A primary link' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@ -1,17 +1,16 @@
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { SoonPill } from '../SoonPill';
const meta: Meta<typeof SoonPill> = {
title: 'UI/Accessories/SoonPill',
component: SoonPill,
decorators: [ComponentDecorator],
};
export default meta;
type Story = StoryObj<typeof SoonPill>;
export const Default: Story = {
render: getRenderWrapperForComponent(<SoonPill />),
};
export const Default: Story = {};

View File

@ -1,33 +1,27 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { RightDrawerTopBar } from '../RightDrawerTopBar';
const meta: Meta<typeof RightDrawerTopBar> = {
title: 'UI/RightDrawer/RightDrawerTopBar',
component: RightDrawerTopBar,
argTypes: {
title: {
control: { type: 'text' },
defaultValue: 'My Title',
},
decorators: [
(Story) => (
<div style={{ width: '500px' }}>
<Story />
</div>
),
ComponentDecorator,
],
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof RightDrawerTopBar>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<div style={{ width: '500px' }}>
<RightDrawerTopBar />
</div>,
),
parameters: {
msw: graphqlMocks,
actions: { argTypesRegex: '^on.*' },
},
args: {},
};
export const Default: Story = {};

View File

@ -1,12 +1,15 @@
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { Tag } from '../Tag';
const meta: Meta<typeof Tag> = {
title: 'UI/Accessories/Tag',
component: Tag,
decorators: [ComponentDecorator],
argTypes: { color: { control: false } },
args: { text: 'Urgent' },
};
export default meta;
@ -26,11 +29,11 @@ const TESTED_COLORS = [
];
export const AllTags: Story = {
render: getRenderWrapperForComponent(
render: (args) => (
<>
{TESTED_COLORS.map((color) => (
<Tag text="Urgent" color={color} />
<Tag {...args} color={color} />
))}
</>,
</>
),
};

View File

@ -1,40 +1,33 @@
import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { ComponentDecorator } from '~/testing/decorators';
import { avatarUrl } from '~/testing/mock-data/users';
import { Avatar } from '../Avatar';
const meta: Meta<typeof Avatar> = {
title: 'Modules/Users/Avatar',
component: Avatar,
decorators: [ComponentDecorator],
args: { avatarUrl, size: 16, placeholder: 'L', type: 'rounded' },
};
export default meta;
type Story = StoryObj<typeof Avatar>;
const avatarUrl =
'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAABSgAwAEAAAAAQAAABQAAAAA/8AAEQgAFAAUAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/dAAQAAv/aAAwDAQACEQMRAD8Ava1q728otYY98joSCTgZrnbXWdTtrhrfVZXWLafmcAEkdgR/hVltQku9Q8+OIEBcGOT+ID0PY1ka1KH2u8ToqnPLbmIqG7u6LtbQ7RXBRec4Uck9eKXcPWsKDWVnhWSL5kYcFelSf2m3901POh8jP//QoyIAnTuKpXsY82NsksUyWPU5q/L9z8RVK++/F/uCsVsaEURwgA4HtT9x9TUcf3KfUGh//9k=';
export const Rounded: Story = {
render: getRenderWrapperForComponent(
<Avatar avatarUrl={avatarUrl} size={16} placeholder="L" type="rounded" />,
),
};
export const Rounded: Story = {};
export const Squared: Story = {
render: getRenderWrapperForComponent(
<Avatar avatarUrl={avatarUrl} size={16} placeholder="L" type="squared" />,
),
args: { type: 'squared' },
};
export const NoAvatarPictureRounded: Story = {
render: getRenderWrapperForComponent(
<Avatar avatarUrl={''} size={16} placeholder="L" type="rounded" />,
),
args: { avatarUrl: '' },
};
export const NoAvatarPictureSquared: Story = {
render: getRenderWrapperForComponent(
<Avatar avatarUrl={''} size={16} placeholder="L" type="squared" />,
),
args: {
...NoAvatarPictureRounded.args,
...Squared.args,
},
};