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 => (
))}
)}
);
}