aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/modal
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/atoms/modal')
-rw-r--r--src/components/atoms/modal/index.ts1
-rw-r--r--src/components/atoms/modal/modal.module.scss66
-rw-r--r--src/components/atoms/modal/modal.stories.tsx59
-rw-r--r--src/components/atoms/modal/modal.test.tsx25
-rw-r--r--src/components/atoms/modal/modal.tsx49
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);