@fayz-ai/ui 0.9.0 → 0.10.1

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 (156) hide show
  1. package/README.md +2 -2
  2. package/dist/analytics/AnalyticsCard.d.ts +10 -0
  3. package/dist/analytics/AnalyticsCard.d.ts.map +1 -0
  4. package/dist/analytics/AnalyticsControls.d.ts +18 -0
  5. package/dist/analytics/AnalyticsControls.d.ts.map +1 -0
  6. package/dist/analytics/AnalyticsGrid.d.ts +16 -0
  7. package/dist/analytics/AnalyticsGrid.d.ts.map +1 -0
  8. package/dist/analytics/context.d.ts +58 -0
  9. package/dist/analytics/context.d.ts.map +1 -0
  10. package/dist/analytics/derive.d.ts +81 -0
  11. package/dist/analytics/derive.d.ts.map +1 -0
  12. package/dist/analytics/index.d.ts +13 -0
  13. package/dist/analytics/index.d.ts.map +1 -0
  14. package/dist/analytics/range.d.ts +22 -0
  15. package/dist/analytics/range.d.ts.map +1 -0
  16. package/dist/{chunk-PVIB724L.js → chunk-22EVFMS3.js} +5 -3
  17. package/dist/chunk-22EVFMS3.js.map +1 -0
  18. package/dist/{chunk-G6OKZ7WL.js → chunk-6V5MGYXH.js} +13 -9
  19. package/dist/chunk-6V5MGYXH.js.map +1 -0
  20. package/dist/{chunk-KV3MHB6N.js → chunk-H63TOSQ7.js} +196 -53
  21. package/dist/chunk-H63TOSQ7.js.map +1 -0
  22. package/dist/{chunk-OUPJDGKE.js → chunk-I6CPKAOD.js} +134 -37
  23. package/dist/chunk-I6CPKAOD.js.map +1 -0
  24. package/dist/{chunk-YRVE4FOD.js → chunk-OKZ6WQB3.js} +3 -3
  25. package/dist/chunk-OKZ6WQB3.js.map +1 -0
  26. package/dist/{chunk-S5KNM4D2.js → chunk-SEIY2F5F.js} +235 -107
  27. package/dist/chunk-SEIY2F5F.js.map +1 -0
  28. package/dist/{chunk-75EFDLST.js → chunk-VAKWUUVC.js} +157 -6
  29. package/dist/chunk-VAKWUUVC.js.map +1 -0
  30. package/dist/dashboard/OnboardingWidget.d.ts +2 -14
  31. package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
  32. package/dist/dashboard/SidebarOnboarding.d.ts +28 -0
  33. package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -0
  34. package/dist/dashboard/index.d.ts +2 -0
  35. package/dist/dashboard/index.d.ts.map +1 -1
  36. package/dist/dashboard/index.js +4 -4
  37. package/dist/dashboard/types.d.ts +18 -0
  38. package/dist/dashboard/types.d.ts.map +1 -1
  39. package/dist/dashboard/useOnboardingProgress.d.ts +28 -0
  40. package/dist/dashboard/useOnboardingProgress.d.ts.map +1 -0
  41. package/dist/icons.js +1 -1
  42. package/dist/index.d.ts +5 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +960 -14
  45. package/dist/index.js.map +1 -1
  46. package/dist/layout/AppShell.d.ts +14 -1
  47. package/dist/layout/AppShell.d.ts.map +1 -1
  48. package/dist/layout/ModulePage.d.ts +11 -2
  49. package/dist/layout/ModulePage.d.ts.map +1 -1
  50. package/dist/layout/Sidebar.d.ts +8 -1
  51. package/dist/layout/Sidebar.d.ts.map +1 -1
  52. package/dist/layout/index.js +4 -4
  53. package/dist/lib/cn.d.ts.map +1 -0
  54. package/dist/preset.d.ts +3 -0
  55. package/dist/preset.d.ts.map +1 -0
  56. package/dist/primitives/data-table.d.ts +19 -4
  57. package/dist/primitives/data-table.d.ts.map +1 -1
  58. package/dist/primitives/date-picker.d.ts +4 -2
  59. package/dist/primitives/date-picker.d.ts.map +1 -1
  60. package/dist/primitives/filter-chip.d.ts +26 -0
  61. package/dist/primitives/filter-chip.d.ts.map +1 -0
  62. package/dist/primitives/header-tabs.d.ts +32 -0
  63. package/dist/primitives/header-tabs.d.ts.map +1 -0
  64. package/dist/primitives/hint.d.ts +25 -0
  65. package/dist/primitives/hint.d.ts.map +1 -0
  66. package/dist/primitives/index.d.ts +7 -0
  67. package/dist/primitives/index.d.ts.map +1 -1
  68. package/dist/primitives/index.js +4 -4
  69. package/dist/primitives/list-view.d.ts +14 -1
  70. package/dist/primitives/list-view.d.ts.map +1 -1
  71. package/dist/primitives/page-header.d.ts +23 -0
  72. package/dist/primitives/page-header.d.ts.map +1 -0
  73. package/dist/primitives/stat-tiles.d.ts +30 -0
  74. package/dist/primitives/stat-tiles.d.ts.map +1 -0
  75. package/dist/primitives/switch.d.ts +11 -0
  76. package/dist/primitives/switch.d.ts.map +1 -0
  77. package/dist/stores/index.d.ts +1 -0
  78. package/dist/stores/index.d.ts.map +1 -1
  79. package/dist/stores/page-header.store.d.ts +69 -0
  80. package/dist/stores/page-header.store.d.ts.map +1 -0
  81. package/package.json +6 -15
  82. package/dist/chunk-75EFDLST.js.map +0 -1
  83. package/dist/chunk-G6OKZ7WL.js.map +0 -1
  84. package/dist/chunk-KV3MHB6N.js.map +0 -1
  85. package/dist/chunk-OUPJDGKE.js.map +0 -1
  86. package/dist/chunk-PVIB724L.js.map +0 -1
  87. package/dist/chunk-S5KNM4D2.js.map +0 -1
  88. package/dist/chunk-YRVE4FOD.js.map +0 -1
  89. package/dist/utils/cn.d.ts.map +0 -1
  90. package/src/dashboard/ChartWidget.tsx +0 -50
  91. package/src/dashboard/ChartWidgetInner.tsx +0 -83
  92. package/src/dashboard/DashboardCanvas.tsx +0 -210
  93. package/src/dashboard/DashboardCustomizeMenu.tsx +0 -53
  94. package/src/dashboard/DashboardGrid.tsx +0 -35
  95. package/src/dashboard/DashboardRange.tsx +0 -62
  96. package/src/dashboard/GoldCard.tsx +0 -38
  97. package/src/dashboard/KpiCard.tsx +0 -238
  98. package/src/dashboard/OnboardingWidget.tsx +0 -140
  99. package/src/dashboard/TableWidget.tsx +0 -51
  100. package/src/dashboard/define.ts +0 -53
  101. package/src/dashboard/icon.ts +0 -13
  102. package/src/dashboard/index.ts +0 -38
  103. package/src/dashboard/layout.ts +0 -50
  104. package/src/dashboard/preferences.ts +0 -71
  105. package/src/dashboard/types.ts +0 -41
  106. package/src/icons.ts +0 -66
  107. package/src/index.ts +0 -238
  108. package/src/layout/AppShell.tsx +0 -987
  109. package/src/layout/ContentSplit.tsx +0 -153
  110. package/src/layout/ModulePage.tsx +0 -466
  111. package/src/layout/PageTransition.tsx +0 -57
  112. package/src/layout/SaveBar.tsx +0 -247
  113. package/src/layout/Sidebar.tsx +0 -535
  114. package/src/layout/Topbar.tsx +0 -462
  115. package/src/layout/index.ts +0 -39
  116. package/src/layout/useCrossModuleBack.ts +0 -30
  117. package/src/primitives/Avatar.tsx +0 -48
  118. package/src/primitives/SearchCombobox.tsx +0 -216
  119. package/src/primitives/badge.tsx +0 -68
  120. package/src/primitives/breadcrumb.tsx +0 -43
  121. package/src/primitives/button.tsx +0 -60
  122. package/src/primitives/card.tsx +0 -79
  123. package/src/primitives/checkbox.tsx +0 -53
  124. package/src/primitives/confirm-dialog.tsx +0 -125
  125. package/src/primitives/currency-input.tsx +0 -121
  126. package/src/primitives/data-table.tsx +0 -160
  127. package/src/primitives/date-picker.tsx +0 -225
  128. package/src/primitives/dropdown.tsx +0 -196
  129. package/src/primitives/index.ts +0 -87
  130. package/src/primitives/input.tsx +0 -50
  131. package/src/primitives/list-view.tsx +0 -252
  132. package/src/primitives/markdown-editor.tsx +0 -75
  133. package/src/primitives/markdown.tsx +0 -169
  134. package/src/primitives/modal.tsx +0 -156
  135. package/src/primitives/phone-link.tsx +0 -138
  136. package/src/primitives/popover.tsx +0 -30
  137. package/src/primitives/search-select.tsx +0 -314
  138. package/src/primitives/segmented-control.tsx +0 -66
  139. package/src/primitives/select.tsx +0 -157
  140. package/src/primitives/separator.tsx +0 -29
  141. package/src/primitives/sheet.tsx +0 -181
  142. package/src/primitives/skeleton.tsx +0 -46
  143. package/src/primitives/tabs.tsx +0 -53
  144. package/src/primitives/time-picker.tsx +0 -148
  145. package/src/primitives/toast.tsx +0 -46
  146. package/src/primitives/tooltip.tsx +0 -28
  147. package/src/stores/index.ts +0 -10
  148. package/src/stores/layout.store.ts +0 -31
  149. package/src/stores/theme.store.ts +0 -457
  150. package/src/tailwind.ts +0 -43
  151. package/src/theme/index.ts +0 -94
  152. package/src/theme/preset.ts +0 -209
  153. package/src/theme/presets.ts +0 -106
  154. package/src/utils/cn.ts +0 -6
  155. /package/dist/{utils → lib}/cn.d.ts +0 -0
  156. /package/{src → dist}/styles.css +0 -0
