From 58005ad6ca761309eb8a92d2489030a7b1bad523 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Fri, 1 Apr 2022 11:39:37 +0200
Subject: chore: add a Hamburger icon component

---
 src/components/atoms/icons/hamburger.test.tsx | 9 +++++++++
 1 file changed, 9 insertions(+)
 create mode 100644 src/components/atoms/icons/hamburger.test.tsx

(limited to 'src/components/atoms/icons/hamburger.test.tsx')

diff --git a/src/components/atoms/icons/hamburger.test.tsx b/src/components/atoms/icons/hamburger.test.tsx
new file mode 100644
index 0000000..f8a3c04
--- /dev/null
+++ b/src/components/atoms/icons/hamburger.test.tsx
@@ -0,0 +1,9 @@
+import { render } from '@test-utils';
+import Hamburger from './hamburger';
+
+describe('Hamburger', () => {
+  it('renders a Hamburger icon', () => {
+    const { container } = render(<Hamburger isActive={false} />);
+    expect(container).toBeDefined();
+  });
+});
-- 
cgit v1.2.3


From ff3a251e75fafce7d95177010401483127973313 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Tue, 12 Apr 2022 15:48:47 +0200
Subject: chore: add a MainNavButton component

I also move the active state from the hamburger to this pseudo-button.
It makes more sense that the button handles the icon shape.
---
 src/components/atoms/icons/hamburger.module.scss   | 33 +++------
 src/components/atoms/icons/hamburger.stories.tsx   | 15 ++--
 src/components/atoms/icons/hamburger.test.tsx      |  2 +-
 src/components/atoms/icons/hamburger.tsx           | 21 +++---
 .../molecules/buttons/main-nav-button.module.scss  | 37 ++++++++++
 .../molecules/buttons/main-nav-button.stories.tsx  | 80 ++++++++++++++++++++++
 .../molecules/buttons/main-nav-button.test.tsx     | 11 +++
 .../molecules/buttons/main-nav-button.tsx          | 75 ++++++++++++++++++++
 8 files changed, 235 insertions(+), 39 deletions(-)
 create mode 100644 src/components/molecules/buttons/main-nav-button.module.scss
 create mode 100644 src/components/molecules/buttons/main-nav-button.stories.tsx
 create mode 100644 src/components/molecules/buttons/main-nav-button.test.tsx
 create mode 100644 src/components/molecules/buttons/main-nav-button.tsx

(limited to 'src/components/atoms/icons/hamburger.test.tsx')

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;
-- 
cgit v1.2.3

