diff options
Diffstat (limited to 'src/components')
8 files changed, 235 insertions, 39 deletions
| diff --git a/src/components/atoms/icons/hamburger.module.scss b/src/components/atoms/icons/hamburger.module.scss index 09e7e30..4fba4df 100644 --- a/src/components/atoms/icons/hamburger.module.scss +++ b/src/components/atoms/icons/hamburger.module.scss @@ -1,16 +1,21 @@  @use "@styles/abstracts/functions" as fun; -.icon { -  display: block; +.wrapper { +  display: flex; +  align-items: center;    width: var(--icon-size, #{fun.convert-px(50)});    height: var(--icon-size, #{fun.convert-px(50)});    position: relative; +} +.icon {    &,    &::before,    &::after {      display: block;      height: fun.convert-px(7); +    width: 100%; +    position: absolute;      background: var(--color-primary-lighter);      background-image: linear-gradient(        to right, @@ -25,33 +30,13 @@    &::before,    &::after {      content: ""; -    position: absolute; -    left: fun.convert-px(-1); -    right: fun.convert-px(-1);    }    &::before { -    bottom: fun.convert-px(15); +    top: fun.convert-px(-15);    }    &::after { -    top: fun.convert-px(15); -  } - -  &--active { -    background: transparent; -    border: transparent; - -    &::before { -      bottom: 0; -      transform-origin: 50% 50%; -      transform: rotate(45deg); -    } - -    &::after { -      top: 0; -      transform-origin: 50% 50%; -      transform: rotate(-45deg); -    } +    bottom: fun.convert-px(-15);    }  } diff --git a/src/components/atoms/icons/hamburger.stories.tsx b/src/components/atoms/icons/hamburger.stories.tsx index 062d3ee..c753e69 100644 --- a/src/components/atoms/icons/hamburger.stories.tsx +++ b/src/components/atoms/icons/hamburger.stories.tsx @@ -9,7 +9,7 @@ export default {        control: {          type: 'text',        }, -      description: 'Set additional classnames.', +      description: 'Set additional classnames to the icon wrapper.',        table: {          category: 'Styles',        }, @@ -18,14 +18,17 @@ export default {          required: false,        },      }, -    isActive: { +    iconClassName: {        control: { -        type: 'boolean', +        type: 'text', +      }, +      description: 'Set additional classnames to the icon.', +      table: { +        category: 'Styles',        }, -      description: 'Transform hamburger into a cross when state is active.',        type: { -        name: 'boolean', -        required: true, +        name: 'string', +        required: false,        },      },    }, diff --git a/src/components/atoms/icons/hamburger.test.tsx b/src/components/atoms/icons/hamburger.test.tsx index f8a3c04..7173a23 100644 --- a/src/components/atoms/icons/hamburger.test.tsx +++ b/src/components/atoms/icons/hamburger.test.tsx @@ -3,7 +3,7 @@ import Hamburger from './hamburger';  describe('Hamburger', () => {    it('renders a Hamburger icon', () => { -    const { container } = render(<Hamburger isActive={false} />); +    const { container } = render(<Hamburger />);      expect(container).toBeDefined();    });  }); diff --git a/src/components/atoms/icons/hamburger.tsx b/src/components/atoms/icons/hamburger.tsx index 6716b26..7e7c2c9 100644 --- a/src/components/atoms/icons/hamburger.tsx +++ b/src/components/atoms/icons/hamburger.tsx @@ -3,13 +3,14 @@ import styles from './hamburger.module.scss';  type HamburgerProps = {    /** -   * Set additional classnames to the icon. +   * Set additional classnames to the icon wrapper.     */    className?: string; +    /** -   * Transform hamburger to a close icon when active. +   * Set additional classnames to the icon.     */ -  isActive: boolean; +  iconClassName?: string;  };  /** @@ -17,11 +18,15 @@ type HamburgerProps = {   *   * Render a Hamburger icon.   */ -const Hamburger: FC<HamburgerProps> = ({ className = '', isActive }) => { -  const stateClass = isActive ? `${styles['icon--active']}` : ''; -  const iconClasses = `${styles.icon} ${stateClass} ${className}`; - -  return <span className={iconClasses}></span>; +const Hamburger: FC<HamburgerProps> = ({ +  className = '', +  iconClassName = '', +}) => { +  return ( +    <span className={`${styles.wrapper} ${className}`}> +      <span className={`${styles.icon} ${iconClassName}`}></span> +    </span> +  );  };  export default Hamburger; diff --git a/src/components/molecules/buttons/main-nav-button.module.scss b/src/components/molecules/buttons/main-nav-button.module.scss new file mode 100644 index 0000000..bc1ed19 --- /dev/null +++ b/src/components/molecules/buttons/main-nav-button.module.scss @@ -0,0 +1,37 @@ +@use "@styles/abstracts/functions" as fun; + +.checkbox { +  position: absolute; +  top: calc(#{fun.convert-px(50)} / 2); +  left: calc(#{fun.convert-px(50)} / 2); +  opacity: 0; +  cursor: pointer; +} + +.label { +  display: block; +  cursor: pointer; + +  .icon { +    &__wrapper { +      --icon-size: #{fun.convert-px(50)}; +    } + +    &--active { +      background: transparent; +      border: transparent; + +      &::before { +        top: 0; +        transform-origin: 50% 50%; +        transform: rotate(-45deg); +      } + +      &::after { +        bottom: 0; +        transform-origin: 50% 50%; +        transform: rotate(45deg); +      } +    } +  } +} diff --git a/src/components/molecules/buttons/main-nav-button.stories.tsx b/src/components/molecules/buttons/main-nav-button.stories.tsx new file mode 100644 index 0000000..39e495c --- /dev/null +++ b/src/components/molecules/buttons/main-nav-button.stories.tsx @@ -0,0 +1,80 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import { IntlProvider } from 'react-intl'; +import MainNavButtonComponent from './main-nav-button'; + +export default { +  title: 'Molecules/Buttons', +  component: MainNavButtonComponent, +  argTypes: { +    checkboxClassName: { +      control: { +        type: 'text', +      }, +      description: 'Set additional classnames to the checkbox.', +      table: { +        category: 'Styles', +      }, +      type: { +        name: 'string', +        required: false, +      }, +    }, +    isActive: { +      control: { +        type: null, +      }, +      description: 'The button state.', +      type: { +        name: 'boolean', +        required: true, +      }, +    }, +    labelClassName: { +      control: { +        type: 'text', +      }, +      description: 'Set additional classnames to the label.', +      table: { +        category: 'Styles', +      }, +      type: { +        name: 'string', +        required: false, +      }, +    }, +    setIsActive: { +      control: { +        type: null, +      }, +      description: 'A callback function to set the button state.', +      type: { +        name: 'function', +        required: true, +      }, +    }, +  }, +} as ComponentMeta<typeof MainNavButtonComponent>; + +const Template: ComponentStory<typeof MainNavButtonComponent> = ({ +  isActive, +  setIsActive: _setIsActive, +  ...args +}) => { +  const [isChecked, setIsChecked] = useState<boolean>(isActive); + +  return ( +    <IntlProvider locale="en"> +      <MainNavButtonComponent +        isActive={isChecked} +        setIsActive={setIsChecked} +        {...args} +      /> +    </IntlProvider> +  ); +}; + +export const MainNavButton = Template.bind({}); +MainNavButton.args = { +  isActive: false, +}; diff --git a/src/components/molecules/buttons/main-nav-button.test.tsx b/src/components/molecules/buttons/main-nav-button.test.tsx new file mode 100644 index 0000000..e757305 --- /dev/null +++ b/src/components/molecules/buttons/main-nav-button.test.tsx @@ -0,0 +1,11 @@ +import { render, screen } from '@test-utils'; +import MainNavButton from './main-nav-button'; + +describe('MainNavButton', () => { +  it('renders a checkbox', () => { +    render(<MainNavButton isActive={false} setIsActive={() => null} />); +    expect( +      screen.getByRole('checkbox', { name: 'Open menu' }) +    ).toBeInTheDocument(); +  }); +}); diff --git a/src/components/molecules/buttons/main-nav-button.tsx b/src/components/molecules/buttons/main-nav-button.tsx new file mode 100644 index 0000000..59407db --- /dev/null +++ b/src/components/molecules/buttons/main-nav-button.tsx @@ -0,0 +1,75 @@ +import Checkbox, { CheckboxProps } from '@components/atoms/forms/checkbox'; +import Label from '@components/atoms/forms/label'; +import Hamburger from '@components/atoms/icons/hamburger'; +import { VFC } from 'react'; +import { useIntl } from 'react-intl'; +import styles from './main-nav-button.module.scss'; + +export type MainNavButtonProps = { +  /** +   * Set additional classnames to the checkbox. +   */ +  checkboxClassName?: string; +  /** +   * The button state. +   */ +  isActive: CheckboxProps['value']; +  /** +   * Set additional classnames to the label. +   */ +  labelClassName?: string; +  /** +   * A callback function to handle button state. +   */ +  setIsActive: CheckboxProps['setValue']; +}; + +/** + * MainNavButton component + * + * Render a hamburger icon or a close icon depending on state. + */ +const MainNavButton: VFC<MainNavButtonProps> = ({ +  checkboxClassName = '', +  isActive, +  labelClassName = '', +  setIsActive, +}) => { +  const intl = useIntl(); +  const label = isActive +    ? intl.formatMessage({ +        defaultMessage: 'Close menu', +        id: 'wT7YZb', +        description: 'MainNavButton: close menu label', +      }) +    : intl.formatMessage({ +        defaultMessage: 'Open menu', +        id: 'P7j8ZZ', +        description: 'MainNavButton: open menu label', +      }); +  const hamburgerModifier = isActive ? 'icon--active' : ''; + +  return ( +    <> +      <Checkbox +        id="main-nav-button" +        name="main-nav-button" +        value={isActive} +        setValue={setIsActive} +        className={`${styles.checkbox} ${checkboxClassName}`} +      /> +      <Label +        htmlFor="main-nav-button" +        className={`${styles.label} ${labelClassName}`} +        aria-label={label} +      > +        <Hamburger +          className={styles.icon__wrapper} +          iconClassName={styles[hamburgerModifier]} +        /> +      </Label> +    </> +  ); +}; + +export default MainNavButton; | 
