@moontra/moonui-pro 2.5.8 → 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,8 +56464,10 @@ function Sidebar({
56430
56464
  }
56431
56465
  }
56432
56466
  }, [onNavigate, isMobile]);
56433
- const handleSearchChange = useCallback((value) => {
56434
- setSearchQuery(value);
56467
+ useEffect(() => {
56468
+ setCurrentSearchQuery(searchQuery);
56469
+ }, [searchQuery]);
56470
+ const handleSearch = useCallback((value) => {
56435
56471
  onSearchChange?.(value);
56436
56472
  }, [onSearchChange]);
56437
56473
  const filterItems = useCallback((items, query) => {
@@ -56446,14 +56482,23 @@ function Sidebar({
56446
56482
  });
56447
56483
  }, []);
56448
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
+ }
56449
56494
  return sections.map((section) => ({
56450
56495
  ...section,
56451
- filteredItems: filterItems(section.items, searchQuery).map((item) => ({
56496
+ filteredItems: filterItems(section.items, currentSearchQuery).map((item) => ({
56452
56497
  ...item,
56453
- filteredChildren: item.items ? filterItems(item.items, searchQuery) : void 0
56498
+ filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : void 0
56454
56499
  }))
56455
56500
  })).filter((section) => section.filteredItems.length > 0);
56456
- }, [sections, searchQuery, filterItems]);
56501
+ }, [sections, currentSearchQuery, filterItems]);
56457
56502
  const renderItem = useCallback((item, depth = 0, filteredChildren) => {
56458
56503
  const isActive2 = item.href === activePath;
56459
56504
  const isPinned = pinnedItems.includes(item.id);
@@ -56529,8 +56574,8 @@ function Sidebar({
56529
56574
  ) })
56530
56575
  ] }, item.id);
56531
56576
  }, [activePath, pinnedItems, expandedSections, collapsed, customStyles, handleItemClick, toggleSection]);
56532
- const SidebarContent = t__default.memo(() => /* @__PURE__ */ jsxs(Fragment, { children: [
56533
- /* @__PURE__ */ jsxs("div", { className: cn(
56577
+ const SidebarHeader = t__default.memo(() => {
56578
+ return /* @__PURE__ */ jsxs("div", { className: cn(
56534
56579
  "flex items-center gap-3 p-4 border-b",
56535
56580
  collapsed && !isMobile && "justify-center px-2"
56536
56581
  ), children: [
@@ -56561,42 +56606,43 @@ function Sidebar({
56561
56606
  children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
56562
56607
  }
56563
56608
  )
56564
- ] }),
56565
- showSearch && (!collapsed || isMobile) && /* @__PURE__ */ jsx("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
56566
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
56567
- /* @__PURE__ */ jsx(
56568
- "input",
56569
- {
56570
- ref: searchInputRef,
56571
- type: "search",
56572
- placeholder: searchPlaceholder,
56573
- value: searchQuery,
56574
- onChange: (e) => handleSearchChange(e.target.value),
56575
- autoComplete: "off",
56576
- autoCorrect: "off",
56577
- autoCapitalize: "off",
56578
- spellCheck: "false",
56579
- 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"
56580
- },
56581
- "sidebar-search-input"
56582
- ),
56583
- 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: [
56584
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: "\u2318" }),
56585
- "K"
56586
- ] })
56587
- ] }) }),
56588
- pinnedItems.length > 0 && (!collapsed || isMobile) && /* @__PURE__ */ jsxs("div", { className: "p-4 border-b", children: [
56589
- /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: "Pinned" }),
56590
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: sections.flatMap(
56591
- (section) => section.items.filter((item) => pinnedItems.includes(item.id))
56592
- ).map((item) => renderItem(item)) })
56593
- ] }),
56594
- /* @__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: [
56595
- section.title && (!collapsed || isMobile) && /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: section.title }),
56596
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: section.filteredItems.map((item) => renderItem(item, 0, item.filteredChildren)) }),
56597
- section.showDivider && index < filteredSections.length - 1 && /* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "mt-4" })
56598
- ] }, section.id)) }) }),
56599
- 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: [
56600
56646
  /* @__PURE__ */ jsx("div", { className: "space-y-1", children: footer.items.map((item) => renderItem(item)) }),
56601
56647
  showThemeToggle && (!collapsed || isMobile) && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between", children: [
56602
56648
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Theme" }),
@@ -56622,8 +56668,16 @@ function Sidebar({
56622
56668
  ] })
56623
56669
  ] })
56624
56670
  ] })
