@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 +2 -1
- package/dist/index.mjs +106 -51
- package/package.json +1 -1
- package/src/components/sidebar/index.tsx +170 -95
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 [
|
|
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,
|
|
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,
|
|
56496
|
+
filteredItems: filterItems(section.items, currentSearchQuery).map((item) => ({
|
|
56448
56497
|
...item,
|
|
56449
|
-
filteredChildren: item.items ? filterItems(item.items,
|
|
56498
|
+
filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : void 0
|
|
56450
56499
|
}))
|
|
56451
56500
|
})).filter((section) => section.filteredItems.length > 0);
|
|
56452
|
-
}, [sections,
|
|
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
|
|
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
|
-
|
|
56562
|
-
|
|
56563
|
-
|
|
56564
|
-
|
|
56565
|
-
|
|
56566
|
-
|
|
56567
|
-
|
|
56568
|
-
|
|
56569
|
-
|
|
56570
|
-
|
|
56571
|
-
|
|
56572
|
-
|
|
56573
|
-
|
|
56574
|
-
|
|
56575
|
-
|
|
56576
|
-
|
|
56577
|
-
|
|
56578
|
-
|
|
56579
|
-
|
|
56580
|
-
|
|
56581
|
-
),
|
|
56582
|
-
|
|
56583
|
-
/* @__PURE__ */ jsx("
|
|
56584
|
-
|
|
56585
|
-
|
|
56586
|
-
|
|
56587
|
-
|
|
56588
|
-
/* @__PURE__ */
|
|
56589
|
-
|
|
56590
|
-
(
|
|
56591
|
-
|
|
56592
|
-
|
|
56593
|
-
|
|
56594
|
-
|
|
56595
|
-
|
|
56596
|
-
|
|
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.
|
|
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 [
|
|
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,
|
|
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,
|
|
342
|
+
filteredItems: filterItems(section.items, currentSearchQuery).map(item => ({
|
|
278
343
|
...item,
|
|
279
|
-
filteredChildren: item.items ? filterItems(item.items,
|
|
344
|
+
filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : undefined
|
|
280
345
|
}))
|
|
281
346
|
})).filter(section => section.filteredItems.length > 0)
|
|
282
|
-
}, [sections,
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
{
|
|
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"
|