@moontra/moonui-pro 2.5.8 → 2.5.10
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 +2 -1
- package/dist/index.mjs +100 -50
- package/package.json +1 -1
- package/src/components/sidebar/index.tsx +161 -92
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,43 @@ 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 = ({
|
|
56310
|
+
searchInputRef,
|
|
56311
|
+
searchPlaceholder,
|
|
56312
|
+
value,
|
|
56313
|
+
onChange,
|
|
56314
|
+
keyboardShortcuts
|
|
56315
|
+
}) => {
|
|
56316
|
+
return /* @__PURE__ */ jsx("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
56317
|
+
/* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
|
|
56318
|
+
/* @__PURE__ */ jsx(
|
|
56319
|
+
"input",
|
|
56320
|
+
{
|
|
56321
|
+
ref: searchInputRef,
|
|
56322
|
+
type: "search",
|
|
56323
|
+
placeholder: searchPlaceholder,
|
|
56324
|
+
value,
|
|
56325
|
+
onChange: (e) => onChange(e.target.value),
|
|
56326
|
+
autoComplete: "off",
|
|
56327
|
+
autoCorrect: "off",
|
|
56328
|
+
autoCapitalize: "off",
|
|
56329
|
+
spellCheck: "false",
|
|
56330
|
+
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"
|
|
56331
|
+
},
|
|
56332
|
+
"sidebar-search-input"
|
|
56333
|
+
),
|
|
56334
|
+
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: [
|
|
56335
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs", children: "\u2318" }),
|
|
56336
|
+
"K"
|
|
56337
|
+
] })
|
|
56338
|
+
] }) });
|
|
56339
|
+
};
|
|
56309
56340
|
function Sidebar({
|
|
56310
56341
|
sections,
|
|
56311
56342
|
footer,
|
|
56312
56343
|
showSearch = true,
|
|
56313
56344
|
searchPlaceholder = "Search...",
|
|
56345
|
+
searchQuery = "",
|
|
56314
56346
|
onSearchChange,
|
|
56315
56347
|
showThemeToggle = false,
|
|
56316
56348
|
theme = "system",
|
|
@@ -56334,7 +56366,7 @@ function Sidebar({
|
|
|
56334
56366
|
const [isOpen, setIsOpen] = useState(false);
|
|
56335
56367
|
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
56336
56368
|
const [expandedSections, setExpandedSections] = useState([]);
|
|
56337
|
-
const [
|
|
56369
|
+
const [currentSearchQuery, setCurrentSearchQuery] = useState(searchQuery);
|
|
56338
56370
|
const [pinnedItems, setPinnedItems] = useState([]);
|
|
56339
56371
|
const searchInputRef = useRef(null);
|
|
56340
56372
|
const mouseX = useMotionValue(0);
|
|
@@ -56357,14 +56389,14 @@ function Sidebar({
|
|
|
56357
56389
|
const state = {
|
|
56358
56390
|
collapsed,
|
|
56359
56391
|
expandedSections,
|
|
56360
|
-
searchQuery,
|
|
56392
|
+
searchQuery: currentSearchQuery,
|
|
56361
56393
|
pinnedItems,
|
|
56362
56394
|
recentItems: []
|
|
56363
56395
|
};
|
|
56364
56396
|
localStorage.setItem(persistKey, JSON.stringify(state));
|
|
56365
56397
|
onStateChange?.(state);
|
|
56366
56398
|
}
|
|
56367
|
-
}, [collapsed, expandedSections,
|
|
56399
|
+
}, [collapsed, expandedSections, currentSearchQuery, pinnedItems, persistState, persistKey, onStateChange]);
|
|
56368
56400
|
useEffect(() => {
|
|
56369
56401
|
const checkMobile = () => {
|
|
56370
56402
|
setIsMobile(window.innerWidth < 768);
|
|
@@ -56430,8 +56462,10 @@ function Sidebar({
|
|
|
56430
56462
|
}
|
|
56431
56463
|
}
|
|
56432
56464
|
}, [onNavigate, isMobile]);
|
|
56433
|
-
|
|
56434
|
-
|
|
56465
|
+
useEffect(() => {
|
|
56466
|
+
setCurrentSearchQuery(searchQuery);
|
|
56467
|
+
}, [searchQuery]);
|
|
56468
|
+
const handleSearch = useCallback((value) => {
|
|
56435
56469
|
onSearchChange?.(value);
|
|
56436
56470
|
}, [onSearchChange]);
|
|
56437
56471
|
const filterItems = useCallback((items, query) => {
|
|
@@ -56446,14 +56480,23 @@ function Sidebar({
|
|
|
56446
56480
|
});
|
|
56447
56481
|
}, []);
|
|
56448
56482
|
const filteredSections = useMemo(() => {
|
|
56483
|
+
if (!currentSearchQuery) {
|
|
56484
|
+
return sections.map((section) => ({
|
|
56485
|
+
...section,
|
|
56486
|
+
filteredItems: section.items.map((item) => ({
|
|
56487
|
+
...item,
|
|
56488
|
+
filteredChildren: item.items
|
|
56489
|
+
}))
|
|
56490
|
+
}));
|
|
56491
|
+
}
|
|
56449
56492
|
return sections.map((section) => ({
|
|
56450
56493
|
...section,
|
|
56451
|
-
filteredItems: filterItems(section.items,
|
|
56494
|
+
filteredItems: filterItems(section.items, currentSearchQuery).map((item) => ({
|
|
56452
56495
|
...item,
|
|
56453
|
-
filteredChildren: item.items ? filterItems(item.items,
|
|
56496
|
+
filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : void 0
|
|
56454
56497
|
}))
|
|
56455
56498
|
})).filter((section) => section.filteredItems.length > 0);
|
|
56456
|
-
}, [sections,
|
|
56499
|
+
}, [sections, currentSearchQuery, filterItems]);
|
|
56457
56500
|
const renderItem = useCallback((item, depth = 0, filteredChildren) => {
|
|
56458
56501
|
const isActive2 = item.href === activePath;
|
|
56459
56502
|
const isPinned = pinnedItems.includes(item.id);
|
|
@@ -56529,8 +56572,9 @@ function Sidebar({
|
|
|
56529
56572
|
) })
|
|
56530
56573
|
] }, item.id);
|
|
56531
56574
|
}, [activePath, pinnedItems, expandedSections, collapsed, customStyles, handleItemClick, toggleSection]);
|
|
56532
|
-
const
|
|
56533
|
-
|
|
56575
|
+
const MemoizedSearchInput = t__default.memo(SearchInput);
|
|
56576
|
+
const SidebarHeader = t__default.memo(() => {
|
|
56577
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
56534
56578
|
"flex items-center gap-3 p-4 border-b",
|
|
56535
56579
|
collapsed && !isMobile && "justify-center px-2"
|
|
56536
56580
|
), children: [
|
|
@@ -56561,42 +56605,40 @@ function Sidebar({
|
|
|
56561
56605
|
children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
56562
56606
|
}
|
|
56563
56607
|
)
|
|
56564
|
-
] })
|
|
56565
|
-
|
|
56566
|
-
|
|
56567
|
-
|
|
56568
|
-
|
|
56569
|
-
|
|
56570
|
-
|
|
56571
|
-
|
|
56572
|
-
|
|
56573
|
-
|
|
56574
|
-
|
|
56575
|
-
|
|
56576
|
-
|
|
56577
|
-
|
|
56578
|
-
|
|
56579
|
-
|
|
56580
|
-
|
|
56581
|
-
|
|
56582
|
-
),
|
|
56583
|
-
|
|
56584
|
-
/* @__PURE__ */ jsx("
|
|
56585
|
-
|
|
56586
|
-
|
|
56587
|
-
|
|
56588
|
-
|
|
56589
|
-
/* @__PURE__ */
|
|
56590
|
-
|
|
56591
|
-
(
|
|
56592
|
-
|
|
56593
|
-
|
|
56594
|
-
|
|
56595
|
-
|
|
56596
|
-
|
|
56597
|
-
|
|
56598
|
-
] }, section.id)) }) }),
|
|
56599
|
-
footer && /* @__PURE__ */ jsxs("div", { className: "border-t p-4", children: [
|
|
56608
|
+
] });
|
|
56609
|
+
});
|
|
56610
|
+
const SidebarSearch = t__default.memo(() => {
|
|
56611
|
+
if (!showSearch || collapsed && !isMobile)
|
|
56612
|
+
return null;
|
|
56613
|
+
return /* @__PURE__ */ jsx(
|
|
56614
|
+
MemoizedSearchInput,
|
|
56615
|
+
{
|
|
56616
|
+
searchInputRef,
|
|
56617
|
+
searchPlaceholder,
|
|
56618
|
+
value: searchQuery,
|
|
56619
|
+
onChange: handleSearch,
|
|
56620
|
+
keyboardShortcuts
|
|
56621
|
+
}
|
|
56622
|
+
);
|
|
56623
|
+
});
|
|
56624
|
+
const SidebarMenuContent = t__default.memo(() => {
|
|
56625
|
+
return /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-6", children: [
|
|
56626
|
+
pinnedItems.length > 0 && (!collapsed || isMobile) && /* @__PURE__ */ jsxs("div", { children: [
|
|
56627
|
+
/* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: "Pinned" }),
|
|
56628
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: sections.flatMap(
|
|
56629
|
+
(section) => section.items.filter((item) => pinnedItems.includes(item.id))
|
|
56630
|
+
).map((item) => renderItem(item)) }),
|
|
56631
|
+
/* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "mt-4" })
|
|
56632
|
+
] }),
|
|
56633
|
+
filteredSections.map((section, index) => /* @__PURE__ */ jsxs("div", { children: [
|
|
56634
|
+
section.title && (!collapsed || isMobile) && /* @__PURE__ */ jsx("h4", { className: "text-xs font-medium text-muted-foreground mb-2", children: section.title }),
|
|
56635
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: section.filteredItems.map((item) => renderItem(item, 0, item.filteredChildren)) }),
|
|
56636
|
+
section.showDivider && index < filteredSections.length - 1 && /* @__PURE__ */ jsx(MoonUISeparatorPro, { className: "mt-4" })
|
|
56637
|
+
] }, section.id))
|
|
56638
|
+
] }) });
|
|
56639
|
+
});
|
|
56640
|
+
const SidebarFooter = t__default.memo(() => {
|
|
56641
|
+
return footer ? /* @__PURE__ */ jsxs("div", { className: "border-t p-4", children: [
|
|
56600
56642
|
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: footer.items.map((item) => renderItem(item)) }),
|
|
56601
56643
|
showThemeToggle && (!collapsed || isMobile) && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between", children: [
|
|
56602
56644
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Theme" }),
|
|
@@ -56622,8 +56664,16 @@ function Sidebar({
|
|
|
56622
56664
|
] })
|
|
56623
56665
|
] })
|
|
56624
56666
|
] })
|
|
56625
|
-
] })
|
|
56626
|
-
|
|
56667
|
+
] }) : null;
|
|
56668
|
+
});
|
|
56669
|
+
const SidebarContent = t__default.memo(() => {
|
|
56670
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56671
|
+
/* @__PURE__ */ jsx(SidebarHeader, {}),
|
|
56672
|
+
/* @__PURE__ */ jsx(SidebarSearch, {}),
|
|
56673
|
+
/* @__PURE__ */ jsx(SidebarMenuContent, {}),
|
|
56674
|
+
/* @__PURE__ */ jsx(SidebarFooter, {})
|
|
56675
|
+
] });
|
|
56676
|
+
});
|
|
56627
56677
|
const sidebarClasses = cn(
|
|
56628
56678
|
"flex h-full flex-col bg-background border-r",
|
|
56629
56679
|
glassmorphism && "bg-background/80 backdrop-blur-xl border-white/10",
|
|
@@ -56644,7 +56694,7 @@ function Sidebar({
|
|
|
56644
56694
|
children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
|
|
56645
56695
|
}
|
|
56646
56696
|
),
|
|
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: [
|
|
56697
|
+
/* @__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
56698
|
animatedBackground && /* @__PURE__ */ jsx(
|
|
56649
56699
|
motion.div,
|
|
56650
56700
|
{
|
|
@@ -56658,7 +56708,7 @@ function Sidebar({
|
|
|
56658
56708
|
] }) }) })
|
|
56659
56709
|
] });
|
|
56660
56710
|
}
|
|
56661
|
-
return /* @__PURE__ */ jsxs("aside", { className: cn(sidebarClasses, "sidebar-container hidden md:flex"), children: [
|
|
56711
|
+
return /* @__PURE__ */ jsxs("aside", { className: cn(sidebarClasses, "sidebar-container hidden md:flex flex-col"), children: [
|
|
56662
56712
|
animatedBackground && /* @__PURE__ */ jsx(
|
|
56663
56713
|
motion.div,
|
|
56664
56714
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.10",
|
|
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,47 @@ import {
|
|
|
36
36
|
DropdownMenuTrigger,
|
|
37
37
|
} from '../ui/dropdown-menu'
|
|
38
38
|
|
|
39
|
+
// Search Input Component - Pure functional to prevent recreation
|
|
40
|
+
const SearchInput = ({
|
|
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
|
+
return (
|
|
54
|
+
<div className="p-4 border-b">
|
|
55
|
+
<div className="relative">
|
|
56
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
57
|
+
<input
|
|
58
|
+
key="sidebar-search-input"
|
|
59
|
+
ref={searchInputRef}
|
|
60
|
+
type="search"
|
|
61
|
+
placeholder={searchPlaceholder}
|
|
62
|
+
value={value}
|
|
63
|
+
onChange={(e) => onChange(e.target.value)}
|
|
64
|
+
autoComplete="off"
|
|
65
|
+
autoCorrect="off"
|
|
66
|
+
autoCapitalize="off"
|
|
67
|
+
spellCheck="false"
|
|
68
|
+
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"
|
|
69
|
+
/>
|
|
70
|
+
{keyboardShortcuts && (
|
|
71
|
+
<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">
|
|
72
|
+
<span className="text-xs">⌘</span>K
|
|
73
|
+
</kbd>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
39
80
|
export interface SidebarItem {
|
|
40
81
|
id: string
|
|
41
82
|
title: string
|
|
@@ -64,6 +105,7 @@ export interface SidebarConfig {
|
|
|
64
105
|
footer?: SidebarSection
|
|
65
106
|
showSearch?: boolean
|
|
66
107
|
searchPlaceholder?: string
|
|
108
|
+
searchQuery?: string
|
|
67
109
|
onSearchChange?: (value: string) => void
|
|
68
110
|
showThemeToggle?: boolean
|
|
69
111
|
theme?: 'light' | 'dark' | 'system'
|
|
@@ -110,6 +152,7 @@ export function Sidebar({
|
|
|
110
152
|
footer,
|
|
111
153
|
showSearch = true,
|
|
112
154
|
searchPlaceholder = "Search...",
|
|
155
|
+
searchQuery = '',
|
|
113
156
|
onSearchChange,
|
|
114
157
|
showThemeToggle = false,
|
|
115
158
|
theme = 'system',
|
|
@@ -133,7 +176,7 @@ export function Sidebar({
|
|
|
133
176
|
const [isOpen, setIsOpen] = useState(false)
|
|
134
177
|
const [collapsed, setCollapsed] = useState(defaultCollapsed)
|
|
135
178
|
const [expandedSections, setExpandedSections] = useState<string[]>([])
|
|
136
|
-
const [
|
|
179
|
+
const [currentSearchQuery, setCurrentSearchQuery] = useState(searchQuery)
|
|
137
180
|
const [pinnedItems, setPinnedItems] = useState<string[]>([])
|
|
138
181
|
const searchInputRef = useRef<HTMLInputElement>(null)
|
|
139
182
|
const mouseX = useMotionValue(0)
|
|
@@ -160,14 +203,14 @@ export function Sidebar({
|
|
|
160
203
|
const state: SidebarState = {
|
|
161
204
|
collapsed,
|
|
162
205
|
expandedSections,
|
|
163
|
-
searchQuery,
|
|
206
|
+
searchQuery: currentSearchQuery,
|
|
164
207
|
pinnedItems,
|
|
165
208
|
recentItems: []
|
|
166
209
|
}
|
|
167
210
|
localStorage.setItem(persistKey, JSON.stringify(state))
|
|
168
211
|
onStateChange?.(state)
|
|
169
212
|
}
|
|
170
|
-
}, [collapsed, expandedSections,
|
|
213
|
+
}, [collapsed, expandedSections, currentSearchQuery, pinnedItems, persistState, persistKey, onStateChange])
|
|
171
214
|
|
|
172
215
|
// Check mobile
|
|
173
216
|
useEffect(() => {
|
|
@@ -257,8 +300,13 @@ export function Sidebar({
|
|
|
257
300
|
}
|
|
258
301
|
}, [onNavigate, isMobile])
|
|
259
302
|
|
|
260
|
-
|
|
261
|
-
|
|
303
|
+
// Update search query from prop
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
setCurrentSearchQuery(searchQuery)
|
|
306
|
+
}, [searchQuery])
|
|
307
|
+
|
|
308
|
+
// Handle search change
|
|
309
|
+
const handleSearch = useCallback((value: string) => {
|
|
262
310
|
onSearchChange?.(value)
|
|
263
311
|
}, [onSearchChange])
|
|
264
312
|
|
|
@@ -276,14 +324,24 @@ export function Sidebar({
|
|
|
276
324
|
|
|
277
325
|
// Filtered sections memoized to prevent re-renders
|
|
278
326
|
const filteredSections = useMemo(() => {
|
|
327
|
+
if (!currentSearchQuery) {
|
|
328
|
+
return sections.map(section => ({
|
|
329
|
+
...section,
|
|
330
|
+
filteredItems: section.items.map(item => ({
|
|
331
|
+
...item,
|
|
332
|
+
filteredChildren: item.items
|
|
333
|
+
}))
|
|
334
|
+
}))
|
|
335
|
+
}
|
|
336
|
+
|
|
279
337
|
return sections.map(section => ({
|
|
280
338
|
...section,
|
|
281
|
-
filteredItems: filterItems(section.items,
|
|
339
|
+
filteredItems: filterItems(section.items, currentSearchQuery).map(item => ({
|
|
282
340
|
...item,
|
|
283
|
-
filteredChildren: item.items ? filterItems(item.items,
|
|
341
|
+
filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : undefined
|
|
284
342
|
}))
|
|
285
343
|
})).filter(section => section.filteredItems.length > 0)
|
|
286
|
-
}, [sections,
|
|
344
|
+
}, [sections, currentSearchQuery, filterItems])
|
|
287
345
|
|
|
288
346
|
const renderItem = useCallback((item: SidebarItem, depth = 0, filteredChildren?: SidebarItem[]) => {
|
|
289
347
|
const isActive = item.href === activePath
|
|
@@ -392,9 +450,13 @@ export function Sidebar({
|
|
|
392
450
|
)
|
|
393
451
|
}, [activePath, pinnedItems, expandedSections, collapsed, customStyles, handleItemClick, toggleSection])
|
|
394
452
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
453
|
+
|
|
454
|
+
// Memoize SearchInput to prevent recreation
|
|
455
|
+
const MemoizedSearchInput = React.memo(SearchInput)
|
|
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,43 @@ export function Sidebar({
|
|
|
433
495
|
</Button>
|
|
434
496
|
)}
|
|
435
497
|
</div>
|
|
498
|
+
)
|
|
499
|
+
})
|
|
436
500
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
)}
|
|
501
|
+
// Search Component - Use memoized version
|
|
502
|
+
const SidebarSearch = React.memo(() => {
|
|
503
|
+
if (!showSearch || (collapsed && !isMobile)) return null
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<MemoizedSearchInput
|
|
507
|
+
searchInputRef={searchInputRef}
|
|
508
|
+
searchPlaceholder={searchPlaceholder}
|
|
509
|
+
value={searchQuery}
|
|
510
|
+
onChange={handleSearch}
|
|
511
|
+
keyboardShortcuts={keyboardShortcuts}
|
|
512
|
+
/>
|
|
513
|
+
)
|
|
514
|
+
})
|
|
475
515
|
|
|
476
|
-
|
|
516
|
+
// Menu Content
|
|
517
|
+
const SidebarMenuContent = React.memo(() => {
|
|
518
|
+
return (
|
|
477
519
|
<ScrollArea className="flex-1 overflow-y-auto">
|
|
478
520
|
<div className="p-4 space-y-6">
|
|
521
|
+
{/* Pinned Items */}
|
|
522
|
+
{pinnedItems.length > 0 && (!collapsed || isMobile) && (
|
|
523
|
+
<div>
|
|
524
|
+
<h4 className="text-xs font-medium text-muted-foreground mb-2">Pinned</h4>
|
|
525
|
+
<div className="space-y-1">
|
|
526
|
+
{sections.flatMap(section =>
|
|
527
|
+
section.items.filter(item => pinnedItems.includes(item.id))
|
|
528
|
+
).map(item => renderItem(item))}
|
|
529
|
+
</div>
|
|
530
|
+
<Separator className="mt-4" />
|
|
531
|
+
</div>
|
|
532
|
+
)}
|
|
533
|
+
|
|
534
|
+
{/* Main Menu Items */}
|
|
479
535
|
{filteredSections.map((section, index) => (
|
|
480
536
|
<div key={section.id}>
|
|
481
537
|
{section.title && (!collapsed || isMobile) && (
|
|
@@ -493,46 +549,59 @@ export function Sidebar({
|
|
|
493
549
|
))}
|
|
494
550
|
</div>
|
|
495
551
|
</ScrollArea>
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
{
|
|
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
|
-
)}
|
|
552
|
+
)
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
// Footer Component
|
|
556
|
+
const SidebarFooter = React.memo(() => {
|
|
557
|
+
return footer ? (
|
|
558
|
+
<div className="border-t p-4">
|
|
559
|
+
<div className="space-y-1">
|
|
560
|
+
{footer.items.map(item => renderItem(item))}
|
|
532
561
|
</div>
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
562
|
+
|
|
563
|
+
{showThemeToggle && (!collapsed || isMobile) && (
|
|
564
|
+
<div className="mt-3 flex items-center justify-between">
|
|
565
|
+
<span className="text-xs text-muted-foreground">Theme</span>
|
|
566
|
+
<DropdownMenu>
|
|
567
|
+
<DropdownMenuTrigger asChild>
|
|
568
|
+
<Button variant="ghost" size="sm" className="h-7 px-2">
|
|
569
|
+
{theme === 'light' && <Sun className="h-3 w-3" />}
|
|
570
|
+
{theme === 'dark' && <Moon className="h-3 w-3" />}
|
|
571
|
+
{theme === 'system' && <Monitor className="h-3 w-3" />}
|
|
572
|
+
</Button>
|
|
573
|
+
</DropdownMenuTrigger>
|
|
574
|
+
<DropdownMenuContent align="end">
|
|
575
|
+
<DropdownMenuItem onClick={() => onThemeChange?.('light')}>
|
|
576
|
+
<Sun className="mr-2 h-4 w-4" />
|
|
577
|
+
Light
|
|
578
|
+
</DropdownMenuItem>
|
|
579
|
+
<DropdownMenuItem onClick={() => onThemeChange?.('dark')}>
|
|
580
|
+
<Moon className="mr-2 h-4 w-4" />
|
|
581
|
+
Dark
|
|
582
|
+
</DropdownMenuItem>
|
|
583
|
+
<DropdownMenuItem onClick={() => onThemeChange?.('system')}>
|
|
584
|
+
<Monitor className="mr-2 h-4 w-4" />
|
|
585
|
+
System
|
|
586
|
+
</DropdownMenuItem>
|
|
587
|
+
</DropdownMenuContent>
|
|
588
|
+
</DropdownMenu>
|
|
589
|
+
</div>
|
|
590
|
+
)}
|
|
591
|
+
</div>
|
|
592
|
+
) : null
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
const SidebarContent = React.memo(() => {
|
|
596
|
+
return (
|
|
597
|
+
<>
|
|
598
|
+
<SidebarHeader />
|
|
599
|
+
<SidebarSearch />
|
|
600
|
+
<SidebarMenuContent />
|
|
601
|
+
<SidebarFooter />
|
|
602
|
+
</>
|
|
603
|
+
)
|
|
604
|
+
})
|
|
536
605
|
|
|
537
606
|
const sidebarClasses = cn(
|
|
538
607
|
"flex h-full flex-col bg-background border-r",
|
|
@@ -559,7 +628,7 @@ export function Sidebar({
|
|
|
559
628
|
|
|
560
629
|
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
|
561
630
|
<SheetContent side="left" className="w-80 p-0">
|
|
562
|
-
<div className={cn(sidebarClasses, "sidebar-container")}>
|
|
631
|
+
<div className={cn(sidebarClasses, "sidebar-container flex flex-col")}>
|
|
563
632
|
{animatedBackground && (
|
|
564
633
|
<motion.div
|
|
565
634
|
className="absolute inset-0 opacity-30"
|
|
@@ -577,7 +646,7 @@ export function Sidebar({
|
|
|
577
646
|
}
|
|
578
647
|
|
|
579
648
|
return (
|
|
580
|
-
<aside className={cn(sidebarClasses, "sidebar-container hidden md:flex")}>
|
|
649
|
+
<aside className={cn(sidebarClasses, "sidebar-container hidden md:flex flex-col")}>
|
|
581
650
|
{animatedBackground && (
|
|
582
651
|
<motion.div
|
|
583
652
|
className="absolute inset-0 opacity-30"
|