@moontra/moonui-pro 2.5.7 → 2.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1836,6 +1836,7 @@ interface SidebarConfig {
1836
1836
  footer?: SidebarSection;
1837
1837
  showSearch?: boolean;
1838
1838
  searchPlaceholder?: string;
1839
+ searchQuery?: string;
1839
1840
  onSearchChange?: (value: string) => void;
1840
1841
  showThemeToggle?: boolean;
1841
1842
  theme?: 'light' | 'dark' | 'system';
@@ -1874,7 +1875,7 @@ interface SidebarProps extends SidebarConfig {
1874
1875
  activePath?: string;
1875
1876
  onNavigate?: (href: string) => void;
1876
1877
  }
1877
- declare function Sidebar({ sections, footer, showSearch, searchPlaceholder, onSearchChange, showThemeToggle, theme, onThemeChange, branding, collapsible, defaultCollapsed, floatingActionButton, glassmorphism, animatedBackground, keyboardShortcuts, persistState, persistKey, onStateChange, customStyles, className, activePath, onNavigate }: SidebarProps): react_jsx_runtime.JSX.Element;
1878
+ declare function Sidebar({ sections, footer, showSearch, searchPlaceholder, searchQuery, onSearchChange, showThemeToggle, theme, onThemeChange, branding, collapsible, defaultCollapsed, floatingActionButton, glassmorphism, animatedBackground, keyboardShortcuts, persistState, persistKey, onStateChange, customStyles, className, activePath, onNavigate }: SidebarProps): react_jsx_runtime.JSX.Element;
1878
1879
 
1879
1880
  declare const enhancedButtonVariants: (props?: ({
1880
1881
  variant?: "link" | "default" | "ghost" | "outline" | "secondary" | "destructive" | "gradient" | "glow" | null | undefined;
package/dist/index.mjs CHANGED
@@ -56306,11 +56306,45 @@ var TableRow2 = t__default.memo(({
56306
56306
  return prevRowId === nextRowId && prevProps.isExpanded === nextProps.isExpanded && prevProps.row.getIsSelected() === nextProps.row.getIsSelected() && prevVisibilityKeys === nextVisibilityKeys && prevVisibilityValues === nextVisibilityValues;
56307
56307
  });
56308
56308
  TableRow2.displayName = "TableRow";
56309
+ var SearchInput = t__default.memo(({
56310
+ searchInputRef,
56311
+ searchPlaceholder,
56312
+ value,
56313
+ onChange,
56314
+ keyboardShortcuts
56315
+ }) => {
56316
+ const handleChange = useCallback((e) => {
56317
+ onChange(e.target.value);
56318
+ }, [onChange]);
56319
+ return /* @__PURE__ */ jsx("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
56320
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
56321
+ /* @__PURE__ */ jsx(
56322
+ "input",
56323
+ {
56324
+ ref: searchInputRef,
56325
+ type: "search",
56326
+ placeholder: searchPlaceholder,
56327
+ value,
56328
+ onChange: handleChange,
56329
+ autoComplete: "off",
56330
+ autoCorrect: "off",
56331
+ autoCapitalize: "off",
56332
+ spellCheck: "false",
56333
+ className: "w-full h-10 pl-9 pr-9 text-sm bg-background border border-input rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
56334
+ }
56335
+ ),
56336
+ keyboardShortcuts && /* @__PURE__ */ jsxs("kbd", { className: "absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 hidden sm:flex", children: [
56337
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: "\u2318" }),
56338
+ "K"
56339
+ ] })
56340
+ ] }) });
56341
+ });
56309
56342
  function Sidebar({
56310
56343
  sections,
56311
56344
  footer,
56312
56345
  showSearch = true,
56313
56346
  searchPlaceholder = "Search...",
56347
+ searchQuery = "",
56314
56348
  onSearchChange,
56315
56349
  showThemeToggle = false,
56316
56350
  theme = "system",
@@ -56334,7 +56368,7 @@ function Sidebar({
56334
56368
  const [isOpen, setIsOpen] = useState(false);
56335
56369
  const [collapsed, setCollapsed] = useState(defaultCollapsed);
56336
56370
  const [expandedSections, setExpandedSections] = useState([]);
56337
- const [searchQuery, setSearchQuery] = useState("");
56371
+ const [currentSearchQuery, setCurrentSearchQuery] = useState(searchQuery);
56338
56372
  const [pinnedItems, setPinnedItems] = useState([]);
56339
56373
  const searchInputRef = useRef(null);
56340
56374
  const mouseX = useMotionValue(0);
@@ -56357,14 +56391,14 @@ function Sidebar({
56357
56391
  const state = {
56358
56392
  collapsed,
56359
56393
  expandedSections,
56360
- searchQuery,
56394
+ searchQuery: currentSearchQuery,
56361
56395
  pinnedItems,
56362
56396
  recentItems: []
56363
56397
  };
56364
56398
  localStorage.setItem(persistKey, JSON.stringify(state));
56365
56399
  onStateChange?.(state);
56366
56400
  }
56367
- }, [collapsed, expandedSections, searchQuery, pinnedItems, persistState, persistKey, onStateChange]);
56401
+ }, [collapsed, expandedSections, currentSearchQuery, pinnedItems, persistState, persistKey, onStateChange]);
56368
56402
  useEffect(() => {
56369
56403
  const checkMobile = () => {
56370
56404
  setIsMobile(window.innerWidth < 768);
@@ -56430,6 +56464,12 @@ function Sidebar({
56430
56464
  }
56431
56465
  }
56432
56466
  }, [onNavigate, isMobile]);
56467
+ useEffect(() => {
56468
+ setCurrentSearchQuery(searchQuery);
56469
+ }, [searchQuery]);
56470
+ const handleSearch = useCallback((value) => {
56471
+ onSearchChange?.(value);
56472
+ }, [onSearchChange]);
56433
56473
  const filterItems = useCallback((items, query) => {
56434
56474
  if (!query)
56435
56475
  return items;
@@ -56442,14 +56482,23 @@ function Sidebar({
56442
56482
  });
56443
56483
  }, []);
56444
56484
  const filteredSections = useMemo(() => {
56485
+ if (!currentSearchQuery) {
56486
+ return sections.map((section) => ({
56487
+ ...section,
56488
+ filteredItems: section.items.map((item) => ({
56489
+ ...item,
56490
+ filteredChildren: item.items
56491
+ }))
56492
+ }));
56493
+ }
56445
56494
  return sections.map((section) => ({
56446
56495
  ...section,
56447
- filteredItems: filterItems(section.items, searchQuery).map((item) => ({
56496
+ filteredItems: filterItems(section.items, currentSearchQuery).map((item) => ({
56448
56497
  ...item,
56449
- filteredChildren: item.items ? filterItems(item.items, searchQuery) : void 0
56498
+ filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : void 0
56450
56499
  }))
56451
56500
  })).filter((section) => section.filteredItems.length > 0);
56452
- }, [sections, searchQuery, filterItems]);
56501
+ }, [sections, currentSearchQuery, filterItems]);
56453
56502
  const renderItem = useCallback((item, depth = 0, filteredChildren) => {
56454
56503
  const isActive2 = item.href === activePath;
56455
56504
  const isPinned = pinnedItems.includes(item.id);
@@ -56525,8 +56574,8 @@ function Sidebar({
56525
56574
  ) })
56526
56575
  ] }, item.id);
56527
56576
  }, [activePath, pinnedItems, expandedSections, collapsed, customStyles, handleItemClick, toggleSection]);
