diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-04-07 22:57:15 +0200 | 
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-04-07 22:57:15 +0200 | 
| commit | a1e8f1e4426ed3560ce1b76fb73a6969388ed253 (patch) | |
| tree | 1322e27552bbf49b3f14e80d3e0111e154b0ab78 /src/components/molecules | |
| parent | 4bd651b9e32c568d86b30463858c20ef290d8c07 (diff) | |
chore: add a SelectWithTooltip component
Diffstat (limited to 'src/components/molecules')
6 files changed, 340 insertions, 3 deletions
| diff --git a/src/components/molecules/buttons/help-button.stories.tsx b/src/components/molecules/buttons/help-button.stories.tsx index 2b04a9c..7ed953e 100644 --- a/src/components/molecules/buttons/help-button.stories.tsx +++ b/src/components/molecules/buttons/help-button.stories.tsx @@ -5,6 +5,34 @@ import HelpButtonComponent from './help-button';  export default {    title: 'Molecules/Buttons',    component: HelpButtonComponent, +  argTypes: { +    classes: { +      control: { +        type: 'text', +      }, +      description: 'Set additional classes to the button.', +      table: { +        category: 'Options', +      }, +      type: { +        name: 'string', +        required: false, +      }, +    }, +    onClick: { +      control: { +        type: null, +      }, +      description: 'A callback function to handle click on button.', +      table: { +        category: 'Events', +      }, +      type: { +        name: 'function', +        required: false, +      }, +    }, +  },  } as ComponentMeta<typeof HelpButtonComponent>;  const Template: ComponentStory<typeof HelpButtonComponent> = (args) => ( diff --git a/src/components/molecules/buttons/help-button.tsx b/src/components/molecules/buttons/help-button.tsx index 4945bb4..d933829 100644 --- a/src/components/molecules/buttons/help-button.tsx +++ b/src/components/molecules/buttons/help-button.tsx @@ -3,14 +3,19 @@ import { FC } from 'react';  import { useIntl } from 'react-intl';  import styles from './help-button.module.scss'; -export type HelpButtonProps = Pick<ButtonProps, 'onClick'>; +export type HelpButtonProps = Pick<ButtonProps, 'onClick'> & { +  /** +   * Set additional classes to the button. +   */ +  classes?: string; +};  /**   * HelpButton component   *   * Render a button with an interrogation mark icon.   */ -const HelpButton: FC<HelpButtonProps> = ({ onClick }) => { +const HelpButton: FC<HelpButtonProps> = ({ classes = '', onClick }) => {    const intl = useIntl();    const text = intl.formatMessage({      defaultMessage: 'Help', @@ -19,7 +24,11 @@ const HelpButton: FC<HelpButtonProps> = ({ onClick }) => {    });    return ( -    <Button shape="circle" additionalClasses={styles.btn} onClick={onClick}> +    <Button +      shape="circle" +      additionalClasses={`${styles.btn} ${classes}`} +      onClick={onClick} +    >        <span className="screen-reader-text">{text}</span>        <span className={styles.icon}>?</span>      </Button> diff --git a/src/components/molecules/forms/select-with-tooltip.module.scss b/src/components/molecules/forms/select-with-tooltip.module.scss new file mode 100644 index 0000000..1f91f74 --- /dev/null +++ b/src/components/molecules/forms/select-with-tooltip.module.scss @@ -0,0 +1,52 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +.wrapper { +  display: flex; +  flex-flow: row wrap; +  align-items: center; +  gap: var(--spacing-xs); +  position: relative; +} + +.label { +  margin-right: auto; +} + +.select { +  width: auto; + +  @include mix.pointer("fine") { +    padding: fun.convert-px(3) var(--spacing-xs); +  } +} + +.btn { +  &--activated { +    background: var(--color-primary); + +    * { +      color: var(--color-fg-inverted); +    } +  } +} + +.tooltip { +  position: absolute; +  top: calc(100% + var(--spacing-xs)); +  right: 0; +  transform-origin: top right; +  transition: all 0.75s ease-in-out 0s; + +  &--hidden { +    opacity: 0; +    visibility: hidden; +    transform: scale(0); +  } + +  &--visible { +    opacity: 1; +    visibility: visible; +    transform: scale(1); +  } +} diff --git a/src/components/molecules/forms/select-with-tooltip.stories.tsx b/src/components/molecules/forms/select-with-tooltip.stories.tsx new file mode 100644 index 0000000..d2d36fa --- /dev/null +++ b/src/components/molecules/forms/select-with-tooltip.stories.tsx @@ -0,0 +1,158 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import { IntlProvider } from 'react-intl'; +import SelectWithTooltipComponent from './select-with-tooltip'; + +export default { +  title: 'Molecules/Forms', +  component: SelectWithTooltipComponent, +  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, +      }, +    }, +    label: { +      control: { +        type: 'text', +      }, +      description: 'The select label.', +      type: { +        name: 'string', +        required: true, +      }, +    }, +    disabled: { +      control: { +        type: 'boolean', +      }, +      description: 'Field state: either enabled or disabled.', +      table: { +        category: 'Options', +        defaultValue: { summary: false }, +      }, +      type: { +        name: 'boolean', +        required: false, +      }, +    }, +    id: { +      control: { +        type: 'text', +      }, +      description: 'Field id.', +      type: { +        name: 'string', +        required: true, +      }, +    }, +    name: { +      control: { +        type: 'text', +      }, +      description: 'Field name.', +      type: { +        name: 'string', +        required: true, +      }, +    }, +    options: { +      control: { +        type: null, +      }, +      description: 'Select options.', +      type: { +        name: 'array', +        required: true, +        value: { +          name: 'string', +        }, +      }, +    }, +    required: { +      control: { +        type: 'boolean', +      }, +      description: 'Determine if the field is required.', +      table: { +        category: 'Options', +        defaultValue: { summary: false }, +      }, +      type: { +        name: 'boolean', +        required: false, +      }, +    }, +    setValue: { +      control: { +        type: null, +      }, +      description: 'Callback function to set field value.', +      table: { +        category: 'Events', +      }, +      type: { +        name: 'function', +        required: true, +      }, +    }, +    value: { +      control: { +        type: 'text', +      }, +      description: 'Field value.', +      type: { +        name: 'string', +        required: true, +      }, +    }, +  }, +} as ComponentMeta<typeof SelectWithTooltipComponent>; + +const selectOptions = [ +  { id: 'option1', name: 'Option 1', value: 'option1' }, +  { id: 'option2', name: 'Option 2', value: 'option2' }, +  { id: 'option3', name: 'Option 3', value: 'option3' }, +]; + +const Template: ComponentStory<typeof SelectWithTooltipComponent> = ({ +  value: _value, +  setValue: _setValue, +  ...args +}) => { +  const [selected, setSelected] = useState<string>('option1'); +  return ( +    <IntlProvider locale="en"> +      <SelectWithTooltipComponent +        value={selected} +        setValue={setSelected} +        {...args} +      /> +    </IntlProvider> +  ); +}; + +export const SelectWithTooltip = Template.bind({}); +SelectWithTooltip.args = { +  content: 'Illo voluptatibus quia minima placeat sit nostrum excepturi.', +  title: 'Possimus quidem dolor', +  id: 'storybook-select', +  label: 'Officiis:', +  name: 'storybook-select', +  options: selectOptions, +}; diff --git a/src/components/molecules/forms/select-with-tooltip.test.tsx b/src/components/molecules/forms/select-with-tooltip.test.tsx new file mode 100644 index 0000000..7a423f5 --- /dev/null +++ b/src/components/molecules/forms/select-with-tooltip.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@test-utils'; +import SelectWithTooltip from './select-with-tooltip'; + +const selectOptions = [ +  { id: 'option1', name: 'Option 1', value: 'option1' }, +  { id: 'option2', name: 'Option 2', value: 'option2' }, +  { id: 'option3', name: 'Option 3', value: 'option3' }, +]; +const selectLabel = 'Jest select'; +const selectValue = selectOptions[0].value; +const tooltipTitle = 'Jest tooltip'; +const tooltipContent = 'Nesciunt voluptatibus voluptatem omnis at quia libero.'; + +describe('SelectWithTooltip', () => { +  it('renders a select', () => { +    render( +      <SelectWithTooltip +        id="jest-select" +        name="jest-select" +        label={selectLabel} +        options={selectOptions} +        value={selectValue} +        setValue={() => null} +        title={tooltipTitle} +        content={tooltipContent} +      /> +    ); +    expect(screen.getByRole('combobox', { name: selectLabel })).toHaveValue( +      selectValue +    ); +  }); +}); diff --git a/src/components/molecules/forms/select-with-tooltip.tsx b/src/components/molecules/forms/select-with-tooltip.tsx new file mode 100644 index 0000000..5e48d62 --- /dev/null +++ b/src/components/molecules/forms/select-with-tooltip.tsx @@ -0,0 +1,58 @@ +import Select, { SelectProps } from '@components/atoms/forms/select'; +import { FC, useState } from 'react'; +import HelpButton from '../buttons/help-button'; +import Tooltip, { TooltipProps } from '../modals/tooltip'; +import styles from './select-with-tooltip.module.scss'; + +export type SelectWithTooltipProps = SelectProps & +  Pick<TooltipProps, 'title' | 'content'> & { +    /** +     * The select label. +     */ +    label: string; +    /** +     * Set additional classes to the tooltip wrapper. +     */ +    tooltipClasses?: string; +  }; + +/** + * SelectWithTooltip component + * + * Render a select with a button to display a tooltip about options. + */ +const SelectWithTooltip: FC<SelectWithTooltipProps> = ({ +  title, +  content, +  id, +  label, +  tooltipClasses = '', +  ...props +}) => { +  const [isTooltipOpened, setIsTooltipOpened] = useState<boolean>(false); +  const buttonModifier = isTooltipOpened ? styles['btn--activated'] : ''; +  const tooltipModifier = isTooltipOpened +    ? styles['tooltip--visible'] +    : styles['tooltip--hidden']; + +  return ( +    <div className={styles.wrapper}> +      <label htmlFor={id} className={styles.label}> +        {label} +      </label> +      <Select id={id} {...props} classes={styles.select} /> +      <HelpButton +        onClick={() => setIsTooltipOpened(!isTooltipOpened)} +        classes={buttonModifier} +      /> +      <Tooltip +        title={title} +        content={content} +        icon="?" +        classes={`${styles.tooltip} ${tooltipModifier} ${tooltipClasses}`} +      /> +    </div> +  ); +}; + +export default SelectWithTooltip; | 
