aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-10-31 16:00:45 +0100
committerArmand Philippot <git@armandphilippot.com>2023-11-11 18:15:27 +0100
commit3ff4c37a7a2c40340c17f9e6c1754444bce0f839 (patch)
tree551ca3df148d46af2bd27995fa98c01378030644 /src/components/molecules
parent0e52a59917406ad03c174e030c6c1c92ab23449d (diff)
refactor(components): rewrite Modal component
* add an optional close button * add an icon prop
Diffstat (limited to 'src/components/molecules')
-rw-r--r--src/components/molecules/forms/switch/switch.tsx2
-rw-r--r--src/components/molecules/index.ts2
-rw-r--r--src/components/molecules/modals/index.ts2
-rw-r--r--src/components/molecules/modals/modal/index.ts1
-rw-r--r--src/components/molecules/modals/modal/modal.module.scss159
-rw-r--r--src/components/molecules/modals/modal/modal.stories.tsx191
-rw-r--r--src/components/molecules/modals/modal/modal.test.tsx48
-rw-r--r--src/components/molecules/modals/modal/modal.tsx103
-rw-r--r--src/components/molecules/modals/tooltip/index.ts (renamed from src/components/molecules/tooltip/index.ts)0
-rw-r--r--src/components/molecules/modals/tooltip/tooltip.module.scss (renamed from src/components/molecules/tooltip/tooltip.module.scss)26
-rw-r--r--src/components/molecules/modals/tooltip/tooltip.stories.tsx (renamed from src/components/molecules/tooltip/tooltip.stories.tsx)19
-rw-r--r--src/components/molecules/modals/tooltip/tooltip.test.tsx (renamed from src/components/molecules/tooltip/tooltip.test.tsx)18
-rw-r--r--src/components/molecules/modals/tooltip/tooltip.tsx (renamed from src/components/molecules/tooltip/tooltip.tsx)9
13 files changed, 534 insertions, 46 deletions
diff --git a/src/components/molecules/forms/switch/switch.tsx b/src/components/molecules/forms/switch/switch.tsx
index ad3e514..c6c1c69 100644
--- a/src/components/molecules/forms/switch/switch.tsx
+++ b/src/components/molecules/forms/switch/switch.tsx
@@ -14,7 +14,7 @@ import {
Label,
Radio,
} from '../../../atoms';
-import type { TooltipProps } from '../../tooltip';
+import type { TooltipProps } from '../../modals';
import styles from './switch.module.scss';
type SwitchItemProps = Omit<LabelProps, 'children' | 'htmlFor' | 'isRequired'> &
diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts
index c2c94d0..04c669f 100644
--- a/src/components/molecules/index.ts
+++ b/src/components/molecules/index.ts
@@ -10,5 +10,5 @@ export * from './grid';
export * from './images';
export * from './layout';
export * from './meta-list';
+export * from './modals';
export * from './nav';
-export * from './tooltip';
diff --git a/src/components/molecules/modals/index.ts b/src/components/molecules/modals/index.ts
new file mode 100644
index 0000000..595be13
--- /dev/null
+++ b/src/components/molecules/modals/index.ts
@@ -0,0 +1,2 @@
+export * from './modal';
+export * from './tooltip';
diff --git a/src/components/molecules/modals/modal/index.ts b/src/components/molecules/modals/modal/index.ts
new file mode 100644
index 0000000..133aa74
--- /dev/null
+++ b/src/components/molecules/modals/modal/index.ts
@@ -0,0 +1 @@
+export * from './modal';
diff --git a/src/components/molecules/modals/modal/modal.module.scss b/src/components/molecules/modals/modal/modal.module.scss
new file mode 100644
index 0000000..81047f3
--- /dev/null
+++ b/src/components/molecules/modals/modal/modal.module.scss
@@ -0,0 +1,159 @@
+@use "../../../../styles/abstracts/functions" as fun;
+@use "../../../../styles/abstracts/mixins" as mix;
+
+.modal {
+ --btn-border-size: #{fun.convert-px(1)};
+ --header-size: #{fun.convert-px(44)};
+ --padding: clamp(var(--spacing-sm), 2.5vw, var(--spacing-md));
+
+ max-width: 100%;
+ padding: var(--padding);
+ 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 {
+ 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;
+ }
+
+ &--secondary {
+ background: var(--color-bg);
+ border: fun.convert-px(2) solid var(--color-primary-dark);
+ border-radius: fun.convert-px(3);
+ }
+
+ &--primary#{&}--has-btn {
+ --btn-offset-y: #{fun.convert-px(-15)};
+
+ margin-top: calc(var(--btn-offset-y) * -1);
+ }
+
+ &--secondary#{&}--has-header {
+ margin-top: calc(var(--header-size) / 2);
+ }
+}
+
+.header {
+ display: flex;
+ flex-flow: row nowrap;
+}
+
+:where(.header) {
+ > .icon,
+ > .title {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ }
+}
+
+.btn {
+ width: var(--header-size);
+ min-height: var(--header-size);
+ background: var(--color-bg);
+ border: var(--btn-border-size) solid var(--color-primary);
+ 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);
+}
+
+:where(.modal--primary) {
+ > .header {
+ align-items: center;
+ gap: var(--spacing-2xs);
+ margin-bottom: var(--spacing-2xs);
+ }
+
+ :where(.header) > .btn {
+ position: absolute;
+ top: var(--btn-offset-y);
+ right: fun.convert-px(-10);
+ border-image: radial-gradient(
+ ellipse at top,
+ var(--color-primary-lighter) 20%,
+ var(--color-primary) 100%
+ )
+ 1;
+ }
+}
+
+:where(.modal--secondary) {
+ :where(.header) {
+ > .icon,
+ > .title {
+ min-height: var(--header-size);
+ border: fun.convert-px(1) solid var(--color-primary-dark);
+ 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);
+ }
+
+ > .icon,
+ > .btn {
+ flex: 0 0 var(--header-size);
+ }
+
+ > .icon {
+ display: flex;
+ place-content: center;
+ background: var(--color-primary);
+ color: var(--color-fg-inverted);
+
+ path {
+ fill: var(--color-fg-inverted);
+ stroke: var(--color-fg-inverted);
+ }
+ }
+
+ > .title {
+ min-height: var(--header-size);
+ padding-inline: var(--spacing-xs);
+ background: var(--color-bg);
+ color: var(--color-primary-darker);
+ font-variant: small-caps;
+
+ > * {
+ margin-block: 0;
+ }
+ }
+ }
+
+ > .header {
+ justify-content: center;
+ width: 100%;
+ margin: calc(var(--header-size) / -2 - var(--padding)) auto
+ var(--spacing-sm);
+
+ > * + * {
+ margin-inline-start: calc(var(--btn-border-size) * -1);
+ }
+ }
+}
+
+.btn:where(:hover, :focus) {
+ .icon {
+ transform: scale(1.2);
+ }
+}
+
+.btn:where(:active) {
+ .icon {
+ transform: scale(0.9);
+ }
+}
diff --git a/src/components/molecules/modals/modal/modal.stories.tsx b/src/components/molecules/modals/modal/modal.stories.tsx
new file mode 100644
index 0000000..744d21f
--- /dev/null
+++ b/src/components/molecules/modals/modal/modal.stories.tsx
@@ -0,0 +1,191 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Heading, Icon } from '../../../atoms';
+import { Modal } from './modal';
+
+/**
+ * Modals - Storybook Meta
+ */
+export default {
+ title: 'Molecules/Modals/Modal',
+ 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:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+};
+
+/**
+ * Modal Stories - Primary with close button
+ */
+export const PrimaryWithCloseBtn = Template.bind({});
+PrimaryWithCloseBtn.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ closeBtnLabel: 'Close the modal',
+};
+
+/**
+ * Modal Stories - Primary with icon
+ */
+export const PrimaryWithIcon = Template.bind({});
+PrimaryWithIcon.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ icon: <Icon aria-hidden shape="help" />,
+};
+
+/**
+ * Modal Stories - Primary with heading
+ */
+export const PrimaryWithHeading = Template.bind({});
+PrimaryWithHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+};
+
+/**
+ * Modal Stories - Primary with icon and heading
+ */
+export const PrimaryWithIconAndHeading = Template.bind({});
+PrimaryWithIconAndHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+ icon: <Icon aria-hidden shape="help" />,
+};
+
+/**
+ * Modal Stories - Primary with close button and heading
+ */
+export const PrimaryWithCloseBtnAndHeading = Template.bind({});
+PrimaryWithCloseBtnAndHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ closeBtnLabel: 'Close the modal',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+};
+
+/**
+ * Modal Stories - Primary with close button and icon
+ */
+export const PrimaryWithCloseBtnAndIcon = Template.bind({});
+PrimaryWithCloseBtnAndIcon.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ closeBtnLabel: 'Close the modal',
+ icon: <Icon aria-hidden shape="help" />,
+};
+
+/**
+ * Modal Stories - Primary with close button, icon and heading
+ */
+export const PrimaryWithCloseBtnIconAndHeading = Template.bind({});
+PrimaryWithCloseBtnIconAndHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ closeBtnLabel: 'Close the modal',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+ icon: <Icon aria-hidden shape="help" />,
+};
+
+/**
+ * Modal Stories - Secondary
+ */
+export const Secondary = Template.bind({});
+Secondary.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ kind: 'secondary',
+};
+
+/**
+ * Modal Stories - Secondary with close button
+ */
+export const SecondaryWithCloseBtn = Template.bind({});
+SecondaryWithCloseBtn.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ kind: 'secondary',
+ closeBtnLabel: 'Close the modal',
+};
+
+/**
+ * Modal Stories - Secondary with heading
+ */
+export const SecondaryWithHeading = Template.bind({});
+SecondaryWithHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+ kind: 'secondary',
+};
+
+/**
+ * Modal Stories - Secondary with icon
+ */
+export const SecondaryWithIcon = Template.bind({});
+SecondaryWithIcon.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ kind: 'secondary',
+ icon: <Icon aria-hidden shape="help" />,
+};
+
+/**
+ * Modal Stories - Secondary with close button and heading
+ */
+export const SecondaryWithCloseBtnAndHeading = Template.bind({});
+SecondaryWithCloseBtnAndHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+ kind: 'secondary',
+ closeBtnLabel: 'Close the modal',
+};
+
+/**
+ * Modal Stories - Secondary with close button and icon
+ */
+export const SecondaryWithCloseBtnAndIcon = Template.bind({});
+SecondaryWithCloseBtnAndIcon.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ closeBtnLabel: 'Close the modal',
+ icon: <Icon aria-hidden shape="help" />,
+ kind: 'secondary',
+};
+
+/**
+ * Modal Stories - Secondary with icon and heading
+ */
+export const SecondaryWithIconAndHeading = Template.bind({});
+SecondaryWithIconAndHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+ icon: <Icon aria-hidden shape="help" />,
+ kind: 'secondary',
+};
+
+/**
+ * Modal Stories - Secondary with close button, icon and heading
+ */
+export const SecondaryWithCloseBtnIconAndHeading = Template.bind({});
+SecondaryWithCloseBtnIconAndHeading.args = {
+ children:
+ 'Sed atque molestiae voluptatem possimus nisi recusandae qui assumenda. Quia rerum sed. Et autem impedit ut nam impedit. Quam ex facere pariatur est. Voluptatem hic beatae asperiores suscipit. Accusamus dolorum fugit placeat alias vel tenetur. Expedita fuga quos ipsum cum ea est expedita quia eaque.',
+ heading: <Heading level={3}>Aut provident eum</Heading>,
+ closeBtnLabel: 'Close the modal',
+ icon: <Icon aria-hidden shape="help" />,
+ kind: 'secondary',
+};
diff --git a/src/components/molecules/modals/modal/modal.test.tsx b/src/components/molecules/modals/modal/modal.test.tsx
new file mode 100644
index 0000000..82b7487
--- /dev/null
+++ b/src/components/molecules/modals/modal/modal.test.tsx
@@ -0,0 +1,48 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { Heading, Icon } from '../../../atoms';
+import { Modal } from './modal';
+
+const children =
+ 'Labore ullam delectus sit modi quam dolores. Ratione id sint aliquid facilis ipsum. Unde necessitatibus provident minus.';
+
+describe('Modal', () => {
+ it('renders the modal contents', () => {
+ render(<Modal>{children}</Modal>);
+
+ expect(rtlScreen.getByText(children)).toBeInTheDocument();
+ });
+
+ it('can render a heading', () => {
+ const heading = 'A custom heading';
+ const level = 2;
+
+ render(
+ <Modal heading={<Heading level={level}>{heading}</Heading>}>
+ {children}
+ </Modal>
+ );
+
+ expect(rtlScreen.getByRole('heading', { level })).toHaveTextContent(
+ heading
+ );
+ });
+
+ it('can render an icon', () => {
+ const label = 'maxime ut eius';
+
+ render(
+ <Modal icon={<Icon aria-label={label} shape="arrow" />}>{children}</Modal>
+ );
+
+ expect(rtlScreen.getByLabelText(label)).toBeInTheDocument();
+ });
+
+ it('can render a close button', () => {
+ const btn = 'consequatur';
+
+ render(<Modal closeBtnLabel={btn}>{children}</Modal>);
+
+ expect(rtlScreen.getByRole('button', { name: btn })).toBeInTheDocument();
+ });
+});
diff --git a/src/components/molecules/modals/modal/modal.tsx b/src/components/molecules/modals/modal/modal.tsx
new file mode 100644
index 0000000..ed55488
--- /dev/null
+++ b/src/components/molecules/modals/modal/modal.tsx
@@ -0,0 +1,103 @@
+import {
+ type ForwardRefRenderFunction,
+ type HTMLAttributes,
+ type ReactNode,
+ forwardRef,
+} from 'react';
+import { Button, Icon } from '../../../atoms';
+import styles from './modal.module.scss';
+
+export type ModalProps = HTMLAttributes<HTMLDivElement> & {
+ /**
+ * The modal body.
+ */
+ children: ReactNode;
+ /**
+ * The close button label.
+ */
+ closeBtnLabel?: string;
+ /**
+ * The modal title.
+ */
+ heading?: ReactNode;
+ /**
+ * Define an icon to illustrate the modal.
+ */
+ icon?: ReactNode;
+ /**
+ * The modal kind.
+ *
+ * @default 'primary'
+ */
+ kind?: 'primary' | 'secondary';
+ /**
+ * A callback function to handle close button action.
+ */
+ onClose?: () => void;
+};
+
+const ModalWithRef: ForwardRefRenderFunction<HTMLDivElement, ModalProps> = (
+ {
+ children,
+ className = '',
+ closeBtnLabel,
+ heading,
+ icon,
+ kind = 'primary',
+ onClose,
+ ...props
+ },
+ ref
+) => {
+ const hasHeader = !!heading || !!icon || !!closeBtnLabel;
+ const modalClass = [
+ styles.modal,
+ styles[hasHeader ? 'modal--has-header' : ''],
+ styles[closeBtnLabel ? 'modal--has-btn' : ''],
+ styles[`modal--${kind}`],
+ className,
+ ].join(' ');
+
+ return (
+ <div {...props} className={modalClass} ref={ref}>
+ {hasHeader ? (
+ <div className={styles.header}>
+ {icon ? (
+ <div aria-hidden className={styles.icon}>
+ {icon}
+ </div>
+ ) : null}
+ {heading ? <div className={styles.title}>{heading}</div> : null}
+ {closeBtnLabel ? (
+ <Button
+ aria-label={closeBtnLabel}
+ className={styles.btn}
+ onClick={onClose}
+ // eslint-disable-next-line react/jsx-no-literals
+ kind="neutral"
+ // eslint-disable-next-line react/jsx-no-literals
+ shape="initial"
+ >
+ <Icon
+ aria-hidden
+ className={styles.icon}
+ // eslint-disable-next-line react/jsx-no-literals
+ shape="cross"
+ // eslint-disable-next-line react/jsx-no-literals
+ size="xs"
+ />
+ </Button>
+ ) : null}
+ </div>
+ ) : null}
+ {children}
+ </div>
+ );
+};
+
+/**
+ * Modal component
+ *
+ * Render a modal component.
+ */
+export const Modal = forwardRef(ModalWithRef);
diff --git a/src/components/molecules/tooltip/index.ts b/src/components/molecules/modals/tooltip/index.ts
index ed8326d..ed8326d 100644
--- a/src/components/molecules/tooltip/index.ts
+++ b/src/components/molecules/modals/tooltip/index.ts
diff --git a/src/components/molecules/tooltip/tooltip.module.scss b/src/components/molecules/modals/tooltip/tooltip.module.scss
index 557d9c7..8e6f877 100644
--- a/src/components/molecules/tooltip/tooltip.module.scss
+++ b/src/components/molecules/modals/tooltip/tooltip.module.scss
@@ -1,6 +1,6 @@
-@use "../../../styles/abstracts/functions" as fun;
-@use "../../../styles/abstracts/mixins" as mix;
-@use "../../../styles/abstracts/variables" as var;
+@use "../../../../styles/abstracts/functions" as fun;
+@use "../../../../styles/abstracts/mixins" as mix;
+@use "../../../../styles/abstracts/variables" as var;
.btn {
margin-right: var(--spacing-xs);
@@ -42,25 +42,5 @@
}
.heading {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- height: 100%;
- margin-left: calc(var(--spacing-xs) * -1.1);
font-size: var(--font-size-sm);
}
-
-.icon {
- align-self: stretch;
- margin-right: var(--spacing-xs);
- background: var(--color-primary-dark);
- 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);
-
- :global {
- path {
- fill: var(--color-fg-inverted);
- margin-inline: var(--spacing-2xs);
- }
- }
-}
diff --git a/src/components/molecules/tooltip/tooltip.stories.tsx b/src/components/molecules/modals/tooltip/tooltip.stories.tsx
index 8a22a06..0cff339 100644
--- a/src/components/molecules/tooltip/tooltip.stories.tsx
+++ b/src/components/molecules/modals/tooltip/tooltip.stories.tsx
@@ -1,12 +1,12 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useBoolean } from '../../../../utils/hooks';
import { Tooltip } from './tooltip';
-import { useState } from 'react';
/**
* Switch - Storybook Meta
*/
export default {
- title: 'Molecules/Tooltip',
+ title: 'Molecules/Modals/Tooltip',
component: Tooltip,
args: {},
argTypes: {},
@@ -17,15 +17,16 @@ const Template: ComponentStory<typeof Tooltip> = ({
onToggle: _onToggle,
...args
}) => {
- const [isOpened, setIsOpened] = useState(isOpen);
-
- const toggle = () => {
- setIsOpened((prev) => !prev);
- };
+ const { deactivate, state: isOpened, toggle } = useBoolean(isOpen);
return (
<div style={{ position: 'relative' }}>
- <Tooltip {...args} isOpen={isOpened} onToggle={toggle} />
+ <Tooltip
+ {...args}
+ isOpen={isOpened}
+ onClickOutside={deactivate}
+ onToggle={toggle}
+ />
</div>
);
};
diff --git a/src/components/molecules/tooltip/tooltip.test.tsx b/src/components/molecules/modals/tooltip/tooltip.test.tsx
index 25a1614..1596670 100644
--- a/src/components/molecules/tooltip/tooltip.test.tsx
+++ b/src/components/molecules/modals/tooltip/tooltip.test.tsx
@@ -1,5 +1,5 @@
import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
+import { render, screen as rtlScreen } from '../../../../../tests/utils';
import { Tooltip } from './tooltip';
const title = 'A custom title';
@@ -10,8 +10,8 @@ describe('Tooltip', () => {
it('renders a title and a body', () => {
render(<Tooltip heading={title}>{children}</Tooltip>);
- expect(screen.getByText(title)).toBeInTheDocument();
- expect(screen.getByText(children)).toBeInTheDocument();
+ expect(rtlScreen.getByText(title)).toBeInTheDocument();
+ expect(rtlScreen.getByText(children)).toBeInTheDocument();
});
it('can render a hidden modal', () => {
@@ -22,9 +22,9 @@ describe('Tooltip', () => {
);
// Neither toBeVisible or toHaveStyle are working.
- //expect(screen.getByText(children)).not.toBeVisible();
- //expect(screen.getByText(children)).toHaveStyle({ visibility: 'hidden' });
- expect(screen.getByText(children)).toHaveClass('tooltip--hidden');
+ //expect(rtlScreen.getByText(children)).not.toBeVisible();
+ //expect(rtlScreen.getByText(children)).toHaveStyle({ visibility: 'hidden' });
+ expect(rtlScreen.getByText(children)).toHaveClass('tooltip--hidden');
});
it('can render a visible modal', () => {
@@ -34,7 +34,9 @@ describe('Tooltip', () => {
</Tooltip>
);
- expect(screen.getByText(children)).toBeVisible();
- expect(screen.getByText(children)).toHaveStyle({ visibility: 'visible' });
+ expect(rtlScreen.getByText(children)).toBeVisible();
+ expect(rtlScreen.getByText(children)).toHaveStyle({
+ visibility: 'visible',
+ });
});
});
diff --git a/src/components/molecules/tooltip/tooltip.tsx b/src/components/molecules/modals/tooltip/tooltip.tsx
index 1f54d68..b3a3f5a 100644
--- a/src/components/molecules/tooltip/tooltip.tsx
+++ b/src/components/molecules/modals/tooltip/tooltip.tsx
@@ -1,8 +1,9 @@
import { type FC, type MouseEventHandler, useRef } from 'react';
import { useIntl } from 'react-intl';
-import { useOnClickOutside } from '../../../utils/hooks';
-import { Heading, Icon, Modal, type ModalProps } from '../../atoms';
-import { HelpButton } from '../buttons';
+import { useOnClickOutside } from '../../../../utils/hooks';
+import { Heading, Icon } from '../../../atoms';
+import { HelpButton } from '../../buttons';
+import { Modal, type ModalProps } from '../modal';
import styles from './tooltip.module.scss';
export type TooltipProps = Omit<ModalProps, 'heading'> & {
@@ -77,10 +78,10 @@ export const Tooltip: FC<TooltipProps> = ({
className={tooltipClass}
heading={
<Heading className={styles.heading} isFake level={6}>
- <Icon aria-hidden className={styles.icon} shape="help" size="sm" />
{heading}
</Heading>
}
+ icon={<Icon aria-hidden shape="help" size="sm" />}
kind="secondary"
ref={modalRef}
>