@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 +2 -1
- package/dist/index.mjs +104 -50
- package/package.json +1 -1
- package/src/components/sidebar/index.tsx +167 -93
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,8 +56464,10 @@ function Sidebar({
|
|
|
56430
56464
|
}
|
|
56431
56465
|
}
|
|
56432
56466
|
}, [onNavigate, isMobile]);
|
|
56433
|
-
|
|
56434
|
-
|
|
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,
|
|
56496
|
+
filteredItems: filterItems(section.items, currentSearchQuery).map((item) => ({
|
|
56452
56497
|
...item,
|
|
56453
|
-
filteredChildren: item.items ? filterItems(item.items,
|
|
56498
|
+
filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : void 0
|
|
56454
56499
|
}))
|
|
56455
56500
|
})).filter((section) => section.filteredItems.length > 0);
|
|
56456
|
-
}, [sections,
|
|
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
|
|
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
|
-
|
|
56566
|
-
|
|
56567
|
-
|
|
56568
|
-
|
|
56569
|
-
|
|
56570
|
-
|
|
56571
|
-
|
|
56572
|
-
|
|
56573
|
-
|
|
56574
|
-
|
|
56575
|
-
|
|
56576
|
-
|
|
56577
|
-
|
|
56578
|
-
|
|
56579
|
-
|
|
56580
|
-
|
|
56581
|
-
|
|
56582
|
-
|
|
56583
|
-
|
|
56584
|
-
|
|
56585
|
-
|
|
56586
|
-
|
|
56587
|
-
|
|
56588
|
-
|
|
56589
|
-
|
|
56590
|
-
|
|
56591
|
-
|
|
56592
|
-
|
|
56593
|
-
|
|
56594
|
-
|
|
56595
|
-
|
|
56596
|
-
|
|
56597
|
-
|
|
56598
|
-
|
|
56599
|
-
|
|
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.
|
|
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 [
|
|
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,
|
|
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
|
-
|
|
261
|
-
|
|
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,
|
|
342
|
+
filteredItems: filterItems(section.items, currentSearchQuery).map(item => ({
|
|
282
343
|
...item,
|
|
283
|
-
filteredChildren: item.items ? filterItems(item.items,
|
|
344
|
+
filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : undefined
|
|
284
345
|
}))
|
|
285
346
|
})).filter(section => section.filteredItems.length > 0)
|
|
286
|
-
}, [sections,
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
-
)}
|
|
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"
|