@moontra/moonui-pro 2.20.2 → 2.20.4
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/package.json +8 -3
- package/plugin/index.d.ts +86 -0
- package/plugin/index.js +308 -0
- package/scripts/postinstall.js +191 -23
- package/src/components/advanced-chart/index.tsx +0 -1246
- package/src/components/advanced-forms/index.tsx +0 -585
- package/src/components/animated-button/index.tsx +0 -385
- package/src/components/calendar/event-dialog.tsx +0 -377
- package/src/components/calendar/index.tsx +0 -1220
- package/src/components/calendar-pro/index.tsx +0 -1697
- package/src/components/color-picker/index.tsx +0 -432
- package/src/components/credit-card-input/index.tsx +0 -406
- package/src/components/dashboard/dashboard-grid.tsx +0 -480
- package/src/components/dashboard/demo.tsx +0 -425
- package/src/components/dashboard/index.tsx +0 -1046
- package/src/components/dashboard/time-range-picker.tsx +0 -336
- package/src/components/dashboard/types.ts +0 -225
- package/src/components/dashboard/widgets/activity-feed.tsx +0 -349
- package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
- package/src/components/dashboard/widgets/comparison-widget.tsx +0 -177
- package/src/components/dashboard/widgets/index.ts +0 -5
- package/src/components/dashboard/widgets/metric-card.tsx +0 -363
- package/src/components/dashboard/widgets/progress-widget.tsx +0 -113
- package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
- package/src/components/data-table/data-table-column-toggle.tsx +0 -169
- package/src/components/data-table/data-table-export.ts +0 -156
- package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
- package/src/components/data-table/index.tsx +0 -845
- package/src/components/draggable-list/index.tsx +0 -100
- package/src/components/error-boundary/index.tsx +0 -232
- package/src/components/file-upload/index.tsx +0 -1660
- package/src/components/floating-action-button/index.tsx +0 -206
- package/src/components/form-wizard/form-wizard-context.tsx +0 -335
- package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
- package/src/components/form-wizard/form-wizard-progress.tsx +0 -329
- package/src/components/form-wizard/form-wizard-step.tsx +0 -111
- package/src/components/form-wizard/index.tsx +0 -102
- package/src/components/form-wizard/types.ts +0 -77
- package/src/components/gesture-drawer/index.tsx +0 -551
- package/src/components/github-stars/github-api.ts +0 -426
- package/src/components/github-stars/hooks.ts +0 -517
- package/src/components/github-stars/index.tsx +0 -375
- package/src/components/github-stars/types.ts +0 -148
- package/src/components/github-stars/variants.tsx +0 -515
- package/src/components/health-check/index.tsx +0 -439
- package/src/components/hover-card-3d/index.tsx +0 -529
- package/src/components/index.ts +0 -130
- package/src/components/internal/index.ts +0 -78
- package/src/components/kanban/add-card-modal.tsx +0 -502
- package/src/components/kanban/card-detail-modal.tsx +0 -761
- package/src/components/kanban/index.ts +0 -13
- package/src/components/kanban/kanban.tsx +0 -1689
- package/src/components/kanban/types.ts +0 -168
- package/src/components/lazy-component/index.tsx +0 -823
- package/src/components/license-error/index.tsx +0 -31
- package/src/components/magnetic-button/index.tsx +0 -216
- package/src/components/memory-efficient-data/index.tsx +0 -1018
- package/src/components/moonui-quiz-form/index.tsx +0 -817
- package/src/components/navbar/index.tsx +0 -781
- package/src/components/optimized-image/index.tsx +0 -425
- package/src/components/performance-debugger/index.tsx +0 -613
- package/src/components/performance-monitor/index.tsx +0 -808
- package/src/components/phone-number-input/index.tsx +0 -343
- package/src/components/phone-number-input/phone-number-input-simple.tsx +0 -167
- package/src/components/pinch-zoom/index.tsx +0 -566
- package/src/components/quiz-form/index.tsx +0 -479
- package/src/components/rich-text-editor/index.tsx +0 -2322
- package/src/components/rich-text-editor/slash-commands-extension.ts +0 -230
- package/src/components/rich-text-editor/slash-commands.css +0 -35
- package/src/components/rich-text-editor/table-styles.css +0 -65
- package/src/components/sidebar/index.tsx +0 -884
- package/src/components/spotlight-card/index.tsx +0 -191
- package/src/components/swipeable-card/index.tsx +0 -100
- package/src/components/timeline/index.tsx +0 -1183
- package/src/components/ui/accordion.tsx +0 -581
- package/src/components/ui/alert-dialog.tsx +0 -141
- package/src/components/ui/alert.tsx +0 -141
- package/src/components/ui/aspect-ratio.tsx +0 -245
- package/src/components/ui/avatar.tsx +0 -155
- package/src/components/ui/badge.tsx +0 -230
- package/src/components/ui/breadcrumb.tsx +0 -216
- package/src/components/ui/button.tsx +0 -228
- package/src/components/ui/calendar.tsx +0 -387
- package/src/components/ui/card.tsx +0 -216
- package/src/components/ui/checkbox.tsx +0 -259
- package/src/components/ui/collapsible.tsx +0 -631
- package/src/components/ui/color-picker.tsx +0 -97
- package/src/components/ui/command.tsx +0 -948
- package/src/components/ui/dialog.tsx +0 -752
- package/src/components/ui/dropdown-menu.tsx +0 -706
- package/src/components/ui/gesture-drawer.tsx +0 -11
- package/src/components/ui/hover-card.tsx +0 -29
- package/src/components/ui/index.ts +0 -222
- package/src/components/ui/input.tsx +0 -224
- package/src/components/ui/label.tsx +0 -29
- package/src/components/ui/lightbox.tsx +0 -606
- package/src/components/ui/magnetic-button.tsx +0 -129
- package/src/components/ui/media-gallery.tsx +0 -611
- package/src/components/ui/navigation-menu.tsx +0 -130
- package/src/components/ui/pagination.tsx +0 -125
- package/src/components/ui/popover.tsx +0 -185
- package/src/components/ui/progress.tsx +0 -30
- package/src/components/ui/radio-group.tsx +0 -257
- package/src/components/ui/scroll-area.tsx +0 -47
- package/src/components/ui/select.tsx +0 -378
- package/src/components/ui/separator.tsx +0 -145
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/skeleton.tsx +0 -20
- package/src/components/ui/slider.tsx +0 -354
- package/src/components/ui/spotlight-card.tsx +0 -119
- package/src/components/ui/switch.tsx +0 -86
- package/src/components/ui/table.tsx +0 -331
- package/src/components/ui/tabs-pro.tsx +0 -542
- package/src/components/ui/tabs.tsx +0 -54
- package/src/components/ui/textarea.tsx +0 -28
- package/src/components/ui/toast.tsx +0 -317
- package/src/components/ui/toggle.tsx +0 -119
- package/src/components/ui/tooltip.tsx +0 -151
- package/src/components/virtual-list/index.tsx +0 -668
- package/src/hooks/use-chart.ts +0 -205
- package/src/hooks/use-data-table.ts +0 -182
- package/src/hooks/use-docs-pro-access.ts +0 -13
- package/src/hooks/use-license-check.ts +0 -65
- package/src/hooks/use-subscription.ts +0 -19
- package/src/hooks/use-toast.ts +0 -15
- package/src/index.ts +0 -22
- package/src/lib/ai-providers.ts +0 -377
- package/src/lib/component-metadata.ts +0 -18
- package/src/lib/micro-interactions.ts +0 -255
- package/src/lib/paddle.ts +0 -17
- package/src/lib/utils.ts +0 -6
- package/src/patterns/login-form/index.tsx +0 -276
- package/src/patterns/login-form/types.ts +0 -67
- package/src/setupTests.ts +0 -41
- package/src/styles/advanced-chart.css +0 -239
- package/src/styles/calendar.css +0 -35
- package/src/styles/design-system.css +0 -363
- package/src/styles/index.css +0 -681
- package/src/styles/tailwind.css +0 -7
- package/src/styles/tokens.css +0 -455
- package/src/types/next-auth.d.ts +0 -21
- package/src/use-intersection-observer.tsx +0 -154
- package/src/use-local-storage.tsx +0 -71
- package/src/use-paddle.ts +0 -138
- package/src/use-performance-optimizer.ts +0 -389
- package/src/use-pro-access.ts +0 -141
- package/src/use-scroll-animation.ts +0 -219
- package/src/use-subscription.ts +0 -37
- package/src/use-toast.ts +0 -32
- package/src/utils/chart-helpers.ts +0 -357
- package/src/utils/cn.ts +0 -6
- package/src/utils/data-processing.ts +0 -151
- package/src/utils/license-validator.tsx +0 -183
|
@@ -1,884 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect, useCallback, useRef, useMemo, createContext, useContext } from 'react'
|
|
4
|
-
import { motion, AnimatePresence, useMotionValue, useSpring } from 'framer-motion'
|
|
5
|
-
import { cn } from '../../lib/utils'
|
|
6
|
-
import { Button } from '../ui/button'
|
|
7
|
-
import { ScrollArea } from '../ui/scroll-area'
|
|
8
|
-
import { Sheet, SheetContent, SheetTrigger } from '../ui/sheet'
|
|
9
|
-
import { Badge } from '../ui/badge'
|
|
10
|
-
import { Separator } from '../ui/separator'
|
|
11
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'
|
|
12
|
-
import { HoverCard, HoverCardContent, HoverCardTrigger } from '../ui/hover-card'
|
|
13
|
-
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
|
|
14
|
-
import {
|
|
15
|
-
Menu,
|
|
16
|
-
X,
|
|
17
|
-
ChevronRight,
|
|
18
|
-
ChevronLeft,
|
|
19
|
-
ChevronDown,
|
|
20
|
-
Home,
|
|
21
|
-
Search,
|
|
22
|
-
Settings,
|
|
23
|
-
HelpCircle,
|
|
24
|
-
Moon,
|
|
25
|
-
Sun,
|
|
26
|
-
Monitor,
|
|
27
|
-
MoreHorizontal,
|
|
28
|
-
Pin,
|
|
29
|
-
PinOff,
|
|
30
|
-
Sparkles,
|
|
31
|
-
Command
|
|
32
|
-
} from 'lucide-react'
|
|
33
|
-
import {
|
|
34
|
-
DropdownMenu,
|
|
35
|
-
DropdownMenuContent,
|
|
36
|
-
DropdownMenuItem,
|
|
37
|
-
DropdownMenuSeparator,
|
|
38
|
-
DropdownMenuTrigger,
|
|
39
|
-
} from '../ui/dropdown-menu'
|
|
40
|
-
|
|
41
|
-
// Search Input Component - with local state to prevent focus loss
|
|
42
|
-
const SearchInput = React.memo(({
|
|
43
|
-
searchInputRef,
|
|
44
|
-
searchPlaceholder,
|
|
45
|
-
initialValue,
|
|
46
|
-
onSearchChange,
|
|
47
|
-
keyboardShortcuts
|
|
48
|
-
}: {
|
|
49
|
-
searchInputRef: React.RefObject<HTMLInputElement | null>
|
|
50
|
-
searchPlaceholder: string
|
|
51
|
-
initialValue: string
|
|
52
|
-
onSearchChange: (value: string) => void
|
|
53
|
-
keyboardShortcuts: boolean
|
|
54
|
-
}) => {
|
|
55
|
-
// Local state to prevent focus loss
|
|
56
|
-
const [localValue, setLocalValue] = useState<string>(initialValue || '')
|
|
57
|
-
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
58
|
-
|
|
59
|
-
// Update local value when initial value changes from parent
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
setLocalValue(initialValue)
|
|
62
|
-
}, [initialValue])
|
|
63
|
-
|
|
64
|
-
// Handle input changes locally and debounce to parent
|
|
65
|
-
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
66
|
-
const newValue = e.target.value
|
|
67
|
-
setLocalValue(newValue)
|
|
68
|
-
|
|
69
|
-
// Clear previous timeout
|
|
70
|
-
if (timeoutRef.current) {
|
|
71
|
-
clearTimeout(timeoutRef.current)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Debounce the parent update with shorter delay
|
|
75
|
-
timeoutRef.current = setTimeout(() => {
|
|
76
|
-
onSearchChange(newValue)
|
|
77
|
-
// Keep focus on input after state update
|
|
78
|
-
if (searchInputRef.current) {
|
|
79
|
-
searchInputRef.current.focus()
|
|
80
|
-
}
|
|
81
|
-
}, 150)
|
|
82
|
-
}, [onSearchChange, searchInputRef])
|
|
83
|
-
|
|
84
|
-
// Cleanup timeout on unmount
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
return () => {
|
|
87
|
-
if (timeoutRef.current) {
|
|
88
|
-
clearTimeout(timeoutRef.current)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}, [])
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<div className="p-4 border-b">
|
|
95
|
-
<div className="relative">
|
|
96
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
|
97
|
-
<input
|
|
98
|
-
ref={searchInputRef}
|
|
99
|
-
type="text"
|
|
100
|
-
placeholder={searchPlaceholder}
|
|
101
|
-
value={localValue}
|
|
102
|
-
onChange={handleChange}
|
|
103
|
-
autoComplete="off"
|
|
104
|
-
autoCorrect="off"
|
|
105
|
-
autoCapitalize="off"
|
|
106
|
-
spellCheck="false"
|
|
107
|
-
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"
|
|
108
|
-
/>
|
|
109
|
-
{keyboardShortcuts && (
|
|
110
|
-
<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">
|
|
111
|
-
<span className="text-xs">⌘</span>K
|
|
112
|
-
</kbd>
|
|
113
|
-
)}
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
SearchInput.displayName = 'SearchInput'
|
|
120
|
-
|
|
121
|
-
export interface SidebarItem {
|
|
122
|
-
id: string
|
|
123
|
-
title: string
|
|
124
|
-
href?: string
|
|
125
|
-
icon?: React.ReactNode
|
|
126
|
-
badge?: string | number
|
|
127
|
-
badgeVariant?: 'secondary' | 'destructive' | 'outline'
|
|
128
|
-
disabled?: boolean
|
|
129
|
-
items?: SidebarItem[]
|
|
130
|
-
action?: () => void
|
|
131
|
-
tooltip?: string
|
|
132
|
-
filteredChildren?: SidebarItem[]
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface SidebarSection {
|
|
136
|
-
id: string
|
|
137
|
-
title?: string
|
|
138
|
-
items: SidebarItem[]
|
|
139
|
-
collapsible?: boolean
|
|
140
|
-
defaultExpanded?: boolean
|
|
141
|
-
showDivider?: boolean
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export interface SidebarConfig {
|
|
145
|
-
sections: SidebarSection[]
|
|
146
|
-
footer?: SidebarSection
|
|
147
|
-
showSearch?: boolean
|
|
148
|
-
searchPlaceholder?: string
|
|
149
|
-
searchQuery?: string
|
|
150
|
-
onSearchChange?: (value: string) => void
|
|
151
|
-
showThemeToggle?: boolean
|
|
152
|
-
theme?: 'light' | 'dark' | 'system'
|
|
153
|
-
onThemeChange?: (theme: 'light' | 'dark' | 'system') => void
|
|
154
|
-
branding?: {
|
|
155
|
-
logo?: React.ReactNode
|
|
156
|
-
title?: string
|
|
157
|
-
href?: string
|
|
158
|
-
}
|
|
159
|
-
collapsible?: boolean
|
|
160
|
-
defaultCollapsed?: boolean
|
|
161
|
-
floatingActionButton?: boolean
|
|
162
|
-
floatingActionButtonPosition?: {
|
|
163
|
-
bottom?: string
|
|
164
|
-
left?: string
|
|
165
|
-
right?: string
|
|
166
|
-
top?: string
|
|
167
|
-
}
|
|
168
|
-
floatingActionButtonClassName?: string
|
|
169
|
-
glassmorphism?: boolean
|
|
170
|
-
animatedBackground?: boolean
|
|
171
|
-
keyboardShortcuts?: boolean
|
|
172
|
-
persistState?: boolean
|
|
173
|
-
persistKey?: string
|
|
174
|
-
onStateChange?: (state: SidebarState) => void
|
|
175
|
-
customStyles?: {
|
|
176
|
-
background?: string
|
|
177
|
-
border?: string
|
|
178
|
-
text?: string
|
|
179
|
-
hover?: string
|
|
180
|
-
active?: string
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export interface SidebarState {
|
|
185
|
-
collapsed: boolean
|
|
186
|
-
expandedSections: string[]
|
|
187
|
-
searchQuery: string
|
|
188
|
-
pinnedItems: string[]
|
|
189
|
-
recentItems: string[]
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
interface SidebarProps extends SidebarConfig {
|
|
193
|
-
className?: string
|
|
194
|
-
activePath?: string
|
|
195
|
-
onNavigate?: (href: string) => void
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function Sidebar({
|
|
199
|
-
sections,
|
|
200
|
-
footer,
|
|
201
|
-
showSearch = true,
|
|
202
|
-
searchPlaceholder = "Search...",
|
|
203
|
-
searchQuery = '',
|
|
204
|
-
onSearchChange,
|
|
205
|
-
showThemeToggle = false,
|
|
206
|
-
theme = 'system',
|
|
207
|
-
onThemeChange,
|
|
208
|
-
branding,
|
|
209
|
-
collapsible = true,
|
|
210
|
-
defaultCollapsed = false,
|
|
211
|
-
floatingActionButton = true,
|
|
212
|
-
floatingActionButtonPosition = { bottom: '1rem', left: '1rem' },
|
|
213
|
-
floatingActionButtonClassName,
|
|
214
|
-
glassmorphism = false,
|
|
215
|
-
animatedBackground = false,
|
|
216
|
-
keyboardShortcuts = true,
|
|
217
|
-
persistState = true,
|
|
218
|
-
persistKey = 'moonui-sidebar-state',
|
|
219
|
-
onStateChange,
|
|
220
|
-
customStyles,
|
|
221
|
-
className,
|
|
222
|
-
activePath,
|
|
223
|
-
onNavigate
|
|
224
|
-
}: SidebarProps) {
|
|
225
|
-
const [isMobile, setIsMobile] = useState(false)
|
|
226
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
227
|
-
const [collapsed, setCollapsed] = useState(defaultCollapsed)
|
|
228
|
-
const [expandedSections, setExpandedSections] = useState<string[]>([])
|
|
229
|
-
const [currentSearchQuery, setCurrentSearchQuery] = useState(searchQuery)
|
|
230
|
-
const [pinnedItems, setPinnedItems] = useState<string[]>([])
|
|
231
|
-
const searchInputRef = useRef<HTMLInputElement | null>(null)
|
|
232
|
-
const mouseX = useMotionValue(0)
|
|
233
|
-
const mouseY = useMotionValue(0)
|
|
234
|
-
const springX = useSpring(mouseX, { stiffness: 300, damping: 30 })
|
|
235
|
-
const springY = useSpring(mouseY, { stiffness: 300, damping: 30 })
|
|
236
|
-
|
|
237
|
-
// Load persisted state
|
|
238
|
-
useEffect(() => {
|
|
239
|
-
if (persistState && typeof window !== 'undefined') {
|
|
240
|
-
const savedState = localStorage.getItem(persistKey)
|
|
241
|
-
if (savedState) {
|
|
242
|
-
const state = JSON.parse(savedState) as SidebarState
|
|
243
|
-
setCollapsed(state.collapsed)
|
|
244
|
-
setExpandedSections(state.expandedSections)
|
|
245
|
-
setPinnedItems(state.pinnedItems || [])
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}, [persistState, persistKey])
|
|
249
|
-
|
|
250
|
-
// Save state changes
|
|
251
|
-
useEffect(() => {
|
|
252
|
-
if (persistState && typeof window !== 'undefined') {
|
|
253
|
-
const state: SidebarState = {
|
|
254
|
-
collapsed,
|
|
255
|
-
expandedSections,
|
|
256
|
-
searchQuery: currentSearchQuery,
|
|
257
|
-
pinnedItems,
|
|
258
|
-
recentItems: []
|
|
259
|
-
}
|
|
260
|
-
localStorage.setItem(persistKey, JSON.stringify(state))
|
|
261
|
-
onStateChange?.(state)
|
|
262
|
-
}
|
|
263
|
-
}, [collapsed, expandedSections, currentSearchQuery, pinnedItems, persistState, persistKey, onStateChange])
|
|
264
|
-
|
|
265
|
-
// Check mobile
|
|
266
|
-
useEffect(() => {
|
|
267
|
-
const checkMobile = () => {
|
|
268
|
-
setIsMobile(window.innerWidth < 768)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
checkMobile()
|
|
272
|
-
window.addEventListener('resize', checkMobile)
|
|
273
|
-
return () => window.removeEventListener('resize', checkMobile)
|
|
274
|
-
}, [])
|
|
275
|
-
|
|
276
|
-
// Initialize expanded sections
|
|
277
|
-
useEffect(() => {
|
|
278
|
-
const sectionsToExpand = sections
|
|
279
|
-
.filter(section => section.defaultExpanded !== false)
|
|
280
|
-
.map(section => section.id)
|
|
281
|
-
setExpandedSections(sectionsToExpand)
|
|
282
|
-
}, [sections])
|
|
283
|
-
|
|
284
|
-
// Keyboard shortcuts
|
|
285
|
-
useEffect(() => {
|
|
286
|
-
if (!keyboardShortcuts) return
|
|
287
|
-
|
|
288
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
289
|
-
// Cmd/Ctrl + K for search
|
|
290
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
291
|
-
e.preventDefault()
|
|
292
|
-
searchInputRef.current?.focus()
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Cmd/Ctrl + B to toggle sidebar
|
|
296
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
|
|
297
|
-
e.preventDefault()
|
|
298
|
-
if (isMobile) {
|
|
299
|
-
setIsOpen(!isOpen)
|
|
300
|
-
} else {
|
|
301
|
-
setCollapsed(!collapsed)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
document.addEventListener('keydown', handleKeyDown)
|
|
307
|
-
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
308
|
-
}, [keyboardShortcuts, isMobile, isOpen, collapsed])
|
|
309
|
-
|
|
310
|
-
// Mouse tracking for animated background
|
|
311
|
-
useEffect(() => {
|
|
312
|
-
if (!animatedBackground) return
|
|
313
|
-
|
|
314
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
315
|
-
const rect = document.querySelector('.sidebar-container')?.getBoundingClientRect()
|
|
316
|
-
if (rect) {
|
|
317
|
-
mouseX.set(e.clientX - rect.left)
|
|
318
|
-
mouseY.set(e.clientY - rect.top)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
document.addEventListener('mousemove', handleMouseMove)
|
|
323
|
-
return () => document.removeEventListener('mousemove', handleMouseMove)
|
|
324
|
-
}, [animatedBackground, mouseX, mouseY])
|
|
325
|
-
|
|
326
|
-
const toggleSection = useCallback((sectionId: string) => {
|
|
327
|
-
setExpandedSections(prev =>
|
|
328
|
-
prev.includes(sectionId)
|
|
329
|
-
? prev.filter(id => id !== sectionId)
|
|
330
|
-
: [...prev, sectionId]
|
|
331
|
-
)
|
|
332
|
-
}, [])
|
|
333
|
-
|
|
334
|
-
const togglePinItem = useCallback((itemId: string) => {
|
|
335
|
-
setPinnedItems(prev =>
|
|
336
|
-
prev.includes(itemId)
|
|
337
|
-
? prev.filter(id => id !== itemId)
|
|
338
|
-
: [...prev, itemId]
|
|
339
|
-
)
|
|
340
|
-
}, [])
|
|
341
|
-
|
|
342
|
-
const handleItemClick = useCallback((item: SidebarItem) => {
|
|
343
|
-
if (item.action) {
|
|
344
|
-
item.action()
|
|
345
|
-
} else if (item.href && onNavigate) {
|
|
346
|
-
onNavigate(item.href)
|
|
347
|
-
if (isMobile) {
|
|
348
|
-
setIsOpen(false)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}, [onNavigate, isMobile])
|
|
352
|
-
|
|
353
|
-
// Update search query from prop
|
|
354
|
-
useEffect(() => {
|
|
355
|
-
setCurrentSearchQuery(searchQuery)
|
|
356
|
-
}, [searchQuery])
|
|
357
|
-
|
|
358
|
-
// Handle search change
|
|
359
|
-
const handleSearch = useCallback((value: string) => {
|
|
360
|
-
setCurrentSearchQuery(value)
|
|
361
|
-
onSearchChange?.(value)
|
|
362
|
-
}, [onSearchChange])
|
|
363
|
-
|
|
364
|
-
const filterItems = useCallback((items: SidebarItem[], query: string): SidebarItem[] => {
|
|
365
|
-
if (!query) return items
|
|
366
|
-
|
|
367
|
-
return items.filter(item => {
|
|
368
|
-
const matchesTitle = item.title.toLowerCase().includes(query.toLowerCase())
|
|
369
|
-
const hasMatchingChildren = item.items?.some(child =>
|
|
370
|
-
child.title.toLowerCase().includes(query.toLowerCase())
|
|
371
|
-
)
|
|
372
|
-
return matchesTitle || hasMatchingChildren
|
|
373
|
-
})
|
|
374
|
-
}, [])
|
|
375
|
-
|
|
376
|
-
// Filtered sections memoized to prevent re-renders
|
|
377
|
-
const filteredSections = useMemo(() => {
|
|
378
|
-
if (!currentSearchQuery) {
|
|
379
|
-
return sections.map(section => ({
|
|
380
|
-
...section,
|
|
381
|
-
filteredItems: section.items.map(item => ({
|
|
382
|
-
...item,
|
|
383
|
-
filteredChildren: item.items
|
|
384
|
-
}))
|
|
385
|
-
}))
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return sections.map(section => ({
|
|
389
|
-
...section,
|
|
390
|
-
filteredItems: filterItems(section.items, currentSearchQuery).map(item => ({
|
|
391
|
-
...item,
|
|
392
|
-
filteredChildren: item.items ? filterItems(item.items, currentSearchQuery) : undefined
|
|
393
|
-
}))
|
|
394
|
-
})).filter(section => section.filteredItems.length > 0)
|
|
395
|
-
}, [sections, currentSearchQuery, filterItems])
|
|
396
|
-
|
|
397
|
-
// Collapsed menü için hover content renderer
|
|
398
|
-
const renderCollapsedHoverContent = useCallback((item: SidebarItem) => {
|
|
399
|
-
const hasChildren = item.items && item.items.length > 0
|
|
400
|
-
|
|
401
|
-
if (!hasChildren && !item.title) return null
|
|
402
|
-
|
|
403
|
-
return (
|
|
404
|
-
<div className={cn(
|
|
405
|
-
"min-w-[200px] p-2",
|
|
406
|
-
glassmorphism && "bg-background/95 backdrop-blur-sm"
|
|
407
|
-
)}>
|
|
408
|
-
<div className="font-medium px-2 py-1 text-sm">{item.title}</div>
|
|
409
|
-
{hasChildren && (
|
|
410
|
-
<div className="mt-1 space-y-0.5">
|
|
411
|
-
{item.items?.map(childItem => {
|
|
412
|
-
const isChildActive = childItem.href === activePath
|
|
413
|
-
const hasGrandChildren = childItem.items && childItem.items.length > 0
|
|
414
|
-
|
|
415
|
-
return (
|
|
416
|
-
<div key={childItem.id} className="relative">
|
|
417
|
-
{hasGrandChildren ? (
|
|
418
|
-
<HoverCard openDelay={200} closeDelay={100}>
|
|
419
|
-
<HoverCardTrigger asChild>
|
|
420
|
-
<button
|
|
421
|
-
onClick={() => handleItemClick(childItem)}
|
|
422
|
-
disabled={childItem.disabled}
|
|
423
|
-
className={cn(
|
|
424
|
-
"w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
|
425
|
-
"hover:bg-accent hover:text-accent-foreground",
|
|
426
|
-
isChildActive && "bg-primary/10 text-primary font-medium",
|
|
427
|
-
childItem.disabled && "opacity-50 cursor-not-allowed"
|
|
428
|
-
)}
|
|
429
|
-
>
|
|
430
|
-
<div className="flex items-center gap-2">
|
|
431
|
-
{childItem.icon && (
|
|
432
|
-
<span className="flex-shrink-0">
|
|
433
|
-
{childItem.icon}
|
|
434
|
-
</span>
|
|
435
|
-
)}
|
|
436
|
-
<span className="truncate">{childItem.title}</span>
|
|
437
|
-
</div>
|
|
438
|
-
<ChevronRight className="h-3 w-3 flex-shrink-0" />
|
|
439
|
-
</button>
|
|
440
|
-
</HoverCardTrigger>
|
|
441
|
-
<HoverCardContent
|
|
442
|
-
side="right"
|
|
443
|
-
align="start"
|
|
444
|
-
sideOffset={10}
|
|
445
|
-
className={cn(
|
|
446
|
-
"p-2",
|
|
447
|
-
glassmorphism && "bg-background/95 backdrop-blur-sm"
|
|
448
|
-
)}
|
|
449
|
-
>
|
|
450
|
-
<div className="space-y-0.5">
|
|
451
|
-
{childItem.items?.map(grandChild => {
|
|
452
|
-
const isGrandChildActive = grandChild.href === activePath
|
|
453
|
-
return (
|
|
454
|
-
<button
|
|
455
|
-
key={grandChild.id}
|
|
456
|
-
onClick={() => handleItemClick(grandChild)}
|
|
457
|
-
disabled={grandChild.disabled}
|
|
458
|
-
className={cn(
|
|
459
|
-
"w-full flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
|
460
|
-
"hover:bg-accent hover:text-accent-foreground",
|
|
461
|
-
isGrandChildActive && "bg-primary/10 text-primary font-medium",
|
|
462
|
-
grandChild.disabled && "opacity-50 cursor-not-allowed"
|
|
463
|
-
)}
|
|
464
|
-
>
|
|
465
|
-
{grandChild.icon && (
|
|
466
|
-
<span className="flex-shrink-0">
|
|
467
|
-
{grandChild.icon}
|
|
468
|
-
</span>
|
|
469
|
-
)}
|
|
470
|
-
<span className="truncate">{grandChild.title}</span>
|
|
471
|
-
{grandChild.badge && (
|
|
472
|
-
<Badge
|
|
473
|
-
variant={grandChild.badgeVariant || 'secondary'}
|
|
474
|
-
className="ml-auto flex-shrink-0"
|
|
475
|
-
>
|
|
476
|
-
{grandChild.badge}
|
|
477
|
-
</Badge>
|
|
478
|
-
)}
|
|
479
|
-
</button>
|
|
480
|
-
)
|
|
481
|
-
})}
|
|
482
|
-
</div>
|
|
483
|
-
</HoverCardContent>
|
|
484
|
-
</HoverCard>
|
|
485
|
-
) : (
|
|
486
|
-
<button
|
|
487
|
-
onClick={() => handleItemClick(childItem)}
|
|
488
|
-
disabled={childItem.disabled}
|
|
489
|
-
className={cn(
|
|
490
|
-
"w-full flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
|
|
491
|
-
"hover:bg-accent hover:text-accent-foreground",
|
|
492
|
-
isChildActive && "bg-primary/10 text-primary font-medium",
|
|
493
|
-
childItem.disabled && "opacity-50 cursor-not-allowed"
|
|
494
|
-
)}
|
|
495
|
-
>
|
|
496
|
-
{childItem.icon && (
|
|
497
|
-
<span className="flex-shrink-0">
|
|
498
|
-
{childItem.icon}
|
|
499
|
-
</span>
|
|
500
|
-
)}
|
|
501
|
-
<span className="truncate">{childItem.title}</span>
|
|
502
|
-
{childItem.badge && (
|
|
503
|
-
<Badge
|
|
504
|
-
variant={childItem.badgeVariant || 'secondary'}
|
|
505
|
-
className="ml-auto flex-shrink-0"
|
|
506
|
-
>
|
|
507
|
-
{childItem.badge}
|
|
508
|
-
</Badge>
|
|
509
|
-
)}
|
|
510
|
-
</button>
|
|
511
|
-
)}
|
|
512
|
-
</div>
|
|
513
|
-
)
|
|
514
|
-
})}
|
|
515
|
-
</div>
|
|
516
|
-
)}
|
|
517
|
-
</div>
|
|
518
|
-
)
|
|
519
|
-
}, [activePath, glassmorphism, handleItemClick])
|
|
520
|
-
|
|
521
|
-
const renderItem = useCallback((item: SidebarItem, depth = 0, filteredChildren?: SidebarItem[]) => {
|
|
522
|
-
const isActive = item.href === activePath
|
|
523
|
-
const isPinned = pinnedItems.includes(item.id)
|
|
524
|
-
const hasChildren = item.items && item.items.length > 0
|
|
525
|
-
const isExpanded = expandedSections.includes(item.id)
|
|
526
|
-
const shouldShowHoverMenu = collapsed && !isMobile && depth === 0 && (hasChildren || item.title)
|
|
527
|
-
|
|
528
|
-
const ItemWrapper = item.tooltip && !collapsed ? TooltipTrigger : React.Fragment
|
|
529
|
-
|
|
530
|
-
const itemContent = (
|
|
531
|
-
<button
|
|
532
|
-
onClick={() => {
|
|
533
|
-
if (hasChildren) {
|
|
534
|
-
toggleSection(item.id)
|
|
535
|
-
} else {
|
|
536
|
-
handleItemClick(item)
|
|
537
|
-
}
|
|
538
|
-
}}
|
|
539
|
-
className={cn(
|
|
540
|
-
"w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-all",
|
|
541
|
-
"hover:bg-accent hover:text-accent-foreground",
|
|
542
|
-
isActive && "bg-primary/10 text-primary font-medium",
|
|
543
|
-
item.disabled && "opacity-50 cursor-not-allowed",
|
|
544
|
-
depth > 0 && "ml-6 text-xs",
|
|
545
|
-
collapsed && depth === 0 && "justify-center px-2",
|
|
546
|
-
customStyles?.hover
|
|
547
|
-
)}
|
|
548
|
-
disabled={item.disabled}
|
|
549
|
-
>
|
|
550
|
-
{item.icon && (
|
|
551
|
-
<span className={cn(
|
|
552
|
-
"flex-shrink-0",
|
|
553
|
-
collapsed && depth === 0 && "mx-auto"
|
|
554
|
-
)}>
|
|
555
|
-
{item.icon}
|
|
556
|
-
</span>
|
|
557
|
-
)}
|
|
558
|
-
|
|
559
|
-
{(!collapsed || depth > 0) && (
|
|
560
|
-
<>
|
|
561
|
-
<span className="flex-1 text-left truncate">{item.title}</span>
|
|
562
|
-
|
|
563
|
-
{item.badge && (
|
|
564
|
-
<Badge
|
|
565
|
-
variant={item.badgeVariant || 'secondary'}
|
|
566
|
-
className="ml-auto flex-shrink-0"
|
|
567
|
-
>
|
|
568
|
-
{item.badge}
|
|
569
|
-
</Badge>
|
|
570
|
-
)}
|
|
571
|
-
|
|
572
|
-
{hasChildren && depth === 0 && (
|
|
573
|
-
<ChevronDown
|
|
574
|
-
className={cn(
|
|
575
|
-
"h-4 w-4 flex-shrink-0 transition-transform",
|
|
576
|
-
isExpanded && "rotate-180"
|
|
577
|
-
)}
|
|
578
|
-
/>
|
|
579
|
-
)}
|
|
580
|
-
|
|
581
|
-
{isPinned && (
|
|
582
|
-
<Pin className="h-3 w-3 flex-shrink-0" />
|
|
583
|
-
)}
|
|
584
|
-
</>
|
|
585
|
-
)}
|
|
586
|
-
</button>
|
|
587
|
-
)
|
|
588
|
-
|
|
589
|
-
// Collapsed durumda hover menü göster
|
|
590
|
-
if (shouldShowHoverMenu) {
|
|
591
|
-
return (
|
|
592
|
-
<div key={item.id}>
|
|
593
|
-
<HoverCard openDelay={200} closeDelay={100}>
|
|
594
|
-
<HoverCardTrigger asChild>
|
|
595
|
-
{itemContent}
|
|
596
|
-
</HoverCardTrigger>
|
|
597
|
-
<HoverCardContent
|
|
598
|
-
side="right"
|
|
599
|
-
align="start"
|
|
600
|
-
sideOffset={10}
|
|
601
|
-
className={cn(
|
|
602
|
-
"p-0 w-auto",
|
|
603
|
-
glassmorphism && "bg-background/95 backdrop-blur-sm",
|
|
604
|
-
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
|
|
605
|
-
)}
|
|
606
|
-
>
|
|
607
|
-
<motion.div
|
|
608
|
-
initial={{ opacity: 0, x: -10 }}
|
|
609
|
-
animate={{ opacity: 1, x: 0 }}
|
|
610
|
-
exit={{ opacity: 0, x: -10 }}
|
|
611
|
-
transition={{ duration: 0.15 }}
|
|
612
|
-
>
|
|
613
|
-
{renderCollapsedHoverContent(item)}
|
|
614
|
-
</motion.div>
|
|
615
|
-
</HoverCardContent>
|
|
616
|
-
</HoverCard>
|
|
617
|
-
</div>
|
|
618
|
-
)
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
return (
|
|
622
|
-
<div key={item.id}>
|
|
623
|
-
{item.tooltip && !collapsed ? (
|
|
624
|
-
<TooltipProvider>
|
|
625
|
-
<Tooltip>
|
|
626
|
-
<ItemWrapper>
|
|
627
|
-
{itemContent}
|
|
628
|
-
</ItemWrapper>
|
|
629
|
-
<TooltipContent side="right">
|
|
630
|
-
<p>{item.tooltip}</p>
|
|
631
|
-
</TooltipContent>
|
|
632
|
-
</Tooltip>
|
|
633
|
-
</TooltipProvider>
|
|
634
|
-
) : (
|
|
635
|
-
itemContent
|
|
636
|
-
)}
|
|
637
|
-
|
|
638
|
-
{hasChildren && !collapsed && filteredChildren && (
|
|
639
|
-
<AnimatePresence>
|
|
640
|
-
{isExpanded && (
|
|
641
|
-
<motion.div
|
|
642
|
-
initial={{ height: 0, opacity: 0 }}
|
|
643
|
-
animate={{ height: "auto", opacity: 1 }}
|
|
644
|
-
exit={{ height: 0, opacity: 0 }}
|
|
645
|
-
transition={{ duration: 0.2 }}
|
|
646
|
-
className="overflow-hidden"
|
|
647
|
-
>
|
|
648
|
-
<div className="pt-1 space-y-1">
|
|
649
|
-
{filteredChildren.map(child =>
|
|
650
|
-
renderItem(child, depth + 1)
|
|
651
|
-
)}
|
|
652
|
-
</div>
|
|
653
|
-
</motion.div>
|
|
654
|
-
)}
|
|
655
|
-
</AnimatePresence>
|
|
656
|
-
)}
|
|
657
|
-
</div>
|
|
658
|
-
)
|
|
659
|
-
}, [activePath, pinnedItems, expandedSections, collapsed, customStyles, handleItemClick, toggleSection, isMobile, glassmorphism, renderCollapsedHoverContent])
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
// Header Component
|
|
664
|
-
const SidebarHeader = React.memo(() => {
|
|
665
|
-
return (
|
|
666
|
-
<div className={cn(
|
|
667
|
-
"flex items-center gap-3 p-4 border-b",
|
|
668
|
-
collapsed && !isMobile && "justify-center px-2"
|
|
669
|
-
)}>
|
|
670
|
-
{branding && (
|
|
671
|
-
<>
|
|
672
|
-
{branding.logo}
|
|
673
|
-
{(!collapsed || isMobile) && branding.title && (
|
|
674
|
-
<span className="font-semibold text-lg">{branding.title}</span>
|
|
675
|
-
)}
|
|
676
|
-
</>
|
|
677
|
-
)}
|
|
678
|
-
|
|
679
|
-
{isMobile && (
|
|
680
|
-
<Button
|
|
681
|
-
variant="ghost"
|
|
682
|
-
size="sm"
|
|
683
|
-
className="ml-auto h-8 w-8 p-0"
|
|
684
|
-
onClick={() => setIsOpen(false)}
|
|
685
|
-
>
|
|
686
|
-
<X className="h-4 w-4" />
|
|
687
|
-
</Button>
|
|
688
|
-
)}
|
|
689
|
-
|
|
690
|
-
{!isMobile && collapsible && (
|
|
691
|
-
<Button
|
|
692
|
-
variant="ghost"
|
|
693
|
-
size="sm"
|
|
694
|
-
className={cn(
|
|
695
|
-
"h-8 w-8 p-0",
|
|
696
|
-
!collapsed && "ml-auto"
|
|
697
|
-
)}
|
|
698
|
-
onClick={() => setCollapsed(!collapsed)}
|
|
699
|
-
>
|
|
700
|
-
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
|
701
|
-
</Button>
|
|
702
|
-
)}
|
|
703
|
-
</div>
|
|
704
|
-
)
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
// Menu Content
|
|
709
|
-
const SidebarMenuContent = React.memo(() => {
|
|
710
|
-
return (
|
|
711
|
-
<ScrollArea className="flex-1 overflow-y-auto">
|
|
712
|
-
<div className="p-4 space-y-6">
|
|
713
|
-
{/* Pinned Items */}
|
|
714
|
-
{pinnedItems.length > 0 && (!collapsed || isMobile) && (
|
|
715
|
-
<div>
|
|
716
|
-
<h4 className="text-xs font-medium text-muted-foreground mb-2">Pinned</h4>
|
|
717
|
-
<div className="space-y-1">
|
|
718
|
-
{sections.flatMap(section =>
|
|
719
|
-
section.items.filter(item => pinnedItems.includes(item.id))
|
|
720
|
-
).map(item => renderItem(item))}
|
|
721
|
-
</div>
|
|
722
|
-
<Separator className="mt-4" />
|
|
723
|
-
</div>
|
|
724
|
-
)}
|
|
725
|
-
|
|
726
|
-
{/* Main Menu Items */}
|
|
727
|
-
{filteredSections.map((section, index) => (
|
|
728
|
-
<div key={section.id}>
|
|
729
|
-
{section.title && (!collapsed || isMobile) && (
|
|
730
|
-
<h4 className="text-xs font-medium text-muted-foreground mb-2">
|
|
731
|
-
{section.title}
|
|
732
|
-
</h4>
|
|
733
|
-
)}
|
|
734
|
-
<div className="space-y-1">
|
|
735
|
-
{section.filteredItems.map(item => renderItem(item, 0, item.filteredChildren))}
|
|
736
|
-
</div>
|
|
737
|
-
{section.showDivider && index < filteredSections.length - 1 && (
|
|
738
|
-
<Separator className="mt-4" />
|
|
739
|
-
)}
|
|
740
|
-
</div>
|
|
741
|
-
))}
|
|
742
|
-
</div>
|
|
743
|
-
</ScrollArea>
|
|
744
|
-
)
|
|
745
|
-
})
|
|
746
|
-
|
|
747
|
-
// Footer Component
|
|
748
|
-
const SidebarFooter = React.memo(() => {
|
|
749
|
-
return footer ? (
|
|
750
|
-
<div className="border-t p-4">
|
|
751
|
-
<div className="space-y-1">
|
|
752
|
-
{footer.items.map(item => renderItem(item))}
|
|
753
|
-
</div>
|
|
754
|
-
|
|
755
|
-
{showThemeToggle && (!collapsed || isMobile) && (
|
|
756
|
-
<div className="mt-3 flex items-center justify-between">
|
|
757
|
-
<span className="text-xs text-muted-foreground">Theme</span>
|
|
758
|
-
<DropdownMenu>
|
|
759
|
-
<DropdownMenuTrigger asChild>
|
|
760
|
-
<Button variant="ghost" size="sm" className="h-7 px-2">
|
|
761
|
-
{theme === 'light' && <Sun className="h-3 w-3" />}
|
|
762
|
-
{theme === 'dark' && <Moon className="h-3 w-3" />}
|
|
763
|
-
{theme === 'system' && <Monitor className="h-3 w-3" />}
|
|
764
|
-
</Button>
|
|
765
|
-
</DropdownMenuTrigger>
|
|
766
|
-
<DropdownMenuContent align="end">
|
|
767
|
-
<DropdownMenuItem onClick={() => onThemeChange?.('light')}>
|
|
768
|
-
<Sun className="mr-2 h-4 w-4" />
|
|
769
|
-
Light
|
|
770
|
-
</DropdownMenuItem>
|
|
771
|
-
<DropdownMenuItem onClick={() => onThemeChange?.('dark')}>
|
|
772
|
-
<Moon className="mr-2 h-4 w-4" />
|
|
773
|
-
Dark
|
|
774
|
-
</DropdownMenuItem>
|
|
775
|
-
<DropdownMenuItem onClick={() => onThemeChange?.('system')}>
|
|
776
|
-
<Monitor className="mr-2 h-4 w-4" />
|
|
777
|
-
System
|
|
778
|
-
</DropdownMenuItem>
|
|
779
|
-
</DropdownMenuContent>
|
|
780
|
-
</DropdownMenu>
|
|
781
|
-
</div>
|
|
782
|
-
)}
|
|
783
|
-
</div>
|
|
784
|
-
) : null
|
|
785
|
-
})
|
|
786
|
-
|
|
787
|
-
const SidebarContent = React.memo(() => {
|
|
788
|
-
return (
|
|
789
|
-
<>
|
|
790
|
-
<SidebarMenuContent />
|
|
791
|
-
<SidebarFooter />
|
|
792
|
-
</>
|
|
793
|
-
)
|
|
794
|
-
})
|
|
795
|
-
|
|
796
|
-
const sidebarClasses = cn(
|
|
797
|
-
"moonui-pro flex h-full flex-col bg-background border-r",
|
|
798
|
-
glassmorphism && "bg-background/80 backdrop-blur-xl border-white/10",
|
|
799
|
-
collapsed && !isMobile && "w-16",
|
|
800
|
-
!collapsed && !isMobile && "w-64",
|
|
801
|
-
customStyles?.background,
|
|
802
|
-
customStyles?.border,
|
|
803
|
-
className
|
|
804
|
-
)
|
|
805
|
-
|
|
806
|
-
if (isMobile) {
|
|
807
|
-
return (
|
|
808
|
-
<>
|
|
809
|
-
{floatingActionButton && (
|
|
810
|
-
<Button
|
|
811
|
-
onClick={() => setIsOpen(true)}
|
|
812
|
-
className={cn(
|
|
813
|
-
"fixed z-40 h-12 w-12 rounded-full shadow-lg md:hidden",
|
|
814
|
-
floatingActionButtonClassName
|
|
815
|
-
)}
|
|
816
|
-
size="icon"
|
|
817
|
-
style={{
|
|
818
|
-
bottom: floatingActionButtonPosition?.bottom,
|
|
819
|
-
left: floatingActionButtonPosition?.left,
|
|
820
|
-
right: floatingActionButtonPosition?.right,
|
|
821
|
-
top: floatingActionButtonPosition?.top
|
|
822
|
-
}}
|
|
823
|
-
>
|
|
824
|
-
<Menu className="h-5 w-5" />
|
|
825
|
-
</Button>
|
|
826
|
-
)}
|
|
827
|
-
|
|
828
|
-
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
|
829
|
-
<SheetContent side="left" className="w-80 p-0">
|
|
830
|
-
<div className={cn(sidebarClasses, "sidebar-container flex flex-col")}>
|
|
831
|
-
{animatedBackground && (
|
|
832
|
-
<motion.div
|
|
833
|
-
className="absolute inset-0 opacity-30"
|
|
834
|
-
style={{
|
|
835
|
-
background: `radial-gradient(circle at ${springX}px ${springY}px, rgba(var(--primary-rgb), 0.15), transparent 50%)`,
|
|
836
|
-
}}
|
|
837
|
-
/>
|
|
838
|
-
)}
|
|
839
|
-
<SidebarHeader />
|
|
840
|
-
{showSearch && (!collapsed || isMobile) && (
|
|
841
|
-
<SearchInput
|
|
842
|
-
key="sidebar-search-mobile"
|
|
843
|
-
searchInputRef={searchInputRef}
|
|
844
|
-
searchPlaceholder={searchPlaceholder}
|
|
845
|
-
initialValue={currentSearchQuery}
|
|
846
|
-
onSearchChange={handleSearch}
|
|
847
|
-
keyboardShortcuts={keyboardShortcuts}
|
|
848
|
-
/>
|
|
849
|
-
)}
|
|
850
|
-
<SidebarContent />
|
|
851
|
-
</div>
|
|
852
|
-
</SheetContent>
|
|
853
|
-
</Sheet>
|
|
854
|
-
</>
|
|
855
|
-
)
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
return (
|
|
859
|
-
<aside className={cn(sidebarClasses, "sidebar-container hidden md:flex flex-col")}>
|
|
860
|
-
{animatedBackground && (
|
|
861
|
-
<motion.div
|
|
862
|
-
className="absolute inset-0 opacity-30"
|
|
863
|
-
style={{
|
|
864
|
-
background: `radial-gradient(circle at ${springX}px ${springY}px, rgba(var(--primary-rgb), 0.15), transparent 50%)`,
|
|
865
|
-
}}
|
|
866
|
-
/>
|
|
867
|
-
)}
|
|
868
|
-
<SidebarHeader />
|
|
869
|
-
{showSearch && (!collapsed || isMobile) && (
|
|
870
|
-
<SearchInput
|
|
871
|
-
key="sidebar-search-desktop"
|
|
872
|
-
searchInputRef={searchInputRef}
|
|
873
|
-
searchPlaceholder={searchPlaceholder}
|
|
874
|
-
initialValue={currentSearchQuery}
|
|
875
|
-
onSearchChange={handleSearch}
|
|
876
|
-
keyboardShortcuts={keyboardShortcuts}
|
|
877
|
-
/>
|
|
878
|
-
)}
|
|
879
|
-
<SidebarContent />
|
|
880
|
-
</aside>
|
|
881
|
-
)
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
export default Sidebar
|