@@ -1,181 +0,0 @@
1
- import * as React from 'react'
2
- import * as DialogPrimitive from '@radix-ui/react-dialog'
3
- import { X } from 'lucide-react'
4
-
5
- import { cn } from '../utils/cn'
6
-
7
- // ---------------------------------------------------------------------------
8
- // Inject sheet animations once (module-level side effect)
9
- // ---------------------------------------------------------------------------
10
-
11
- const STYLE_ID = 'saas-sheet-styles'
12
- if (typeof document !== 'undefined' && !document.getElementById(STYLE_ID)) {
13
- const style = document.createElement('style')
14
- style.id = STYLE_ID
15
- style.textContent = `
16
- @keyframes saas-sheet-fade-in { from { opacity: 0 } to { opacity: 1 } }
17
- @keyframes saas-sheet-fade-out { from { opacity: 1 } to { opacity: 0 } }
18
- @keyframes saas-sheet-slide-in { from { transform: translateX(100%) } to { transform: translateX(0) } }
19
- @keyframes saas-sheet-slide-out { from { transform: translateX(0) } to { transform: translateX(100%) } }
20
- .saas-sheet-overlay[data-state="open"] { animation: saas-sheet-fade-in 200ms ease-out }
21
- .saas-sheet-overlay[data-state="closed"] { animation: saas-sheet-fade-out 150ms ease-in }
22
- .saas-sheet-content[data-state="open"] { animation: saas-sheet-slide-in 280ms cubic-bezier(0.16, 1, 0.3, 1) }
23
- .saas-sheet-content[data-state="closed"] { animation: saas-sheet-slide-out 200ms ease-in }
24
- `
25
- document.head.appendChild(style)
26
- }
27
-
28
- // ---------------------------------------------------------------------------
29
- // Root, Trigger, Close — re-exported from Radix
30
- // ---------------------------------------------------------------------------
31
-
32
- const Sheet = DialogPrimitive.Root
33
- const SheetTrigger = DialogPrimitive.Trigger
34
- const SheetClose = DialogPrimitive.Close
35
- const SheetPortal = DialogPrimitive.Portal
36
-
37
- // ---------------------------------------------------------------------------
38
- // Overlay
39
- // ---------------------------------------------------------------------------
40
-
41
- const SheetOverlay = React.forwardRef<
42
- React.ElementRef<typeof DialogPrimitive.Overlay>,
43
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
44
- >(({ className, ...props }, ref) => (
45
- <DialogPrimitive.Overlay
46
- ref={ref}
47
- className={cn(
48
- 'saas-sheet-overlay fixed inset-0 z-50 bg-black/40 backdrop-blur-[2px]',
49
- className
50
- )}
51
- {...props}
52
- />
53
- ))
54
- SheetOverlay.displayName = 'SheetOverlay'
55
-
56
- // ---------------------------------------------------------------------------
57
- // Content — slides in from the right
58
- // ---------------------------------------------------------------------------
59
-
60
- const SheetContent = React.forwardRef<
61
- React.ElementRef<typeof DialogPrimitive.Content>,
62
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
63
- /** Width class override — defaults to max-w-md */
64
- width?: string
65
- /** Hide the built-in close button */
66
- hideClose?: boolean
67
- /** Overlay style: "blur" (default), "dim" (no blur), "none" (transparent) */
68
- overlay?: 'blur' | 'dim' | 'none'
69
- }
70
- >(({ className, children, width, hideClose, overlay = 'blur', ...props }, ref) => (
71
- <SheetPortal>
72
- <SheetOverlay className={overlay === 'none' ? 'bg-transparent backdrop-blur-0' : overlay === 'dim' ? 'backdrop-blur-0' : undefined} />
73
- <DialogPrimitive.Content
74
- ref={ref}
75
- className={cn(
76
- 'fayz-glass-surface saas-sheet-content fixed inset-y-0 right-0 z-50 flex flex-col border-l bg-card shadow-2xl outline-none',
77
- 'w-full',
78
- width ?? 'max-w-md',
79
- className
80
- )}
81
- {...props}
82
- >
83
- {children}
84
- {!hideClose && (
85
- <DialogPrimitive.Close className="absolute right-4 top-4 z-10 flex h-7 w-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring">
86
- <X className="h-4 w-4" />
87
- <span className="sr-only">Close</span>
88
- </DialogPrimitive.Close>
89
- )}
90
- </DialogPrimitive.Content>
91
- </SheetPortal>
92
- ))
93
- SheetContent.displayName = 'SheetContent'
94
-
95
- // ---------------------------------------------------------------------------
96
- // Header — top section with title + description
97
- // ---------------------------------------------------------------------------
98
-
99
- function SheetHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
100
- return (
101
- <div
102
- className={cn('flex flex-col gap-1 px-5 py-4 border-b shrink-0', className)}
103
- {...props}
104
- />
105
- )
106
- }
107
- SheetHeader.displayName = 'SheetHeader'
108
-
109
- // ---------------------------------------------------------------------------
110
- // Footer — bottom section for actions
111
- // ---------------------------------------------------------------------------
112
-
113
- function SheetFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
114
- return (
115
- <div
116
- className={cn('flex items-center gap-2 px-5 py-3 border-t shrink-0', className)}
117
- {...props}
118
- />
119
- )
120
- }
121
- SheetFooter.displayName = 'SheetFooter'
122
-
123
- // ---------------------------------------------------------------------------
124
- // Title
125
- // ---------------------------------------------------------------------------
126
-
127
- const SheetTitle = React.forwardRef<
128
- React.ElementRef<typeof DialogPrimitive.Title>,
129
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
130
- >(({ className, ...props }, ref) => (
131
- <DialogPrimitive.Title
132
- ref={ref}
133
- className={cn('text-sm font-semibold leading-none tracking-tight', className)}
134
- {...props}
135
- />
136
- ))
137
- SheetTitle.displayName = 'SheetTitle'
138
-
139
- // ---------------------------------------------------------------------------
140
- // Description
141
- // ---------------------------------------------------------------------------
142
-
143
- const SheetDescription = React.forwardRef<
144
- React.ElementRef<typeof DialogPrimitive.Description>,
145
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
146
- >(({ className, ...props }, ref) => (
147
- <DialogPrimitive.Description
148
- ref={ref}
149
- className={cn('text-xs text-muted-foreground', className)}
150
- {...props}
151
- />
152
- ))
153
- SheetDescription.displayName = 'SheetDescription'
154
-
155
- // ---------------------------------------------------------------------------
156
- // Body — scrollable content area
157
- // ---------------------------------------------------------------------------
158
-
159
- function SheetBody({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
160
- return (
161
- <div
162
- className={cn('flex-1 overflow-y-auto px-5 py-5', className)}
163
- {...props}
164
- />
165
- )
166
- }
167
- SheetBody.displayName = 'SheetBody'
168
-
169
- export {
170
- Sheet,
171
- SheetPortal,
172
- SheetOverlay,
173
- SheetTrigger,
174
- SheetClose,
175
- SheetContent,
176
- SheetHeader,
177
- SheetBody,
178
- SheetFooter,
179
- SheetTitle,
180
- SheetDescription,
181
- }
@@ -1,46 +0,0 @@
1
- import { cn } from '../utils/cn'
2
-
3
- function Skeleton({
4
- className,
5
- ...props
6
- }: React.HTMLAttributes<HTMLDivElement>) {
7
- return (
8
- <div
9
- className={cn('animate-pulse rounded-md bg-muted', className)}
10
- {...props}
11
- />
12
- )
13
- }
14
-
15
- /**
16
- * Skeleton loader that mimics a table with rows and columns.
17
- * Drop-in replacement for "Loading..." text in list views.
18
- */
19
- function TableSkeleton({ columns = 4, rows = 2 }: { columns?: number; rows?: number }) {
20
- return (
21
- <div className="rounded-lg border overflow-hidden">
22
- <div className="border-b bg-muted/30 flex gap-4 px-4 py-2.5">
23
- {Array.from({ length: columns }).map((_, i) => (
24
- <Skeleton key={i} className="h-3 flex-1 rounded bg-muted-foreground/10" />
25
- ))}
26
- </div>
27
- <div className="divide-y">
28
- {Array.from({ length: rows }).map((_, r) => (
29
- <div key={r} className="flex items-center gap-4 px-4 py-3.5">
30
- {Array.from({ length: columns }).map((_, c) => (
31
- <Skeleton
32
- key={c}
33
- className={cn(
34
- 'h-3 rounded',
35
- c === 0 ? 'flex-[2]' : 'flex-1',
36
- )}
37
- />
38
- ))}
39
- </div>
40
- ))}
41
- </div>
42
- </div>
43
- )
44
- }
45
-
46
- export { Skeleton, TableSkeleton }
@@ -1,53 +0,0 @@
1
- import * as React from 'react'
2
- import * as TabsPrimitive from '@radix-ui/react-tabs'
3
-
4
- import { cn } from '../utils/cn'
5
-
6
- const Tabs = TabsPrimitive.Root
7
-
8
- const TabsList = React.forwardRef<
9
- React.ElementRef<typeof TabsPrimitive.List>,
10
- React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
11
- >(({ className, ...props }, ref) => (
12
- <TabsPrimitive.List
13
- ref={ref}
14
- className={cn(
15
- 'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
16
- className
17
- )}
18
- {...props}
19
- />
20
- ))
21
- TabsList.displayName = TabsPrimitive.List.displayName
22
-
23
- const TabsTrigger = React.forwardRef<
24
- React.ElementRef<typeof TabsPrimitive.Trigger>,
25
- React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
26
- >(({ className, ...props }, ref) => (
27
- <TabsPrimitive.Trigger
28
- ref={ref}
29
- className={cn(
30
- 'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
31
- className
32
- )}
33
- {...props}
34
- />
35
- ))
36
- TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37
-
38
- const TabsContent = React.forwardRef<
39
- React.ElementRef<typeof TabsPrimitive.Content>,
40
- React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
41
- >(({ className, ...props }, ref) => (
42
- <TabsPrimitive.Content
43
- ref={ref}
44
- className={cn(
45
- 'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
46
- className
47
- )}
48
- {...props}
49
- />
50
- ))
51
- TabsContent.displayName = TabsPrimitive.Content.displayName
52
-
53
- export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -1,148 +0,0 @@
1
- import React, { useState, useEffect, useRef, useMemo } from 'react'
2
- import { createPortal } from 'react-dom'
3
- import { Clock } from 'lucide-react'
4
-
5
- // ---------------------------------------------------------------------------
6
- // TimePicker — dropdown time slot selector, Google Calendar style
7
- // ---------------------------------------------------------------------------
8
-
9
- function formatTime12(h: number, m: number) {
10
- const period = h >= 12 ? 'pm' : 'am'
11
- const h12 = h % 12 || 12
12
- return `${h12}:${String(m).padStart(2, '0')}${period}`
13
- }
14
-
15
- function formatTime24(h: number, m: number) {
16
- return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`
17
- }
18
-
19
- interface TimePickerProps {
20
- /** Value as HH:MM (24h) */
21
- value: string
22
- onChange: (value: string) => void
23
- /** Interval in minutes between slots (default: 15) */
24
- interval?: number
25
- /** Min time as HH:MM (default: '00:00') */
26
- min?: string
27
- /** Max time as HH:MM (default: '23:45') */
28
- max?: string
29
- /** Use 12h format for display (default: false) */
30
- use12h?: boolean
31
- /** Read-only mode */
32
- readOnly?: boolean
33
- className?: string
34
- /** Open the dropdown immediately on mount */
35
- defaultOpen?: boolean
36
- /** Called when the dropdown opens or closes */
37
- onOpenChange?: (open: boolean) => void
38
- }
39
-
40
- export function TimePicker({ value, onChange, interval = 15, min = '00:00', max = '23:45', use12h = false, readOnly, className, defaultOpen, onOpenChange }: TimePickerProps) {
41
- const [open, setOpen] = useState(defaultOpen ?? false)
42
- const ref = useRef<HTMLDivElement>(null)
43
- const triggerRef = useRef<HTMLButtonElement>(null)
44
- const listRef = useRef<HTMLDivElement>(null)
45
- const [pos, setPos] = useState<{ top: number; left: number; width: number }>({ top: 0, left: 0, width: 0 })
46
-
47
- // Position the portal dropdown
48
- useEffect(() => {
49
- if (!open || !triggerRef.current) return
50
- const rect = triggerRef.current.getBoundingClientRect()
51
- setPos({ top: rect.bottom + 4, left: rect.left, width: Math.max(rect.width, 120) })
52
- }, [open])
53
-
54
- useEffect(() => {
55
- if (!open) return
56
- const handler = (e: MouseEvent) => {
57
- const target = e.target as Node
58
- if (ref.current?.contains(target)) return
59
- if (listRef.current?.contains(target)) return
60
- setOpen(false)
61
- onOpenChange?.(false)
62
- }
63
- document.addEventListener('mousedown', handler)
64
- return () => document.removeEventListener('mousedown', handler)
65
- }, [open])
66
-
67
- // Scroll to selected time when opening
68
- useEffect(() => {
69
- if (!open || !listRef.current || !value) return
70
- const el = listRef.current.querySelector('[data-selected="true"]')
71
- if (el) el.scrollIntoView({ block: 'center' })
72
- }, [open, value])
73
-
74
- const slots = useMemo(() => {
75
- const [minH, minM] = min.split(':').map(Number)
76
- const [maxH, maxM] = max.split(':').map(Number)
77
- const minTotal = minH * 60 + minM
78
- const maxTotal = maxH * 60 + maxM
79
- const result: { value: string; label: string }[] = []
80
-
81
- for (let t = minTotal; t <= maxTotal; t += interval) {
82
- const h = Math.floor(t / 60)
83
- const m = t % 60
84
- result.push({
85
- value: formatTime24(h, m),
86
- label: use12h ? formatTime12(h, m) : formatTime24(h, m),
87
- })
88
- }
89
- return result
90
- }, [interval, min, max, use12h])
91
-
92
- const displayValue = useMemo(() => {
93
- if (!value) return ''
94
- const [h, m] = value.split(':').map(Number)
95
- return use12h ? formatTime12(h, m) : formatTime24(h, m)
96
- }, [value, use12h])
97
-
98
- function handleKeyDown(e: React.KeyboardEvent) {
99
- if (readOnly) return
100
- if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
101
- e.preventDefault()
102
- const idx = slots.findIndex((s) => s.value === value)
103
- const next = e.key === 'ArrowDown'
104
- ? Math.min(idx + 1, slots.length - 1)
105
- : Math.max(idx - 1, 0)
106
- if (slots[next]) onChange(slots[next].value)
107
- } else if (e.key === 'Enter' || e.key === 'Escape') {
108
- setOpen(false)
109
- }
110
- }
111
-
112
- return (
113
- <div ref={ref} className={`relative ${className ?? ''}`}>
114
- {/* Trigger */}
115
- <button ref={triggerRef} type="button"
116
- onClick={() => !readOnly && setOpen((p) => !p)}
117
- onKeyDown={handleKeyDown}
118
- disabled={readOnly}
119
- className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-sm w-full text-left transition-colors ${
120
- readOnly ? 'bg-muted/40 text-muted-foreground cursor-default' : 'bg-background hover:bg-muted/30'
121
- }`}>
122
- <Clock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
123
- <span>{displayValue || '--:--'}</span>
124
- </button>
125
-
126
- {/* Dropdown — portal to escape overflow containers */}
127
- {open && !readOnly && createPortal(
128
- <div ref={listRef} data-modal-passthrough
129
- style={{ position: 'fixed', top: pos.top, left: pos.left, width: pos.width, zIndex: 9999, pointerEvents: 'auto' }}
130
- className="fayz-glass-surface min-w-[120px] max-h-48 overflow-y-auto rounded-xl border bg-popover shadow-xl py-1">
131
- {slots.map((slot) => {
132
- const selected = slot.value === value
133
- return (
134
- <button key={slot.value} type="button" data-selected={selected}
135
- onClick={() => { onChange(slot.value); setOpen(false) }}
136
- className={`block w-full px-3 py-1.5 text-sm text-left transition-colors ${
137
- selected ? 'bg-primary/10 text-primary font-medium' : 'hover:bg-muted/50'
138
- }`}>
139
- {slot.label}
140
- </button>
141
- )
142
- })}
143
- </div>,
144
- document.body,
145
- )}
146
- </div>
147
- )
148
- }
@@ -1,46 +0,0 @@
1
- import * as React from 'react'
2
- import { Toaster, toast } from 'sonner'
3
- import { useSaveBarVisible } from '../layout/SaveBar'
4
-
5
- interface ToastProviderProps {
6
- position?:
7
- | 'top-left'
8
- | 'top-center'
9
- | 'top-right'
10
- | 'bottom-left'
11
- | 'bottom-center'
12
- | 'bottom-right'
13
- theme?: 'light' | 'dark' | 'system'
14
- }
15
-
16
- function ToastProvider({
17
- position = 'bottom-center',
18
- theme = 'system',
19
- }: ToastProviderProps) {
20
- // Stack toasts ABOVE the floating SaveBar (instead of overlapping it) by
21
- // lifting the bottom offset while the bar is on screen.
22
- const saveBarVisible = useSaveBarVisible()
23
- const bottomOffset = position.startsWith('bottom') && saveBarVisible ? 84 : 24
24
- return (
25
- <Toaster
26
- position={position}
27
- theme={theme}
28
- offset={bottomOffset}
29
- closeButton
30
- richColors
31
- toastOptions={{
32
- classNames: {
33
- // Format harmonized with the SaveBar island: soft ring + xl shadow,
34
- // generous rounded-2xl radius (full-pill looks wrong on multi-line
35
- // toasts). Colors stay semantic (richColors).
36
- toast: 'group !gap-3 !rounded-3xl !border !px-4 !py-3 !shadow-xl !ring-1 !ring-black/5',
37
- title: '!text-sm !font-semibold',
38
- description: '!text-xs !mt-0.5',
39
- closeButton: '!opacity-0 group-hover:!opacity-60 hover:!opacity-100 !transition-opacity',
40
- },
41
- }}
42
- />
43
- )
44
- }
45
-
46
- export { ToastProvider, toast }
@@ -1,28 +0,0 @@
1
- import * as React from 'react'
2
- import * as TooltipPrimitive from '@radix-ui/react-tooltip'
3
-
4
- import { cn } from '../utils/cn'
5
-
6
- const TooltipProvider = TooltipPrimitive.Provider
7
-
8
- const Tooltip = TooltipPrimitive.Root
9
-
10
- const TooltipTrigger = TooltipPrimitive.Trigger
11
-
12
- const TooltipContent = React.forwardRef<
13
- React.ElementRef<typeof TooltipPrimitive.Content>,
14
- React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
15
- >(({ className, sideOffset = 4, ...props }, ref) => (
16
- <TooltipPrimitive.Content
17
- ref={ref}
18
- sideOffset={sideOffset}
19
- className={cn(
20
- 'fayz-glass-surface z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
21
- className
22
- )}
23
- {...props}
24
- />
25
- ))
26
- TooltipContent.displayName = TooltipPrimitive.Content.displayName
27
-
28
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -1,10 +0,0 @@
1
- export { useLayoutStore } from './layout.store'
2
- export {
3
- useThemeStore,
4
- lightTheme,
5
- darkTheme,
6
- type ThemeTokens,
7
- type SemanticColors,
8
- type UIPerceptionTokens,
9
- type CreateThemeOptions,
10
- } from './theme.store'
@@ -1,31 +0,0 @@
1
- import { create } from 'zustand'
2
-
3
- interface LayoutState {
4
- sidebarOpen: boolean
5
- sidebarCollapsed: boolean
6
- mobileDrawerOpen: boolean
7
- commandPaletteOpen: boolean
8
- toggleSidebar: () => void
9
- setSidebarCollapsed: (collapsed: boolean) => void
10
- toggleMobileDrawer: () => void
11
- setCommandPaletteOpen: (open: boolean) => void
12
- }
13
-
14
- export const useLayoutStore = create<LayoutState>((set) => ({
15
- sidebarOpen: true,
16
- sidebarCollapsed: false,
17
- mobileDrawerOpen: false,
18
- commandPaletteOpen: false,
19
-
20
- toggleSidebar: () =>
21
- set((state) => ({ sidebarOpen: !state.sidebarOpen })),
22
-
23
- setSidebarCollapsed: (collapsed) =>
24
- set({ sidebarCollapsed: collapsed }),
25
-
26
- toggleMobileDrawer: () =>
27
- set((state) => ({ mobileDrawerOpen: !state.mobileDrawerOpen })),
28
-
29
- setCommandPaletteOpen: (open) =>
30
- set({ commandPaletteOpen: open }),
31
- }))