From 5d3e8a4d0c2ce2ad8f22df857ab3ce54fcfc38ac Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Fri, 3 Nov 2023 12:22:47 +0100
Subject: refactor(components): replace Toolbar with Navbar component

* remove SearchModal and SettingsModal components
* add a generic NavbarItem component (instead of the previous toolbar
items to avoid unreadable styles...)
* move FlippingLabel component logic into NavbarItem since it is only
used here
---
 .../organisms/navbar/navbar-item/navbar-item.tsx   | 179 +++++++++++++++++++++
 1 file changed, 179 insertions(+)
 create mode 100644 src/components/organisms/navbar/navbar-item/navbar-item.tsx

(limited to 'src/components/organisms/navbar/navbar-item/navbar-item.tsx')

diff --git a/src/components/organisms/navbar/navbar-item/navbar-item.tsx b/src/components/organisms/navbar/navbar-item/navbar-item.tsx
new file mode 100644
index 0000000..8ef6ce3
--- /dev/null
+++ b/src/components/organisms/navbar/navbar-item/navbar-item.tsx
@@ -0,0 +1,179 @@
+import {
+  type ReactNode,
+  useCallback,
+  type ForwardRefRenderFunction,
+  forwardRef,
+  useRef,
+} from 'react';
+import {
+  useOnClickOutside,
+  type useOnClickOutsideHandler,
+} from '../../../../utils/hooks';
+import {
+  Checkbox,
+  Heading,
+  Icon,
+  type IconShape,
+  Label,
+  Overlay,
+  Flip,
+  FlipSide,
+  type ListItemProps,
+  ListItem,
+} from '../../../atoms';
+import { Modal } from '../../../molecules';
+import styles from './navbar-item.module.scss';
+
+export type NavbarItemProps = Omit<
+  ListItemProps,
+  'children' | 'hideMarker' | 'id'
+> & {
+  /**
+   * The modal contents.
+   */
+  children: ReactNode;
+  /**
+   * An icon to illustrate the nav item.
+   */
+  icon: IconShape;
+  /**
+   * The item id.
+   */
+  id: string;
+  /**
+   * Should the modal be visible?
+   */
+  isActive: boolean;
+  /**
+   * An accessible name for the nav item.
+   */
+  label: string;
+  /**
+   * The modal heading.
+   */
+  modalHeading?: string;
+  /**
+   * Make the modal always visible from the given breakpoint.
+   */
+  modalVisibleFrom?: 'sm' | 'md';
+  /**
+   * A callback function to handle modal deactivation.
+   */
+  onDeactivate?: () => void;
+  /**
+   * A callback function to handle modal toggle.
+   */
+  onToggle: () => void;
+  /**
+   * Should we add the icon on the modal?
+   *
+   * @default false
+   */
+  showIconOnModal?: boolean;
+};
+
+const NavbarItemWithRef: ForwardRefRenderFunction<
+  HTMLLIElement,
+  NavbarItemProps
+> = (
+  {
+    children,
+    className = '',
+    icon,
+    id,
+    isActive,
+    label,
+    modalHeading,
+    modalVisibleFrom,
+    onDeactivate,
+    onToggle,
+    showIconOnModal = false,
+    ...props
+  },
+  ref
+) => {
+  const itemClass = [
+    styles.item,
+    modalVisibleFrom
+      ? styles[`item--hidden-controller-${modalVisibleFrom}`]
+      : '',
+    className,
+  ].join(' ');
+  const labelRef = useRef<HTMLLabelElement>(null);
+  const checkboxRef = useRef<HTMLInputElement>(null);
+  const deactivateItem: useOnClickOutsideHandler = useCallback(
+    (e) => {
+      const isCheckbox =
+        e.target && checkboxRef.current?.contains(e.target as Node);
+      const isLabel = e.target && labelRef.current?.contains(e.target as Node);
+
+      if (onDeactivate && !isCheckbox && !isLabel) onDeactivate();
+    },
+    [onDeactivate]
+  );
+  const modalRef = useOnClickOutside<HTMLDivElement>(deactivateItem);
+
+  return (
+    <ListItem {...props} className={itemClass} hideMarker ref={ref}>
+      <Checkbox
+        className={styles.checkbox}
+        id={id}
+        isChecked={isActive}
+        name={id}
+        onChange={onToggle}
+        ref={checkboxRef}
+        value={id}
+      />
+      <Label
+        aria-label={label}
+        className={styles.label}
+        htmlFor={id}
+        ref={labelRef}
+      >
+        {icon === 'hamburger' ? (
+          <Icon
+            aria-hidden
+            className={styles[`icon--${icon}`]}
+            shape={icon}
+            // eslint-disable-next-line react/jsx-no-literals
+            size="lg"
+          />
+        ) : (
+          <Flip aria-hidden className={styles.flip} showBack={isActive}>
+            <FlipSide className={styles.flip__side}>
+              <Icon
+                shape={icon}
+                // eslint-disable-next-line react/jsx-no-literals
+                size="lg"
+              />
+            </FlipSide>
+            <FlipSide className={styles.flip__side} isBack>
+              <Icon
+                // eslint-disable-next-line react/jsx-no-literals
+                shape="cross"
+              />
+            </FlipSide>
+          </Flip>
+        )}
+      </Label>
+      <Overlay className={styles.overlay} isVisible={isActive}>
+        <Modal
+          className={styles.modal}
+          heading={
+            modalHeading ? (
+              <Heading isFake level={3}>
+                {modalHeading}
+              </Heading>
+            ) : null
+          }
+          icon={showIconOnModal ? <Icon shape={icon} /> : null}
+          ref={modalRef}
+        >
+          {children}
+        </Modal>
+      </Overlay>
+    </ListItem>
+  );
+};
+
+export const NavbarItem = forwardRef(NavbarItemWithRef);
-- 
cgit v1.2.3