56528
- const SidebarContent = t__default.memo(() => /* @__PURE__ */ jsxs(Fragment, { children: [
56529
- /* @__PURE__ */ jsxs("div", { className: cn(
56577
+ const SidebarHeader = t__default.memo(() => {
56578
+ return /* @__PURE__ */ jsxs("div", { className: cn(
56530
56579
  "flex items-center gap-3 p-4 border-b",
56531
56580
  collapsed && !isMobile && "justify-center px-2"
56532
56581
  ), children: [
@@ -56557,45 +56606,43 @@ function Sidebar({
56557
56606
  children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
56558
56607
  }
56559
56608
  )
56560
- ] }),
56561
- showSearch && (!collapsed || isMobile) && /* @__PURE__ */ jsx("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
56562
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
56563
- /* @__PURE__ */ jsx(
56564
- MoonUIInputPro,
56565
- {
56566
- ref: searchInputRef,
56567
- type: "search",
56568
- placeholder: searchPlaceholder,
56569
- value: searchQuery,
56570
- onChange: (e) => {
56571
- setSearchQuery(e.target.value);
56572
- onSearchChange?.(e.target.value);
56573
- },
56574
- autoComplete: "off",
56575
- autoCorrect: "off",
56576
- autoCapitalize: "off",
56577
- spellCheck: "false",
56578
- className: "pl-9 pr-9"
56579
- },
56580
- "sidebar-search-input"
56581
- ),
56582
- keyboardShortcuts && /* @__PURE__ */ jsxs("kbd", { className: "absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 hidden sm:flex", children: [
56583
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: "\u2318" }),
56584
- "K"
56585
- ] })
56586
- ] }) }),
56587
- pinnedItems.length > 0 && (!collapsed || isMobile) && /* @__PURE__ */ jsxs("div", { className: "p-4 border-b", children: [
56588
- /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: "Pinned" }),
56589
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: sections.flatMap(
56590
- (section) => section.items.filter((item) => pinnedItems.includes(item.id))
56591
- ).map((item) => renderItem(item)) })
56592
- ] }),
56593
- /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "p-4 space-y-6", children: filteredSections.map((section, index) => /* @__PURE__ */ jsxs("div", { children: [
56594
- section.title && (!collapsed || isMobile) && /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: section.title }),
56595
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: section.filteredItems.map((item) => renderItem(item, 0, item.filteredChildren)) }),
56596
- section.showDivider && index < filteredSections.length - 1 && /* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "mt-4" })
56597
- ] }, section.id)) }) }),
56598
- footer && /* @__PURE__ */ jsxs("div", { className: "border-t p-4", children: [
56609
+ ] });
56610
+ });
56611
+ const searchElement = useMemo(() => {
56612
+ if (!showSearch)
56613
+ return null;
56614
+ return /* @__PURE__ */ jsx(
56615
+ SearchInput,
56616
+ {
56617
+ searchInputRef,
56618
+ searchPlaceholder,
56619
+ value: searchQuery,
56620
+ onChange: handleSearch,
56621
+ keyboardShortcuts
56622
+ }
56623
+ );
56624
+ }, [showSearch, searchPlaceholder, searchQuery, handleSearch, keyboardShortcuts]);
56625
+ const SidebarSearch = t__default.memo(() => {
56626
+ return showSearch && (!collapsed || isMobile) ? searchElement : null;
56627
+ });
56628
+ const SidebarMenuContent = t__default.memo(() => {
56629
+ return /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-6", children: [
56630
+ pinnedItems.length > 0 && (!collapsed || isMobile) && /* @__PURE__ */ jsxs("div", { children: [
56631
+ /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: "Pinned" }),
56632
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: sections.flatMap(
56633
+ (section) => section.items.filter((item) => pinnedItems.includes(item.id))
56634
+ ).map((item) => renderItem(item)) }),
56635
+ /* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "mt-4" })
56636
+ ] }),
56637
+ filteredSections.map((section, index) => /* @__PURE__ */ jsxs("div", { children: [
56638
+ section.title && (!collapsed || isMobile) && /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: section.title }),
56639
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: section.filteredItems.map((item) => renderItem(item, 0, item.filteredChildren)) }),
56640
+ section.showDivider && index < filteredSections.length - 1 && /* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "mt-4" })
56641
+ ] }, section.id))
56642
+ ] }) });
56643
+ });
56644
+ const SidebarFooter = t__default.memo(() => {
56645
+ return footer ? /* @__PURE__ */ jsxs("div", { className: "border-t p-4", children: [
56599
56646
  /* @__PURE__ */ jsx("div", { className: "space-y-1", children: footer.items.map((item) => renderItem(item)) }),
56600
56647
  showThemeToggle && (!collapsed || isMobile) && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between", children: [
56601
56648
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Theme" }),
@@ -56621,8 +56668,16 @@ function Sidebar({
56621
56668
  ] })
56622
56669
  ] })
