@moontra/moonui-pro 2.20.0 → 2.20.2

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.
Files changed (76) hide show
  1. package/dist/index.d.ts +691 -261
  2. package/dist/index.mjs +7419 -4935
  3. package/package.json +4 -3
  4. package/scripts/postbuild.js +27 -0
  5. package/src/components/advanced-chart/index.tsx +5 -1
  6. package/src/components/advanced-forms/index.tsx +175 -16
  7. package/src/components/calendar/event-dialog.tsx +18 -13
  8. package/src/components/calendar/index.tsx +197 -50
  9. package/src/components/dashboard/dashboard-grid.tsx +21 -3
  10. package/src/components/dashboard/types.ts +3 -0
  11. package/src/components/dashboard/widgets/activity-feed.tsx +6 -1
  12. package/src/components/dashboard/widgets/comparison-widget.tsx +177 -0
  13. package/src/components/dashboard/widgets/index.ts +5 -0
  14. package/src/components/dashboard/widgets/metric-card.tsx +21 -1
  15. package/src/components/dashboard/widgets/progress-widget.tsx +113 -0
  16. package/src/components/error-boundary/index.tsx +160 -37
  17. package/src/components/form-wizard/form-wizard-context.tsx +54 -26
  18. package/src/components/form-wizard/form-wizard-progress.tsx +33 -2
  19. package/src/components/form-wizard/types.ts +2 -1
  20. package/src/components/github-stars/hooks.ts +1 -0
  21. package/src/components/github-stars/variants.tsx +3 -1
  22. package/src/components/health-check/index.tsx +14 -14
  23. package/src/components/hover-card-3d/index.tsx +2 -3
  24. package/src/components/index.ts +5 -3
  25. package/src/components/kanban/kanban.tsx +23 -18
  26. package/src/components/license-error/index.tsx +2 -0
  27. package/src/components/magnetic-button/index.tsx +56 -7
  28. package/src/components/memory-efficient-data/index.tsx +117 -115
  29. package/src/components/navbar/index.tsx +781 -0
  30. package/src/components/performance-debugger/index.tsx +62 -38
  31. package/src/components/performance-monitor/index.tsx +47 -33
  32. package/src/components/phone-number-input/index.tsx +32 -27
  33. package/src/components/phone-number-input/phone-number-input-simple.tsx +167 -0
  34. package/src/components/rich-text-editor/index.tsx +26 -28
  35. package/src/components/rich-text-editor/slash-commands-extension.ts +15 -5
  36. package/src/components/sidebar/index.tsx +32 -13
  37. package/src/components/timeline/index.tsx +84 -49
  38. package/src/components/ui/accordion.tsx +550 -42
  39. package/src/components/ui/avatar.tsx +2 -0
  40. package/src/components/ui/badge.tsx +2 -0
  41. package/src/components/ui/breadcrumb.tsx +2 -0
  42. package/src/components/ui/button.tsx +39 -33
  43. package/src/components/ui/card.tsx +2 -0
  44. package/src/components/ui/collapsible.tsx +546 -50
  45. package/src/components/ui/command.tsx +790 -67
  46. package/src/components/ui/dialog.tsx +510 -92
  47. package/src/components/ui/dropdown-menu.tsx +540 -52
  48. package/src/components/ui/index.ts +37 -5
  49. package/src/components/ui/input.tsx +2 -0
  50. package/src/components/ui/magnetic-button.tsx +1 -1
  51. package/src/components/ui/media-gallery.tsx +1 -2
  52. package/src/components/ui/navigation-menu.tsx +130 -0
  53. package/src/components/ui/pagination.tsx +2 -0
  54. package/src/components/ui/select.tsx +6 -2
  55. package/src/components/ui/spotlight-card.tsx +1 -1
  56. package/src/components/ui/table.tsx +2 -0
  57. package/src/components/ui/tabs-pro.tsx +542 -0
  58. package/src/components/ui/tabs.tsx +23 -167
  59. package/src/components/ui/toggle.tsx +13 -13
  60. package/src/index.ts +11 -3
  61. package/src/styles/index.css +596 -0
  62. package/src/use-performance-optimizer.ts +1 -1
  63. package/src/utils/chart-helpers.ts +1 -1
  64. package/src/__tests__/use-intersection-observer.test.tsx +0 -216
  65. package/src/__tests__/use-local-storage.test.tsx +0 -174
  66. package/src/__tests__/use-pro-access.test.tsx +0 -183
  67. package/src/components/advanced-chart/advanced-chart.test.tsx +0 -281
  68. package/src/components/data-table/data-table.test.tsx +0 -187
  69. package/src/components/enhanced/badge.tsx +0 -191
  70. package/src/components/enhanced/button.tsx +0 -362
  71. package/src/components/enhanced/card.tsx +0 -266
  72. package/src/components/enhanced/dialog.tsx +0 -246
  73. package/src/components/enhanced/index.ts +0 -4
  74. package/src/components/file-upload/file-upload.test.tsx +0 -243
  75. package/src/components/rich-text-editor/index-old-backup.tsx +0 -437
  76. package/src/types/moonui.d.ts +0 -22
