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 @@
1import {
2 ArchiveIcon,
3 BotIcon,
4 GitBranchIcon,
5 KeyboardIcon,
6 Link2Icon,
7 Settings2Icon,
8} from "lucide-react";
9import type { ComponentType } from "react";
10
11export type SettingsSectionPath =
12 | "/settings/general"
13 | "/settings/keybindings"
14 | "/settings/providers"
15 | "/settings/source-control"
16 | "/settings/connections"
17 | "/settings/archived";
18
19export interface SettingsNavItem {
20 readonly icon: ComponentType<{ className?: string }>;
21 readonly label: string;
22 readonly to: SettingsSectionPath;
23}
24
25export 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 @@
1import { useCallback, type ComponentType } from "react";1import { useCallback } from "react";
2import {2import { ArrowLeftIcon } from "lucide-react";
3 ArchiveIcon,
4 ArrowLeftIcon,
5 BotIcon,
6 GitBranchIcon,
7 KeyboardIcon,
8 Link2Icon,
9 Settings2Icon,
10} from "lucide-react";
11import { useCanGoBack, useNavigate } from "@tanstack/react-router";3import { useCanGoBack, useNavigate } from "@tanstack/react-router";
12 4
13import {5import {
@@ -20,27 +12,8 @@ import { · 6 unmodified lines
20 SidebarSeparator,12 SidebarSeparator,
21 useSidebar,13 useSidebar,
22} from "../ui/sidebar";14} from "../ui/sidebar";
23 15import { SETTINGS_NAV_ITEMS, type SettingsSectionPath } from "./SettingsSidebarNav.items";
24export type SettingsSectionPath =16import { SettingsSidebarNavItem } from "./SettingsSidebarNavItem";
25 | "/settings/general"
26 | "/settings/keybindings"
27 | "/settings/providers"
28 | "/settings/source-control"
29 | "/settings/connections"
30 | "/settings/archived";
31
32export 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];
44 17
45export function SettingsSidebarNav({ pathname }: { pathname: string }) {18export function SettingsSidebarNav({ pathname }: { pathname: string }) {
46 const navigate = useNavigate();19 const navigate = useNavigate();
@@ -71,33 +44,14 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { · 24 unmodified lines
71 <SidebarContent className="overflow-x-hidden">44 <SidebarContent className="overflow-x-hidden">
72 <SidebarGroup className="px-2 py-3">45 <SidebarGroup className="px-2 py-3">
73 <SidebarMenu>46 <SidebarMenu>
74 {SETTINGS_NAV_ITEMS.map((item) => {47 {SETTINGS_NAV_ITEMS.map((item) => (
75 const Icon = item.icon;48 <SettingsSidebarNavItem
76 const isActive = pathname === item.to;49 key={item.to}
77 return (50 item={item}
78 <SidebarMenuItem key={item.to}>51 isActive={pathname === item.to}
79 <SidebarMenuButton52 onSelect={handleSectionClick}
80 size="sm"53 />
81 isActive={isActive}54 ))}
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 })}
101 </SidebarMenu>55 </SidebarMenu>
102 </SidebarGroup>56 </SidebarGroup>
103 </SidebarContent>57 </SidebarContent>
apps/web/src/components/settings/SettingsSidebarNavItem.tsx+45−0
@@ -0,0 +1,45 @@
1import { memo, useCallback } from "react";
2
3import { SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar";
4import type { SettingsNavItem, SettingsSectionPath } from "./SettingsSidebarNav.items";
5
6interface SettingsSidebarNavItemProps {
7 readonly isActive: boolean;
8 readonly item: SettingsNavItem;
9 readonly onSelect: (to: SettingsSectionPath) => void;
10}
11
12export 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});