import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, type ComponentType, type KeyboardEvent, type ReactNode, } from "react"; import { ArchiveIcon, BlocksIcon, BotIcon, GitBranchIcon, KeyboardIcon, Link2Icon, PaletteIcon, SearchIcon, Settings2Icon, XIcon, } from "lucide-react"; import { useLocation, useNavigate, useRouterState } from "@tanstack/react-router"; import { Button } from "../ui/button"; import { Collapsible, CollapsiblePanel } from "../ui/collapsible"; import { Input } from "../ui/input"; import { Kbd } from "../ui/kbd"; import { cn } from "../../lib/utils"; import { SidebarContent, SidebarFooter, SidebarGroup, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, useSidebar, } from "../ui/sidebar"; import { SidebarUtilityMenu } from "../sidebar/SidebarChrome"; import { scrollToSettingsTarget } from "./settingsLayout"; import { getVisibleSettingsSectionIds, observeSettingsSectionVisibility, type SettingsSectionVisibilityState, } from "./settingsSectionVisibility"; import { searchSettings, SETTINGS_SECTION_LABELS, type SettingsPath, type SettingsSearchItem, } from "./settingsSearch"; import { useAvailableSettingsSearchItems } from "./useAvailableSettingsSearchItems"; const T3ConnectSidebarSignIn = lazy(() => import("../clerk/T3ConnectSidebarSignIn").then((module) => ({ default: module.T3ConnectSidebarSignIn, })), ); const T3ConnectSidebarAvatar = lazy(() => import("../clerk/T3ConnectSidebarSignIn").then((module) => ({ default: module.T3ConnectSidebarAvatar, })), ); const SETTINGS_SECTION_ICONS: Readonly< Record> > = { "/settings/general": Settings2Icon, "/settings/appearance": PaletteIcon, "/settings/keybindings": KeyboardIcon, "/settings/providers": BotIcon, "/settings/integrations": BlocksIcon, "/settings/source-control": GitBranchIcon, "/settings/connections": Link2Icon, "/settings/archived": ArchiveIcon, }; export const SETTINGS_NAV_ITEMS: ReadonlyArray<{ label: string; to: SettingsPath; icon: ComponentType<{ className?: string }>; }> = (Object.keys(SETTINGS_SECTION_LABELS) as SettingsPath[]).map((to) => ({ to, label: SETTINGS_SECTION_LABELS[to], icon: SETTINGS_SECTION_ICONS[to], })); const SETTINGS_PAGE_SECTIONS: Partial< Readonly>> > = { "/settings/general": [ { label: "Organization", targetId: "organization" }, { label: "Behavior", targetId: "behavior" }, { label: "Projects & threads", targetId: "projects-and-threads" }, { label: "Confirmations", targetId: "confirmations" }, { label: "Text generation", targetId: "text-generation" }, { label: "About", targetId: "about" }, { label: "Legacy features", targetId: "legacy-features" }, ], "/settings/appearance": [ { label: "Colors & themes", targetId: "appearance" }, { label: "Interface", targetId: "appearance-interface" }, { label: "Motion", targetId: "motion" }, { label: "Typography", targetId: "typography" }, ], "/settings/source-control": [ { label: "Version control", targetId: "source-control" }, { label: "Text generation", targetId: "source-control-text-generation" }, ], "/settings/connections": [ { label: "This environment", targetId: "connections-environment" }, { label: "Remote environments", targetId: "remote-environments" }, ], }; function SettingsSectionIcon({ to }: { to: SettingsPath }) { const Icon = SETTINGS_SECTION_ICONS[to]; return ; } function SettingsSubmenuCollapse({ open, children, }: { readonly open: boolean; readonly children: ReactNode; }) { return ( {children} ); } export function SettingsSidebarNav({ pathname }: { pathname: string }) { const navigate = useNavigate(); const currentHash = useLocation({ select: (location) => location.hash }); const resolvedPathname = useRouterState({ select: (state) => state.resolvedLocation?.pathname, }); const { isMobile, setOpenMobile, open, setOpen } = useSidebar(); const searchInputRef = useRef(null); const [query, setQuery] = useState(""); const [activeResultIndex, setActiveResultIndex] = useState(0); const [sectionVisibility, setSectionVisibility] = useState( null, ); const searchableItems = useAvailableSettingsSearchItems(); const results = useMemo(() => searchSettings(query, searchableItems), [query, searchableItems]); const isSearching = query.trim().length > 0; const hasResults = results.length > 0; const activeSettingsPath = SETTINGS_NAV_ITEMS.find( (item) => pathname === item.to || pathname.startsWith(`${item.to}/`), )?.to; const observedVisibilityScope = useMemo(() => { const path = SETTINGS_NAV_ITEMS.find( (item) => resolvedPathname === item.to || resolvedPathname?.startsWith(`${item.to}/`) === true, )?.to; const pageSections = path ? SETTINGS_PAGE_SECTIONS[path] : undefined; return path && pageSections ? { path, pageSections } : null; }, [resolvedPathname]); const visiblePageSectionIds = getVisibleSettingsSectionIds({ activePath: activeSettingsPath, scope: observedVisibilityScope, visibility: sectionVisibility, }); useEffect(() => { if (!observedVisibilityScope) return; const container = document.querySelector("[data-settings-page-layout]"); if (!container) return; return observeSettingsSectionVisibility({ container, targetIds: observedVisibilityScope.pageSections.map((section) => section.targetId), onChange(targetIds) { setSectionVisibility({ scope: observedVisibilityScope, targetIds: new Set(targetIds) }); }, }); }, [observedVisibilityScope]); useEffect(() => { setActiveResultIndex((index) => Math.min(index, Math.max(results.length - 1, 0))); }, [results.length]); useEffect(() => { const result = results[activeResultIndex]; if (!result) return; document .getElementById(`settings-search-result-${result.id}`) ?.scrollIntoView({ block: "nearest" }); }, [activeResultIndex, results]); useEffect(() => { const handleKeyDown = (event: globalThis.KeyboardEvent) => { if (event.key !== "/" || event.metaKey || event.ctrlKey || event.altKey) return; const target = event.target; if ( target instanceof HTMLElement && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable || // Keep focus inside open dialogs and popups instead of escaping // their focus trap into the sidebar search. target.closest('[role="dialog"], [aria-modal="true"], [data-slot$="popup"]') !== null) ) { return; } event.preventDefault(); if (isMobile) { setOpenMobile(true); } else if (!open) { setOpen(true); } requestAnimationFrame(() => { searchInputRef.current?.focus(); searchInputRef.current?.select(); }); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isMobile, open, setOpen, setOpenMobile]); const handleSectionClick = useCallback( (to: SettingsPath) => { if (isMobile) { setOpenMobile(false); } void navigate({ to, hash: "", replace: true, hashScrollIntoView: false, }); }, [isMobile, navigate, setOpenMobile], ); const handlePageSectionClick = useCallback( (to: SettingsPath, targetId: string) => { if (isMobile) { setOpenMobile(false); } if (pathname === to && scrollToSettingsTarget(targetId, { highlight: false })) { return; } void navigate({ to, hash: targetId, replace: true, hashScrollIntoView: false, state: { settingsTargetHighlight: false }, }); }, [isMobile, navigate, pathname, setOpenMobile], ); const clearSearch = useCallback(() => { setQuery(""); setActiveResultIndex(0); }, []); const handleSearchResultClick = useCallback( (item: SettingsSearchItem) => { clearSearch(); if (isMobile) { setOpenMobile(false); } const targetId = item.targetId ?? item.id; if (pathname === item.to && currentHash.replace(/^#/, "") === targetId) { scrollToSettingsTarget(targetId); return; } void navigate({ to: item.to, hash: targetId, replace: true, hashScrollIntoView: false, state: { settingsTargetHighlight: true }, }); }, [clearSearch, currentHash, isMobile, navigate, pathname, setOpenMobile], ); const handleSearchKeyDown = useCallback( (event: KeyboardEvent) => { if (event.key === "Escape" && isSearching) { event.preventDefault(); event.stopPropagation(); clearSearch(); return; } if (results.length === 0) return; if (event.key === "ArrowDown") { event.preventDefault(); setActiveResultIndex((index) => (index + 1) % results.length); return; } if (event.key === "ArrowUp") { event.preventDefault(); setActiveResultIndex((index) => (index - 1 + results.length) % results.length); return; } if (event.key === "Enter") { event.preventDefault(); const result = results[activeResultIndex]; if (result) handleSearchResultClick(result); } }, [activeResultIndex, clearSearch, handleSearchResultClick, isSearching, results], ); return ( <>
{ setQuery(event.currentTarget.value); setActiveResultIndex(0); }} onKeyDown={handleSearchKeyDown} placeholder="Search" aria-label="Search settings" role="combobox" aria-autocomplete="list" aria-expanded={isSearching && hasResults} aria-controls={isSearching && hasResults ? "settings-search-results" : undefined} aria-activedescendant={ isSearching && results[activeResultIndex] ? `settings-search-result-${results[activeResultIndex].id}` : undefined } className="min-w-0 flex-1 [&_[data-slot=input]]:h-auto [&_[data-slot=input]]:p-0 [&_[data-slot=input]]:leading-normal [&_[data-slot=input]]:text-sm [&_[data-slot=input]]:font-medium [&_[data-slot=input]]:text-sidebar-foreground [&_[data-slot=input]]:placeholder:text-sidebar-muted-foreground" /> {isSearching ? ( ) : ( / )}
{isSearching && results.length === 0 ? (

No settings found

) : null} {isSearching ? ( {results.map((item, index) => ( setActiveResultIndex(index)} onClick={() => handleSearchResultClick(item)} > {item.title} {SETTINGS_SECTION_LABELS[item.to]} ))} ) : ( {SETTINGS_NAV_ITEMS.map((item) => { const Icon = item.icon; const pageSections = SETTINGS_PAGE_SECTIONS[item.to]; const isActive = activeSettingsPath === item.to; return ( handleSectionClick(item.to)} > {item.label} {pageSections ? ( {pageSections.map((section) => ( } size="sm" data-visible={visiblePageSectionIds.has(section.targetId)} className={cn( "w-full text-sidebar-muted-foreground/65", visiblePageSectionIds.has(section.targetId) && "font-medium text-sidebar-foreground", )} onClick={() => handlePageSectionClick(item.to, section.targetId)} > {section.label} ))} ) : null} ); })} )}
); }