56625
- ] })
56626
- ] }));
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
+ });
56627
56681
  const sidebarClasses = cn(
56628
56682
  "flex h-full flex-col bg-background border-r",
56629
56683
  glassmorphism && "bg-background/80 backdrop-blur-xl border-white/10",
@@ -56644,7 +56698,7 @@ function Sidebar({
56644
56698
  children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
56645
56699
  }
56646
56700
  ),
56647
- /* @__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: [
56648
56702
  animatedBackground && /* @__PURE__ */ jsx(
56649
56703
  motion.div,
56650
56704
  {
@@ -56658,7 +56712,7 @@ function Sidebar({
56658
56712
  ] }) }) })
56659
56713
  ] });
56660
56714
  }
56661
- 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: [
56662
56716
  animatedBackground && /* @__PURE__ */ jsx(
56663
56717
  motion.div,
56664
56718
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.5.8",
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'
@@ -36,6 +36,50 @@ import {
36
36
  DropdownMenuTrigger,
37
37
  } from '../ui/dropdown-menu'
38
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
+
39
83
  export interface SidebarItem {
40
84
  id: string
41
85
  title: string
@@ -64,6 +108,7 @@ export interface SidebarConfig {
64
108
  footer?: SidebarSection
65
109
  showSearch?: boolean
66
110
  searchPlaceholder?: string
111
+ searchQuery?: string
67
112
  onSearchChange?: (value: string) => void
68
113
  showThemeToggle?: boolean
69
114
  theme?: 'light' | 'dark' | 'system'
@@ -110,6 +155,7 @@ export function Sidebar({
110
155
  footer,
111
156
  showSearch = true,
112
157
  searchPlaceholder = "Search...",
158
+ searchQuery = '',
113
159
  onSearchChange,
114
160
  showThemeToggle = false,
115
161
  theme = 'system',
@@ -133,7 +179,7 @@ export function Sidebar({
133
179
  const [isOpen, setIsOpen] = useState(false)
134
180
  const [collapsed, setCollapsed] = useState(defaultCollapsed)
135
181
  const [expandedSections, setExpandedSections] = useState<string[]>([])
136
- const [searchQuery, setSearchQuery] = useState('')
182
+ const [currentSearchQuery, setCurrentSearchQuery] = useState(searchQuery)
137
183
  const [pinnedItems, setPinnedItems] = useState<string[]>([])
138
184
  const searchInputRef = useRef<HTMLInputElement>(null)
139
185
  const mouseX = useMotionValue(0)
@@ -160,14 +206,14 @@ export function Sidebar({
160
206
  const state: SidebarState = {
161
207
  collapsed,
162
208
  expandedSections,
163
- searchQuery,
209
+ searchQuery: currentSearchQuery,
164
210
  pinnedItems,
165
211
  recentItems: []
166
212
  }
167
213
  localStorage.setItem(persistKey, JSON.stringify(state))
168
214
  onStateChange?.(state)
169
215
  }
170
- }, [collapsed, expandedSections, searchQuery, pinnedItems, persistState, persistKey, onStateChange])
216
+ }, [collapsed, expandedSections, currentSearchQuery, pinnedItems, persistState, persistKey, onStateChange])
171
217
 
172
218
  // Check mobile
173
219
  useEffect(() => {
@@ -257,8 +303,13 @@ export function Sidebar({
257
303
  }
258
304
  }, [onNavigate, isMobile])
259
305
 
260
- const handleSearchChange = useCallback((value: string) => {
261
- setSearchQuery(value)
306
+ // Update search query from prop
307
+ useEffect(() => {
308
+ setCurrentSearchQuery(searchQuery)
309
+ }, [searchQuery])
310
+
311
+ // Handle search change
312
+ const handleSearch = useCallback((value: string) => {
262
313
  onSearchChange?.(value)
263
314
  }, [onSearchChange])
264
315
 
@@ -276,14 +327,24 @@ export function Sidebar({
276
327
 
277
328
  // Filtered sections memoized to prevent re-renders
278
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
+
279
340
  return sections.map(section => ({
280
341
  ...section,
281
- filteredItems: filterItems(section.items, searchQuery).map(item => ({
342
+ filteredItems: filterItems(section.items, currentSearchQuery).map(item => ({
282
343
  ...item,
283
- filteredChildren: item.items ? filterItems(item.items, searchQuery) : undefined
344
+ filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : undefined
284
345
  }))
285
346
  })).filter(section => section.filteredItems.length > 0)
286
- }, [sections, searchQuery, filterItems])
347
+ }, [sections, currentSearchQuery, filterItems])
287
348
 
288
349
  const renderItem = useCallback((item: SidebarItem, depth = 0, filteredChildren?: SidebarItem[]) => {
289
350
  const isActive = item.href === activePath
@@ -392,9 +453,10 @@ export function Sidebar({
392
453
  )
393
454
  }, [activePath, pinnedItems, expandedSections, collapsed, customStyles, handleItemClick, toggleSection])
394
455
 
395
- const SidebarContent = React.memo(() => (
396
- <>
397
- {/* Header */}
456
+
457
+ // Header Component
458
+ const SidebarHeader = React.memo(() => {
459
+ return (
398
460
  <div className={cn(
399
461
  "flex items-center gap-3 p-4 border-b",
400
462
  collapsed && !isMobile && "justify-center px-2"
@@ -433,49 +495,48 @@ export function Sidebar({
433
495
  </Button>
434
496
  )}
435
497
  </div>
498
+ )
499
+ })
436
500
 
437
- {/* Search */}
438
- {showSearch && (!collapsed || isMobile) && (
439
- <div className="p-4 border-b">
440
- <div className="relative">
441
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
442
- <input
443
- key="sidebar-search-input"
444
- ref={searchInputRef}
445
- type="search"
446
- placeholder={searchPlaceholder}
447
- value={searchQuery}
448
- onChange={(e) => handleSearchChange(e.target.value)}
449
- autoComplete="off"
450
- autoCorrect="off"
451
- autoCapitalize="off"
452
- spellCheck="false"
453
- 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"
454
- />
455
- {keyboardShortcuts && (
456
- <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">
457
- <span className="text-xs">⌘</span>K
458
- </kbd>
459
- )}
460
- </div>
461
- </div>
462
- )}
463
-
464
- {/* Pinned Items */}
465
- {pinnedItems.length > 0 && (!collapsed || isMobile) && (
466
- <div className="p-4 border-b">
467
- <h4 className="text-xs font-medium text-muted-foreground mb-2">Pinned</h4>
468
- <div className="space-y-1">
469
- {sections.flatMap(section =>
470
- section.items.filter(item => pinnedItems.includes(item.id))
471
- ).map(item => renderItem(item))}
472
- </div>
473
- </div>
474
- )}
475
-
476
- {/* 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 (
477
524
  <ScrollArea className="flex-1 overflow-y-auto">
478
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 */}
479
540
  {filteredSections.map((section, index) => (
480
541
  <div key={section.id}>
481
542
  {section.title && (!collapsed || isMobile) && (
@@ -493,46 +554,59 @@ export function Sidebar({
493
554
  ))}
494
555
  </div>
495
556
  </ScrollArea>
496
-
497
- {/* Footer */}
498
- {footer && (
499
- <div className="border-t p-4">
500
- <div className="space-y-1">
501
- {footer.items.map(item => renderItem(item))}
502
- </div>
503
-
504
- {showThemeToggle && (!collapsed || isMobile) && (
505
- <div className="mt-3 flex items-center justify-between">
506
- <span className="text-xs text-muted-foreground">Theme</span>
507
- <DropdownMenu>
508
- <DropdownMenuTrigger asChild>
509
- <Button variant="ghost" size="sm" className="h-7 px-2">
510
- {theme === 'light' && <Sun className="h-3 w-3" />}
511
- {theme === 'dark' && <Moon className="h-3 w-3" />}
512
- {theme === 'system' && <Monitor className="h-3 w-3" />}
513
- </Button>
514
- </DropdownMenuTrigger>
515
- <DropdownMenuContent align="end">
516
- <DropdownMenuItem onClick={() => onThemeChange?.('light')}>
517
- <Sun className="mr-2 h-4 w-4" />
518
- Light
519
- </DropdownMenuItem>
520
- <DropdownMenuItem onClick={() => onThemeChange?.('dark')}>
521
- <Moon className="mr-2 h-4 w-4" />
522
- Dark
523
- </DropdownMenuItem>
524
- <DropdownMenuItem onClick={() => onThemeChange?.('system')}>
525
- <Monitor className="mr-2 h-4 w-4" />
526
- System
527
- </DropdownMenuItem>
528
- </DropdownMenuContent>
529
- </DropdownMenu>
530
- </div>
531
- )}
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))}
532
566
  </div>
533
- )}
534
- </>
535
- ))
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
+ })
536
610
 
537
611
  const sidebarClasses = cn(
538
612
  "flex h-full flex-col bg-background border-r",
@@ -559,7 +633,7 @@ export function Sidebar({
559
633
 
560
634
  <Sheet open={isOpen} onOpenChange={setIsOpen}>
561
635
  <SheetContent side="left" className="w-80 p-0">
562
- <div className={cn(sidebarClasses, "sidebar-container")}>
636
+ <div className={cn(sidebarClasses, "sidebar-container flex flex-col")}>
563
637
  {animatedBackground && (
564
638
  <motion.div
565
639
  className="absolute inset-0 opacity-30"
@@ -577,7 +651,7 @@ export function Sidebar({
577
651
  }
578
652
 
579
653
  return (
580
- <aside className={cn(sidebarClasses, "sidebar-container hidden md:flex")}>
654
+ <aside className={cn(sidebarClasses, "sidebar-container hidden md:flex flex-col")}>
581
655
  {animatedBackground && (
582
656
  <motion.div
583
657
  className="absolute inset-0 opacity-30"