56623
56670
  ] })
56624
- ] })
56625
- ] }));
56671
+ ] }) : null;
56672
+ });
56673
+ const SidebarContent = t__default.memo(() => {
56674
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
56675
+ /* @__PURE__ */ jsx(SidebarHeader, {}),
56676
+ /* @__PURE__ */ jsx(SidebarSearch, {}),
56677
+ /* @__PURE__ */ jsx(SidebarMenuContent, {}),
56678
+ /* @__PURE__ */ jsx(SidebarFooter, {})
56679
+ ] });
56680
+ });
56626
56681
  const sidebarClasses = cn(
56627
56682
  "flex h-full flex-col bg-background border-r",
56628
56683
  glassmorphism && "bg-background/80 backdrop-blur-xl border-white/10",
@@ -56643,7 +56698,7 @@ function Sidebar({
56643
56698
  children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
56644
56699
  }
56645
56700
  ),
56646
- /* @__PURE__ */ jsx(Sheet, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsx(SheetContent, { side: "left", className: "w-80 p-0", children: /* @__PURE__ */ jsxs("div", { className: cn(sidebarClasses, "sidebar-container"), children: [
56701
+ /* @__PURE__ */ jsx(Sheet, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsx(SheetContent, { side: "left", className: "w-80 p-0", children: /* @__PURE__ */ jsxs("div", { className: cn(sidebarClasses, "sidebar-container flex flex-col"), children: [
56647
56702
  animatedBackground && /* @__PURE__ */ jsx(
56648
56703
  motion.div,
56649
56704
  {
@@ -56657,7 +56712,7 @@ function Sidebar({
56657
56712
  ] }) }) })
56658
56713
  ] });
56659
56714
  }
56660
- return /* @__PURE__ */ jsxs("aside", { className: cn(sidebarClasses, "sidebar-container hidden md:flex"), children: [
56715
+ return /* @__PURE__ */ jsxs("aside", { className: cn(sidebarClasses, "sidebar-container hidden md:flex flex-col"), children: [
56661
56716
  animatedBackground && /* @__PURE__ */ jsx(
56662
56717
  motion.div,
56663
56718
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.5.7",
3
+ "version": "2.5.9",
4
4
  "description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'
3
+ import React, { useState, useEffect, useCallback, useRef, useMemo, createContext, useContext } from 'react'
4
4
  import { motion, AnimatePresence, useMotionValue, useSpring } from 'framer-motion'
5
5
  import { cn } from '../../lib/utils'
6
6
  import { Button } from '../ui/button'
@@ -28,7 +28,6 @@ import {
28
28
  Sparkles,
29
29
  Command
30
30
  } from 'lucide-react'
31
- import { Input } from '../ui/input'
32
31
  import {
33
32
  DropdownMenu,
34
33
  DropdownMenuContent,
@@ -37,6 +36,50 @@ import {
37
36
  DropdownMenuTrigger,
38
37
  } from '../ui/dropdown-menu'
39
38
 
39
+ // Search Input Component
40
+ const SearchInput = React.memo(({
41
+ searchInputRef,
42
+ searchPlaceholder,
43
+ value,
44
+ onChange,
45
+ keyboardShortcuts
46
+ }: {
47
+ searchInputRef: React.RefObject<HTMLInputElement | null>
48
+ searchPlaceholder: string
49
+ value: string
50
+ onChange: (value: string) => void
51
+ keyboardShortcuts: boolean
52
+ }) => {
53
+ const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
54
+ onChange(e.target.value)
55
+ }, [onChange])
56
+
57
+ return (
58
+ <div className="p-4 border-b">
59
+ <div className="relative">
60
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
61
+ <input
62
+ ref={searchInputRef}
63
+ type="search"
64
+ placeholder={searchPlaceholder}
65
+ value={value}
66
+ onChange={handleChange}
67
+ autoComplete="off"
68
+ autoCorrect="off"
69
+ autoCapitalize="off"
70
+ spellCheck="false"
71
+ className="w-full h-10 pl-9 pr-9 text-sm bg-background border border-input rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
72
+ />
73
+ {keyboardShortcuts && (
74
+ <kbd className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 hidden sm:flex">
75
+ <span className="text-xs">⌘</span>K
76
+ </kbd>
77
+ )}
78
+ </div>
79
+ </div>
80
+ )
81
+ })
82
+
40
83
  export interface SidebarItem {
41
84
  id: string
42
85
  title: string
@@ -65,6 +108,7 @@ export interface SidebarConfig {
65
108
  footer?: SidebarSection
66
109
  showSearch?: boolean
67
110
  searchPlaceholder?: string
111
+ searchQuery?: string
68
112
  onSearchChange?: (value: string) => void
69
113
  showThemeToggle?: boolean
70
114
  theme?: 'light' | 'dark' | 'system'
@@ -111,6 +155,7 @@ export function Sidebar({
111
155
  footer,
112
156
  showSearch = true,
113
157
  searchPlaceholder = "Search...",
158
+ searchQuery = '',
114
159
  onSearchChange,
115
160
  showThemeToggle = false,
116
161
  theme = 'system',
@@ -134,7 +179,7 @@ export function Sidebar({
134
179
  const [isOpen, setIsOpen] = useState(false)
135
180
  const [collapsed, setCollapsed] = useState(defaultCollapsed)
136
181
  const [expandedSections, setExpandedSections] = useState<string[]>([])
137
- const [searchQuery, setSearchQuery] = useState('')
182
+ const [currentSearchQuery, setCurrentSearchQuery] = useState(searchQuery)
138
183
  const [pinnedItems, setPinnedItems] = useState<string[]>([])
139
184
  const searchInputRef = useRef<HTMLInputElement>(null)
140
185
  const mouseX = useMotionValue(0)
@@ -161,14 +206,14 @@ export function Sidebar({
161
206
  const state: SidebarState = {
162
207
  collapsed,
163
208
  expandedSections,
164
- searchQuery,
209
+ searchQuery: currentSearchQuery,
165
210
  pinnedItems,
166
211
  recentItems: []
167
212
  }
168
213
  localStorage.setItem(persistKey, JSON.stringify(state))
169
214
  onStateChange?.(state)
170
215
  }
171
- }, [collapsed, expandedSections, searchQuery, pinnedItems, persistState, persistKey, onStateChange])
216
+ }, [collapsed, expandedSections, currentSearchQuery, pinnedItems, persistState, persistKey, onStateChange])
172
217
 
173
218
  // Check mobile
174
219
  useEffect(() => {
@@ -258,6 +303,16 @@ export function Sidebar({
258
303
  }
259
304
  }, [onNavigate, isMobile])
260
305
 
306
+ // Update search query from prop
307
+ useEffect(() => {
308
+ setCurrentSearchQuery(searchQuery)
309
+ }, [searchQuery])
310
+
311
+ // Handle search change
312
+ const handleSearch = useCallback((value: string) => {
313
+ onSearchChange?.(value)
314
+ }, [onSearchChange])
315
+
261
316
  const filterItems = useCallback((items: SidebarItem[], query: string): SidebarItem[] => {
262
317
  if (!query) return items
263
318
 
@@ -272,14 +327,24 @@ export function Sidebar({
272
327
 
273
328
  // Filtered sections memoized to prevent re-renders
274
329
  const filteredSections = useMemo(() => {
330
+ if (!currentSearchQuery) {
331
+ return sections.map(section => ({
332
+ ...section,
333
+ filteredItems: section.items.map(item => ({
334
+ ...item,
335
+ filteredChildren: item.items
336
+ }))
337
+ }))
338
+ }
339
+
275
340
  return sections.map(section => ({
276
341
  ...section,
277
- filteredItems: filterItems(section.items, searchQuery).map(item => ({
342
+ filteredItems: filterItems(section.items, currentSearchQuery).map(item => ({
278
343
  ...item,
279
- filteredChildren: item.items ? filterItems(item.items, searchQuery) : undefined
344
+ filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : undefined
280
345
  }))
281
346
  })).filter(section => section.filteredItems.length > 0)
282
- }, [sections, searchQuery, filterItems])
347
+ }, [sections, currentSearchQuery, filterItems])
283
348
 
284
349
  const renderItem = useCallback((item: SidebarItem, depth = 0, filteredChildren?: SidebarItem[]) => {
285
350
  const isActive = item.href === activePath
@@ -388,9 +453,10 @@ export function Sidebar({
388
453
  )
389
454
  }, [activePath, pinnedItems, expandedSections, collapsed, customStyles, handleItemClick, toggleSection])
390
455
 
391
- const SidebarContent = React.memo(() => (
392
- <>
393
- {/* Header */}
456
+
457
+ // Header Component
458
+ const SidebarHeader = React.memo(() => {
459
+ return (
394
460
  <div className={cn(
395
461
  "flex items-center gap-3 p-4 border-b",
396
462
  collapsed && !isMobile && "justify-center px-2"
@@ -429,52 +495,48 @@ export function Sidebar({
429
495
  </Button>
430
496
  )}
431
497
  </div>
498
+ )
499
+ })
432
500
 
433
- {/* Search */}
434
- {showSearch && (!collapsed || isMobile) && (
435
- <div className="p-4 border-b">
436
- <div className="relative">
437
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
438
- <Input
439
- key="sidebar-search-input"
440
- ref={searchInputRef}
441
- type="search"
442
- placeholder={searchPlaceholder}
443
- value={searchQuery}
444
- onChange={(e) => {
445
- setSearchQuery(e.target.value)
446
- onSearchChange?.(e.target.value)
447
- }}
448
- autoComplete="off"
449
- autoCorrect="off"
450
- autoCapitalize="off"
451
- spellCheck="false"
452
- className="pl-9 pr-9"
453
- />
454
- {keyboardShortcuts && (
455
- <kbd className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 hidden sm:flex">
456
- <span className="text-xs">⌘</span>K
457
- </kbd>
458
- )}
459
- </div>
460
- </div>
461
- )}
462
-
463
- {/* Pinned Items */}
464
- {pinnedItems.length > 0 && (!collapsed || isMobile) && (
465
- <div className="p-4 border-b">
466
- <h4 className="text-xs font-medium text-muted-foreground mb-2">Pinned</h4>
467
- <div className="space-y-1">
468
- {sections.flatMap(section =>
469
- section.items.filter(item => pinnedItems.includes(item.id))
470
- ).map(item => renderItem(item))}
471
- </div>
472
- </div>
473
- )}
474
-
475
- {/* Main Content */}
501
+ // Search element with controlled value
502
+ const searchElement = useMemo(() => {
503
+ if (!showSearch) return null
504
+
505
+ return (
506
+ <SearchInput
507
+ searchInputRef={searchInputRef}
508
+ searchPlaceholder={searchPlaceholder}
509
+ value={searchQuery}
510
+ onChange={handleSearch}
511
+ keyboardShortcuts={keyboardShortcuts}
512
+ />
513
+ )
514
+ }, [showSearch, searchPlaceholder, searchQuery, handleSearch, keyboardShortcuts])
515
+
516
+ // Search Component wrapper
517
+ const SidebarSearch = React.memo(() => {
518
+ return showSearch && (!collapsed || isMobile) ? searchElement : null
519
+ })
520
+
521
+ // Menu Content
522
+ const SidebarMenuContent = React.memo(() => {
523
+ return (
476
524
  <ScrollArea className="flex-1 overflow-y-auto">
477
525
  <div className="p-4 space-y-6">
526
+ {/* Pinned Items */}
527
+ {pinnedItems.length > 0 && (!collapsed || isMobile) && (
528
+ <div>
529
+ <h4 className="text-xs font-medium text-muted-foreground mb-2">Pinned</h4>
530
+ <div className="space-y-1">
531
+ {sections.flatMap(section =>
532
+ section.items.filter(item => pinnedItems.includes(item.id))
533
+ ).map(item => renderItem(item))}
534
+ </div>
535
+ <Separator className="mt-4" />
536
+ </div>
537
+ )}
538
+
539
+ {/* Main Menu Items */}
478
540
  {filteredSections.map((section, index) => (
479
541
  <div key={section.id}>
480
542
  {section.title && (!collapsed || isMobile) && (
@@ -492,46 +554,59 @@ export function Sidebar({
492
554
  ))}
493
555
  </div>
494
556
  </ScrollArea>
495
-
496
- {/* Footer */}
497
- {footer && (
498
- <div className="border-t p-4">
499
- <div className="space-y-1">
500
- {footer.items.map(item => renderItem(item))}
501
- </div>
502
-
503
- {showThemeToggle && (!collapsed || isMobile) && (
504
- <div className="mt-3 flex items-center justify-between">
505
- <span className="text-xs text-muted-foreground">Theme</span>
506
- <DropdownMenu>
507
- <DropdownMenuTrigger asChild>
508
- <Button variant="ghost" size="sm" className="h-7 px-2">
509
- {theme === 'light' && <Sun className="h-3 w-3" />}
510
- {theme === 'dark' && <Moon className="h-3 w-3" />}
511
- {theme === 'system' && <Monitor className="h-3 w-3" />}
512
- </Button>
513
- </DropdownMenuTrigger>
514
- <DropdownMenuContent align="end">
515
- <DropdownMenuItem onClick={() => onThemeChange?.('light')}>
516
- <Sun className="mr-2 h-4 w-4" />
517
- Light
518
- </DropdownMenuItem>
519
- <DropdownMenuItem onClick={() => onThemeChange?.('dark')}>
520
- <Moon className="mr-2 h-4 w-4" />
521
- Dark
522
- </DropdownMenuItem>
523
- <DropdownMenuItem onClick={() => onThemeChange?.('system')}>
524
- <Monitor className="mr-2 h-4 w-4" />
525
- System
526
- </DropdownMenuItem>
527
- </DropdownMenuContent>
528
- </DropdownMenu>
529
- </div>
530
- )}
557
+ )
558
+ })
559
+
560
+ // Footer Component
561
+ const SidebarFooter = React.memo(() => {
562
+ return footer ? (
563
+ <div className="border-t p-4">
564
+ <div className="space-y-1">
565
+ {footer.items.map(item => renderItem(item))}
531
566
  </div>
532
- )}
533
- </>
534
- ))
567
+
568
+ {showThemeToggle && (!collapsed || isMobile) && (
569
+ <div className="mt-3 flex items-center justify-between">
570
+ <span className="text-xs text-muted-foreground">Theme</span>
571
+ <DropdownMenu>
572
+ <DropdownMenuTrigger asChild>
573
+ <Button variant="ghost" size="sm" className="h-7 px-2">
574
+ {theme === 'light' && <Sun className="h-3 w-3" />}
575
+ {theme === 'dark' && <Moon className="h-3 w-3" />}
576
+ {theme === 'system' && <Monitor className="h-3 w-3" />}
577
+ </Button>
578
+ </DropdownMenuTrigger>
579
+ <DropdownMenuContent align="end">
580
+ <DropdownMenuItem onClick={() => onThemeChange?.('light')}>
581
+ <Sun className="mr-2 h-4 w-4" />
582
+ Light
583
+ </DropdownMenuItem>
584
+ <DropdownMenuItem onClick={() => onThemeChange?.('dark')}>
585
+ <Moon className="mr-2 h-4 w-4" />
586
+ Dark
587
+ </DropdownMenuItem>
588
+ <DropdownMenuItem onClick={() => onThemeChange?.('system')}>
589
+ <Monitor className="mr-2 h-4 w-4" />
590
+ System
591
+ </DropdownMenuItem>
592
+ </DropdownMenuContent>
593
+ </DropdownMenu>
594
+ </div>
595
+ )}
596
+ </div>
597
+ ) : null
598
+ })
599
+
600
+ const SidebarContent = React.memo(() => {
601
+ return (
602
+ <>
603
+ <SidebarHeader />
604
+ <SidebarSearch />
605
+ <SidebarMenuContent />
606
+ <SidebarFooter />
607
+ </>
608
+ )
609
+ })
535
610
 
536
611
  const sidebarClasses = cn(
537
612
  "flex h-full flex-col bg-background border-r",
@@ -558,7 +633,7 @@ export function Sidebar({
558
633
 
559
634
  <Sheet open={isOpen} onOpenChange={setIsOpen}>
560
635
  <SheetContent side="left" className="w-80 p-0">
561
- <div className={cn(sidebarClasses, "sidebar-container")}>
636
+ <div className={cn(sidebarClasses, "sidebar-container flex flex-col")}>
562
637
  {animatedBackground && (
563
638
  <motion.div
564
639
  className="absolute inset-0 opacity-30"
@@ -576,7 +651,7 @@ export function Sidebar({
576
651
  }
577
652
 
578
653
  return (
579
- <aside className={cn(sidebarClasses, "sidebar-container hidden md:flex")}>
654
+ <aside className={cn(sidebarClasses, "sidebar-container hidden md:flex flex-col")}>
580
655
  {animatedBackground && (
581
656
  <motion.div
582
657
  className="absolute inset-0 opacity-30"