Update Modal documentation in storybook

Change-Id: I4b64feffccaae46b32d5619cec4cc9551ff9e7ed
This commit is contained in:
Roman Jaroš 2023-08-27 10:12:08 +02:00
parent 3a9b0872e4
commit 315756790e
11 changed files with 286 additions and 132 deletions

View file

@ -1,7 +1,3 @@
# Prokyon # Prokyon
Framework for react app. Suite of React components
## Release and deploy
- use Jenkins parametrized pipeline

View file

@ -3,28 +3,20 @@ import clsx from 'clsx';
import { DirectionEnum } from '@prokyon/types/common'; import { DirectionEnum } from '@prokyon/types/common';
import { Title, TitleSizeEnum } from './Title';
export type SectionProps = { export type SectionProps = {
Title: FC;
children: any; children: any;
margin?: Partial<Record<DirectionEnum, number>>; space?: Partial<Record<DirectionEnum, number>>;
title?: string;
titleMargin?: Partial<Record<DirectionEnum, number>>;
titleSize?: TitleSizeEnum;
}; };
const Section: FC<SectionProps> = ({ title, children, titleSize, margin, titleMargin }) => { const Section: FC<SectionProps> = ({ Title, children, space = { [DirectionEnum.bottom]: 4 } }) => {
const margins: string[] = []; const spaces: string[] = [];
for (const direct in margin) { for (const direct in space) {
margins.push(`m${direct}-${margin[direct]}`); spaces.push(`m${direct}-${space[direct]}`);
} }
return ( return (
<div className={clsx(margins)}> <div className={clsx(spaces)}>
{title && ( {Title && <Title />}
<Title size={titleSize} margin={titleMargin}>
{title}
</Title>
)}
{children} {children}
</div> </div>
); );

View file

@ -1,11 +1,27 @@
import React from 'react'; import React from 'react';
import clsx from 'clsx';
import { isNilOrEmpty } from '@prokyon/utils'; import { isNilOrEmpty } from '@prokyon/utils';
import Message from '../../Message'; import Message from '../../Message';
import { useToaster } from '../hooks'; import { useToaster } from '../hooks';
export function Toasters() { export enum ToastersPosition {
topLeft,
topCenter,
topRight,
rightCenter,
bottomRight,
bottomCenter,
bottomLeft,
leftCenter,
}
export type ToastersProps = {
position: ToastersPosition;
};
export function Toasters({ position = ToastersPosition.topRight }) {
const { toasters } = useToaster(); const { toasters } = useToaster();
if (isNilOrEmpty(toasters)) { if (isNilOrEmpty(toasters)) {
@ -13,7 +29,17 @@ export function Toasters() {
} }
return ( return (
<div className="fixed top-4 right-6 sm-m:left-6 w-80 sm-m:w-auto z-10"> <div
className={clsx('toasters', {
'toasters--topLeft': ToastersPosition.topLeft === position,
'toasters--topCenter': ToastersPosition.topCenter === position,
'toasters--topRight': ToastersPosition.topRight === position,
'toasters--rightCenter': ToastersPosition.rightCenter === position,
'toasters--bottomRight': ToastersPosition.bottomRight === position,
'toasters--bottomCenter': ToastersPosition.bottomCenter === position,
'toasters--bottomLeft': ToastersPosition.bottomLeft === position,
'toasters--leftCenter': ToastersPosition.leftCenter === position,
})}>
{Object.keys(toasters).map((toasterId) => { {Object.keys(toasters).map((toasterId) => {
const toaster = toasters[toasterId]; const toaster = toasters[toasterId];
return ( return (

View file

@ -16,8 +16,10 @@ const buildState = (theme, color, bg) => {
'& > div:nth-of-type(2)': { '& > div:nth-of-type(2)': {
width: '100%', width: '100%',
border: `1px solid ${color}`, border: `1px solid ${color}`,
backgroundColor: bg, backgroundColor: 'white',
borderLeft: 'none', borderLeft: 'none',
paddingLeft: theme('spacing.5'),
paddingRight: theme('spacing.5'),
borderRadius: `0 ${theme('borderRadius.md')} ${theme('borderRadius.md')} 0`, borderRadius: `0 ${theme('borderRadius.md')} ${theme('borderRadius.md')} 0`,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -32,7 +34,7 @@ const message = (theme) => ({
color: theme('colors.black'), color: theme('colors.black'),
borderRadius: theme('borderRadius.md'), borderRadius: theme('borderRadius.md'),
backgroundColor: 'white', backgroundColor: 'white',
'&--none': buildState(theme, theme('colors.gray.500'), theme('colors.gray.50')), '&--none': buildState(theme, theme('colors.gray.400'), theme('colors.gray.200')),
'&--info': buildState(theme, theme('colors.info.300'), theme('colors.info.50')), '&--info': buildState(theme, theme('colors.info.300'), theme('colors.info.50')),
'&--success': buildState(theme, theme('colors.success.300'), theme('colors.success.50')), '&--success': buildState(theme, theme('colors.success.300'), theme('colors.success.50')),
'&--warning': buildState(theme, theme('colors.warning.300'), theme('colors.warning.50')), '&--warning': buildState(theme, theme('colors.warning.300'), theme('colors.warning.50')),

View file

@ -60,7 +60,7 @@ const skeleton = (theme) => ({
borderRadius: theme('borderRadius.sm'), borderRadius: theme('borderRadius.sm'),
position: 'relative', position: 'relative',
paddingTop: '2.5rem', paddingTop: '2.5rem',
margin: '0 .125rem', margin: '.125rem',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexShrink: 0, flexShrink: 0,

View file

@ -0,0 +1,52 @@
const buildState = ({ theme, color, backgroundColor, borderColor }) => {
return {};
};
const toaster = (theme) => ({
'.toasters': {
position: 'fixed',
zIndex: 10,
'&--topLeft': {
top: '3rem',
right: '3rem',
},
'&--topCenter': {
top: '3rem',
right: '3rem',
},
'&--topRight': {
top: '3rem',
right: '3rem',
},
'&--rightCenter': {
top: '3rem',
right: '3rem',
},
'&--bottomRight': {
top: '3rem',
right: '3rem',
},
'&--bottomCenter': {
top: '3rem',
right: '3rem',
},
'&--bottomLeft': {
top: '3rem',
right: '3rem',
},
'&--leftCenter': {
top: '3rem',
right: '3rem',
},
[`@media (max-width: ${theme('screens.sm-m.max')})`]: {
left: '2rem',
with: 'auto',
},
},
});
module.exports = ({ addComponents, theme }) => {
addComponents(toaster(theme));
};
module.exports.toaster = toaster;

View file

@ -44,6 +44,7 @@ module.exports = {
plugin(require('./components/modal')), plugin(require('./components/modal')),
plugin(require('./components/calendar')), plugin(require('./components/calendar')),
plugin(require('./components/human')), plugin(require('./components/human')),
plugin(require('./components/toaster')),
function ({ addVariant }) { function ({ addVariant }) {
addVariant('child', '& > *'); addVariant('child', '& > *');
}, },

View file

@ -1,91 +0,0 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Modals } from '@prokyon/components/Modal';
import { AddModalButton } from '@prokyon/components/Modal/components/AddModalButton';
import { ModalWrapper } from '@prokyon/components/Modal/context';
import { TextField } from '@prokyon/components/TextField';
type Story = StoryObj<typeof AddModalButton>;
export default {
component: AddModalButton,
parameters: {
docs: {
description: {
component:
"`import { AddModalButton } from '@prokyon/components/Modal/components/AddModalButton';`",
},
},
},
tags: ['autodocs'],
decorators: [
(Story) => (
<ModalWrapper>
<Story />
<Modals />
</ModalWrapper>
),
],
} as Meta;
export const Default: Story = {
name: 'Default',
args: {
id: 'default',
title: 'Open modal 1',
modalTitle: 'Default modal',
Component: () => <div>Hi there</div>,
},
};
export const Small: Story = {
name: 'Small',
args: {
id: 'small',
title: 'Open modal',
modalTitle: 'Small modal',
Component: () => (
<div>
<TextField name="search" label="Search" />
</div>
),
style: {
width: 250,
},
},
};
const ModalWindow = () => <div>Hi there, again</div>;
export const Multiple: Story = {
name: 'Multiple',
args: {
id: 'multiple',
title: 'Open modal',
modalTitle: 'First modal',
Component: () => (
<div>
Hello
<AddModalButton
Component={ModalWindow}
title="Open next modal"
modalTitle="Second modal"
id="secondModal"
/>
</div>
),
},
};
export const WithCloseCallback: Story = {
args: {
id: 'withCloseCallback',
title: 'Open modal',
modalTitle: 'Modal',
Component: () => <div>Hello</div>,
props: {
onClose: () => console.info('Close callback called.'),
},
},
};

View file

@ -1,8 +1,38 @@
import { Title } from '@storybook/blocks'; import { Canvas, Meta } from '@storybook/blocks';
<Title>Modals</Title> import * as ModalStories from './Modals.stories';
`import Modals from '@prokyon/components/Modal/components/Modals';` <Meta of={ModalStories} />
# Modals
## Before start
### In root application use
```tsx
import Modals from '@prokyon/components/Modal/components/Modals';
import { ModalWrapper } from '@prokyon/components/Modal/context';
return (
<ModalWrapper>
...
<Modals />
</ModalWrapper>
);
```
## Use `AddModalButton`
```tsx
import { AddModalButton } from '@prokyon/components/Modal/components/AddModalButton';
return <AddModalButton />;
```
<Canvas of={ModalStories.Default} withToolbar />
## Use hook `useModal()`
```tsx ```tsx
import { useModal } from '@prokyon/components/Modal/hooks'; import { useModal } from '@prokyon/components/Modal/hooks';
@ -27,10 +57,16 @@ const { openModal } = useModal();
openModal('modal1'); openModal('modal1');
``` ```
Or, call `openModal` function outside of React component. <Canvas of={ModalStories.Hook} withToolbar />
## Use function `openModal()`
Outside of React component.
```ts ```ts
import { openModal } from '@prokyon/components/Modal/context'; import { openModal } from '@prokyon/components/Modal/context';
openModal('modal1'); openModal('modal1');
``` ```
<Canvas of={ModalStories.OutsideReact} withToolbar />

View file

@ -0,0 +1,136 @@
import React, { useEffect } from 'react';
import { action } from '@storybook/addon-actions';
import { Meta, StoryObj } from '@storybook/react';
import { Button } from '@prokyon/components/Button';
import { Modals } from '@prokyon/components/Modal';
import { AddModalButton } from '@prokyon/components/Modal/components/AddModalButton';
import { ModalWrapper, openModal as openOutsideReactModal } from '@prokyon/components/Modal/context';
import { useModal } from '@prokyon/components/Modal/hooks';
import { TextField } from '@prokyon/components/TextField';
type Story = StoryObj<typeof AddModalButton>;
export default {
component: Modals,
decorators: [
(Story) => (
<div className="h-80">
<ModalWrapper>
<Story />
<Modals />
</ModalWrapper>
</div>
),
],
} as Meta;
export const Default: Story = {
name: 'Default',
render: () => {
return (
<AddModalButton
id="default"
title="Open modal"
modalTitle="Default modal"
Component={() => <div>Hi there</div>}
/>
);
},
};
export const Hook: Story = {
name: 'Hook',
render: () => {
const Comp = () => {
const { registerModal, openModal } = useModal();
useEffect(() => {
registerModal({
Component: () => <div>Hi there</div>,
id: 'modal2',
title: 'Modal Title',
});
}, []);
return <Button label="Open Modal" onClick={() => openModal('modal2')} />;
};
return <Comp />;
},
};
export const OutsideReact: Story = {
name: 'Function',
render: () => {
return (
<Button
label="Open Modal"
onClick={() =>
openOutsideReactModal({
Component: () => <div>Hi there</div>,
id: 'modal3',
})
}
/>
);
},
};
export const Small: Story = {
name: 'Small',
render: () => {
return (
<AddModalButton
id="small"
title="Open modal"
modalTitle="Small modal"
Component={() => (
<div>
<TextField name="search" label="Search" />
</div>
)}
style={{
width: 250,
}}
/>
);
},
};
export const Multiple: Story = {
name: 'Multiple',
render: () => {
return (
<AddModalButton
id="multiple"
title="Open modal"
modalTitle="First modal"
Component={() => (
<div>
Hello
<AddModalButton
Component={() => <div>Hi there, again</div>}
title="Open next modal"
modalTitle="Second modal"
id="secondModal"
/>
</div>
)}
/>
);
},
};
export const WithCloseCallback: Story = {
render: () => {
return (
<AddModalButton
id="withCloseCallback"
title="Open modal"
modalTitle="Modal"
Component={() => <div>Hello</div>}
props={{
onClose: () => action('Close callback called.'),
}}
/>
);
},
};

View file

@ -1,7 +1,8 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import Section from '@prokyon/components/Section'; import Section from '@prokyon/components/Section';
import { TitleSizeEnum } from '@prokyon/components/Title'; import { Title, TitleSizeEnum } from '@prokyon/components/Title';
import { DirectionEnum } from '@prokyon/types/common'; import { DirectionEnum } from '@prokyon/types/common';
type Story = StoryObj<typeof Section>; type Story = StoryObj<typeof Section>;
@ -20,34 +21,37 @@ export default {
export const Default: Story = { export const Default: Story = {
args: { args: {
title: 'Section', Title: () => <Title>Section</Title>,
children: 'Content',
},
};
export const WithoutTitle: Story = {
args: {
children: 'Content', children: 'Content',
}, },
}; };
export const Medium: Story = { export const Medium: Story = {
args: { args: {
title: 'Section', Title: () => <Title size={TitleSizeEnum.md}>Section</Title>,
children: 'content', children: 'content',
titleSize: TitleSizeEnum.md, space: { [DirectionEnum.y]: 2 },
margin: { [DirectionEnum.y]: 2 },
}, },
}; };
export const Large: Story = { export const Large: Story = {
args: { args: {
title: 'Section', Title: () => <Title size={TitleSizeEnum.lg}>Section</Title>,
children: 'content', children: 'content',
titleSize: TitleSizeEnum.lg, space: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 3 },
margin: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 3 },
}, },
}; };
export const ExtraLarge: Story = { export const ExtraLarge: Story = {
args: { args: {
title: 'Section', Title: () => <Title size={TitleSizeEnum.xl}>Section</Title>,
children: 'content', children: 'content',
titleSize: TitleSizeEnum.xl, space: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 4 },
margin: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 4 },
}, },
}; };