From 2b62f26c52893f538668e4e944267559c6bff280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matth=C3=A4us=20N=C3=B6ssing?= Date: Tue, 19 Sep 2023 10:27:57 +0200 Subject: [PATCH 1/2] change alert and actions to tsx instead of mdx --- src/components/action/Actions.stories.mdx | 64 ----- src/components/action/Actions.stories.tsx | 70 +++++ src/components/action/Actions.tsx | 2 +- src/components/alert/Alert.stories.mdx | 243 ----------------- src/components/alert/Alert.stories.tsx | 252 ++++++++++++++++++ .../content/__test__/WithPlaceholder.test.tsx | 90 +++++++ 6 files changed, 413 insertions(+), 308 deletions(-) delete mode 100644 src/components/action/Actions.stories.mdx create mode 100644 src/components/action/Actions.stories.tsx delete mode 100644 src/components/alert/Alert.stories.mdx create mode 100644 src/components/alert/Alert.stories.tsx create mode 100644 src/components/content/__test__/WithPlaceholder.test.tsx diff --git a/src/components/action/Actions.stories.mdx b/src/components/action/Actions.stories.mdx deleted file mode 100644 index 0f20992b..00000000 --- a/src/components/action/Actions.stories.mdx +++ /dev/null @@ -1,64 +0,0 @@ -import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs' -import { Theme } from '../../../.storybook/components' -import { Button, ButtonVariant } from '../button' -import { Tone } from '../types' -import { Actions } from './Actions' - - - -# Actions - -The `Actions` component positions children in a flex container. -It makes sure that there is space between each item and that they stack on mobile. -This is very handy in a content area or a dialog, where you have two buttons next to each other. - -export const Template = ({ children, ...args }) => ( - {children} -) - - - Save, - 'Two buttons': ( - <> - - - - ), - }, - control: { type: 'select' }, - }, - }} - > - {Template.bind({})} - - - -### Props - - - - - - - -## Multiple children - - - - - - - - - diff --git a/src/components/action/Actions.stories.tsx b/src/components/action/Actions.stories.tsx new file mode 100644 index 00000000..8a6c6728 --- /dev/null +++ b/src/components/action/Actions.stories.tsx @@ -0,0 +1,70 @@ +import { + Controls, + Markdown, + Primary, + Stories, + Subheading, + Title, +} from '@storybook/addon-docs' +import { Meta, StoryObj } from '@storybook/react' +import { Button, ButtonVariant } from '../button' +import { Tone } from '../types' +import { Actions } from './Actions' +import { Theme } from '../../../.storybook/components' + +const children = { + options: ['One button', 'Two buttons'], + mapping: { + 'One button': , + 'Two buttons': ( + <> + + + + ), + }, + control: { type: 'select' }, +} + +const meta = { + component: Actions, + title: 'Components/Actions/Actions', + args: { + children: children.mapping['One button'], + }, + argTypes: { + children, + }, + parameters: { + docs: { + page: () => ( + <> + + <Markdown> + The `Actions` component positions children in a flex container. It + makes sure that there is space between each item and that they stack + on mobile. This is very handy in a content area or a dialog, where + you have two buttons next to each other. + </Markdown> + <Primary /> + <Subheading>Props</Subheading> + <Controls /> + <Theme component="action" /> + <Stories /> + </> + ), + }, + }, +} satisfies Meta<typeof Actions> + +export default meta +type Story = StoryObj<typeof Actions> + +export const Default: Story = {} +export const MultipleChildren: Story = { + args: { + children: { ...children.mapping['Two buttons'] }, + }, +} diff --git a/src/components/action/Actions.tsx b/src/components/action/Actions.tsx index a6e904eb..af80509a 100644 --- a/src/components/action/Actions.tsx +++ b/src/components/action/Actions.tsx @@ -10,7 +10,7 @@ export type ActionsProps = ClassNameProps & { **/ position?: ActionsPosition /** - * Defineds the children to be rendered. + * Defines the children to be rendered. */ children?: ReactNode } diff --git a/src/components/alert/Alert.stories.mdx b/src/components/alert/Alert.stories.mdx deleted file mode 100644 index d2a75bed..00000000 --- a/src/components/alert/Alert.stories.mdx +++ /dev/null @@ -1,243 +0,0 @@ -import IconWarning from '@aboutbits/react-material-icons/dist/IconWarning' -import IconCheck from '@aboutbits/react-material-icons/dist/IconCheck' -import IconInfo from '@aboutbits/react-material-icons/dist/IconInfo' -import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs' -import { Theme } from '../../../.storybook/components' -import { Size, Tone } from '../types' -import { ButtonVariant } from '../button/types' -import { Button } from '../button' -import { Alert } from './Alert' -import { AlertActionsPosition } from './types' -import { - AlertSuccess, - AlertWarning, - AlertCritical, - AlertInformative, -} from './ConvenientAlerts' - -<Meta title="Components/Alert/Alert" component={Alert} /> - -# Alert - -This alert component can be used to highlight a message to the user. -It supports different types of tones. It is possible to specify an icon, a title, actions and the position of the actions. - -The library also provides convenient alert components with predefined tone and icon. - -export const Template = ({ children, ...args }) => ( - <Alert {...args}>{children}</Alert> -) - -export const DissmissCriticalButton = ( - <Button - variant={ButtonVariant.Transparent} - size={Size.Sm} - tone={Tone.Critical} - > - Dismiss - </Button> -) - -export const YesNoCriticalButtons = ( - <> - <Button - variant={ButtonVariant.Transparent} - size={Size.Sm} - tone={Tone.Critical} - > - Yes - </Button> - <Button - variant={ButtonVariant.Transparent} - size={Size.Sm} - tone={Tone.Critical} - > - No - </Button> - </> -) - -<Canvas> - <Story - name="Default" - args={{ - children: - 'This alert component can be used to highlight a message to the user.', - tone: Tone.Critical, - icon: 'Warning', - }} - argTypes={{ - title: { control: 'text' }, - tone: { - options: Object.values(Tone), - control: { type: 'select' }, - }, - icon: { - options: ['None', 'Warning', 'Check', 'Info'], - mapping: { - None: undefined, - Warning: IconWarning, - Check: IconCheck, - Info: IconInfo, - }, - control: { type: 'select' }, - }, - actions: { - options: ['None', 'DismissCritical', 'YesNoCritical'], - mapping: { - None: undefined, - DismissCritical: DissmissCriticalButton, - YesNoCritical: YesNoCriticalButtons, - }, - control: { type: 'select' }, - }, - }} - > - {Template.bind({})} - </Story> -</Canvas> - -### Props - -<ArgsTable story="Default" /> - - - -<Theme component="alert" /> - -## Tone - -<Canvas> - <Story name="Primary"> - <Alert tone={Tone.Primary}>Primary message</Alert> - </Story> - <Story name="Neutral"> - <Alert tone={Tone.Neutral}>Neutral message</Alert> - </Story> - <Story name="Warning"> - <Alert tone={Tone.Warning}>Warning message</Alert> - </Story> - <Story name="Critical"> - <Alert tone={Tone.Critical}>Critical message</Alert> - </Story> - <Story name="Success"> - <Alert tone={Tone.Success}>Success message</Alert> - </Story> - <Story name="Informative"> - <Alert tone={Tone.Informative}>Informative message</Alert> - </Story> -</Canvas> - -### Icon - -<Canvas> - <Story name="Icon"> - <Alert icon={IconCheck} tone={Tone.Success}> - Success Message - </Alert> - </Story> -</Canvas> - -### Title - -<Canvas> - <Story name="Title"> - <Alert icon={IconCheck} tone={Tone.Success} title="Success"> - Congratulations, this is a success message! - </Alert> - </Story> -</Canvas> - -## Actions - -<Canvas> - <Story name="ActionsResponsive"> - <Alert - icon={IconCheck} - tone={Tone.Success} - actions={ - <Button - tone={Tone.Success} - variant={ButtonVariant.Transparent} - size={Size.Sm} - > - Dismiss - </Button> - } - actionsPosition={AlertActionsPosition.Responsive} - > - Success message with <b>responsive</b> actions - </Alert> - </Story> -</Canvas> - -<Canvas> - <Story name="ActionsFixedRight"> - <Alert - icon={IconCheck} - tone={Tone.Success} - actions={ - <Button - tone={Tone.Success} - variant={ButtonVariant.Transparent} - size={Size.Sm} - > - Dismiss - </Button> - } - actionsPosition={AlertActionsPosition.FixedRight} - > - Success message with actions <b>fixed to the right</b> - </Alert> - </Story> -</Canvas> - -<Canvas> - <Story name="ActionsFixedBottom"> - <Alert - icon={IconCheck} - tone={Tone.Success} - actions={ - <Button - tone={Tone.Success} - variant={ButtonVariant.Transparent} - size={Size.Sm} - > - Dismiss - </Button> - } - actionsPosition={AlertActionsPosition.FixedBottom} - > - Success message with actions <b>fixed to the bottom</b> - </Alert> - </Story> -</Canvas> - -## Very Long Message - -<Canvas> - <Story name="Very Long Message"> - <Alert icon={IconWarning} tone={Tone.Critical}> - Duis mauris ligula, aliquam id maximus at, aliquam ut dui. Morbi ac neque - mattis libero cursus lobortis quis ac urna. Nullam vel augue vitae lacus - fermentum ullamcorper eu eget sapien. - </Alert> - </Story> -</Canvas> - -## Convenient alert components - -<Canvas> - <Story name="AlertSuccess"> - <AlertSuccess>Success message</AlertSuccess> - </Story> - <Story name="AlertWarning"> - <AlertWarning>Warning message</AlertWarning> - </Story> - <Story name="AlertCritical"> - <AlertCritical>Critical message</AlertCritical> - </Story> - <Story name="AlertInformative"> - <AlertInformative>Informative message</AlertInformative> - </Story> -</Canvas> diff --git a/src/components/alert/Alert.stories.tsx b/src/components/alert/Alert.stories.tsx new file mode 100644 index 00000000..4c02c1f3 --- /dev/null +++ b/src/components/alert/Alert.stories.tsx @@ -0,0 +1,252 @@ +import { + Controls, + Markdown, + Primary, + Stories, + Subheading, + Title, +} from '@storybook/addon-docs' +import { Meta, StoryObj } from '@storybook/react' +import { Button, ButtonVariant } from '../button' +import { Size, Tone } from '../types' +import { Theme } from '../../../.storybook/components' +import { Alert } from './Alert' +import IconWarning from '@aboutbits/react-material-icons/dist/IconWarning' +import IconCheck from '@aboutbits/react-material-icons/dist/IconCheck' +import IconInfo from '@aboutbits/react-material-icons/dist/IconInfo' +import { AlertActionsPosition } from './types' +import { + AlertCritical, + AlertInformative, + AlertSuccess, + AlertWarning, +} from './ConvenientAlerts' + +const icons = { + options: ['None', 'Warning', 'Check', 'Info'], + mapping: { + None: undefined, + Warning: IconWarning, + Check: IconCheck, + Info: IconInfo, + }, + control: { type: 'select' }, +} +const DissmissCriticalButton = ( + <Button + variant={ButtonVariant.Transparent} + size={Size.Sm} + tone={Tone.Critical} + > + Dismiss + </Button> +) + +const DissmissSuccessButton = ( + <Button + variant={ButtonVariant.Transparent} + size={Size.Sm} + tone={Tone.Success} + > + Dismiss + </Button> +) + +const YesNoCriticalButtons = ( + <> + <Button + variant={ButtonVariant.Transparent} + size={Size.Sm} + tone={Tone.Critical} + > + Yes + </Button> + <Button + variant={ButtonVariant.Transparent} + size={Size.Sm} + tone={Tone.Critical} + > + No + </Button> + </> +) + +const actions = { + options: ['None', 'DismissCritical', 'YesNoCritical'], + mapping: { + None: undefined, + DismissCritical: DissmissCriticalButton, + YesNoCritical: YesNoCriticalButtons, + }, + control: { type: 'select' }, +} + +const meta = { + component: Alert, + title: 'Components/Alert/Alert', + args: { + children: + 'This alert component can be used to highlight a message to the user.', + tone: Tone.Critical, + icon: IconWarning, + }, + argTypes: { + title: { control: 'text' }, + icon: icons, + actions: actions, + }, + parameters: { + docs: { + page: () => ( + <> + <Title /> + <Markdown> + This alert component can be used to highlight a message to the user. + It supports different types of tones. It is possible to specify an + icon, a title, actions and the position of the actions. The library + also provides convenient alert components with predefined tone and + icon. + </Markdown> + <Primary /> + <Subheading>Props</Subheading> + <Controls /> + <Theme component="alert" /> + <Stories /> + </> + ), + }, + }, +} satisfies Meta<typeof Alert> + +export default meta +type Story = StoryObj<typeof Alert> + +export const Default: Story = {} + +export const Tones: Story = { + args: { + icon: undefined, + }, + render: (args) => ( + <div className="flex flex-row space-x-4"> + <Alert {...args} tone={Tone.Primary}> + Primary message + </Alert> + <Alert {...args} tone={Tone.Neutral}> + Neutral message + </Alert> + <Alert {...args} tone={Tone.Warning}> + Warning message + </Alert> + <Alert {...args} tone={Tone.Critical}> + Critical message + </Alert> + <Alert {...args} tone={Tone.Success}> + Success message + </Alert> + <Alert {...args} tone={Tone.Informative}> + Informative message + </Alert> + </div> + ), +} + +export const ConvinientAlertComponents: Story = { + render: () => ( + <div className="flex flex-row space-x-4"> + <AlertSuccess>Success message</AlertSuccess> + <AlertWarning>Warning message</AlertWarning> + <AlertCritical>Critical message</AlertCritical> + <AlertInformative>Informative message</AlertInformative> + </div> + ), + parameters: { + controls: { + disable: true, + }, + }, +} + +export const ActionsResponsive: Story = { + args: { + icon: IconCheck, + tone: Tone.Success, + actions: DissmissSuccessButton, + actionsPosition: AlertActionsPosition.Responsive, + }, + render: (args) => ( + <Alert {...args}> + Success message with actions <b>responsive</b> + </Alert> + ), + argTypes: { + children: { control: { disabled: true } }, + }, +} + +export const ActionsFixedRight: Story = { + args: { + icon: IconCheck, + tone: Tone.Success, + actions: DissmissSuccessButton, + actionsPosition: AlertActionsPosition.FixedRight, + }, + render: (args) => ( + <Alert {...args}> + Success message with actions <b>fixed to the right</b> + </Alert> + ), + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, +} + +export const ActionsFixedBottom: Story = { + args: { + icon: IconCheck, + tone: Tone.Success, + actions: DissmissSuccessButton, + actionsPosition: AlertActionsPosition.FixedBottom, + }, + render: (args) => ( + <Alert {...args}> + Success message with actions <b>fixed to the right</b> + </Alert> + ), + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, +} + +export const VeryLongMessage: Story = { + args: { + icon: IconWarning, + tone: Tone.Critical, + }, + render: (args) => ( + <Alert {...args}> + Duis mauris ligula, aliquam id maximus at, aliquam ut dui. Morbi ac neque + mattis libero cursus lobortis quis ac urna. Nullam vel augue vitae lacus + fermentum ullamcorper eu eget sapien. + </Alert> + ), + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, +} + +export const WithTitle: Story = { + args: { title: 'This is the title' }, +} diff --git a/src/components/content/__test__/WithPlaceholder.test.tsx b/src/components/content/__test__/WithPlaceholder.test.tsx new file mode 100644 index 00000000..10676493 --- /dev/null +++ b/src/components/content/__test__/WithPlaceholder.test.tsx @@ -0,0 +1,90 @@ +import { queryByText, render } from '@testing-library/react' +import { ReactNode } from 'react' +import { WithPlaceholder } from '../WithPlaceholder/WithPlaceholder' + +const expectTextContent = ( + children: ReactNode, + text: number | string, + placeholder?: number | string, +) => { + const { container } = render( + <WithPlaceholder placeholder={placeholder}>{children}</WithPlaceholder>, + ) + expect(queryByText(container, text)).toHaveTextContent(text.toString()) +} + +const expectNotTextContent = ( + children: ReactNode, + text: number | string, + placeholder?: number | string, +) => { + const { container } = render( + <WithPlaceholder placeholder={placeholder}>{children}</WithPlaceholder>, + ) + expect(queryByText(container, text)).toBeNull() +} + +const placeholder = '/' + +const expectPlaceholder = (children: ReactNode) => { + expectTextContent(children, placeholder, placeholder) +} + +const expectNotPlaceholder = (children: ReactNode) => { + expectNotTextContent(children, placeholder, placeholder) +} + +describe('WithPlaceholder', () => { + it('should render the placeholder if NaN is passed', () => { + expectPlaceholder(NaN) + }) + it('should render the placeholder if undefined is passed', () => { + expectPlaceholder(undefined) + }) + it('should render the placeholder if null is passed', () => { + expectPlaceholder(null) + }) + it('should render the placeholder if an empty string is passed', () => { + expectPlaceholder('') + }) + it('should render 0 if 0 is passed', () => { + const value = 0 + expectTextContent(value, value) + }) + it('should render 1 if 1 is passed', () => { + const value = 1 + expectTextContent(value, value) + }) + it('should render -1 if -1 is passed', () => { + const value = -1 + expectTextContent(value, value) + }) + it('should render the given string if the string is not empty', () => { + const value = 'test' + expectTextContent(value, value) + }) + it('should render the given div containing a string', () => { + const value = 'test' + expectTextContent(<div>{value}</div>, value) + }) + it('should render the given div containing 0', () => { + const value = 0 + expectTextContent(<div>{value}</div>, value) + }) + it('should render the given div even if it contains NaN', () => { + const value = NaN + expectTextContent(<div>{value}</div>, value) + }) + it('should render the given div even if it contains undefined', () => { + const value = undefined + expectNotPlaceholder(<div>{value}</div>) + }) + it('should render the given div even if it contains null', () => { + const value = null + expectNotPlaceholder(<div>{value}</div>) + }) + it('should render the given div even if it contains an empty string', () => { + const value = '' + expectNotPlaceholder(<div>{value}</div>) + }) +}) From 2088a8762dc767edf55ecaf6e43a71df1ecc9cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matth=C3=A4us=20N=C3=B6ssing?= <noessing.matthaeus@protonmail.com> Date: Tue, 19 Sep 2023 10:32:01 +0200 Subject: [PATCH 2/2] lint fix --- src/components/action/Actions.stories.tsx | 2 +- src/components/alert/Alert.stories.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/action/Actions.stories.tsx b/src/components/action/Actions.stories.tsx index 8a6c6728..6e4cd9b4 100644 --- a/src/components/action/Actions.stories.tsx +++ b/src/components/action/Actions.stories.tsx @@ -9,8 +9,8 @@ import { import { Meta, StoryObj } from '@storybook/react' import { Button, ButtonVariant } from '../button' import { Tone } from '../types' -import { Actions } from './Actions' import { Theme } from '../../../.storybook/components' +import { Actions } from './Actions' const children = { options: ['One button', 'Two buttons'], diff --git a/src/components/alert/Alert.stories.tsx b/src/components/alert/Alert.stories.tsx index 4c02c1f3..eba7c4cc 100644 --- a/src/components/alert/Alert.stories.tsx +++ b/src/components/alert/Alert.stories.tsx @@ -7,13 +7,13 @@ import { Title, } from '@storybook/addon-docs' import { Meta, StoryObj } from '@storybook/react' +import IconWarning from '@aboutbits/react-material-icons/dist/IconWarning' +import IconCheck from '@aboutbits/react-material-icons/dist/IconCheck' +import IconInfo from '@aboutbits/react-material-icons/dist/IconInfo' import { Button, ButtonVariant } from '../button' import { Size, Tone } from '../types' import { Theme } from '../../../.storybook/components' import { Alert } from './Alert' -import IconWarning from '@aboutbits/react-material-icons/dist/IconWarning' -import IconCheck from '@aboutbits/react-material-icons/dist/IconCheck' -import IconInfo from '@aboutbits/react-material-icons/dist/IconInfo' import { AlertActionsPosition } from './types' import { AlertCritical, @@ -93,7 +93,7 @@ const meta = { argTypes: { title: { control: 'text' }, icon: icons, - actions: actions, + actions, }, parameters: { docs: {