@fayz-ai/ui 0.9.0 → 0.10.0

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 +4 -13
  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/styles.css +0 -461
  151. package/src/tailwind.ts +0 -43
  152. package/src/theme/index.ts +0 -94
  153. package/src/theme/preset.ts +0 -209
  154. package/src/theme/presets.ts +0 -106
  155. package/src/utils/cn.ts +0 -6
  156. /package/dist/{utils → lib}/cn.d.ts +0 -0
@@ -1,247 +0,0 @@
1
- import * as React from 'react'
2
- import { Loader2 } from 'lucide-react'
3
- import { useTranslation } from '@fayz-ai/core'
4
- import { cn } from '../utils/cn'
5
-
6
- // ---------------------------------------------------------------------------
7
- // SaveBar — the single, app-wide "Unsaved changes" floating island. A form
8
- // declares its state via useSaveBar({ dirty, saving, onSave, onDiscard }); the
9
- // shell renders ONE <SaveBar/> (centered, floating above the content area) that
10
- // appears whenever the active form is dirty. Mirrors the BackStyleProvider /
11
- // PageHeaderActions idiom: decide-once at the shell, hook-in at the page.
12
- //
13
- // Handlers live in a ref (not state) so per-render closure changes never cause
14
- // re-render loops; only the primitive `dirty`/`saving` flags drive visibility.
15
- // ---------------------------------------------------------------------------
16
-
17
- export interface SaveBarRegistration {
18
- /** Show the bar when true (form has unsaved changes). */
19
- dirty: boolean
20
- /** Save in progress — disables buttons and shows a spinner. */
21
- saving?: boolean
22
- onSave: () => void
23
- onDiscard?: () => void
24
- /** Override the "Unsaved changes" message. */
25
- message?: string
26
- /** Override the Save / Discard button labels. */
27
- saveLabel?: string
28
- discardLabel?: string
29
- }
30
-
31
- interface SaveBarState {
32
- visible: boolean
33
- saving: boolean
34
- message?: string
35
- saveLabel?: string
36
- discardLabel?: string
37
- }
38
-
39
- interface SaveBarApi {
40
- setState: React.Dispatch<React.SetStateAction<SaveBarState>>
41
- handlers: React.MutableRefObject<{ onSave?: () => void; onDiscard?: () => void }>
42
- }
43
-
44
- // Two contexts on purpose: the API context is STABLE (so useSaveBar's effect
45
- // deps don't change every render → no loop), the state context changes to
46
- // re-render only <SaveBar/>.
47
- const SaveBarApiContext = React.createContext<SaveBarApi | null>(null)
48
- const SaveBarStateContext = React.createContext<SaveBarState>({ visible: false, saving: false })
49
-
50
- // Module-level "is the SaveBar showing" signal so sibling UI that isn't a
51
- // descendant of SaveBarProvider (e.g. the ToastProvider) can react to it —
52
- // used to lift toasts above the bar so they stack instead of overlapping.
53
- let _saveBarVisible = false
54
- const _saveBarSubs = new Set<() => void>()
55
- function setSaveBarVisibleSignal(v: boolean) {
56
- if (v === _saveBarVisible) return
57
- _saveBarVisible = v
58
- _saveBarSubs.forEach((fn) => fn())
59
- }
60
-
61
- /** Subscribe to whether the floating SaveBar is currently on screen. */
62
- export function useSaveBarVisible(): boolean {
63
- return React.useSyncExternalStore(
64
- (cb) => { _saveBarSubs.add(cb); return () => { _saveBarSubs.delete(cb) } },
65
- () => _saveBarVisible,
66
- () => false,
67
- )
68
- }
69
-
70
- export function SaveBarProvider({ children }: { children: React.ReactNode }) {
71
- const [state, setState] = React.useState<SaveBarState>({ visible: false, saving: false })
72
- const handlers = React.useRef<{ onSave?: () => void; onDiscard?: () => void }>({})
73
- const api = React.useMemo<SaveBarApi>(() => ({ setState, handlers }), [])
74
-
75
- // Stack of registered back-navigation handlers (latest-mounted wins) so the
76
- // app-wide Escape key can navigate "up" to the parent route. Lives here, next
77
- // to the SaveBar handlers, so ONE keydown listener arbitrates both — Escape
78
- // discards a dirty form first, and only navigates back when none is open.
79
- const backStack = React.useRef<React.MutableRefObject<(() => void) | undefined>[]>([])
80
- const backApi = React.useMemo<BackNavApi>(() => ({ stack: backStack }), [])
81
-
82
- // Broadcast visibility to the module-level signal (for the ToastProvider).
83
- React.useEffect(() => { setSaveBarVisibleSignal(state.visible) }, [state.visible])
84
-
85
- // App-wide keyboard shortcuts, arbitrated in one place:
86
- // • Cmd/Ctrl+Enter → save the active (dirty) form
87
- // • Escape (form dirty) → discard the form
88
- // • Escape (no dirty form) → navigate back to the parent route
89
- React.useEffect(() => {
90
- function onKey(e: KeyboardEvent) {
91
- if (state.visible && !state.saving) {
92
- if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
93
- e.preventDefault()
94
- handlers.current.onSave?.()
95
- } else if (e.key === 'Escape') {
96
- e.preventDefault()
97
- handlers.current.onDiscard?.()
98
- }
99
- return
100
- }
101
- // No open/dirty form — Escape walks "up" via the topmost back handler.
102
- if (e.key === 'Escape' && !e.defaultPrevented && !isEditableTarget(e.target)) {
103
- for (let i = backStack.current.length - 1; i >= 0; i--) {
104
- const back = backStack.current[i].current
105
- if (back) { e.preventDefault(); back(); return }
106
- }
107
- }
108
- }
109
- window.addEventListener('keydown', onKey)
110
- return () => window.removeEventListener('keydown', onKey)
111
- }, [state.visible, state.saving])
112
-
113
- return (
114
- <SaveBarApiContext.Provider value={api}>
115
- <BackNavApiContext.Provider value={backApi}>
116
- <SaveBarStateContext.Provider value={state}>
117
- {children}
118
- </SaveBarStateContext.Provider>
119
- </BackNavApiContext.Provider>
120
- </SaveBarApiContext.Provider>
121
- )
122
- }
123
-
124
- // ---------------------------------------------------------------------------
125
- // Back-navigation registry — lets any subpage register its "go to parent" action
126
- // so the shell's single Escape handler can drive it (see SaveBarProvider above).
127
- // SubpageHeader already receives onBack, so wiring it there gives every plugin
128
- // detail page Escape-to-parent for free, with zero per-plugin code.
129
- // ---------------------------------------------------------------------------
130
-
131
- interface BackNavApi {
132
- stack: React.MutableRefObject<React.MutableRefObject<(() => void) | undefined>[]>
133
- }
134
- const BackNavApiContext = React.createContext<BackNavApi | null>(null)
135
-
136
- /** True when the keyboard event originates from a field where Escape has its
137
- * own meaning (clear/close), so we shouldn't hijack it for back-navigation. */
138
- function isEditableTarget(target: EventTarget | null): boolean {
139
- const el = target as HTMLElement | null
140
- if (!el || !el.tagName) return false
141
- const tag = el.tagName.toLowerCase()
142
- return tag === 'input' || tag === 'textarea' || tag === 'select' || el.isContentEditable
143
- }
144
-
145
- /**
146
- * Register a back-navigation handler with the app-wide Escape key. Call it in a
147
- * subpage (or pass through SubpageHeader's onBack); while mounted, pressing
148
- * Escape with no dirty form open invokes the most-recently-registered handler.
149
- * Pass `undefined` to opt out (e.g. a top-level page with nothing to go back to).
150
- */
151
- export function useBackHandler(onBack?: () => void): void {
152
- const api = React.useContext(BackNavApiContext)
153
- // Hold the latest closure in a ref so re-renders don't churn the stack.
154
- const ref = React.useRef(onBack)
155
- React.useEffect(() => { ref.current = onBack })
156
- React.useEffect(() => {
157
- if (!api) return
158
- const stack = api.stack.current
159
- stack.push(ref)
160
- return () => {
161
- const i = stack.indexOf(ref)
162
- if (i !== -1) stack.splice(i, 1)
163
- }
164
- }, [api])
165
- }
166
-
167
- /**
168
- * Register the current form with the app-wide SaveBar. Call it unconditionally
169
- * in a form view; it shows the island while `dirty`, keeps handlers current, and
170
- * hides the island automatically when the form unmounts (e.g. on navigation).
171
- */
172
- export function useSaveBar({ dirty, saving = false, onSave, onDiscard, message, saveLabel, discardLabel }: SaveBarRegistration): void {
173
- const api = React.useContext(SaveBarApiContext)
174
-
175
- // Keep latest handlers in the shared ref (no state churn from new closures).
176
- React.useEffect(() => {
177
- if (api) api.handlers.current = { onSave, onDiscard }
178
- })
179
-
180
- // Drive visibility from primitives only; `api` is stable → no render loops.
181
- React.useEffect(() => {
182
- if (!api) return
183
- api.setState({ visible: dirty, saving, message, saveLabel, discardLabel })
184
- return () => api.setState((s) => ({ ...s, visible: false }))
185
- }, [api, dirty, saving, message, saveLabel, discardLabel])
186
- }
187
-
188
- /** The floating island. Rendered once by the shell inside the content column. */
189
- export function SaveBar({ className }: { className?: string }) {
190
- const state = React.useContext(SaveBarStateContext)
191
- const api = React.useContext(SaveBarApiContext)
192
- const t = useTranslation()
193
-
194
- // Keep the island mounted through its exit animation: `rendered` lags behind
195
- // `visible` so we can play fade-out before unmounting.
196
- const [rendered, setRendered] = React.useState(state.visible)
197
- const [closing, setClosing] = React.useState(false)
198
-
199
- React.useEffect(() => {
200
- if (state.visible) {
201
- setRendered(true)
202
- setClosing(false)
203
- return
204
- }
205
- if (!rendered) return
206
- setClosing(true)
207
- const id = setTimeout(() => { setRendered(false); setClosing(false) }, 200)
208
- return () => clearTimeout(id)
209
- }, [state.visible, rendered])
210
-
211
- if (!api || !rendered) return null
212
- const { saving, message, saveLabel, discardLabel } = state
213
-
214
- return (
215
- <div className={cn('pointer-events-none fixed inset-x-0 bottom-5 z-40 flex justify-center px-4', className)}>
216
- {/* Dark island for strong contrast against the light page (auto-inverts in dark mode). */}
217
- <div className={cn(
218
- 'pointer-events-auto flex items-center gap-5 rounded-full bg-foreground py-2 pl-5 pr-2 text-background shadow-xl ring-1 ring-black/5 duration-200',
219
- closing ? 'animate-out fade-out slide-out-to-bottom-2' : 'animate-in fade-in slide-in-from-bottom-2',
220
- )}>
221
- <span className="flex items-center gap-2 text-sm font-medium">
222
- <span className="h-2 w-2 shrink-0 rounded-full bg-amber-400" />
223
- {message ?? t('saveBar.unsaved')}
224
- </span>
225
- <div className="flex items-center gap-1">
226
- <button
227
- type="button"
228
- disabled={saving}
229
- onClick={() => api.handlers.current.onDiscard?.()}
230
- className="rounded-full px-3 py-1.5 text-sm font-medium text-background/70 transition-colors hover:bg-background/10 hover:text-background disabled:opacity-50"
231
- >
232
- {discardLabel ?? t('common.discard')}
233
- </button>
234
- <button
235
- type="button"
236
- disabled={saving}
237
- onClick={() => api.handlers.current.onSave?.()}
238
- className="inline-flex items-center rounded-full bg-background px-4 py-1.5 text-sm font-semibold text-foreground transition-colors hover:bg-background/90 disabled:opacity-60"
239
- >
240
- {saving && <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />}
241
- {saveLabel ?? t('common.save')}
242
- </button>
243
- </div>
244
- </div>
245
- </div>
246
- )
247
- }