aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules/tooltip
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-09-22 19:34:01 +0200
committerArmand Philippot <git@armandphilippot.com>2023-10-24 12:23:48 +0200
commita6ff5eee45215effb3344cb5d631a27a7c0369aa (patch)
tree5051747acf72318b4fc5c18d603e3757fbefdfdb /src/components/molecules/tooltip
parent651ea4fc992e77d2f36b3c68f8e7a70644246067 (diff)
refactor(components): rewrite form components
Diffstat (limited to 'src/components/molecules/tooltip')
-rw-r--r--src/components/molecules/tooltip/index.ts1
-rw-r--r--src/components/molecules/tooltip/tooltip.module.scss72
-rw-r--r--src/components/molecules/tooltip/tooltip.stories.tsx42
-rw-r--r--src/components/molecules/tooltip/tooltip.test.tsx39
-rw-r--r--src/components/molecules/tooltip/tooltip.tsx92
5 files changed, 246 insertions, 0 deletions
diff --git a/src/components/molecules/tooltip/index.ts b/src/components/molecules/tooltip/index.ts
new file mode 100644
index 0000000..ed8326d
--- /dev/null
+++ b/src/components/molecules/tooltip/index.ts
@@ -0,0 +1 @@
+export * from './tooltip';
diff --git a/src/components/molecules/tooltip/tooltip.module.scss b/src/components/molecules/tooltip/tooltip.module.scss
new file mode 100644
index 0000000..029767f
--- /dev/null
+++ b/src/components/molecules/tooltip/tooltip.module.scss
@@ -0,0 +1,72 @@
+@use "../../../styles/abstracts/functions" as fun;
+@use "../../../styles/abstracts/mixins" as mix;
+@use "../../../styles/abstracts/variables" as var;
+
+.btn {
+ margin-right: var(--spacing-xs);
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 10;
+ font-size: var(--font-size-sm);
+ transition: all 0.75s ease-in-out 0s;
+
+ @media screen and (max-height: #{var.get-breakpoint("2xs")}) {
+ width: calc(97.5vw - var(--spacing-md));
+ right: 0;
+ }
+
+ &--down {
+ top: calc(100% + var(--spacing-xs));
+ transform-origin: top;
+ }
+
+ &--up {
+ bottom: calc(100% + var(--spacing-2xs));
+ transform-origin: bottom;
+ }
+
+ &--hidden {
+ flex: 0 0 0;
+ opacity: 0;
+ visibility: hidden;
+ transform: scale(0);
+ }
+
+ &--visible {
+ opacity: 1;
+ visibility: visible;
+ transform: scale(1);
+
+ & ~ .btn {
+ background: var(--color-primary);
+
+ * {
+ color: var(--color-fg-inverted);
+ }
+ }
+ }
+}
+
+.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;
+ display: flex;
+ align-items: center;
+ 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/tooltip/tooltip.stories.tsx b/src/components/molecules/tooltip/tooltip.stories.tsx
new file mode 100644
index 0000000..8a22a06
--- /dev/null
+++ b/src/components/molecules/tooltip/tooltip.stories.tsx
@@ -0,0 +1,42 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Tooltip } from './tooltip';
+import { useState } from 'react';
+
+/**
+ * Switch - Storybook Meta
+ */
+export default {
+ title: 'Molecules/Tooltip',
+ component: Tooltip,
+ args: {},
+ argTypes: {},
+} as ComponentMeta<typeof Tooltip>;
+
+const Template: ComponentStory<typeof Tooltip> = ({
+ isOpen,
+ onToggle: _onToggle,
+ ...args
+}) => {
+ const [isOpened, setIsOpened] = useState(isOpen);
+
+ const toggle = () => {
+ setIsOpened((prev) => !prev);
+ };
+
+ return (
+ <div style={{ position: 'relative' }}>
+ <Tooltip {...args} isOpen={isOpened} onToggle={toggle} />
+ </div>
+ );
+};
+
+/**
+ * Tooltip Stories - Example
+ */
+export const Example = Template.bind({});
+Example.args = {
+ children:
+ 'Inventore natus dignissimos aut illum modi asperiores. Et voluptatibus delectus.',
+ heading: 'A title',
+ isOpen: false,
+};
diff --git a/src/components/molecules/tooltip/tooltip.test.tsx b/src/components/molecules/tooltip/tooltip.test.tsx
new file mode 100644
index 0000000..af2c7e4
--- /dev/null
+++ b/src/components/molecules/tooltip/tooltip.test.tsx
@@ -0,0 +1,39 @@
+import { render, screen } from '../../../../tests/utils';
+import { Tooltip } from './tooltip';
+
+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('Tooltip', () => {
+ it('renders a title and a body', () => {
+ render(<Tooltip heading={title}>{children}</Tooltip>);
+
+ expect(screen.getByText(title)).toBeInTheDocument();
+ expect(screen.getByText(children)).toBeInTheDocument();
+ });
+
+ it('can render a hidden modal', () => {
+ render(
+ <Tooltip heading={title} isOpen={false}>
+ {children}
+ </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');
+ });
+
+ it('can render a visible modal', () => {
+ render(
+ <Tooltip heading={title} isOpen>
+ {children}
+ </Tooltip>
+ );
+
+ expect(screen.getByText(children)).toBeVisible();
+ expect(screen.getByText(children)).toHaveStyle({ visibility: 'visible' });
+ });
+});
diff --git a/src/components/molecules/tooltip/tooltip.tsx b/src/components/molecules/tooltip/tooltip.tsx
new file mode 100644
index 0000000..43ceced
--- /dev/null
+++ b/src/components/molecules/tooltip/tooltip.tsx
@@ -0,0 +1,92 @@
+import { FC, MouseEventHandler, useRef } from 'react';
+import { Heading, Modal, ModalProps } from '../../atoms';
+import { HelpButton } from '../buttons';
+import styles from './tooltip.module.scss';
+import { useOnClickOutside } from '../../../utils/hooks';
+
+export type TooltipProps = Omit<ModalProps, 'heading'> & {
+ /**
+ * The tooltip direction when opening.
+ *
+ * @default "downwards"
+ */
+ direction?: 'downwards' | 'upwards';
+ /**
+ * The tooltip heading.
+ */
+ heading: string;
+ /**
+ * Should the tooltip be opened?
+ *
+ * @default false
+ */
+ isOpen?: boolean;
+ /**
+ * A callback function to trigger when clicking outside the modal.
+ */
+ onClickOutside?: () => void;
+ /**
+ * An event handler when clicking on the help button.
+ */
+ onToggle?: MouseEventHandler<HTMLButtonElement>;
+};
+
+/**
+ * Tooltip component
+ *
+ * Render a button and a modal. Note: you should add a CSS rule
+ * `position: relative;` on the consumer.
+ */
+export const Tooltip: FC<TooltipProps> = ({
+ children,
+ className = '',
+ direction = 'downwards',
+ heading,
+ isOpen,
+ onClickOutside,
+ onToggle,
+ ...props
+}) => {
+ const directionModifier =
+ direction === 'upwards' ? 'tooltip--up' : 'tooltip--down';
+ const visibilityModifier = isOpen ? 'tooltip--visible' : 'tooltip--hidden';
+ const tooltipClass = `${styles.tooltip} ${styles[directionModifier]} ${styles[visibilityModifier]} ${className}`;
+ const btnRef = useRef<HTMLButtonElement>(null);
+
+ const closeModal = (target: Node) => {
+ if (!onClickOutside) return;
+
+ if (btnRef.current && !btnRef.current.contains(target)) {
+ onClickOutside();
+ }
+ };
+
+ const modalRef = useOnClickOutside<HTMLDivElement>(closeModal);
+
+ return (
+ <>
+ <Modal
+ {...props}
+ className={tooltipClass}
+ heading={
+ <Heading className={styles.heading} isFake level={6}>
+ <span aria-hidden className={styles.icon}>
+ ?
+ </span>
+ {heading}
+ </Heading>
+ }
+ kind="secondary"
+ ref={modalRef}
+ >
+ {children}
+ </Modal>
+ <HelpButton
+ aria-pressed={isOpen}
+ className={styles.btn}
+ onClick={onToggle}
+ ref={btnRef}
+ />
+ </>
+ );
+};