@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,216 +0,0 @@
1
- import React, { useState, useEffect, useRef, useCallback } from 'react'
2
- import { createPortal } from 'react-dom'
3
- import { Search, Plus } from 'lucide-react'
4
-
5
- // ---------------------------------------------------------------------------
6
- // Reusable search combobox with keyboard navigation + optional create
7
- // ---------------------------------------------------------------------------
8
-
9
- export interface ComboboxOption {
10
- id: string
11
- label: string
12
- subtitle?: string
13
- price?: number
14
- data?: Record<string, unknown>
15
- }
16
-
17
- interface SearchComboboxProps {
18
- value: string
19
- onChange: (value: string) => void
20
- onSelect: (option: ComboboxOption) => void
21
- options: ComboboxOption[]
22
- placeholder?: string
23
- autoFocus?: boolean
24
- onBlurEmpty?: () => void
25
- renderRight?: (option: ComboboxOption) => React.ReactNode
26
- /** Show a "+ Create ..." option when search has no exact match */
27
- allowCreate?: boolean
28
- /** Called when user clicks the create option. Receives the current search text. */
29
- onCreateNew?: (searchText: string) => void
30
- /** Label for the create option (default: 'Create') */
31
- createLabel?: string
32
- className?: string
33
- /** Minimal style — no border/bg, just underline on focus. For inline use. */
34
- minimal?: boolean
35
- /** Inline label shown when collapsed. Click reveals the search input. */
36
- inlineLabel?: string
37
- /** Icon for the inline label: 'search' (default) or 'plus' (for "add" actions) */
38
- inlineIcon?: 'search' | 'plus'
39
- /** Show a loading skeleton in the dropdown while results are being fetched */
40
- loading?: boolean
41
- /** Called when the input gains focus */
42
- onFocus?: () => void
43
- }
44
-
45
- export function SearchCombobox({
46
- value, onChange, onSelect, options, placeholder, autoFocus, onBlurEmpty,
47
- renderRight, allowCreate, onCreateNew, createLabel = 'Create', className, minimal, inlineLabel, inlineIcon = 'search',
48
- loading, onFocus: onFocusProp,
49
- }: SearchComboboxProps) {
50
- const [open, setOpen] = useState(false)
51
- const [activeIndex, setActiveIndex] = useState(-1)
52
- const [expanded, setExpanded] = useState(!inlineLabel)
53
- const [dropRect, setDropRect] = useState<DOMRect | null>(null)
54
- const inputRef = useRef<HTMLInputElement>(null)
55
- const wrapperRef = useRef<HTMLDivElement>(null)
56
- const listRef = useRef<HTMLDivElement>(null)
57
-
58
- // When value is set externally (e.g. edit mode), auto-expand; collapse when cleared
59
- useEffect(() => {
60
- if (value && inlineLabel) setExpanded(true)
61
- if (!value && inlineLabel && !open) setExpanded(false)
62
- }, [value, open])
63
-
64
- function updateRect() {
65
- if (wrapperRef.current) setDropRect(wrapperRef.current.getBoundingClientRect())
66
- }
67
-
68
- // Show create option only when dropdown is open, has text, and no exact match
69
- const showCreate = open && allowCreate && onCreateNew && value.trim().length > 0 &&
70
- !options.some((o) => o.label.toLowerCase() === value.trim().toLowerCase())
71
-
72
- const totalItems = options.length + (showCreate ? 1 : 0)
73
-
74
- useEffect(() => { setActiveIndex(-1) }, [options])
75
- useEffect(() => {
76
- if ((options.length > 0 || showCreate || loading) && document.activeElement === inputRef.current) setOpen(true)
77
- }, [options, showCreate, loading])
78
-
79
- const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
80
- if (!open || totalItems === 0) {
81
- if (e.key === 'ArrowDown' && totalItems > 0) {
82
- e.preventDefault()
83
- setOpen(true)
84
- setActiveIndex(0)
85
- }
86
- return
87
- }
88
-
89
- switch (e.key) {
90
- case 'ArrowDown':
91
- e.preventDefault()
92
- setActiveIndex((prev) => (prev + 1) % totalItems)
93
- break
94
- case 'ArrowUp':
95
- e.preventDefault()
96
- setActiveIndex((prev) => (prev <= 0 ? totalItems - 1 : prev - 1))
97
- break
98
- case 'Enter':
99
- e.preventDefault()
100
- if (activeIndex >= 0 && activeIndex < options.length) {
101
- onSelect(options[activeIndex])
102
- setOpen(false)
103
- setActiveIndex(-1)
104
- } else if (showCreate && activeIndex === options.length) {
105
- onCreateNew?.(value.trim())
106
- setOpen(false)
107
- setActiveIndex(-1)
108
- }
109
- break
110
- case 'Escape':
111
- e.preventDefault()
112
- setOpen(false)
113
- setActiveIndex(-1)
114
- break
115
- }
116
- }, [open, options, totalItems, activeIndex, onSelect, showCreate, onCreateNew, value])
117
-
118
- useEffect(() => {
119
- if (activeIndex < 0 || !listRef.current) return
120
- const items = listRef.current.children
121
- if (items[activeIndex]) (items[activeIndex] as HTMLElement).scrollIntoView({ block: 'nearest' })
122
- }, [activeIndex])
123
-
124
- function handleBlur() {
125
- setTimeout(() => {
126
- setOpen(false)
127
- setActiveIndex(-1)
128
- if (!value && onBlurEmpty) onBlurEmpty()
129
- if (!value && inlineLabel) setExpanded(false)
130
- }, 200)
131
- }
132
-
133
- const hasDropdown = open && (options.length > 0 || showCreate || loading)
134
-
135
- // Collapsed inline label — click to expand
136
- if (!expanded && inlineLabel) {
137
- const isPlus = inlineIcon === 'plus'
138
- return (
139
- <button type="button" onClick={() => { setExpanded(true); setTimeout(() => inputRef.current?.focus(), 50) }}
140
- className={`flex items-center gap-1.5 w-full text-left text-sm py-1.5 pl-2 transition-colors ${
141
- isPlus ? 'text-primary hover:text-primary/80' : 'text-muted-foreground hover:text-foreground'
142
- } ${className ?? ''}`}>
143
- {isPlus && <Plus className="h-3.5 w-3.5 shrink-0" />}
144
- {inlineLabel}
145
- </button>
146
- )
147
- }
148
-
149
- return (
150
- <div ref={wrapperRef} className={`relative ${className ?? ''}`}>
151
- {!minimal && (
152
- <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
153
- )}
154
- <input
155
- ref={inputRef}
156
- type="text"
157
- value={value}
158
- onChange={(e) => { onChange(e.target.value); updateRect(); setOpen(true) }}
159
- onFocus={() => { updateRect(); onFocusProp?.(); if (options.length > 0 || showCreate || loading) setOpen(true) }}
160
- onBlur={handleBlur}
161
- onKeyDown={handleKeyDown}
162
- placeholder={placeholder}
163
- autoFocus={autoFocus}
164
- className={minimal
165
- ? 'w-full bg-transparent pl-2 pr-3 py-1.5 text-sm placeholder:text-muted-foreground focus:outline-none border-b border-transparent hover:border-border focus:border-foreground transition-colors'
166
- : 'w-full rounded-lg border bg-background pl-8 pr-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
167
- }
168
- role="combobox"
169
- aria-expanded={hasDropdown}
170
- aria-autocomplete="list"
171
- aria-activedescendant={activeIndex >= 0 ? `combobox-opt-${activeIndex}` : undefined}
172
- />
173
- {hasDropdown && dropRect && createPortal(
174
- <div ref={listRef} role="listbox" data-modal-passthrough
175
- style={{ position: 'fixed', left: dropRect.left, top: dropRect.bottom + 4, width: dropRect.width, zIndex: 9999, pointerEvents: 'auto' }}
176
- className="fayz-glass-surface max-h-52 overflow-y-auto rounded-lg border bg-popover shadow-lg py-0.5 animate-in fade-in zoom-in-95 duration-100">
177
- {loading && options.length === 0 && (
178
- <div className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground">
179
- <svg className="h-3.5 w-3.5 animate-spin shrink-0" viewBox="0 0 16 16" fill="none">
180
- <circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.5" opacity="0.25" />
181
- <path d="M14.5 8a6.5 6.5 0 00-6.5-6.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
182
- </svg>
183
- <span>Buscando…</span>
184
- </div>
185
- )}
186
- {options.map((opt, i) => (
187
- <div key={opt.id} id={`combobox-opt-${i}`} role="option" aria-selected={i === activeIndex}
188
- onMouseDown={(e) => { e.preventDefault(); onSelect(opt); setOpen(false) }}
189
- onMouseEnter={() => setActiveIndex(i)}
190
- className={`flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors ${
191
- i === activeIndex ? 'bg-primary/10 text-primary' : 'hover:bg-muted/50'
192
- }`}>
193
- <div className="flex-1 min-w-0">
194
- <p className="truncate font-medium">{opt.label}</p>
195
- {opt.subtitle && <p className="truncate text-[11px] text-muted-foreground">{opt.subtitle}</p>}
196
- </div>
197
- {renderRight && <div className="shrink-0">{renderRight(opt)}</div>}
198
- </div>
199
- ))}
200
- {showCreate && (
201
- <div id={`combobox-opt-${options.length}`} role="option" aria-selected={activeIndex === options.length}
202
- onMouseDown={(e) => { e.preventDefault(); onCreateNew?.(value.trim()); setOpen(false) }}
203
- onMouseEnter={() => setActiveIndex(options.length)}
204
- className={`flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors border-t ${
205
- activeIndex === options.length ? 'bg-primary/10 text-primary' : 'hover:bg-muted/50'
206
- }`}>
207
- <Plus className="h-3.5 w-3.5 shrink-0" />
208
- <span>{createLabel} &ldquo;<span className="font-medium">{value.trim()}</span>&rdquo;</span>
209
- </div>
210
- )}
211
- </div>,
212
- document.body,
213
- )}
214
- </div>
215
- )
216
- }
@@ -1,68 +0,0 @@
1
- import * as React from 'react'
2
- import { cva, type VariantProps } from 'class-variance-authority'
3
-
4
- import { cn } from '../utils/cn'
5
-
6
- const badgeVariants = cva(
7
- 'inline-flex items-center gap-1.5 rounded px-2 py-0.5 text-xs font-medium leading-[18px] transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8
- {
9
- variants: {
10
- variant: {
11
- default: 'bg-muted text-muted-foreground',
12
- secondary: 'bg-muted text-muted-foreground',
13
- info: 'bg-info-soft text-info-soft-foreground',
14
- success: 'bg-success-soft text-success-soft-foreground',
15
- warning: 'bg-warning-soft text-warning-soft-foreground',
16
- destructive: 'bg-destructive-soft text-destructive-soft-foreground',
17
- magic: 'bg-magic-soft text-magic-soft-foreground',
18
- strong: 'bg-primary text-primary-foreground',
19
- outline: 'border border-border text-foreground',
20
- },
21
- },
22
- defaultVariants: {
23
- variant: 'default',
24
- },
25
- },
26
- )
27
-
28
- const VARIANTS_WITH_DOT = new Set([
29
- 'info',
30
- 'success',
31
- 'warning',
32
- 'destructive',
33
- 'magic',
34
- ])
35
-
36
- const DOT_COLOR: Record<string, string> = {
37
- info: 'hsl(var(--info))',
38
- success: 'hsl(var(--success))',
39
- warning: 'hsl(var(--warning))',
40
- destructive: 'hsl(var(--destructive))',
41
- magic: 'hsl(var(--magic))',
42
- }
43
-
44
- export interface BadgeProps
45
- extends React.HTMLAttributes<HTMLDivElement>,
46
- VariantProps<typeof badgeVariants> {
47
- // Show a leading status dot. Defaults to true for status variants
48
- // (info/success/warning/destructive/magic), false for neutral/outline/strong.
49
- dot?: boolean
50
- }
51
-
52
- function Badge({ className, variant, dot, children, ...props }: BadgeProps) {
53
- const v = variant ?? 'default'
54
- const showDot = dot ?? VARIANTS_WITH_DOT.has(v)
55
- return (
56
- <div className={cn(badgeVariants({ variant }), className)} {...props}>
57
- {showDot && (
58
- <span
59
- className="inline-block size-1.5 rounded-full"
60
- style={{ background: DOT_COLOR[v] ?? 'currentColor' }}
61
- />
62
- )}
63
- {children}
64
- </div>
65
- )
66
- }
67
-
68
- export { Badge, badgeVariants }
@@ -1,43 +0,0 @@
1
- import * as React from 'react'
2
- import { ArrowLeft } from 'lucide-react'
3
- import { useBackHandler } from '../layout/SaveBar'
4
- import { useCrossModuleBack } from '../layout/useCrossModuleBack'
5
-
6
- interface BreadcrumbProps {
7
- /** Parent label (e.g. "Invoices", "Clients") — clicking navigates back */
8
- parent: string
9
- /** Current item name (e.g. "Invoice #123", "John Doe") */
10
- current?: string
11
- /** Navigate back handler */
12
- onBack: () => void
13
- }
14
-
15
- export function Breadcrumb({ parent, current, onBack }: BreadcrumbProps) {
16
- // When the user came from another module, point "back" at that actual
17
- // previous page; otherwise keep this page's own parent/onBack.
18
- const { onBack: effectiveOnBack, parentLabel: effectiveParent } = useCrossModuleBack(onBack, parent)
19
- // Wire this page's back action into the app-wide Escape key (no per-plugin code).
20
- useBackHandler(effectiveOnBack)
21
- return (
22
- <nav className="flex items-center gap-1.5 text-sm text-muted-foreground">
23
- <button
24
- type="button"
25
- onClick={effectiveOnBack}
26
- className="inline-flex items-center gap-1 hover:text-foreground transition-colors"
27
- >
28
- <ArrowLeft className="h-3.5 w-3.5" />
29
- {effectiveParent}
30
- </button>
31
- {current !== undefined && (
32
- <>
33
- <span>/</span>
34
- {current ? (
35
- <span className="text-foreground font-medium truncate max-w-[200px]">{current}</span>
36
- ) : (
37
- <span className="inline-block h-4 w-24 rounded bg-muted animate-pulse align-middle" />
38
- )}
39
- </>
40
- )}
41
- </nav>
42
- )
43
- }
@@ -1,60 +0,0 @@
1
- import * as React from 'react'
2
- import { Slot } from '@radix-ui/react-slot'
3
- import { cva, type VariantProps } from 'class-variance-authority'
4
-
5
- import { cn } from '../utils/cn'
6
-
7
- const buttonVariants = cva(
8
- 'inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-button text-sm font-semibold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9
- {
10
- variants: {
11
- variant: {
12
- // Polaris primary — near-black brand with the stronger primary bevel
13
- default:
14
- 'bg-primary text-primary-foreground border border-primary shadow-button-primary hover:bg-primary/90 active:shadow-button-inset',
15
- destructive:
16
- 'bg-destructive text-destructive-foreground border border-destructive shadow-button-primary hover:bg-destructive/90 active:shadow-button-inset',
17
- // Default/secondary surface — white with hairline + bevel
18
- outline:
19
- 'border border-border bg-card text-foreground shadow-button hover:bg-muted active:shadow-button-inset',
20
- secondary:
21
- 'border border-border bg-card text-foreground shadow-button hover:bg-muted active:shadow-button-inset',
22
- ghost: 'hover:bg-muted hover:text-foreground',
23
- link: 'text-info underline-offset-4 hover:underline',
24
- },
25
- size: {
26
- // Polaris button sizes: md 30px, sm 26px, lg 36px
27
- default: 'h-[30px] px-3 py-1',
28
- sm: 'h-[26px] rounded-button px-2.5 text-xs',
29
- lg: 'h-9 rounded-button px-4 text-sm',
30
- icon: 'h-[30px] w-[30px]',
31
- },
32
- },
33
- defaultVariants: {
34
- variant: 'default',
35
- size: 'default',
36
- },
37
- }
38
- )
39
-
40
- export interface ButtonProps
41
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
42
- VariantProps<typeof buttonVariants> {
43
- asChild?: boolean
44
- }
45
-
46
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
47
- ({ className, variant, size, asChild = false, ...props }, ref) => {
48
- const Comp = asChild ? Slot : 'button'
49
- return (
50
- <Comp
51
- className={cn(buttonVariants({ variant, size, className }))}
52
- ref={ref}
53
- {...props}
54
- />
55
- )
56
- }
57
- )
58
- Button.displayName = 'Button'
59
-
60
- export { Button, buttonVariants }
@@ -1,79 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { cn } from '../utils/cn'
4
-
5
- const Card = React.forwardRef<
6
- HTMLDivElement,
7
- React.HTMLAttributes<HTMLDivElement>
8
- >(({ className, ...props }, ref) => (
9
- <div
10
- ref={ref}
11
- className={cn(
12
- 'fayz-glass-surface rounded-card border bg-card text-card-foreground shadow-sm',
13
- className
14
- )}
15
- {...props}
16
- />
17
- ))
18
- Card.displayName = 'Card'
19
-
20
- const CardHeader = React.forwardRef<
21
- HTMLDivElement,
22
- React.HTMLAttributes<HTMLDivElement>
23
- >(({ className, ...props }, ref) => (
24
- <div
25
- ref={ref}
26
- className={cn('flex flex-col space-y-1 p-4', className)}
27
- {...props}
28
- />
29
- ))
30
- CardHeader.displayName = 'CardHeader'
31
-
32
- const CardTitle = React.forwardRef<
33
- HTMLParagraphElement,
34
- React.HTMLAttributes<HTMLHeadingElement>
35
- >(({ className, ...props }, ref) => (
36
- <h3
37
- ref={ref}
38
- className={cn(
39
- 'text-sm font-semibold leading-tight tracking-tight',
40
- className
41
- )}
42
- {...props}
43
- />
44
- ))
45
- CardTitle.displayName = 'CardTitle'
46
-
47
- const CardDescription = React.forwardRef<
48
- HTMLParagraphElement,
49
- React.HTMLAttributes<HTMLParagraphElement>
50
- >(({ className, ...props }, ref) => (
51
- <p
52
- ref={ref}
53
- className={cn('text-xs text-muted-foreground', className)}
54
- {...props}
55
- />
56
- ))
57
- CardDescription.displayName = 'CardDescription'
58
-
59
- const CardContent = React.forwardRef<
60
- HTMLDivElement,
61
- React.HTMLAttributes<HTMLDivElement>
62
- >(({ className, ...props }, ref) => (
63
- <div ref={ref} className={cn('p-4 pt-0', className)} {...props} />
64
- ))
65
- CardContent.displayName = 'CardContent'
66
-
67
- const CardFooter = React.forwardRef<
68
- HTMLDivElement,
69
- React.HTMLAttributes<HTMLDivElement>
70
- >(({ className, ...props }, ref) => (
71
- <div
72
- ref={ref}
73
- className={cn('flex items-center p-4 pt-0', className)}
74
- {...props}
75
- />
76
- ))
77
- CardFooter.displayName = 'CardFooter'
78
-
79
- export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -1,53 +0,0 @@
1
- import * as React from 'react'
2
- import { Check } from 'lucide-react'
3
- import { cn } from '../utils/cn'
4
-
5
- type CheckboxColor = 'primary' | 'success' | 'warning' | 'destructive'
6
-
7
- interface CheckboxProps {
8
- checked?: boolean
9
- onChange?: (checked: boolean) => void
10
- disabled?: boolean
11
- className?: string
12
- color?: CheckboxColor
13
- id?: string
14
- }
15
-
16
- const COLOR_CLASSES: Record<CheckboxColor, string> = {
17
- primary: 'border-primary bg-primary text-primary-foreground',
18
- success: 'border-success bg-success text-white dark:border-success dark:bg-success',
19
- warning: 'border-warning bg-warning text-white',
20
- destructive: 'border-destructive bg-destructive text-destructive-foreground',
21
- }
22
-
23
- const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
24
- ({ checked = false, onChange, disabled, className, color = 'primary', id, ...props }, ref) => {
25
- return (
26
- <button
27
- ref={ref}
28
- id={id}
29
- type="button"
30
- role="checkbox"
31
- aria-checked={checked}
32
- disabled={disabled}
33
- onClick={() => onChange?.(!checked)}
34
- className={cn(
35
- 'peer inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border-2 transition-all duration-150',
36
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
37
- 'disabled:cursor-not-allowed disabled:opacity-50',
38
- checked
39
- ? `${COLOR_CLASSES[color]} shadow-sm`
40
- : 'border-muted-foreground/40 bg-background hover:border-muted-foreground/60',
41
- className,
42
- )}
43
- {...props}
44
- >
45
- {checked && <Check className="h-3.5 w-3.5" strokeWidth={3} />}
46
- </button>
47
- )
48
- },
49
- )
50
- Checkbox.displayName = 'Checkbox'
51
-
52
- export { Checkbox }
53
- export type { CheckboxColor, CheckboxProps }
@@ -1,125 +0,0 @@
1
- import * as React from 'react'
2
- import { createPortal } from 'react-dom'
3
- import { AlertTriangle, Info, CheckCircle2, XCircle } from 'lucide-react'
4
- import { cn } from '../utils/cn'
5
- import { Button } from './button'
6
-
7
- type ConfirmVariant = 'default' | 'destructive' | 'warning' | 'success'
8
-
9
- interface ConfirmDialogProps {
10
- open: boolean
11
- onConfirm: () => void
12
- onCancel: () => void
13
- title: string
14
- description?: string
15
- confirmLabel?: string
16
- cancelLabel?: string
17
- variant?: ConfirmVariant
18
- loading?: boolean
19
- }
20
-
21
- const VARIANT_CONFIG: Record<
22
- ConfirmVariant,
23
- {
24
- icon: React.ElementType
25
- iconBg: string
26
- iconColor: string
27
- buttonVariant: 'default' | 'destructive'
28
- }
29
- > = {
30
- default: {
31
- icon: Info,
32
- iconBg: 'bg-info/10',
33
- iconColor: 'text-info',
34
- buttonVariant: 'default',
35
- },
36
- destructive: {
37
- icon: XCircle,
38
- iconBg: 'bg-destructive/10',
39
- iconColor: 'text-destructive',
40
- buttonVariant: 'destructive',
41
- },
42
- warning: {
43
- icon: AlertTriangle,
44
- iconBg: 'bg-warning/10',
45
- iconColor: 'text-warning',
46
- buttonVariant: 'destructive',
47
- },
48
- success: {
49
- icon: CheckCircle2,
50
- iconBg: 'bg-success/10',
51
- iconColor: 'text-success',
52
- buttonVariant: 'default',
53
- },
54
- }
55
-
56
- export function ConfirmDialog({
57
- open,
58
- onConfirm,
59
- onCancel,
60
- title,
61
- description,
62
- confirmLabel = 'Confirm',
63
- cancelLabel = 'Cancel',
64
- variant = 'default',
65
- loading = false,
66
- }: ConfirmDialogProps) {
67
- const config = VARIANT_CONFIG[variant]
68
- const Icon = config.icon
69
-
70
- React.useEffect(() => {
71
- if (!open) return
72
- function handleKey(e: KeyboardEvent) {
73
- if (e.key === 'Escape') onCancel()
74
- }
75
- document.addEventListener('keydown', handleKey)
76
- return () => document.removeEventListener('keydown', handleKey)
77
- }, [open, onCancel])
78
-
79
- if (!open) return null
80
-
81
- return createPortal(
82
- <div className="fixed inset-0 z-[60] flex items-center justify-center">
83
- <div
84
- className="absolute inset-0 bg-black/50 backdrop-blur-[2px] animate-fade-in"
85
- onClick={onCancel}
86
- />
87
-
88
- <div
89
- className="relative z-10 w-full max-w-sm mx-4 rounded-modal border border-border bg-card shadow-lg animate-zoom-in"
90
- role="alertdialog"
91
- aria-modal="true"
92
- aria-labelledby="confirm-title"
93
- aria-describedby={description ? 'confirm-desc' : undefined}
94
- >
95
- <div className="p-6">
96
- <div className="flex gap-4">
97
- <div className={cn('flex h-10 w-10 items-center justify-center rounded-full shrink-0', config.iconBg)}>
98
- <Icon className={cn('h-5 w-5', config.iconColor)} />
99
- </div>
100
- <div className="min-w-0 pt-0.5">
101
- <h3 id="confirm-title" className="text-sm font-semibold text-foreground">
102
- {title}
103
- </h3>
104
- {description && (
105
- <p id="confirm-desc" className="text-xs text-muted-foreground mt-1 leading-relaxed">
106
- {description}
107
- </p>
108
- )}
109
- </div>
110
- </div>
111
-
112
- <div className="flex justify-end gap-2 mt-5">
113
- <Button variant="outline" size="sm" onClick={onCancel} disabled={loading}>
114
- {cancelLabel}
115
- </Button>
116
- <Button variant={config.buttonVariant} size="sm" onClick={onConfirm} disabled={loading}>
117
- {loading ? 'Processing...' : confirmLabel}
118
- </Button>
119
- </div>
120
- </div>
121
- </div>
122
- </div>,
123
- document.body,
124
- )
125
- }