UNPKG

@intility/bifrost-react

Version:

React library for Intility's design system, Bifrost.

161 lines (160 loc) 6.8 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { forwardRef, useEffect, useState } from "react"; import { FocusTrap } from "focus-trap-react"; import classNames from "classnames"; import { faBars } from "@fortawesome/free-solid-svg-icons/faBars"; import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark"; import NavTop from "./Nav.Top.js"; import NavSide from "./Nav.Side.js"; import NavMobile from "./Nav.Mobile.js"; import NavItem from "./Nav.Item.js"; import NavLogo from "./Nav.Logo.js"; import NavProvider from "./NavContext.internal.js"; import Button from "../Button/Button.js"; import useLocale from "../../hooks/useLocale.js"; import useBreakpoint from "../../hooks/useBreakpoint.js"; /** * Responsive component for Bifrost navigation menus */ const Nav = /*#__PURE__*/ forwardRef(({ children, top, topProps, side, sideProps, mobile, mobileProps, logo, hideBranding = false, noMain = false, ...props }, ref)=>{ const locale = useLocale(); const toXL = useBreakpoint(null, "xl"); const [mobileOpen, setMobileStateOpen] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const setMobileOpen = (open)=>{ if (mobileOpen === open) return; setIsAnimating(true); setMobileStateOpen(open); }; useEffect(()=>{ if (typeof document === "undefined") return; function closeMobileNavOnEsc(e) { if (e.key === "Escape") setMobileOpen(false); } document.addEventListener("keydown", closeMobileNavOnEsc); return ()=>document.removeEventListener("keydown", closeMobileNavOnEsc); }, [ setMobileOpen ]); // hide root scrollbar while nav mobile is open useEffect(()=>{ if (typeof window === "undefined") return; if (mobileOpen) { document.documentElement.classList.add("bf-nav-mobile-is-open"); return ()=>{ document.documentElement.classList.remove("bf-nav-mobile-is-open"); }; } else { document.documentElement.classList.remove("bf-nav-mobile-is-open"); } }, [ mobileOpen ]); // make sure the <main id> is a non-empty string const mainId = props.id || "bf-nav-main"; const MainElement = noMain ? "div" : "main"; logo = typeof logo === "string" ? /*#__PURE__*/ _jsx(NavLogo, { children: logo }) : logo; return /*#__PURE__*/ _jsxs(NavProvider, { sideCollapsed: sideProps?.collapsed ?? false, setSideCollapsed: sideProps?.onCollapsedChange, mobileOpen: mobileOpen, setMobileOpen: setMobileOpen, children: [ /*#__PURE__*/ _jsx("a", { href: "#" + mainId, className: "bf-hide-until-focus bf-button bf-button-small", children: locale.skipToMain }), side && /*#__PURE__*/ _jsx(NavSide, { logo: top ? /*#__PURE__*/ _jsx(_Fragment, {}) : logo, hideBranding: hideBranding, ...sideProps, className: classNames("from-xl", sideProps?.className), children: side }), /*#__PURE__*/ _jsx(NavTop, { preLogo: (mobile || side) && /*#__PURE__*/ _jsx("button", { type: "button", className: "to-xl bf-nav-mobile-toggle", "aria-label": locale.mainNav, "aria-expanded": mobileOpen, onClick: (e)=>{ setMobileOpen(!mobileOpen); e.stopPropagation(); }, children: /*#__PURE__*/ _jsx(NavItem, { icon: mobileOpen ? faXmark : faBars }) }), logo: logo, ...topProps, onClick: (e)=>{ if (mobileOpen) setMobileOpen(false); topProps?.onClick?.(e); }, className: classNames(topProps?.className, { "to-xl": !top }), children: top }), (mobile || side) && /*#__PURE__*/ _jsx(FocusTrap, { focusTrapOptions: { clickOutsideDeactivates: true, initialFocus: false }, active: mobileOpen && toXL, children: /*#__PURE__*/ _jsxs(NavMobile, { hideBranding: hideBranding, ...mobileProps, onClick: (e)=>{ // automatically close mobile nav on link clicks if (e.target.closest("a")) { setMobileOpen(false); } mobileProps?.onClick?.(e); }, onOverlayClick: (e)=>{ setMobileOpen(false); e.stopPropagation(); }, className: classNames(mobileProps?.className, "to-xl", "bf-scrollbar-small", { "bf-nav-mobile-open": mobileOpen, "bf-nav-mobile-close": !mobileOpen, // prevent keyboard focus (display: none; applied after animation end) // this will also prevent close animation from playing on first load "bf-nav-mobile-gone": !mobileOpen && !isAnimating }), onAnimationEnd: ()=>{ setIsAnimating(false); }, // always hide content from screen readers when closed "aria-hidden": !mobileOpen, children: [ mobile ?? side, /*#__PURE__*/ _jsx("div", { children: /*#__PURE__*/ _jsx(Button, { small: true, className: "bf-hide-until-focus", onClick: ()=>setMobileOpen(false), children: locale.closeMenu }) }) ] }) }), /*#__PURE__*/ _jsx(MainElement, { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ref: ref, ...props, className: classNames(props.className, "bf-nav-main"), id: mainId, children: children }) ] }); }); Nav.displayName = "Nav"; export default Nav;