From ff2b6c55cc691f0b62396d9ba481c75fc870cd6a Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Thu, 7 Apr 2022 14:18:18 +0200 Subject: chore: add a Tooltip component --- src/components/molecules/layout/modal.module.scss | 21 -------- src/components/molecules/layout/modal.stories.tsx | 57 ---------------------- src/components/molecules/layout/modal.test.tsx | 9 ---- src/components/molecules/layout/modal.tsx | 48 ------------------ src/components/molecules/modals/modal.module.scss | 21 ++++++++ src/components/molecules/modals/modal.stories.tsx | 56 +++++++++++++++++++++ src/components/molecules/modals/modal.test.tsx | 9 ++++ src/components/molecules/modals/modal.tsx | 48 ++++++++++++++++++ .../molecules/modals/tooltip.module.scss | 46 +++++++++++++++++ .../molecules/modals/tooltip.stories.tsx | 41 ++++++++++++++++ src/components/molecules/modals/tooltip.test.tsx | 24 +++++++++ src/components/molecules/modals/tooltip.tsx | 57 ++++++++++++++++++++++ 12 files changed, 302 insertions(+), 135 deletions(-) delete mode 100644 src/components/molecules/layout/modal.module.scss delete mode 100644 src/components/molecules/layout/modal.stories.tsx delete mode 100644 src/components/molecules/layout/modal.test.tsx delete mode 100644 src/components/molecules/layout/modal.tsx create mode 100644 src/components/molecules/modals/modal.module.scss create mode 100644 src/components/molecules/modals/modal.stories.tsx create mode 100644 src/components/molecules/modals/modal.test.tsx create mode 100644 src/components/molecules/modals/modal.tsx create mode 100644 src/components/molecules/modals/tooltip.module.scss create mode 100644 src/components/molecules/modals/tooltip.stories.tsx create mode 100644 src/components/molecules/modals/tooltip.test.tsx create mode 100644 src/components/molecules/modals/tooltip.tsx (limited to 'src') diff --git a/src/components/molecules/layout/modal.module.scss b/src/components/molecules/layout/modal.module.scss deleted file mode 100644 index 2fff562..0000000 --- a/src/components/molecules/layout/modal.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -@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 deleted file mode 100644 index 396e89e..0000000 --- a/src/components/molecules/layout/modal.stories.tsx +++ /dev/null @@ -1,57 +0,0 @@ -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; - -const Template: ComponentStory = (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 deleted file mode 100644 index 14fb224..0000000 --- a/src/components/molecules/layout/modal.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { render, screen } from '@test-utils'; -import Modal from './modal'; - -describe('Modal', () => { - it('renders a title', () => { - render(); - expect(screen.getByText('A custom title')).toBeInTheDocument(); - }); -}); diff --git a/src/components/molecules/layout/modal.tsx b/src/components/molecules/layout/modal.tsx deleted file mode 100644 index 4dc3b0a..0000000 --- a/src/components/molecules/layout/modal.tsx +++ /dev/null @@ -1,48 +0,0 @@ -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(() => import('@components/atoms/icons/cog')); -const SearchIcon = dynamic( - () => import('@components/atoms/icons/magnifying-glass') -); - -/** - * Modal component - * - * Render a modal component with an optional title and icon. - */ -const Modal: FC = ({ children, icon, title }) => { - const getIcon = (id: Icons) => { - switch (id) { - case 'cogs': - return ; - case 'search': - return ; - default: - return <>; - } - }; - - return ( -
- {title && ( - - {icon && {getIcon(icon)}} - {title} - - )} - {children} -
- ); -}; - -export default Modal; diff --git a/src/components/molecules/modals/modal.module.scss b/src/components/molecules/modals/modal.module.scss new file mode 100644 index 0000000..2fff562 --- /dev/null +++ b/src/components/molecules/modals/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/modals/modal.stories.tsx b/src/components/molecules/modals/modal.stories.tsx new file mode 100644 index 0000000..b68a24b --- /dev/null +++ b/src/components/molecules/modals/modal.stories.tsx @@ -0,0 +1,56 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import ModalComponent from './modal'; + +export default { + title: 'Molecules/Modals', + 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; + +const Template: ComponentStory = (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/modals/modal.test.tsx b/src/components/molecules/modals/modal.test.tsx new file mode 100644 index 0000000..14fb224 --- /dev/null +++ b/src/components/molecules/modals/modal.test.tsx @@ -0,0 +1,9 @@ +import { render, screen } from '@test-utils'; +import Modal from './modal'; + +describe('Modal', () => { + it('renders a title', () => { + render(); + expect(screen.getByText('A custom title')).toBeInTheDocument(); + }); +}); diff --git a/src/components/molecules/modals/modal.tsx b/src/components/molecules/modals/modal.tsx new file mode 100644 index 0000000..4dc3b0a --- /dev/null +++ b/src/components/molecules/modals/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(() => import('@components/atoms/icons/cog')); +const SearchIcon = dynamic( + () => import('@components/atoms/icons/magnifying-glass') +); + +/** + * Modal component + * + * Render a modal component with an optional title and icon. + */ +const Modal: FC = ({ children, icon, title }) => { + const getIcon = (id: Icons) => { + switch (id) { + case 'cogs': + return ; + case 'search': + return ; + default: + return <>; + } + }; + + return ( +
+ {title && ( + + {icon && {getIcon(icon)}} + {title} + + )} + {children} +
+ ); +}; + +export default Modal; diff --git a/src/components/molecules/modals/tooltip.module.scss b/src/components/molecules/modals/tooltip.module.scss new file mode 100644 index 0000000..94aa3dd --- /dev/null +++ b/src/components/molecules/modals/tooltip.module.scss @@ -0,0 +1,46 @@ +@use "@styles/abstracts/functions" as fun; + +.wrapper { + --title-height: #{fun.convert-px(40)}; + + margin-top: calc(var(--title-height) / 2); + padding: calc((var(--title-height) / 2) + var(--spacing-sm)) var(--spacing-sm) + var(--spacing-sm); + position: relative; + background: var(--color-bg); + border: fun.convert-px(2) solid var(--color-primary-dark); + border-radius: fun.convert-px(3); + box-shadow: fun.convert-px(1) fun.convert-px(1) 0 0 var(--color-shadow), + fun.convert-px(2) fun.convert-px(2) fun.convert-px(1) fun.convert-px(1) + var(--color-shadow-light); +} + +.title { + display: flex; + align-items: center; + height: var(--title-height); + padding-right: var(--spacing-xs); + position: absolute; + top: calc(var(--title-height) / -2); + left: 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-size: var(--font-size-sm); + font-variant: small-caps; + font-weight: 500; +} + +.icon { + display: flex; + align-items: center; + height: var(--title-height); + margin-right: var(--spacing-xs); + padding: 0 var(--spacing-2xs); + 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); + color: var(--color-fg-inverted); + font-weight: 600; +} diff --git a/src/components/molecules/modals/tooltip.stories.tsx b/src/components/molecules/modals/tooltip.stories.tsx new file mode 100644 index 0000000..a57cf34 --- /dev/null +++ b/src/components/molecules/modals/tooltip.stories.tsx @@ -0,0 +1,41 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import TooltipComponent from './tooltip'; + +export default { + title: 'Molecules/Modals', + component: TooltipComponent, + argTypes: { + content: { + control: { + type: 'text', + }, + description: 'The tooltip body.', + type: { + name: 'string', + required: true, + }, + }, + title: { + control: { + type: 'text', + }, + description: 'The tooltip title', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const Tooltip = Template.bind({}); +Tooltip.args = { + content: + 'Minima tempora fuga omnis ratione doloribus ut. Totam ea vitae consequatur. Fuga hic ipsum. In non debitis ex assumenda ut dicta. Sit ut maxime eligendi est.', + icon: '?', + title: 'Laborum enim vero', +}; diff --git a/src/components/molecules/modals/tooltip.test.tsx b/src/components/molecules/modals/tooltip.test.tsx new file mode 100644 index 0000000..24f20d8 --- /dev/null +++ b/src/components/molecules/modals/tooltip.test.tsx @@ -0,0 +1,24 @@ +import { render, screen } from '@test-utils'; +import Tooltip from './tooltip'; + +const title = 'Illum eum at'; +const content = + 'Non accusantium ad. Est et impedit iste animi voluptas cum accusamus accusantium. Repellat ut sint pariatur cumque cupiditate. Animi occaecati odio ut debitis ipsam similique. Repudiandae aut earum occaecati consequatur laborum ut nobis iusto. Adipisci laboriosam id.'; +const icon = '?'; + +describe('Tooltip', () => { + it('renders a title', () => { + render(); + expect(screen.getByText(title)).toBeInTheDocument(); + }); + + it('renders an explanation', () => { + render(); + expect(screen.getByText(content)).toBeInTheDocument(); + }); + + it('renders an icon', () => { + render(); + expect(screen.getByText(icon)).toBeInTheDocument(); + }); +}); diff --git a/src/components/molecules/modals/tooltip.tsx b/src/components/molecules/modals/tooltip.tsx new file mode 100644 index 0000000..ceb0b14 --- /dev/null +++ b/src/components/molecules/modals/tooltip.tsx @@ -0,0 +1,57 @@ +import List, { type ListItem } from '@components/atoms/lists/list'; +import { FC, ReactNode } from 'react'; +import styles from './tooltip.module.scss'; + +export type TooltipProps = { + /** + * Set additional classes to the tooltip wrapper. + */ + classes?: string; + /** + * The tooltip body. + */ + content: string | string[]; + /** + * An icon to illustrate tooltip content. + */ + icon: ReactNode; + /** + * The tooltip title. + */ + title: string; +}; + +/** + * Tooltip component + * + * Render a tooltip modal. + */ +const Tooltip: FC = ({ classes = '', content, icon, title }) => { + /** + * Format an array of strings to an array of object with id and value. + * + * @param {string[]} array - An array of strings. + * @returns {ListItem[]} The array formatted to be used as list items. + */ + const getListItems = (array: string[]): ListItem[] => { + return array.map((string, index) => { + return { id: `item-${index}`, value: string }; + }); + }; + + return ( +
+
+ {icon} + {title} +
+ {Array.isArray(content) ? ( + + ) : ( + content + )} +
+ ); +}; + +export default Tooltip; -- cgit v1.2.3