History

Optimize settings sidebar nav renders

Cursor Agent committed 5 months ago

Commit 2fccd1f1 parent 4f0f24f3 files · +89 −58Browse files

Unified · Split

3 files
  1. apps/web/src/components/settings/SettingsSidebarNav.items.ts +32 −0
  2. apps/web/src/components/settings/SettingsSidebarNav.tsx +12 −58
  3. apps/web/src/components/settings/SettingsSidebarNavItem.tsx +45 −0
apps/web/src/components/settings/SettingsSidebarNav.items.ts+32−0
@@ -0,0 +1,32 @@
1+import {
2+ ArchiveIcon,
3+ BotIcon,
4+ GitBranchIcon,
5+ KeyboardIcon,
6+ Link2Icon,
7+ Settings2Icon,
8+} from "lucide-react";
9+import type { ComponentType } from "react";
10+
11+export type SettingsSectionPath =
12+ | "/settings/general"
13+ | "/settings/keybindings"
14+ | "/settings/providers"
15+ | "/settings/source-control"
16+ | "/settings/connections"
17+ | "/settings/archived";
18+
19+export interface SettingsNavItem {
20+ readonly icon: ComponentType<{ className?: string }>;
21+ readonly label: string;
22+ readonly to: SettingsSectionPath;
23+}
24+
25+export const SETTINGS_NAV_ITEMS: ReadonlyArray<SettingsNavItem> = [
26+ { label: "General", to: "/settings/general", icon: Settings2Icon },
27+ { label: "Keybindings", to: "/settings/keybindings", icon: KeyboardIcon },
28+ { label: "Providers", to: "/settings/providers", icon: BotIcon },
29+ { label: "Source Control", to: "/settings/source-control", icon: GitBranchIcon },
30+ { label: "Connections", to: "/settings/connections", icon: Link2Icon },
31+ { label: "Archive", to: "/settings/archived", icon: ArchiveIcon },
32+];
apps/web/src/components/settings/SettingsSidebarNav.tsx+12−58
@@ -1,13 +1,5 @@
1−import { useCallback, type ComponentType } from "react";
2−import {
3− ArchiveIcon,
4− ArrowLeftIcon,
5− BotIcon,
6− GitBranchIcon,
7− KeyboardIcon,
8− Link2Icon,
9− Settings2Icon,
10−} from "lucide-react";
1+import { useCallback } from "react";
2+import { ArrowLeftIcon } from "lucide-react";
113 import { useCanGoBack, useNavigate } from "@tanstack/react-router";
124
135 import {
@@ -20,27 +12,8 @@ import { · 6 unmodified lines
2012 SidebarSeparator,
2113 useSidebar,
2214 } from "../ui/sidebar";
23−
24−export type SettingsSectionPath =
25− | "/settings/general"
26− | "/settings/keybindings"
27− | "/settings/providers"
28− | "/settings/source-control"
29− | "/settings/connections"
30− | "/settings/archived";
31−
32−export const SETTINGS_NAV_ITEMS: ReadonlyArray<{
33− label: string;
34− to: SettingsSectionPath;
35− icon: ComponentType<{ className?: string }>;
36−}> = [
37− { label: "General", to: "/settings/general", icon: Settings2Icon },
38− { label: "Keybindings", to: "/settings/keybindings", icon: KeyboardIcon },
39− { label: "Providers", to: "/settings/providers", icon: BotIcon },
40− { label: "Source Control", to: "/settings/source-control", icon: GitBranchIcon },
41− { label: "Connections", to: "/settings/connections", icon: Link2Icon },
42− { label: "Archive", to: "/settings/archived", icon: ArchiveIcon },
43−];
15+import { SETTINGS_NAV_ITEMS, type SettingsSectionPath } from "./SettingsSidebarNav.items";
16+import { SettingsSidebarNavItem } from "./SettingsSidebarNavItem";
4417
4518 export function SettingsSidebarNav({ pathname }: { pathname: string }) {
4619 const navigate = useNavigate();
@@ -71,33 +44,14 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { · 24 unmodified lines
7144 <SidebarContent className="overflow-x-hidden">
7245 <SidebarGroup className="px-2 py-3">
7346 <SidebarMenu>
74− {SETTINGS_NAV_ITEMS.map((item) => {
75− const Icon = item.icon;
76− const isActive = pathname === item.to;
77− return (
78− <SidebarMenuItem key={item.to}>
79− <SidebarMenuButton
80− size="sm"
81− isActive={isActive}
82− className={
83− isActive
84− ? "gap-2.5 px-2.5 py-2 text-left text-[13px] font-medium text-foreground"
85− : "gap-2.5 px-2.5 py-2 text-left text-[13px] text-muted-foreground/70 hover:text-foreground/80"
86− }
87− onClick={() => handleSectionClick(item.to)}
88− >
89− <Icon
90− className={
91− isActive
92− ? "size-4 shrink-0 text-foreground"
93− : "size-4 shrink-0 text-muted-foreground/60"
94− }
95− />
96− <span className="truncate">{item.label}</span>
97− </SidebarMenuButton>
98− </SidebarMenuItem>
99− );
100− })}
47+ {SETTINGS_NAV_ITEMS.map((item) => (
48+ <SettingsSidebarNavItem
49+ key={item.to}
50+ item={item}
51+ isActive={pathname === item.to}
52+ onSelect={handleSectionClick}
53+ />
54+ ))}
10155 </SidebarMenu>
10256 </SidebarGroup>
10357 </SidebarContent>
apps/web/src/components/settings/SettingsSidebarNavItem.tsx+45−0
@@ -0,0 +1,45 @@
1+import { memo, useCallback } from "react";
2+
3+import { SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar";
4+import type { SettingsNavItem, SettingsSectionPath } from "./SettingsSidebarNav.items";
5+
6+interface SettingsSidebarNavItemProps {
7+ readonly isActive: boolean;
8+ readonly item: SettingsNavItem;
9+ readonly onSelect: (to: SettingsSectionPath) => void;
10+}
11+
12+export const SettingsSidebarNavItem = memo(function SettingsSidebarNavItem({
13+ isActive,
14+ item,
15+ onSelect,
16+}: SettingsSidebarNavItemProps) {
17+ const Icon = item.icon;
18+ const handleClick = useCallback(() => {
19+ onSelect(item.to);
20+ }, [item.to, onSelect]);
21+
22+ return (
23+ <SidebarMenuItem>
24+ <SidebarMenuButton
25+ size="sm"
26+ isActive={isActive}
27+ className={
28+ isActive
29+ ? "gap-2.5 px-2.5 py-2 text-left text-[13px] font-medium text-foreground"
30+ : "gap-2.5 px-2.5 py-2 text-left text-[13px] text-muted-foreground/70 hover:text-foreground/80"
31+ }
32+ onClick={handleClick}
33+ >
34+ <Icon
35+ className={
36+ isActive
37+ ? "size-4 shrink-0 text-foreground"
38+ : "size-4 shrink-0 text-muted-foreground/60"
39+ }
40+ />
41+ <span className="truncate">{item.label}</span>
42+ </SidebarMenuButton>
43+ </SidebarMenuItem>
44+ );
45+});