aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/atoms/headings/heading.stories.tsx13
-rw-r--r--src/components/atoms/headings/heading.tsx6
-rw-r--r--src/components/atoms/icons/cog.module.scss1
-rw-r--r--src/components/atoms/icons/magnifying-glass.module.scss1
-rw-r--r--src/components/molecules/layout/modal.module.scss21
-rw-r--r--src/components/molecules/layout/modal.stories.tsx57
-rw-r--r--src/components/molecules/layout/modal.test.tsx9
-rw-r--r--src/components/molecules/layout/modal.tsx48
8 files changed, 151 insertions, 5 deletions
diff --git a/src/components/atoms/headings/heading.stories.tsx b/src/components/atoms/headings/heading.stories.tsx
index 0b286fe..cea3532 100644
--- a/src/components/atoms/headings/heading.stories.tsx
+++ b/src/components/atoms/headings/heading.stories.tsx
@@ -9,6 +9,19 @@ export default {
withMargin: true,
},
argTypes: {
+ additionalClasses: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classes.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
children: {
description: 'Heading body.',
type: {
diff --git a/src/components/atoms/headings/heading.tsx b/src/components/atoms/headings/heading.tsx
index 77580cc..136571d 100644
--- a/src/components/atoms/headings/heading.tsx
+++ b/src/components/atoms/headings/heading.tsx
@@ -1,7 +1,7 @@
import { FC } from 'react';
import styles from './heading.module.scss';
-type HeadingProps = {
+export type HeadingProps = {
/**
* Adds additional classes.
*/
@@ -33,12 +33,12 @@ const Heading: FC<HeadingProps> = ({
withMargin = true,
}) => {
const TitleTag = isFake ? `p` : (`h${level}` as keyof JSX.IntrinsicElements);
- const variantClass = withMargin ? 'heading--margin' : 'heading--regular';
const levelClass = `heading--${level}`;
+ const marginClass = withMargin ? 'heading--margin' : 'heading--regular';
return (
<TitleTag
- className={`${styles.heading} ${styles[variantClass]} ${styles[levelClass]} ${additionalClasses}`}
+ className={`${styles.heading} ${styles[levelClass]} ${styles[marginClass]} ${additionalClasses}`}
>
{children}
</TitleTag>
diff --git a/src/components/atoms/icons/cog.module.scss b/src/components/atoms/icons/cog.module.scss
index 8c48fcc..5201598 100644
--- a/src/components/atoms/icons/cog.module.scss
+++ b/src/components/atoms/icons/cog.module.scss
@@ -1,7 +1,6 @@
@use "@styles/abstracts/functions" as fun;
.icon {
- display: block;
width: var(--icon-size, #{fun.convert-px(40)});
fill: var(--color-primary-lighter);
stroke: var(--color-primary-darker);
diff --git a/src/components/atoms/icons/magnifying-glass.module.scss b/src/components/atoms/icons/magnifying-glass.module.scss
index dca76fb..d14bec5 100644
--- a/src/components/atoms/icons/magnifying-glass.module.scss
+++ b/src/components/atoms/icons/magnifying-glass.module.scss
@@ -1,7 +1,6 @@
@use "@styles/abstracts/functions" as fun;
.icon {
- display: block;
width: var(--icon-size, #{fun.convert-px(40)});
}
diff --git a/src/components/molecules/layout/modal.module.scss b/src/components/molecules/layout/modal.module.scss
new file mode 100644
index 0000000..2fff562
--- /dev/null
+++ b/src/components/molecules/layout/modal.module.scss
@@ -0,0 +1,21 @@
+@use "@styles/abstracts/functions" as fun;
+
+.wrapper {
+ padding: var(--spacing-md);
+ background: var(--color-bg-secondary);
+ border: fun.convert-px(4) solid;
+ border-image: radial-gradient(
+ ellipse at top,
+ var(--color-primary-lighter) 20%,
+ var(--color-primary) 100%
+ )
+ 1;
+ box-shadow: fun.convert-px(2) fun.convert-px(-2) fun.convert-px(3)
+ fun.convert-px(-1) var(--color-shadow-dark);
+}
+
+.icon {
+ --icon-size: #{fun.convert-px(30)};
+
+ margin-right: var(--spacing-2xs);
+}
diff --git a/src/components/molecules/layout/modal.stories.tsx b/src/components/molecules/layout/modal.stories.tsx
new file mode 100644
index 0000000..396e89e
--- /dev/null
+++ b/src/components/molecules/layout/modal.stories.tsx
@@ -0,0 +1,57 @@
+import Cog from '@components/atoms/icons/cog';
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import ModalComponent from './modal';
+
+export default {
+ title: 'Molecules/Layout',
+ component: ModalComponent,
+ argTypes: {
+ children: {
+ control: {
+ type: 'text',
+ },
+ description: 'The modal body.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ icon: {
+ control: {
+ type: 'select',
+ },
+ description: 'The title icon.',
+ options: ['', 'cogs', 'search'],
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ title: {
+ control: {
+ type: 'text',
+ },
+ description: 'The modal title.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta<typeof ModalComponent>;
+
+const Template: ComponentStory<typeof ModalComponent> = (args) => (
+ <ModalComponent {...args} />
+);
+
+export const Modal = Template.bind({});
+Modal.args = {
+ children:
+ 'Inventore natus dignissimos aut illum modi asperiores. Et voluptatibus delectus.',
+};
diff --git a/src/components/molecules/layout/modal.test.tsx b/src/components/molecules/layout/modal.test.tsx
new file mode 100644
index 0000000..14fb224
--- /dev/null
+++ b/src/components/molecules/layout/modal.test.tsx
@@ -0,0 +1,9 @@
+import { render, screen } from '@test-utils';
+import Modal from './modal';
+
+describe('Modal', () => {
+ it('renders a title', () => {
+ render(<Modal title="A custom title" />);
+ expect(screen.getByText('A custom title')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/molecules/layout/modal.tsx b/src/components/molecules/layout/modal.tsx
new file mode 100644
index 0000000..4dc3b0a
--- /dev/null
+++ b/src/components/molecules/layout/modal.tsx
@@ -0,0 +1,48 @@
+import Heading from '@components/atoms/headings/heading';
+import dynamic from 'next/dynamic';
+import { FC, ReactNode } from 'react';
+import styles from './modal.module.scss';
+
+export type Icons = 'cogs' | 'search';
+
+export type ModalProps = {
+ icon?: Icons;
+ title?: string;
+};
+
+const CogIcon = dynamic<ReactNode>(() => import('@components/atoms/icons/cog'));
+const SearchIcon = dynamic<ReactNode>(
+ () => import('@components/atoms/icons/magnifying-glass')
+);
+
+/**
+ * Modal component
+ *
+ * Render a modal component with an optional title and icon.
+ */
+const Modal: FC<ModalProps> = ({ children, icon, title }) => {
+ const getIcon = (id: Icons) => {
+ switch (id) {
+ case 'cogs':
+ return <CogIcon />;
+ case 'search':
+ return <SearchIcon />;
+ default:
+ return <></>;
+ }
+ };
+
+ return (
+ <div className={styles.wrapper}>
+ {title && (
+ <Heading isFake={true} level={3}>
+ {icon && <span className={styles.icon}>{getIcon(icon)}</span>}
+ {title}
+ </Heading>
+ )}
+ {children}
+ </div>
+ );
+};
+
+export default Modal;