diff --git a/apps/website/src/components/shared/NavDesktop.tsx b/apps/website/src/components/shared/NavDesktop.tsx index 7c1e25513..4f721e2ac 100644 --- a/apps/website/src/components/shared/NavDesktop.tsx +++ b/apps/website/src/components/shared/NavDesktop.tsx @@ -10,103 +10,22 @@ import { import { Button } from '../ui/Button'; import { GitHubIcon } from '../ui/GitHubIcon'; import { GITHUB_REPO_URL } from '../../lib/positioning'; -import { LibraryMark } from '../docs/LibraryMark'; -import { NAV_TRIGGERS, type NavItem, type NavPanel } from './nav-config'; +import { NavPanelBody } from './NavPanelBody'; +import { NAV_TRIGGERS, type NavPanel } from './nav-config'; /** Long enough to cross the gap between trigger and panel diagonally. */ const OPEN_DELAY_MS = 100; const CLOSE_DELAY_MS = 150; -export function trackNavItem(item: NavItem, surface: 'nav' | 'mobile_nav') { - const ctaId: `nav_${string}` | `mobile_nav_${string}` = - surface === 'nav' ? `nav_${item.ctaId}` : `mobile_nav_${item.ctaId}`; - if (item.external) { - trackExternalLinkClick(item.href, { - surface, - cta_id: ctaId, - cta_text: item.label, - }); - return; - } - trackCtaClick({ - surface, - destination_url: item.href, - cta_id: ctaId, - cta_text: item.label, - }); -} - -export function NavPanelItem({ - item, - surface, - onNavigate, -}: { - item: NavItem; - surface: 'nav' | 'mobile_nav'; - onNavigate?: () => void; -}) { - const Icon = item.icon; - const body = ( - <> - - - {item.label} - {item.description} - - - ); - const onClick = () => { - trackNavItem(item, surface); - onNavigate?.(); - }; - - if (item.external) { - return ( - - {body} - - ); - } - return ( - - {body} - - ); -} - function Panel({ panel, id }: { panel: NavPanel; id: string }) { return (
-
- {panel.columns.map((column, index) => ( -
- {column.heading ? ( - {column.heading} - ) : null} - {column.items.map((item) => ( - - ))} -
- ))} -
- {panel.footer ? ( -
- {panel.footer.lead} - -
- ) : null} +
); } diff --git a/apps/website/src/components/shared/NavMobile.tsx b/apps/website/src/components/shared/NavMobile.tsx index cecad1578..524abdb4e 100644 --- a/apps/website/src/components/shared/NavMobile.tsx +++ b/apps/website/src/components/shared/NavMobile.tsx @@ -19,7 +19,7 @@ import { Button } from '../ui/Button'; import { GitHubIcon } from '../ui/GitHubIcon'; import { GITHUB_REPO_URL } from '../../lib/positioning'; import { DocsContextContent } from '../docs/DocsControlPlane'; -import { NavPanelItem } from './NavDesktop'; +import { NavPanelBody } from './NavPanelBody'; import { NAV_TRIGGERS } from './nav-config'; const toAnalyticsLibrary = (library: LibraryId | null): AnalyticsLibrary => { @@ -436,38 +436,12 @@ export function NavMobile({ {panel ? (
- {panel.columns.map((column, index) => ( -
- {column.heading ? ( - - {column.heading} - - ) : null} - {column.items.map((item) => ( - closeMobileMenu()} - /> - ))} -
- ))} - {panel.footer ? ( -
- - {panel.footer.lead} - - closeMobileMenu()} - /> -
- ) : null} + closeMobileMenu()} + />
) : null} diff --git a/apps/website/src/components/shared/NavPanelBody.tsx b/apps/website/src/components/shared/NavPanelBody.tsx new file mode 100644 index 000000000..9ba2c5656 --- /dev/null +++ b/apps/website/src/components/shared/NavPanelBody.tsx @@ -0,0 +1,139 @@ +'use client'; + +import Link from 'next/link'; +import { + trackCtaClick, + trackExternalLinkClick, +} from '../../lib/analytics/client'; +import { LibraryMark } from '../docs/LibraryMark'; +import type { NavItem, NavPanel } from './nav-config'; + +export function trackNavItem(item: NavItem, surface: 'nav' | 'mobile_nav') { + const ctaId: `nav_${string}` | `mobile_nav_${string}` = + surface === 'nav' ? `nav_${item.ctaId}` : `mobile_nav_${item.ctaId}`; + if (item.external) { + trackExternalLinkClick(item.href, { + surface, + cta_id: ctaId, + cta_text: item.label, + }); + return; + } + trackCtaClick({ + surface, + destination_url: item.href, + cta_id: ctaId, + cta_text: item.label, + }); +} + +export function NavPanelItem({ + item, + surface, + onNavigate, +}: { + item: NavItem; + surface: 'nav' | 'mobile_nav'; + onNavigate?: () => void; +}) { + const Icon = item.icon; + const body = ( + <> + + + {item.label} + {item.description} + + + ); + const onClick = () => { + trackNavItem(item, surface); + onNavigate?.(); + }; + + if (item.external) { + return ( + + {body} + + ); + } + return ( + + {body} + + ); +} + +/** + * The contents of one nav panel — its columns, then its footer — shared by the + * desktop hover panel and the mobile drill-in stack. + * + * Only the wrapper class names and the mobile drawer's close callback differ + * between the two surfaces; the column/footer shape itself is the same, and + * writing it twice let the two drift. The caller still owns the outermost + * element, because that is where the surfaces genuinely diverge: desktop needs + * the panel's id and `data-columns`, mobile does not. + */ +export function NavPanelBody({ + panel, + surface, + columnsClassName, + columnClassName, + onNavigate, +}: { + panel: NavPanel; + surface: 'nav' | 'mobile_nav'; + /** Desktop grids its columns inside a wrapper; the mobile stack has none. */ + columnsClassName?: string; + columnClassName: string; + onNavigate?: () => void; +}) { + const columns = panel.columns.map((column, index) => ( +
+ {column.heading ? ( + {column.heading} + ) : null} + {column.items.map((item) => ( + + ))} +
+ )); + + return ( + <> + {columnsClassName ? ( +
{columns}
+ ) : ( + columns + )} + {panel.footer ? ( +
+ {panel.footer.lead} + +
+ ) : null} + + ); +}