@@ -0,0 +1,781 @@
1
+ "use client"
2
+
3
+ import React, { useState, useEffect, useCallback, useRef } from 'react'
4
+ import { motion, AnimatePresence } from 'framer-motion'
5
+ import { cn } from '../../lib/utils'
6
+ import { Button } from '../ui/button'
7
+ import { Badge } from '../ui/badge'
8
+ import { Separator } from '../ui/separator'
9
+ import { Sheet, SheetContent, SheetTrigger } from '../ui/sheet'
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuSeparator,
15
+ DropdownMenuTrigger,
16
+ DropdownMenuSub,
17
+ DropdownMenuSubContent,
18
+ DropdownMenuSubTrigger,
19
+ } from '../ui/dropdown-menu'
20
+ import {
21
+ NavigationMenu,
22
+ NavigationMenuContent,
23
+ NavigationMenuItem,
24
+ NavigationMenuLink,
25
+ NavigationMenuList,
26
+ NavigationMenuTrigger,
27
+ navigationMenuTriggerStyle,
28
+ } from '../ui/navigation-menu'
29
+ import {
30
+ Menu,
31
+ X,
32
+ ChevronDown,
33
+ Search,
34
+ Bell,
35
+ User,
36
+ Settings,
37
+ LogOut,
38
+ Moon,
39
+ Sun,
40
+ Monitor,
41
+ Command,
42
+ Sparkles
43
+ } from 'lucide-react'
44
+ import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'
45
+ import { Input } from '../ui/input'
46
+ import {
47
+ CommandDialog,
48
+ CommandEmpty,
49
+ CommandGroup,
50
+ CommandInput,
51
+ CommandItem,
52
+ CommandList,
53
+ } from '../ui/command'
54
+
55
+ export interface NavbarItem {
56
+ id: string
57
+ title: string
58
+ href?: string
59
+ icon?: React.ReactNode
60
+ badge?: string | number
61
+ badgeVariant?: 'primary' | 'secondary' | 'destructive' | 'outline'
62
+ disabled?: boolean
63
+ items?: NavbarItem[]
64
+ action?: () => void
65
+ description?: string
66
+ thumbnail?: string
67
+ }
68
+
69
+ export interface NavbarSection {
70
+ id: string
71
+ items: NavbarItem[]
72
+ showInMobile?: boolean
73
+ }
74
+
75
+ export interface NavbarConfig {
76
+ sections: NavbarSection[]
77
+ branding?: {
78
+ logo?: React.ReactNode
79
+ title?: string
80
+ href?: string
81
+ }
82
+ showSearch?: boolean
83
+ searchPlaceholder?: string
84
+ onSearch?: (value: string) => void
85
+ showThemeToggle?: boolean
86
+ theme?: 'light' | 'dark' | 'system'
87
+ onThemeChange?: (theme: 'light' | 'dark' | 'system') => void
88
+ userMenu?: {
89
+ user?: {
90
+ name?: string
91
+ email?: string
92
+ avatar?: string
93
+ }
94
+ items?: NavbarItem[]
95
+ }
96
+ notifications?: {
97
+ count?: number
98
+ items?: Array<{
99
+ id: string
100
+ title: string
101
+ description?: string
102
+ time?: string
103
+ read?: boolean
104
+ }>
105
+ onNotificationClick?: (id: string) => void
106
+ }
107
+ cta?: {
108
+ text: string
109
+ href?: string
110
+ action?: () => void
111
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
112
+ }
113
+ sticky?: boolean
114
+ glassmorphism?: boolean
115
+ animatedBackground?: boolean
116
+ keyboardShortcuts?: boolean
117
+ mobileBreakpoint?: number
118
+ customStyles?: {
119
+ background?: string
120
+ border?: string
121
+ text?: string
122
+ hover?: string
123
+ }
124
+ variant?: 'default' | 'floating' | 'minimal' | 'transparent'
125
+ size?: 'sm' | 'md' | 'lg'
126
+ blur?: boolean
127
+ shadow?: boolean
128
+ rounded?: boolean
129
+ logoPosition?: 'left' | 'center'
130
+ mobileMenuPosition?: 'left' | 'right'
131
+ hideOnScroll?: boolean
132
+ showScrollProgress?: boolean
133
+ maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'
134
+ }
135
+
136
+ interface NavbarProps extends NavbarConfig {
137
+ className?: string
138
+ activePath?: string
139
+ onNavigate?: (href: string) => void
140
+ }
141
+
142
+ export function Navbar({
143
+ sections,
144
+ branding,
145
+ showSearch = true,
146
+ searchPlaceholder = "Search...",
147
+ onSearch,
148
+ showThemeToggle = false,
149
+ theme = 'system',
150
+ onThemeChange,
151
+ userMenu,
152
+ notifications,
153
+ cta,
154
+ sticky = true,
155
+ glassmorphism = false,
156
+ animatedBackground = false,
157
+ keyboardShortcuts = true,
158
+ mobileBreakpoint = 768,
159
+ customStyles,
160
+ variant = 'default',
161
+ size = 'md',
162
+ blur = true,
163
+ shadow = false,
164
+ rounded = false,
165
+ logoPosition = 'left',
166
+ mobileMenuPosition = 'left',
167
+ hideOnScroll = false,
168
+ showScrollProgress = false,
169
+ maxWidth = '2xl',
170
+ className,
171
+ activePath,
172
+ onNavigate
173
+ }: NavbarProps) {
174
+ const [isMobile, setIsMobile] = useState(false)
175
+ const [isOpen, setIsOpen] = useState(false)
176
+ const [isSearchOpen, setIsSearchOpen] = useState(false)
177
+ const [isCommandOpen, setIsCommandOpen] = useState(false)
178
+ const [searchValue, setSearchValue] = useState('')
179
+ const [scrolled, setScrolled] = useState(false)
180
+ const [hidden, setHidden] = useState(false)
181
+ const [scrollProgress, setScrollProgress] = useState(0)
182
+ const lastScrollY = useRef(0)
183
+ const navRef = useRef<HTMLElement>(null)
184
+
185
+ // Check mobile
186
+ useEffect(() => {
187
+ const checkMobile = () => {
188
+ setIsMobile(window.innerWidth < mobileBreakpoint)
189
+ }
190
+
191
+ checkMobile()
192
+ window.addEventListener('resize', checkMobile)
193
+ return () => window.removeEventListener('resize', checkMobile)
194
+ }, [mobileBreakpoint])
195
+
196
+ // Handle scroll for sticky navbar and hide on scroll
197
+ useEffect(() => {
198
+ if (!sticky && !hideOnScroll && !showScrollProgress) return
199
+
200
+ const handleScroll = () => {
201
+ const currentScrollY = window.scrollY
202
+
203
+ // Set scrolled state
204
+ setScrolled(currentScrollY > 10)
205
+
206
+ // Hide on scroll
207
+ if (hideOnScroll) {
208
+ if (currentScrollY > lastScrollY.current && currentScrollY > 100) {
209
+ setHidden(true)
210
+ } else {
211
+ setHidden(false)
212
+ }
213
+ lastScrollY.current = currentScrollY
214
+ }
215
+
216
+ // Scroll progress
217
+ if (showScrollProgress) {
218
+ const winScroll = window.scrollY
219
+ const height = document.documentElement.scrollHeight - document.documentElement.clientHeight
220
+ const scrolled = (winScroll / height) * 100
221
+ setScrollProgress(scrolled)
222
+ }
223
+ }
224
+
225
+ window.addEventListener('scroll', handleScroll, { passive: true })
226
+ return () => window.removeEventListener('scroll', handleScroll)
227
+ }, [sticky, hideOnScroll, showScrollProgress])
228
+
229
+ // Keyboard shortcuts
230
+ useEffect(() => {
231
+ if (!keyboardShortcuts) return
232
+
233
+ const handleKeyDown = (e: KeyboardEvent) => {
234
+ // Cmd/Ctrl + K for search
235
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
236
+ e.preventDefault()
237
+ setIsCommandOpen(true)
238
+ }
239
+ }
240
+
241
+ document.addEventListener('keydown', handleKeyDown)
242
+ return () => document.removeEventListener('keydown', handleKeyDown)
243
+ }, [keyboardShortcuts])
244
+
245
+ const handleItemClick = useCallback((item: NavbarItem) => {
246
+ if (item.action) {
247
+ item.action()
248
+ } else if (item.href && onNavigate) {
249
+ onNavigate(item.href)
250
+ if (isMobile) {
251
+ setIsOpen(false)
252
+ }
253
+ }
254
+ }, [onNavigate, isMobile])
255
+
256
+ const handleSearch = useCallback((value: string) => {
257
+ setSearchValue(value)
258
+ onSearch?.(value)
259
+ }, [onSearch])
260
+
261
+ // Render mega menu content
262
+ const renderMegaMenuContent = (items: NavbarItem[]) => (
263
+ <div className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
264
+ {items.map(item => (
265
+ <NavigationMenuLink
266
+ key={item.id}
267
+ className={cn(
268
+ "block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
269
+ item.disabled && "opacity-50 cursor-not-allowed"
270
+ )}
271
+ onClick={() => !item.disabled && handleItemClick(item)}
272
+ >
273
+ <div className="flex items-center gap-2">
274
+ {item.icon && <span className="flex-shrink-0">{item.icon}</span>}
275
+ <div className="text-sm font-medium leading-none">{item.title}</div>
276
+ {item.badge && (
277
+ <Badge variant={item.badgeVariant || 'secondary'} className="ml-auto">
278
+ {item.badge}
279
+ </Badge>
280
+ )}
281
+ </div>
282
+ {item.description && (
283
+ <p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
284
+ {item.description}
285
+ </p>
286
+ )}
287
+ </NavigationMenuLink>
288
+ ))}
289
+ </div>
290
+ )
291
+
292
+ // Render dropdown menu
293
+ const renderDropdownMenu = (items: NavbarItem[]) => (
294
+ <>
295
+ {items.map(item => {
296
+ if (item.items && item.items.length > 0) {
297
+ return (
298
+ <DropdownMenuSub key={item.id}>
299
+ <DropdownMenuSubTrigger disabled={item.disabled}>
300
+ <span className="flex items-center gap-2">
301
+ {item.icon && <span>{item.icon}</span>}
302
+ {item.title}
303
+ </span>
304
+ </DropdownMenuSubTrigger>
305
+ <DropdownMenuSubContent>
306
+ {renderDropdownMenu(item.items)}
307
+ </DropdownMenuSubContent>
308
+ </DropdownMenuSub>
309
+ )
310
+ }
311
+
312
+ return (
313
+ <DropdownMenuItem
314
+ key={item.id}
315
+ disabled={item.disabled}
316
+ onClick={() => handleItemClick(item)}
317
+ >
318
+ <span className="flex items-center gap-2">
319
+ {item.icon && <span>{item.icon}</span>}
320
+ {item.title}
321
+ </span>
322
+ {item.badge && (
323
+ <Badge variant={item.badgeVariant || 'secondary'} className="ml-auto">
324
+ {item.badge}
325
+ </Badge>
326
+ )}
327
+ </DropdownMenuItem>
328
+ )
329
+ })}
330
+ </>
331
+ )
332
+
333
+ // Mobile menu
334
+ const MobileMenu = () => (
335
+ <Sheet open={isOpen} onOpenChange={setIsOpen}>
336
+ <SheetTrigger asChild>
337
+ <Button variant="ghost" size="icon" className="md:hidden">
338
+ <Menu className="h-5 w-5" />
339
+ </Button>
340
+ </SheetTrigger>
341
+ <SheetContent side={mobileMenuPosition} className="w-80 p-0">
342
+ <div className="flex h-full flex-col">
343
+ <div className="flex items-center justify-between p-4 border-b">
344
+ {branding && (
345
+ <div className="flex items-center gap-2">
346
+ {branding.logo}
347
+ {branding.title && (
348
+ <span className="font-semibold text-lg">{branding.title}</span>
349
+ )}
350
+ </div>
351
+ )}
352
+ <Button
353
+ variant="ghost"
354
+ size="icon"
355
+ onClick={() => setIsOpen(false)}
356
+ className="h-8 w-8"
357
+ >
358
+ <X className="h-4 w-4" />
359
+ </Button>
360
+ </div>
361
+
362
+ <div className="flex-1 overflow-y-auto p-4">
363
+ {sections.map(section => (
364
+ <div key={section.id} className="space-y-1">
365
+ {section.items.map(item => (
366
+ <div key={item.id}>
367
+ {item.items && item.items.length > 0 ? (
368
+ <DropdownMenu>
369
+ <DropdownMenuTrigger asChild>
370
+ <Button
371
+ variant="ghost"
372
+ className="w-full justify-between"
373
+ disabled={item.disabled}
374
+ >
375
+ <span className="flex items-center gap-2">
376
+ {item.icon && <span>{item.icon}</span>}
377
+ {item.title}
378
+ </span>
379
+ <ChevronDown className="h-4 w-4" />
380
+ </Button>
381
+ </DropdownMenuTrigger>
382
+ <DropdownMenuContent align="start" className="w-56">
383
+ {item.items && renderDropdownMenu(item.items)}
384
+ </DropdownMenuContent>
385
+ </DropdownMenu>
386
+ ) : (
387
+ <Button
388
+ variant="ghost"
389
+ className="w-full justify-start"
390
+ disabled={item.disabled}
391
+ onClick={() => handleItemClick(item)}
392
+ >
393
+ <span className="flex items-center gap-2">
394
+ {item.icon && <span>{item.icon}</span>}
395
+ {item.title}
396
+ </span>
397
+ {item.badge && (
398
+ <Badge variant={item.badgeVariant || 'secondary'} className="ml-auto">
399
+ {item.badge}
400
+ </Badge>
401
+ )}
402
+ </Button>
403
+ )}
404
+ </div>
405
+ ))}
406
+ <Separator className="my-2" />
407
+ </div>
408
+ ))}
409
+ </div>
410
+ </div>
411
+ </SheetContent>
412
+ </Sheet>
413
+ )
414
+
415
+ // Get height based on size
416
+ const getHeight = () => {
417
+ switch (size) {
418
+ case 'sm': return 'h-14'
419
+ case 'lg': return 'h-20'
420
+ default: return 'h-16'
421
+ }
422
+ }
423
+
424
+ const navClasses = cn(
425
+ "moonui-pro w-full transition-all duration-300",
426
+ // Base variant styles
427
+ variant === 'default' && "border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60",
428
+ variant === 'floating' && "mx-4 mt-4 rounded-lg border bg-background/95 backdrop-blur shadow-lg",
429
+ variant === 'minimal' && "bg-background",
430
+ variant === 'transparent' && "bg-transparent",
431
+ // Sticky behavior
432
+ sticky && "sticky top-0 z-50",
433
+ // Scroll states
434
+ scrolled && sticky && variant !== 'transparent' && "shadow-sm",
435
+ scrolled && variant === 'transparent' && "bg-background/95 backdrop-blur border-b",
436
+ hidden && "-translate-y-full",
437
+ // Glass effect
438
+ glassmorphism && "bg-background/80 backdrop-blur-xl border-white/10",
439
+ blur && "backdrop-blur-md",
440
+ shadow && "shadow-lg",
441
+ rounded && "rounded-lg",
442
+ // Custom styles
443
+ customStyles?.background,
444
+ customStyles?.border,
445
+ className
446
+ )
447
+
448
+ return (
449
+ <>
450
+ <nav ref={navRef} className={navClasses}>
451
+ <div className={cn(
452
+ "flex items-center",
453
+ getHeight(),
454
+ maxWidth === 'sm' && "container max-w-screen-sm",
455
+ maxWidth === 'md' && "container max-w-screen-md",
456
+ maxWidth === 'lg' && "container max-w-screen-lg",
457
+ maxWidth === 'xl' && "container max-w-screen-xl",
458
+ maxWidth === '2xl' && "container max-w-screen-2xl",
459
+ maxWidth === 'full' && "w-full px-4 sm:px-6 lg:px-8"
460
+ )}>
461
+ {/* Branding */}
462
+ {branding && (
463
+ <div className={cn(
464
+ "flex items-center gap-2",
465
+ logoPosition === 'center' && !isMobile ? "absolute left-1/2 -translate-x-1/2" : "mr-4"
466
+ )}>
467
+ {logoPosition === 'left' && mobileMenuPosition === 'left' && <MobileMenu />}
468
+ <a
469
+ href={branding.href || '/'}
470
+ onClick={(e) => {
471
+ if (branding.href && onNavigate) {
472
+ e.preventDefault()
473
+ onNavigate(branding.href)
474
+ }
475
+ }}
476
+ className="flex items-center gap-2"
477
+ >
478
+ {branding.logo}
479
+ {branding.title && !isMobile && (
480
+ <span className="font-semibold text-lg">{branding.title}</span>
481
+ )}
482
+ </a>
483
+ </div>
484
+ )}
485
+
486
+ {/* Desktop Navigation */}
487
+ {!isMobile && (
488
+ <NavigationMenu className="hidden md:flex">
489
+ <NavigationMenuList>
490
+ {sections.map(section => (
491
+ <React.Fragment key={section.id}>
492
+ {section.items.map(item => {
493
+ const hasChildren = item.items && item.items.length > 0
494
+ const isActive = item.href === activePath
495
+
496
+ if (hasChildren) {
497
+ return (
498
+ <NavigationMenuItem key={item.id}>
499
+ <NavigationMenuTrigger
500
+ className={cn(
501
+ isActive && "bg-accent text-accent-foreground",
502
+ item.disabled && "opacity-50 cursor-not-allowed"
503
+ )}
504
+ disabled={item.disabled}
505
+ >
506
+ <span className="flex items-center gap-2">
507
+ {item.icon && <span>{item.icon}</span>}
508
+ {item.title}
509
+ </span>
510
+ </NavigationMenuTrigger>
511
+ <NavigationMenuContent>
512
+ {item.items && renderMegaMenuContent(item.items)}
513
+ </NavigationMenuContent>
514
+ </NavigationMenuItem>
515
+ )
516
+ }
517
+
518
+ return (
519
+ <NavigationMenuItem key={item.id}>
520
+ <NavigationMenuLink
521
+ className={cn(
522
+ navigationMenuTriggerStyle(),
523
+ isActive && "bg-accent text-accent-foreground",
524
+ item.disabled && "opacity-50 cursor-not-allowed"
525
+ )}
526
+ onClick={() => !item.disabled && handleItemClick(item)}
527
+ >
528
+ <span className="flex items-center gap-2">
529
+ {item.icon && <span>{item.icon}</span>}
530
+ {item.title}
531
+ </span>
532
+ {item.badge && (
533
+ <Badge variant={item.badgeVariant || 'secondary'} className="ml-2">
534
+ {item.badge}
535
+ </Badge>
536
+ )}
537
+ </NavigationMenuLink>
538
+ </NavigationMenuItem>
539
+ )
540
+ })}
541
+ </React.Fragment>
542
+ ))}
543
+ </NavigationMenuList>
544
+ </NavigationMenu>
545
+ )}
546
+
547
+ {/* Right side actions */}
548
+ <div className="ml-auto flex items-center gap-2">
549
+ {/* Search */}
550
+ {showSearch && (
551
+ <>
552
+ {isMobile ? (
553
+ <Button
554
+ variant="ghost"
555
+ size="icon"
556
+ onClick={() => setIsCommandOpen(true)}
557
+ >
558
+ <Search className="h-5 w-5" />
559
+ </Button>
560
+ ) : (
561
+ <div className="relative">
562
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
563
+ <Input
564
+ type="search"
565
+ placeholder={searchPlaceholder}
566
+ className="w-64 pl-9 pr-4 h-9"
567
+ value={searchValue}
568
+ onChange={(e) => handleSearch(e.target.value)}
569
+ />
570
+ {keyboardShortcuts && (
571
+ <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">
572
+ <span className="text-xs">⌘</span>K
573
+ </kbd>
574
+ )}
575
+ </div>
576
+ )}
577
+ </>
578
+ )}
579
+
580
+ {/* Theme Toggle */}
581
+ {showThemeToggle && (
582
+ <DropdownMenu>
583
+ <DropdownMenuTrigger asChild>
584
+ <Button variant="ghost" size="icon">
585
+ {theme === 'light' && <Sun className="h-5 w-5" />}
586
+ {theme === 'dark' && <Moon className="h-5 w-5" />}
587
+ {theme === 'system' && <Monitor className="h-5 w-5" />}
588
+ </Button>
589
+ </DropdownMenuTrigger>
590
+ <DropdownMenuContent align="end">
591
+ <DropdownMenuItem onClick={() => onThemeChange?.('light')}>
592
+ <Sun className="mr-2 h-4 w-4" />
593
+ Light
594
+ </DropdownMenuItem>
595
+ <DropdownMenuItem onClick={() => onThemeChange?.('dark')}>
596
+ <Moon className="mr-2 h-4 w-4" />
597
+ Dark
598
+ </DropdownMenuItem>
599
+ <DropdownMenuItem onClick={() => onThemeChange?.('system')}>
600
+ <Monitor className="mr-2 h-4 w-4" />
601
+ System
602
+ </DropdownMenuItem>
603
+ </DropdownMenuContent>
604
+ </DropdownMenu>
605
+ )}
606
+
607
+ {/* Notifications */}
608
+ {notifications && (
609
+ <DropdownMenu>
610
+ <DropdownMenuTrigger asChild>
611
+ <Button variant="ghost" size="icon" className="relative">
612
+ <Bell className="h-5 w-5" />
613
+ {notifications.count && notifications.count > 0 && (
614
+ <span className="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground flex items-center justify-center">
615
+ {notifications.count}
616
+ </span>
617
+ )}
618
+ </Button>
619
+ </DropdownMenuTrigger>
620
+ <DropdownMenuContent align="end" className="w-80">
621
+ <div className="flex items-center justify-between p-4">
622
+ <h4 className="text-sm font-semibold">Notifications</h4>
623
+ {notifications.count && notifications.count > 0 && (
624
+ <Badge variant="secondary">{notifications.count} new</Badge>
625
+ )}
626
+ </div>
627
+ <DropdownMenuSeparator />
628
+ <div className="max-h-[300px] overflow-y-auto">
629
+ {notifications.items && notifications.items.length > 0 ? (
630
+ notifications.items.map(notification => (
631
+ <DropdownMenuItem
632
+ key={notification.id}
633
+ className="flex flex-col items-start p-4 cursor-pointer"
634
+ onClick={() => notifications.onNotificationClick?.(notification.id)}
635
+ >
636
+ <div className="flex w-full items-start justify-between">
637
+ <p className={cn(
638
+ "text-sm font-medium",
639
+ !notification.read && "text-primary"
640
+ )}>
641
+ {notification.title}
642
+ </p>
643
+ {!notification.read && (
644
+ <span className="h-2 w-2 rounded-full bg-primary" />
645
+ )}
646
+ </div>
647
+ {notification.description && (
648
+ <p className="text-xs text-muted-foreground mt-1">
649
+ {notification.description}
650
+ </p>
651
+ )}
652
+ {notification.time && (
653
+ <p className="text-xs text-muted-foreground mt-2">
654
+ {notification.time}
655
+ </p>
656
+ )}
657
+ </DropdownMenuItem>
658
+ ))
659
+ ) : (
660
+ <div className="p-4 text-center text-sm text-muted-foreground">
661
+ No notifications
662
+ </div>
663
+ )}
664
+ </div>
665
+ </DropdownMenuContent>
666
+ </DropdownMenu>
667
+ )}
668
+
669
+ {/* CTA Button */}
670
+ {cta && (
671
+ <Button
672
+ variant={cta.variant || 'primary'}
673
+ size="sm"
674
+ onClick={() => {
675
+ if (cta.action) {
676
+ cta.action()
677
+ } else if (cta.href && onNavigate) {
678
+ onNavigate(cta.href)
679
+ }
680
+ }}
681
+ className={!isMobile ? 'ml-2' : ''}
682
+ >
683
+ {cta.text}
684
+ </Button>
685
+ )}
686
+
687
+ {/* User Menu */}
688
+ {userMenu && (
689
+ <DropdownMenu>
690
+ <DropdownMenuTrigger asChild>
691
+ <Button variant="ghost" size="icon" className="relative">
692
+ <Avatar className="h-8 w-8">
693
+ <AvatarImage src={userMenu.user?.avatar} alt={userMenu.user?.name} />
694
+ <AvatarFallback>
695
+ {userMenu.user?.name?.charAt(0) || <User className="h-4 w-4" />}
696
+ </AvatarFallback>
697
+ </Avatar>
698
+ </Button>
699
+ </DropdownMenuTrigger>
700
+ <DropdownMenuContent align="end" className="w-56">
701
+ {userMenu.user && (
702
+ <>
703
+ <div className="p-2">
704
+ <p className="text-sm font-medium">{userMenu.user.name}</p>
705
+ <p className="text-xs text-muted-foreground">{userMenu.user.email}</p>
706
+ </div>
707
+ <DropdownMenuSeparator />
708
+ </>
709
+ )}
710
+ {userMenu.items && renderDropdownMenu(userMenu.items)}
711
+ </DropdownMenuContent>
712
+ </DropdownMenu>
713
+ )}
714
+ </div>
715
+ </div>
716
+
717
+ {/* Animated background */}
718
+ {animatedBackground && (
719
+ <motion.div
720
+ className="absolute inset-0 -z-10 opacity-30"
721
+ animate={{
722
+ background: [
723
+ 'radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.3), transparent 50%)',
724
+ 'radial-gradient(circle at 80% 50%, rgba(120, 119, 198, 0.3), transparent 50%)',
725
+ 'radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.3), transparent 50%)',
726
+ ],
727
+ }}
728
+ transition={{
729
+ duration: 10,
730
+ repeat: Infinity,
731
+ ease: "linear"
732
+ }}
733
+ />
734
+ )}
735
+
736
+ {/* Scroll Progress Bar */}
737
+ {showScrollProgress && (
738
+ <div className="absolute bottom-0 left-0 w-full h-0.5 bg-muted">
739
+ <div
740
+ className="h-full bg-primary transition-all duration-150"
741
+ style={{ width: `${scrollProgress}%` }}
742
+ />
743
+ </div>
744
+ )}
745
+ </nav>
746
+
747
+ {/* Command Dialog for search */}
748
+ <CommandDialog open={isCommandOpen} onOpenChange={setIsCommandOpen}>
749
+ <CommandInput
750
+ placeholder={searchPlaceholder}
751
+ value={searchValue}
752
+ onValueChange={handleSearch}
753
+ />
754
+ <CommandList>
755
+ <CommandEmpty>No results found.</CommandEmpty>
756
+ {sections.map(section => (
757
+ <CommandGroup key={section.id}>
758
+ {section.items.map(item => (
759
+ <CommandItem
760
+ key={item.id}
761
+ disabled={item.disabled}
762
+ onSelect={() => {
763
+ handleItemClick(item)
764
+ setIsCommandOpen(false)
765
+ }}
766
+ >
767
+ <span className="flex items-center gap-2">
768
+ {item.icon && <span>{item.icon}</span>}
769
+ {item.title}
770
+ </span>
771
+ </CommandItem>
772
+ ))}
773
+ </CommandGroup>
774
+ ))}
775
+ </CommandList>
776
+ </CommandDialog>
777
+ </>
778
+ )
779
+ }
780
+
781
+ export default Navbar