import React, { useEffect, useRef, useState } from 'react' import { Search, LifeBuoy, ArrowUpRight, ChevronsUpDown, ChevronDown, ChevronRight, Check, MapPin, CornerDownRight } from 'lucide-react' import { useTranslation } from '../../i18n' import { useHelpStore, docTitle } from '../../store/helpStore' import { ctxKey, guideKey, guidesFor, docsRoute, topLevelHelpContexts, childHelpContexts, helpContextTrail } from '../../help/registry' import type { HelpContext, HelpGuide } from '../../help/types' import { helpIcon } from './helpIcons' import HelpDocLink from './HelpDocLink' import { ThisScreenChip } from './HelpHome' const GITHUB_DISCUSSIONS = 'https://github.com/liketrek/TREK/discussions/new?category=ideas' const DISCORD = 'https://discord.gg/NhZBDSd4qW' /** * Left pane of the help dialog: search, the current screen with its guides, * the docs that go with it, and the ways to reach a human. */ export default function HelpNav({ context, currentId, activeGuideId }: { /** The screen whose help is shown. */ context: HelpContext | null /** The screen the reader is actually on, for the "this screen" marker. */ currentId: string | null activeGuideId: string | null }): React.ReactElement { const { t } = useTranslation() const query = useHelpStore(s => s.query) const setQuery = useHelpStore(s => s.setQuery) const closeGuide = useHelpStore(s => s.closeGuide) const docIndex = useHelpStore(s => s.docIndex) const searchRef = useRef(null) useEffect(() => { searchRef.current?.focus({ preventScroll: true }) }, []) const guides = context ? guidesFor(context) : [] const overviewActive = !activeGuideId && !query.trim() // The tabs of the screen on show, or of its parent when the reader is on one of them. const family = context ? childHelpContexts(context.parent ?? context.id) : [] // Folded by default: the guides matter more, the tabs are one click away. const [familyOpen, setFamilyOpen] = useState(false) return ( ) } /** * Pick which screen's help to read without leaving the one you are on. The * current screen is marked; choosing it again returns the dialog to following * the screen the reader is on. */ /** * Which screen the panel shows, and the list to pick another. Screens with * sub-screens (Journey and its journal, Settings and its tabs) fold: one group * is open at a time, and the group of the screen on show starts open. */ function ScreenSwitcher({ shown, currentId }: { shown: HelpContext | null; currentId: string | null }): React.ReactElement { const { t } = useTranslation() const browse = useHelpStore(s => s.browse) const [open, setOpen] = useState(false) const [openGroup, setOpenGroup] = useState(null) const rootRef = useRef(null) const screens = topLevelHelpContexts() const ShownIcon = shown ? helpIcon(shown.icon) : MapPin const trail = shown ? helpContextTrail(shown) : [] // Click outside or Escape closes the list; Escape must not bubble into the dialog's own close. useEffect(() => { if (!open) return const onDown = (e: MouseEvent) => { if (!rootRef.current?.contains(e.target as Node)) setOpen(false) } const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.stopPropagation(); setOpen(false) } } document.addEventListener('mousedown', onDown) window.addEventListener('keydown', onKey, true) return () => { document.removeEventListener('mousedown', onDown) window.removeEventListener('keydown', onKey, true) } }, [open]) const choose = (id: string) => { browse(id === currentId ? null : id) setOpen(false) } const toggleList = () => { setOpen(o => { if (!o) setOpenGroup(trail.length > 1 ? trail[0].id : null) return !o }) } return (
{open && (
    {screens.map(screen => { const children = childHelpContexts(screen.id) const expanded = openGroup === screen.id return (
  • choose(screen.id)} group={children.length > 0 ? { expanded, count: children.length, toggle: () => setOpenGroup(expanded ? null : screen.id) } : undefined} /> {children.length > 0 && expanded && (
      {children.map(child => (
    • choose(child.id)} nested />
    • ))}
    )}
  • ) })}
)}
) } /** One screen in the switcher list; a group row carries the fold toggle on its right. */ function ScreenRow({ screen, selected, current, onPick, group, nested = false, }: { screen: HelpContext selected: boolean current: boolean onPick: () => void group?: { expanded: boolean; count: number; toggle: () => void } nested?: boolean }): React.ReactElement { const { t } = useTranslation() const Icon = helpIcon(screen.icon) const Fold = group?.expanded ? ChevronDown : ChevronRight return (
{group && ( )}
) } /** A sibling or child screen in the nav; picking it browses that screen's overview. */ function NavScreen({ screen, shown, current }: { screen: HelpContext; shown: boolean; current: boolean }): React.ReactElement { const { t } = useTranslation() const browse = useHelpStore(s => s.browse) const Icon = helpIcon(screen.icon) return ( ) } function NavLabel({ children }: { children: React.ReactNode }): React.ReactElement { return

{children}

} function NavGuide({ guide, active }: { guide: HelpGuide; active: boolean }): React.ReactElement { const { t } = useTranslation() const openGuide = useHelpStore(s => s.openGuide) const Icon = helpIcon(guide.icon) return ( ) } /** GitHub's mark, from their brand kit; lucide dropped its brand icons. */ function GitHubMark({ className }: { className?: string }): React.ReactElement { return ( ) } /** Discord's mark, the same path the navbar's profile menu uses. */ function DiscordMark({ className }: { className?: string }): React.ReactElement { return ( ) }