import { useCallback, useMemo } from 'react'; import { NavLink, useLocation } from 'react-router-dom'; import { ArrowUpRightIcon, ChevronDownIcon, XIcon } from 'lucide-react'; import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTitle, } from '../ui/sheet'; import { navCount, useSeoBadges } from '../../context/seo-badges.context'; import { classNames } from '../../lib/utils'; import { activeSectionKey, activeTreeKey, containsActivePath, matchPathsActive, queryLinkClaims, searchLinkActive, type NavItem, type NavSection, } from '../../lib/navigation/nav-model'; import { useNavAccordion, useNavSections, type NavAccordion, } from '../../lib/navigation/use-nav-accordion'; import { canSeeSeoAnalytics } from '../../lib/seo/analytics-access'; import { buildVisibleNav, type VisibleNavOptions } from './nav-config'; import { NavCountPill } from './nav-count'; import { NavBadge, NavTag } from './nav-pills'; import NavUserChip from './nav-user-chip'; import { Wordmark } from './wordmark'; interface MobileNavProps extends VisibleNavOptions { open: boolean; onOpenChange: (open: boolean) => void; email?: string; } export function MobileNav({ open, onOpenChange, email, canSeeBulkUpload, }: MobileNavProps) { const close = useCallback(() => onOpenChange(false), [onOpenChange]); const badges = useSeoBadges().badges; const seoTypes = badges?.types; const seoSetupDone = badges?.setup_done ?? true; const seoModules = badges?.modules; const { sections, utilityItems } = useMemo( () => buildVisibleNav({ canSeeBulkUpload, canSeeSeoAnalytics: canSeeSeoAnalytics(), seoSetupDone, seoTypes, seoModules, }), [canSeeBulkUpload, seoSetupDone, seoTypes, seoModules] ); const location = useLocation(); const sectionAccordion = useNavSections( activeSectionKey(location.pathname, sections) ); return (
{utilityItems.map(item => ( ))}
); } function MobileNavSectionView({ section, onClose, accordion, }: { section: NavSection; onClose: () => void; accordion: NavAccordion; }) { const location = useLocation(); const menuKey = section.menuKey; const isCollapsible = Boolean(menuKey); const open = menuKey ? accordion.isOpen(menuKey) : true; const subTrees = useNavAccordion( activeTreeKey(location.pathname, section.items, location.search) ); const onOverview = section.to ? location.pathname === section.to : false; const headerRowClass = 'group mt-3 flex w-full items-center gap-2 rounded-lg px-3 py-2 transition-colors hover:bg-nav-hover'; const headerTitleClass = classNames( 'text-[12px] font-bold uppercase leading-none tracking-[0.18em] transition-colors', onOverview ? 'text-nav-accent' : 'text-foreground' ); const chevron = ( ); return (
{section.title !== '' && (isCollapsible && section.to ? (
{section.title} {section.tag && }
) : isCollapsible ? ( ) : (
{section.title} {section.tag && }
))} {open && section.items.map(item => ( ))}
); } function MobileNavRow({ item, onClose, depth = 0, accordion, siblingClaims = false, }: { item: NavItem; onClose: () => void; depth?: number; accordion?: NavAccordion; siblingClaims?: boolean; }) { const Icon = item.icon; const children = item.children ?? []; const hasChildren = children.length > 0; const menuKey = item.menuKey; const isCollapsibleTree = hasChildren && Boolean(menuKey); const isSub = depth >= 1; const location = useLocation(); const hasActiveDescendant = isCollapsibleTree ? containsActivePath(location.pathname, children) : false; const forcedActive = matchPathsActive(location.pathname, item); const searchActive = searchLinkActive( location.pathname, location.search, item ); const childClaimed = queryLinkClaims( location.pathname, location.search, children ); const treeOpen = isCollapsibleTree && menuKey ? (accordion?.isOpen(menuKey) ?? false) : false; // A page with rows under it opens them on the page itself too. const trees = useMemo( () => children.map(child => child.children && child.menuKey ? { ...child, children: [ ...child.children, { label: child.label, icon: child.icon, to: child.to }, ], } : child ), [children] ); const nested = useNavAccordion( activeTreeKey(location.pathname, trees, location.search) ); const showChildren = isCollapsibleTree ? treeOpen : true; const { counts } = useSeoBadges(); const count = navCount(counts, item.to); const iconSize = isSub ? 'h-4 w-4' : 'h-[18px] w-[18px]'; const rowBase = classNames( 'flex items-center gap-2.5 rounded-[10px] font-semibold transition-colors', isSub ? 'px-2.5 py-2 text-[13.5px]' : 'px-3 py-2.5 text-[14px]' ); if (!item.to && !hasChildren) { return (

{item.label}

); } return (
{item.to && item.external ? ( {item.label} ) : item.to ? (
classNames( rowBase, 'min-w-0 flex-1', item.emphasis ? 'border border-[rgba(200,23,93,0.30)] bg-[linear-gradient(95deg,rgba(200,23,93,0.14),rgba(123,63,228,0.08))] text-foreground' : (searchActive ?? (isActive && !siblingClaims)) || forcedActive ? 'bg-nav-accent/12 text-nav-accent' : isSub ? 'text-foreground [&>svg]:text-nav-idle hover:bg-nav-hover hover:text-foreground' : 'text-foreground hover:bg-nav-hover' ) } > {depth < 2 && ( )} {item.label} {item.tag && } {item.badge && } {count && } {isCollapsibleTree && menuKey && ( )}
) : ( )} {hasChildren && showChildren && (
{children.map(child => ( ))}
)}
); }