diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-03 12:22:47 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:15:27 +0100 |
| commit | 5d3e8a4d0c2ce2ad8f22df857ab3ce54fcfc38ac (patch) | |
| tree | a758333b29e2e6614de609acb312ea9ff0d3a33b /src/components/organisms/navbar/navbar-item/navbar-item.tsx | |
| parent | 655be4404630a20ae4ca40c4af84afcc2e63557b (diff) | |
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
Diffstat (limited to 'src/components/organisms/navbar/navbar-item/navbar-item.tsx')
| -rw-r--r-- | src/components/organisms/navbar/navbar-item/navbar-item.tsx | 179 |
1 files changed, 179 insertions, 0 deletions
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); |
