diff options
Diffstat (limited to 'src/components/atoms/modal')
| -rw-r--r-- | src/components/atoms/modal/index.ts | 1 | ||||
| -rw-r--r-- | src/components/atoms/modal/modal.module.scss | 66 | ||||
| -rw-r--r-- | src/components/atoms/modal/modal.stories.tsx | 59 | ||||
| -rw-r--r-- | src/components/atoms/modal/modal.test.tsx | 25 | ||||
| -rw-r--r-- | src/components/atoms/modal/modal.tsx | 49 |
5 files changed, 200 insertions, 0 deletions
diff --git a/src/components/atoms/modal/index.ts b/src/components/atoms/modal/index.ts new file mode 100644 index 0000000..133aa74 --- /dev/null +++ b/src/components/atoms/modal/index.ts @@ -0,0 +1 @@ +export * from './modal'; diff --git a/src/components/atoms/modal/modal.module.scss b/src/components/atoms/modal/modal.module.scss new file mode 100644 index 0000000..6650235 --- /dev/null +++ b/src/components/atoms/modal/modal.module.scss @@ -0,0 +1,66 @@ +@use "../../../styles/abstracts/functions" as fun; +@use "../../../styles/abstracts/mixins" as mix; + +.modal { + position: relative; + box-shadow: + fun.convert-px(0.2) fun.convert-px(0.2) fun.convert-px(0.3) 0 + var(--color-shadow), + fun.convert-px(1.5) fun.convert-px(1.5) fun.convert-px(2.5) + fun.convert-px(-0.3) var(--color-shadow), + fun.convert-px(4.7) fun.convert-px(4.7) fun.convert-px(8) fun.convert-px(-1) + var(--color-shadow); + + &--primary { + padding: clamp(var(--spacing-xs), 2.5vw, var(--spacing-md)); + background: var(--color-bg-secondary); + border: fun.convert-px(3) solid; + border-image: radial-gradient( + ellipse at top, + var(--color-primary-lighter) 20%, + var(--color-primary) 100% + ) + 1; + + .title { + margin-bottom: var(--spacing-2xs); + } + + @include mix.media("screen") { + @include mix.dimensions(null, "sm") { + border-left: none; + border-right: none; + } + } + } + + &--secondary { + padding: clamp(var(--spacing-xs), 2.2vw, var(--spacing-sm)); + background: var(--color-bg); + border: fun.convert-px(2) solid var(--color-primary-dark); + border-radius: fun.convert-px(3); + + .title { + padding-inline: var(--spacing-xs); + background: var(--color-bg); + border: fun.convert-px(1) solid var(--color-primary-dark); + box-shadow: fun.convert-px(1) fun.convert-px(1) 0 0 var(--color-shadow); + color: var(--color-primary-darker); + font-variant: small-caps; + + > * { + margin-block: 0; + } + } + } + + &--secondary#{&}--has-title { + --title-height: #{fun.convert-px(40)}; + + .title { + width: fit-content; + height: var(--title-height); + margin: calc(var(--title-height) * -1) auto var(--spacing-xs); + } + } +} diff --git a/src/components/atoms/modal/modal.stories.tsx b/src/components/atoms/modal/modal.stories.tsx new file mode 100644 index 0000000..d0c2f0b --- /dev/null +++ b/src/components/atoms/modal/modal.stories.tsx @@ -0,0 +1,59 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Modal } from './modal'; +import { Heading } from '../headings'; + +/** + * Switch - Storybook Meta + */ +export default { + title: 'Atoms/Modals', + component: Modal, + args: {}, + argTypes: {}, +} as ComponentMeta<typeof Modal>; + +const Template: ComponentStory<typeof Modal> = (args) => <Modal {...args} />; + +/** + * Modal Stories - Primary + */ +export const Primary = Template.bind({}); +Primary.args = { + children: + 'Inventore natus dignissimos aut illum modi asperiores. Et voluptatibus delectus.', +}; + +/** + * Modal Stories - Primary With Heading + */ +export const PrimaryWithHeading = Template.bind({}); +PrimaryWithHeading.args = { + children: + 'Inventore natus dignissimos aut illum modi asperiores. Et voluptatibus delectus.', + heading: <Heading level={3}>Aut provident eum</Heading>, +}; + +/** + * Modal Stories - Secondary + */ +export const Secondary = Template.bind({}); +Secondary.args = { + children: + 'Inventore natus dignissimos aut illum modi asperiores. Et voluptatibus delectus.', + kind: 'secondary', +}; + +/** + * Modal Stories - Secondary with heading + */ +export const SecondaryWithHeading = Template.bind({}); +SecondaryWithHeading.args = { + children: + 'Inventore natus dignissimos aut illum modi asperiores. Et voluptatibus delectus.', + heading: ( + <Heading isFake level={4}> + Aut provident eum + </Heading> + ), + kind: 'secondary', +}; diff --git a/src/components/atoms/modal/modal.test.tsx b/src/components/atoms/modal/modal.test.tsx new file mode 100644 index 0000000..5f32d02 --- /dev/null +++ b/src/components/atoms/modal/modal.test.tsx @@ -0,0 +1,25 @@ +import { render, screen } from '../../../../tests/utils'; +import { Heading } from '../headings'; +import { Modal } from './modal'; + +const title = 'A custom title'; +const children = + 'Labore ullam delectus sit modi quam dolores. Ratione id sint aliquid facilis ipsum. Unde necessitatibus provident minus.'; + +describe('Modal', () => { + it('renders a title', () => { + const level = 2; + + render( + <Modal heading={<Heading level={level}>{title}</Heading>}> + {children} + </Modal> + ); + expect(screen.getByRole('heading', { level })).toHaveTextContent(title); + }); + + it('renders the modal body', () => { + render(<Modal>{children}</Modal>); + expect(screen.getByText(children)).toBeInTheDocument(); + }); +}); diff --git a/src/components/atoms/modal/modal.tsx b/src/components/atoms/modal/modal.tsx new file mode 100644 index 0000000..78b4f6e --- /dev/null +++ b/src/components/atoms/modal/modal.tsx @@ -0,0 +1,49 @@ +import { + ForwardRefRenderFunction, + HTMLAttributes, + ReactElement, + ReactNode, + forwardRef, +} from 'react'; +import { HeadingProps } from '../headings'; +import styles from './modal.module.scss'; + +export type ModalProps = HTMLAttributes<HTMLDivElement> & { + /** + * The modal body. + */ + children: ReactNode; + /** + * The modal title. + */ + heading?: ReactElement<HeadingProps>; + /** + * The modal kind. + * + * @default 'primary' + */ + kind?: 'primary' | 'secondary'; +}; + +const ModalWithRef: ForwardRefRenderFunction<HTMLDivElement, ModalProps> = ( + { children, className = '', heading, kind = 'primary', ...props }, + ref +) => { + const headingModifier = heading ? 'modal--has-title' : ''; + const kindModifier = `modal--${kind}`; + const modalClass = `${styles.modal} ${styles[headingModifier]} ${styles[kindModifier]} ${className}`; + + return ( + <div {...props} className={modalClass} ref={ref}> + {heading ? <div className={styles.title}>{heading}</div> : null} + {children} + </div> + ); +}; + +/** + * Modal component + * + * Render a modal component. + */ +export const Modal = forwardRef(ModalWithRef); |
