@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.
- package/README.md +2 -2
- package/dist/analytics/AnalyticsCard.d.ts +10 -0
- package/dist/analytics/AnalyticsCard.d.ts.map +1 -0
- package/dist/analytics/AnalyticsControls.d.ts +18 -0
- package/dist/analytics/AnalyticsControls.d.ts.map +1 -0
- package/dist/analytics/AnalyticsGrid.d.ts +16 -0
- package/dist/analytics/AnalyticsGrid.d.ts.map +1 -0
- package/dist/analytics/context.d.ts +58 -0
- package/dist/analytics/context.d.ts.map +1 -0
- package/dist/analytics/derive.d.ts +81 -0
- package/dist/analytics/derive.d.ts.map +1 -0
- package/dist/analytics/index.d.ts +13 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/range.d.ts +22 -0
- package/dist/analytics/range.d.ts.map +1 -0
- package/dist/{chunk-PVIB724L.js → chunk-22EVFMS3.js} +5 -3
- package/dist/chunk-22EVFMS3.js.map +1 -0
- package/dist/{chunk-G6OKZ7WL.js → chunk-6V5MGYXH.js} +13 -9
- package/dist/chunk-6V5MGYXH.js.map +1 -0
- package/dist/{chunk-KV3MHB6N.js → chunk-H63TOSQ7.js} +196 -53
- package/dist/chunk-H63TOSQ7.js.map +1 -0
- package/dist/{chunk-OUPJDGKE.js → chunk-I6CPKAOD.js} +134 -37
- package/dist/chunk-I6CPKAOD.js.map +1 -0
- package/dist/{chunk-YRVE4FOD.js → chunk-OKZ6WQB3.js} +3 -3
- package/dist/chunk-OKZ6WQB3.js.map +1 -0
- package/dist/{chunk-S5KNM4D2.js → chunk-SEIY2F5F.js} +235 -107
- package/dist/chunk-SEIY2F5F.js.map +1 -0
- package/dist/{chunk-75EFDLST.js → chunk-VAKWUUVC.js} +157 -6
- package/dist/chunk-VAKWUUVC.js.map +1 -0
- package/dist/dashboard/OnboardingWidget.d.ts +2 -14
- package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
- package/dist/dashboard/SidebarOnboarding.d.ts +28 -0
- package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -0
- package/dist/dashboard/index.d.ts +2 -0
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +4 -4
- package/dist/dashboard/types.d.ts +18 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/dashboard/useOnboardingProgress.d.ts +28 -0
- package/dist/dashboard/useOnboardingProgress.d.ts.map +1 -0
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +960 -14
- package/dist/index.js.map +1 -1
- package/dist/layout/AppShell.d.ts +14 -1
- package/dist/layout/AppShell.d.ts.map +1 -1
- package/dist/layout/ModulePage.d.ts +11 -2
- package/dist/layout/ModulePage.d.ts.map +1 -1
- package/dist/layout/Sidebar.d.ts +8 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/index.js +4 -4
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/preset.d.ts +3 -0
- package/dist/preset.d.ts.map +1 -0
- package/dist/primitives/data-table.d.ts +19 -4
- package/dist/primitives/data-table.d.ts.map +1 -1
- package/dist/primitives/date-picker.d.ts +4 -2
- package/dist/primitives/date-picker.d.ts.map +1 -1
- package/dist/primitives/filter-chip.d.ts +26 -0
- package/dist/primitives/filter-chip.d.ts.map +1 -0
- package/dist/primitives/header-tabs.d.ts +32 -0
- package/dist/primitives/header-tabs.d.ts.map +1 -0
- package/dist/primitives/hint.d.ts +25 -0
- package/dist/primitives/hint.d.ts.map +1 -0
- package/dist/primitives/index.d.ts +7 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +4 -4
- package/dist/primitives/list-view.d.ts +14 -1
- package/dist/primitives/list-view.d.ts.map +1 -1
- package/dist/primitives/page-header.d.ts +23 -0
- package/dist/primitives/page-header.d.ts.map +1 -0
- package/dist/primitives/stat-tiles.d.ts +30 -0
- package/dist/primitives/stat-tiles.d.ts.map +1 -0
- package/dist/primitives/switch.d.ts +11 -0
- package/dist/primitives/switch.d.ts.map +1 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/page-header.store.d.ts +69 -0
- package/dist/stores/page-header.store.d.ts.map +1 -0
- package/package.json +6 -15
- package/dist/chunk-75EFDLST.js.map +0 -1
- package/dist/chunk-G6OKZ7WL.js.map +0 -1
- package/dist/chunk-KV3MHB6N.js.map +0 -1
- package/dist/chunk-OUPJDGKE.js.map +0 -1
- package/dist/chunk-PVIB724L.js.map +0 -1
- package/dist/chunk-S5KNM4D2.js.map +0 -1
- package/dist/chunk-YRVE4FOD.js.map +0 -1
- package/dist/utils/cn.d.ts.map +0 -1
- package/src/dashboard/ChartWidget.tsx +0 -50
- package/src/dashboard/ChartWidgetInner.tsx +0 -83
- package/src/dashboard/DashboardCanvas.tsx +0 -210
- package/src/dashboard/DashboardCustomizeMenu.tsx +0 -53
- package/src/dashboard/DashboardGrid.tsx +0 -35
- package/src/dashboard/DashboardRange.tsx +0 -62
- package/src/dashboard/GoldCard.tsx +0 -38
- package/src/dashboard/KpiCard.tsx +0 -238
- package/src/dashboard/OnboardingWidget.tsx +0 -140
- package/src/dashboard/TableWidget.tsx +0 -51
- package/src/dashboard/define.ts +0 -53
- package/src/dashboard/icon.ts +0 -13
- package/src/dashboard/index.ts +0 -38
- package/src/dashboard/layout.ts +0 -50
- package/src/dashboard/preferences.ts +0 -71
- package/src/dashboard/types.ts +0 -41
- package/src/icons.ts +0 -66
- package/src/index.ts +0 -238
- package/src/layout/AppShell.tsx +0 -987
- package/src/layout/ContentSplit.tsx +0 -153
- package/src/layout/ModulePage.tsx +0 -466
- package/src/layout/PageTransition.tsx +0 -57
- package/src/layout/SaveBar.tsx +0 -247
- package/src/layout/Sidebar.tsx +0 -535
- package/src/layout/Topbar.tsx +0 -462
- package/src/layout/index.ts +0 -39
- package/src/layout/useCrossModuleBack.ts +0 -30
- package/src/primitives/Avatar.tsx +0 -48
- package/src/primitives/SearchCombobox.tsx +0 -216
- package/src/primitives/badge.tsx +0 -68
- package/src/primitives/breadcrumb.tsx +0 -43
- package/src/primitives/button.tsx +0 -60
- package/src/primitives/card.tsx +0 -79
- package/src/primitives/checkbox.tsx +0 -53
- package/src/primitives/confirm-dialog.tsx +0 -125
- package/src/primitives/currency-input.tsx +0 -121
- package/src/primitives/data-table.tsx +0 -160
- package/src/primitives/date-picker.tsx +0 -225
- package/src/primitives/dropdown.tsx +0 -196
- package/src/primitives/index.ts +0 -87
- package/src/primitives/input.tsx +0 -50
- package/src/primitives/list-view.tsx +0 -252
- package/src/primitives/markdown-editor.tsx +0 -75
- package/src/primitives/markdown.tsx +0 -169
- package/src/primitives/modal.tsx +0 -156
- package/src/primitives/phone-link.tsx +0 -138
- package/src/primitives/popover.tsx +0 -30
- package/src/primitives/search-select.tsx +0 -314
- package/src/primitives/segmented-control.tsx +0 -66
- package/src/primitives/select.tsx +0 -157
- package/src/primitives/separator.tsx +0 -29
- package/src/primitives/sheet.tsx +0 -181
- package/src/primitives/skeleton.tsx +0 -46
- package/src/primitives/tabs.tsx +0 -53
- package/src/primitives/time-picker.tsx +0 -148
- package/src/primitives/toast.tsx +0 -46
- package/src/primitives/tooltip.tsx +0 -28
- package/src/stores/index.ts +0 -10
- package/src/stores/layout.store.ts +0 -31
- package/src/stores/theme.store.ts +0 -457
- package/src/tailwind.ts +0 -43
- package/src/theme/index.ts +0 -94
- package/src/theme/preset.ts +0 -209
- package/src/theme/presets.ts +0 -106
- package/src/utils/cn.ts +0 -6
- /package/dist/{utils → lib}/cn.d.ts +0 -0
- /package/{src → dist}/styles.css +0 -0
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { Search, X, ChevronDown, Loader2 } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Types
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
export interface SearchSelectOption {
|
|
10
|
-
id: string
|
|
11
|
-
label: string
|
|
12
|
-
subtitle?: string
|
|
13
|
-
icon?: React.ReactNode
|
|
14
|
-
group?: string
|
|
15
|
-
disabled?: boolean
|
|
16
|
-
data?: any
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface SearchSelectProps {
|
|
20
|
-
/** Currently selected option ID */
|
|
21
|
-
value?: string
|
|
22
|
-
/** Display text when a value is selected (overrides auto-lookup) */
|
|
23
|
-
displayValue?: string
|
|
24
|
-
/** Callback when an option is selected */
|
|
25
|
-
onChange: (id: string, option: SearchSelectOption | null) => void
|
|
26
|
-
/** Search function — async or sync. Called with debounced query. */
|
|
27
|
-
onSearch: (query: string) => Promise<SearchSelectOption[]> | SearchSelectOption[]
|
|
28
|
-
/** Placeholder text */
|
|
29
|
-
placeholder?: string
|
|
30
|
-
/** Label above the input */
|
|
31
|
-
label?: string
|
|
32
|
-
/** Required field indicator */
|
|
33
|
-
required?: boolean
|
|
34
|
-
/** Debounce delay in ms (default: 250) */
|
|
35
|
-
debounce?: number
|
|
36
|
-
/** Minimum characters before searching (default: 1) */
|
|
37
|
-
minChars?: number
|
|
38
|
-
/** Show a "Create new" action at the bottom */
|
|
39
|
-
allowCreate?: boolean
|
|
40
|
-
/** Label for create action (default: "Add") */
|
|
41
|
-
createLabel?: string
|
|
42
|
-
/** Callback when create is clicked. Can be async — component shows optimistic state while waiting. */
|
|
43
|
-
onCreate?: (query: string) => void | Promise<void>
|
|
44
|
-
/** Custom render for each option */
|
|
45
|
-
renderOption?: (option: SearchSelectOption) => React.ReactNode
|
|
46
|
-
/** Load initial results when dropdown opens (calls onSearch('')). Default
|
|
47
|
-
* true — clicking lists options before any typing; pass false to require
|
|
48
|
-
* a query first (e.g. very large datasets with no cheap top-N). */
|
|
49
|
-
loadOnOpen?: boolean
|
|
50
|
-
/** Additional className for the container */
|
|
51
|
-
className?: string
|
|
52
|
-
/** Disabled state */
|
|
53
|
-
disabled?: boolean
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// Component
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
export function SearchSelect({
|
|
61
|
-
value,
|
|
62
|
-
displayValue,
|
|
63
|
-
onChange,
|
|
64
|
-
onSearch,
|
|
65
|
-
placeholder = 'Search...',
|
|
66
|
-
label,
|
|
67
|
-
required,
|
|
68
|
-
debounce = 250,
|
|
69
|
-
minChars = 1,
|
|
70
|
-
allowCreate,
|
|
71
|
-
createLabel = 'Add',
|
|
72
|
-
onCreate,
|
|
73
|
-
renderOption,
|
|
74
|
-
loadOnOpen = true,
|
|
75
|
-
className,
|
|
76
|
-
disabled,
|
|
77
|
-
}: SearchSelectProps) {
|
|
78
|
-
const [open, setOpen] = React.useState(false)
|
|
79
|
-
const [query, setQuery] = React.useState('')
|
|
80
|
-
const [results, setResults] = React.useState<SearchSelectOption[]>([])
|
|
81
|
-
const [loading, setLoading] = React.useState(false)
|
|
82
|
-
const [creating, setCreating] = React.useState(false)
|
|
83
|
-
const [selectedLabel, setSelectedLabel] = React.useState(displayValue ?? '')
|
|
84
|
-
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
85
|
-
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
86
|
-
const timerRef = React.useRef<ReturnType<typeof setTimeout>>()
|
|
87
|
-
|
|
88
|
-
// Close on outside click
|
|
89
|
-
React.useEffect(() => {
|
|
90
|
-
function handler(e: MouseEvent) {
|
|
91
|
-
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
|
92
|
-
setOpen(false)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
document.addEventListener('mousedown', handler)
|
|
96
|
-
return () => document.removeEventListener('mousedown', handler)
|
|
97
|
-
}, [])
|
|
98
|
-
|
|
99
|
-
// Update display when value/displayValue changes externally
|
|
100
|
-
React.useEffect(() => {
|
|
101
|
-
if (displayValue !== undefined) setSelectedLabel(displayValue)
|
|
102
|
-
}, [displayValue])
|
|
103
|
-
|
|
104
|
-
// Load initial results when dropdown opens with empty query
|
|
105
|
-
React.useEffect(() => {
|
|
106
|
-
if (open && loadOnOpen && query.length === 0 && results.length === 0 && !loading) {
|
|
107
|
-
setLoading(true)
|
|
108
|
-
Promise.resolve(onSearch('')).then(setResults).catch(() => setResults([])).finally(() => setLoading(false))
|
|
109
|
-
}
|
|
110
|
-
}, [open, loadOnOpen])
|
|
111
|
-
|
|
112
|
-
function handleQueryChange(q: string) {
|
|
113
|
-
setQuery(q)
|
|
114
|
-
if (timerRef.current) clearTimeout(timerRef.current)
|
|
115
|
-
|
|
116
|
-
if (q.length < minChars && !(loadOnOpen && q.length === 0)) {
|
|
117
|
-
setResults([])
|
|
118
|
-
setLoading(false)
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
setLoading(true)
|
|
123
|
-
timerRef.current = setTimeout(async () => {
|
|
124
|
-
try {
|
|
125
|
-
const res = await onSearch(q)
|
|
126
|
-
setResults(res)
|
|
127
|
-
} catch {
|
|
128
|
-
setResults([])
|
|
129
|
-
}
|
|
130
|
-
setLoading(false)
|
|
131
|
-
}, debounce)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function handleSelect(option: SearchSelectOption) {
|
|
135
|
-
onChange(option.id, option)
|
|
136
|
-
setSelectedLabel(option.label)
|
|
137
|
-
setOpen(false)
|
|
138
|
-
setQuery('')
|
|
139
|
-
setResults([])
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function handleClear() {
|
|
143
|
-
onChange('', null)
|
|
144
|
-
setSelectedLabel('')
|
|
145
|
-
setQuery('')
|
|
146
|
-
setResults([])
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function handleCreate(name: string) {
|
|
150
|
-
if (!onCreate) return
|
|
151
|
-
setCreating(true)
|
|
152
|
-
setSelectedLabel(name)
|
|
153
|
-
setOpen(false)
|
|
154
|
-
setQuery('')
|
|
155
|
-
setResults([])
|
|
156
|
-
try {
|
|
157
|
-
await onCreate(name)
|
|
158
|
-
} finally {
|
|
159
|
-
setCreating(false)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function handleOpen() {
|
|
164
|
-
if (disabled || creating) return
|
|
165
|
-
setOpen(true)
|
|
166
|
-
setQuery('')
|
|
167
|
-
// Stale results from a previous typed query would render for the empty
|
|
168
|
-
// query now that the dropdown shows immediately — drop them so the
|
|
169
|
-
// loadOnOpen effect refetches the initial listing.
|
|
170
|
-
setResults([])
|
|
171
|
-
setTimeout(() => inputRef.current?.focus(), 0)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Group results
|
|
175
|
-
const groups = React.useMemo(() => {
|
|
176
|
-
const map = new Map<string, SearchSelectOption[]>()
|
|
177
|
-
for (const opt of results) {
|
|
178
|
-
const group = opt.group ?? ''
|
|
179
|
-
if (!map.has(group)) map.set(group, [])
|
|
180
|
-
map.get(group)!.push(opt)
|
|
181
|
-
}
|
|
182
|
-
return map
|
|
183
|
-
}, [results])
|
|
184
|
-
|
|
185
|
-
const hasValue = !!value && !!selectedLabel
|
|
186
|
-
// With loadOnOpen the dropdown must show for the EMPTY query too — gating it
|
|
187
|
-
// on minChars silently hid the initial listing loadOnOpen had just fetched.
|
|
188
|
-
const showDropdown = open && (query.length >= minChars || loadOnOpen)
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<div ref={containerRef} className={cn('relative', className)}>
|
|
192
|
-
{label && (
|
|
193
|
-
<label className="text-xs font-medium text-muted-foreground">
|
|
194
|
-
{label}{required && ' *'}
|
|
195
|
-
</label>
|
|
196
|
-
)}
|
|
197
|
-
|
|
198
|
-
{/* Trigger — shows selected value or search input */}
|
|
199
|
-
<div
|
|
200
|
-
className={cn(
|
|
201
|
-
'mt-1 flex items-center gap-2 rounded-lg border bg-background transition-colors',
|
|
202
|
-
open ? 'ring-2 ring-primary/20' : 'hover:bg-muted/30',
|
|
203
|
-
disabled && 'opacity-50 cursor-not-allowed',
|
|
204
|
-
)}
|
|
205
|
-
>
|
|
206
|
-
<Search className="h-3.5 w-3.5 text-muted-foreground ml-3 shrink-0" />
|
|
207
|
-
{open ? (
|
|
208
|
-
<input
|
|
209
|
-
ref={inputRef}
|
|
210
|
-
type="text"
|
|
211
|
-
value={query}
|
|
212
|
-
onChange={(e) => handleQueryChange(e.target.value)}
|
|
213
|
-
placeholder={placeholder}
|
|
214
|
-
className="flex-1 bg-transparent py-2 pr-3 text-sm outline-none"
|
|
215
|
-
onKeyDown={(e) => {
|
|
216
|
-
if (e.key === 'Escape') setOpen(false)
|
|
217
|
-
}}
|
|
218
|
-
/>
|
|
219
|
-
) : (
|
|
220
|
-
<button
|
|
221
|
-
type="button"
|
|
222
|
-
onClick={handleOpen}
|
|
223
|
-
className="flex-1 text-left py-2 pr-3 text-sm outline-none"
|
|
224
|
-
disabled={disabled || creating}
|
|
225
|
-
>
|
|
226
|
-
{creating ? (
|
|
227
|
-
<span className="flex items-center gap-1.5 text-muted-foreground">
|
|
228
|
-
<Loader2 className="h-3 w-3 animate-spin" />
|
|
229
|
-
<span className="font-medium text-foreground">{selectedLabel}</span>
|
|
230
|
-
</span>
|
|
231
|
-
) : hasValue ? (
|
|
232
|
-
<span className="font-medium">{selectedLabel}</span>
|
|
233
|
-
) : (
|
|
234
|
-
<span className="text-muted-foreground">{placeholder}</span>
|
|
235
|
-
)}
|
|
236
|
-
</button>
|
|
237
|
-
)}
|
|
238
|
-
{hasValue && !open && (
|
|
239
|
-
<button onClick={handleClear} className="pr-3 text-muted-foreground hover:text-foreground">
|
|
240
|
-
<X className="h-3 w-3" />
|
|
241
|
-
</button>
|
|
242
|
-
)}
|
|
243
|
-
{!hasValue && !open && (
|
|
244
|
-
<ChevronDown className="h-3 w-3 text-muted-foreground mr-3 shrink-0" />
|
|
245
|
-
)}
|
|
246
|
-
</div>
|
|
247
|
-
|
|
248
|
-
{/* Dropdown */}
|
|
249
|
-
{showDropdown && (
|
|
250
|
-
<div className="fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden">
|
|
251
|
-
{loading ? (
|
|
252
|
-
<div className="flex items-center justify-center gap-2 px-4 py-6 text-xs text-muted-foreground">
|
|
253
|
-
<Loader2 className="h-3.5 w-3.5 animate-spin" /> Searching...
|
|
254
|
-
</div>
|
|
255
|
-
) : results.length === 0 ? (
|
|
256
|
-
<div className="px-4 py-4 text-center">
|
|
257
|
-
<p className="text-xs text-muted-foreground">{query ? `No results for "${query}"` : 'No results'}</p>
|
|
258
|
-
{allowCreate && onCreate && (
|
|
259
|
-
<button
|
|
260
|
-
onClick={() => handleCreate(query)}
|
|
261
|
-
className="mt-2 text-xs text-primary hover:underline"
|
|
262
|
-
>
|
|
263
|
-
+ {createLabel} "{query}"
|
|
264
|
-
</button>
|
|
265
|
-
)}
|
|
266
|
-
</div>
|
|
267
|
-
) : (
|
|
268
|
-
<div className="max-h-64 overflow-auto py-1">
|
|
269
|
-
{[...groups.entries()].map(([group, options]) => (
|
|
270
|
-
<React.Fragment key={group}>
|
|
271
|
-
{group && (
|
|
272
|
-
<div className="sticky top-0 bg-muted/50 px-3 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
273
|
-
{group}
|
|
274
|
-
</div>
|
|
275
|
-
)}
|
|
276
|
-
{options.map((opt) => (
|
|
277
|
-
<button
|
|
278
|
-
key={opt.id}
|
|
279
|
-
onClick={() => handleSelect(opt)}
|
|
280
|
-
disabled={opt.disabled}
|
|
281
|
-
className={cn(
|
|
282
|
-
'w-full text-left px-3 py-2 hover:bg-muted/50 transition-colors flex items-center gap-2.5',
|
|
283
|
-
opt.disabled && 'opacity-40 cursor-not-allowed',
|
|
284
|
-
opt.id === value && 'bg-muted/30',
|
|
285
|
-
)}
|
|
286
|
-
>
|
|
287
|
-
{renderOption ? renderOption(opt) : (
|
|
288
|
-
<>
|
|
289
|
-
{opt.icon}
|
|
290
|
-
<div className="min-w-0 flex-1">
|
|
291
|
-
<p className="text-sm truncate">{opt.label}</p>
|
|
292
|
-
{opt.subtitle && <p className="text-[10px] text-muted-foreground truncate">{opt.subtitle}</p>}
|
|
293
|
-
</div>
|
|
294
|
-
</>
|
|
295
|
-
)}
|
|
296
|
-
</button>
|
|
297
|
-
))}
|
|
298
|
-
</React.Fragment>
|
|
299
|
-
))}
|
|
300
|
-
{allowCreate && onCreate && (
|
|
301
|
-
<button
|
|
302
|
-
onClick={() => handleCreate(query)}
|
|
303
|
-
className="w-full text-left px-3 py-2 text-xs text-primary hover:bg-muted/50 transition-colors border-t"
|
|
304
|
-
>
|
|
305
|
-
+ {createLabel} "{query}"
|
|
306
|
-
</button>
|
|
307
|
-
)}
|
|
308
|
-
</div>
|
|
309
|
-
)}
|
|
310
|
-
</div>
|
|
311
|
-
)}
|
|
312
|
-
</div>
|
|
313
|
-
)
|
|
314
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { cn } from '../utils/cn'
|
|
3
|
-
|
|
4
|
-
export interface SegmentedOption<T extends string = string> {
|
|
5
|
-
value: T
|
|
6
|
-
label: React.ReactNode
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface SegmentedControlProps<T extends string = string> {
|
|
10
|
-
/** Options as plain values (label = value) or `{ value, label }` objects. */
|
|
11
|
-
options: ReadonlyArray<T | SegmentedOption<T>>
|
|
12
|
-
value: T
|
|
13
|
-
onChange: (value: T) => void
|
|
14
|
-
/** Matches the equivalent Button heights: sm = 26px, md = 30px. */
|
|
15
|
-
size?: 'sm' | 'md'
|
|
16
|
-
className?: string
|
|
17
|
-
'aria-label'?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const SIZE: Record<'sm' | 'md', { container: string; segment: string }> = {
|
|
21
|
-
sm: { container: 'h-[26px]', segment: 'px-2.5 text-xs' },
|
|
22
|
-
md: { container: 'h-[30px]', segment: 'px-3 text-sm' },
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Canonical segmented (pill) control — same chrome as Button (rounded-button
|
|
27
|
-
* radius, hairline border + shadow-button bevel on a card surface). Use this for
|
|
28
|
-
* any 7d/30d/90d-style toggle so every surface stays visually consistent instead
|
|
29
|
-
* of hand-rolling a styled toggle group.
|
|
30
|
-
*/
|
|
31
|
-
export function SegmentedControl<T extends string = string>({
|
|
32
|
-
options, value, onChange, size = 'sm', className, ...rest
|
|
33
|
-
}: SegmentedControlProps<T>) {
|
|
34
|
-
const items = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o)) as SegmentedOption<T>[]
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
role="tablist"
|
|
38
|
-
aria-label={rest['aria-label']}
|
|
39
|
-
className={cn(
|
|
40
|
-
'inline-flex items-center rounded-button border border-border bg-card p-0.5 shadow-button',
|
|
41
|
-
SIZE[size].container,
|
|
42
|
-
className,
|
|
43
|
-
)}
|
|
44
|
-
>
|
|
45
|
-
{items.map((item) => {
|
|
46
|
-
const active = item.value === value
|
|
47
|
-
return (
|
|
48
|
-
<button
|
|
49
|
-
key={item.value}
|
|
50
|
-
type="button"
|
|
51
|
-
role="tab"
|
|
52
|
-
aria-selected={active}
|
|
53
|
-
onClick={() => onChange(item.value)}
|
|
54
|
-
className={cn(
|
|
55
|
-
'flex h-full items-center rounded-button font-semibold transition-colors',
|
|
56
|
-
SIZE[size].segment,
|
|
57
|
-
active ? 'bg-foreground text-background' : 'text-muted-foreground hover:bg-muted/60',
|
|
58
|
-
)}
|
|
59
|
-
>
|
|
60
|
-
{item.label}
|
|
61
|
-
</button>
|
|
62
|
-
)
|
|
63
|
-
})}
|
|
64
|
-
</div>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import * as SelectPrimitive from '@radix-ui/react-select'
|
|
3
|
-
import { ChevronDown, ChevronUp, Check } from 'lucide-react'
|
|
4
|
-
|
|
5
|
-
import { cn } from '../utils/cn'
|
|
6
|
-
|
|
7
|
-
const Select = SelectPrimitive.Root
|
|
8
|
-
|
|
9
|
-
const SelectGroup = SelectPrimitive.Group
|
|
10
|
-
|
|
11
|
-
const SelectValue = SelectPrimitive.Value
|
|
12
|
-
|
|
13
|
-
const SelectTrigger = React.forwardRef<
|
|
14
|
-
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
15
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
16
|
-
>(({ className, children, ...props }, ref) => (
|
|
17
|
-
<SelectPrimitive.Trigger
|
|
18
|
-
ref={ref}
|
|
19
|
-
className={cn(
|
|
20
|
-
'flex h-10 w-full items-center justify-between rounded-input border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
|
21
|
-
className
|
|
22
|
-
)}
|
|
23
|
-
{...props}
|
|
24
|
-
>
|
|
25
|
-
{children}
|
|
26
|
-
<SelectPrimitive.Icon asChild>
|
|
27
|
-
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
28
|
-
</SelectPrimitive.Icon>
|
|
29
|
-
</SelectPrimitive.Trigger>
|
|
30
|
-
))
|
|
31
|
-
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
32
|
-
|
|
33
|
-
const SelectScrollUpButton = React.forwardRef<
|
|
34
|
-
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
35
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
36
|
-
>(({ className, ...props }, ref) => (
|
|
37
|
-
<SelectPrimitive.ScrollUpButton
|
|
38
|
-
ref={ref}
|
|
39
|
-
className={cn(
|
|
40
|
-
'flex cursor-default items-center justify-center py-1',
|
|
41
|
-
className
|
|
42
|
-
)}
|
|
43
|
-
{...props}
|
|
44
|
-
>
|
|
45
|
-
<ChevronUp className="h-4 w-4" />
|
|
46
|
-
</SelectPrimitive.ScrollUpButton>
|
|
47
|
-
))
|
|
48
|
-
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
|
49
|
-
|
|
50
|
-
const SelectScrollDownButton = React.forwardRef<
|
|
51
|
-
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
52
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
53
|
-
>(({ className, ...props }, ref) => (
|
|
54
|
-
<SelectPrimitive.ScrollDownButton
|
|
55
|
-
ref={ref}
|
|
56
|
-
className={cn(
|
|
57
|
-
'flex cursor-default items-center justify-center py-1',
|
|
58
|
-
className
|
|
59
|
-
)}
|
|
60
|
-
{...props}
|
|
61
|
-
>
|
|
62
|
-
<ChevronDown className="h-4 w-4" />
|
|
63
|
-
</SelectPrimitive.ScrollDownButton>
|
|
64
|
-
))
|
|
65
|
-
SelectScrollDownButton.displayName =
|
|
66
|
-
SelectPrimitive.ScrollDownButton.displayName
|
|
67
|
-
|
|
68
|
-
const SelectContent = React.forwardRef<
|
|
69
|
-
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
70
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
71
|
-
>(({ className, children, position = 'popper', ...props }, ref) => (
|
|
72
|
-
<SelectPrimitive.Portal>
|
|
73
|
-
<SelectPrimitive.Content
|
|
74
|
-
ref={ref}
|
|
75
|
-
className={cn(
|
|
76
|
-
'fayz-glass-surface relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-card border bg-popover text-popover-foreground shadow-md',
|
|
77
|
-
position === 'popper' &&
|
|
78
|
-
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
79
|
-
className
|
|
80
|
-
)}
|
|
81
|
-
position={position}
|
|
82
|
-
{...props}
|
|
83
|
-
>
|
|
84
|
-
<SelectScrollUpButton />
|
|
85
|
-
<SelectPrimitive.Viewport
|
|
86
|
-
className={cn(
|
|
87
|
-
'p-1',
|
|
88
|
-
position === 'popper' &&
|
|
89
|
-
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
|
|
90
|
-
)}
|
|
91
|
-
>
|
|
92
|
-
{children}
|
|
93
|
-
</SelectPrimitive.Viewport>
|
|
94
|
-
<SelectScrollDownButton />
|
|
95
|
-
</SelectPrimitive.Content>
|
|
96
|
-
</SelectPrimitive.Portal>
|
|
97
|
-
))
|
|
98
|
-
SelectContent.displayName = SelectPrimitive.Content.displayName
|
|
99
|
-
|
|
100
|
-
const SelectLabel = React.forwardRef<
|
|
101
|
-
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
102
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
103
|
-
>(({ className, ...props }, ref) => (
|
|
104
|
-
<SelectPrimitive.Label
|
|
105
|
-
ref={ref}
|
|
106
|
-
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
|
|
107
|
-
{...props}
|
|
108
|
-
/>
|
|
109
|
-
))
|
|
110
|
-
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
|
111
|
-
|
|
112
|
-
const SelectItem = React.forwardRef<
|
|
113
|
-
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
114
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
115
|
-
>(({ className, children, ...props }, ref) => (
|
|
116
|
-
<SelectPrimitive.Item
|
|
117
|
-
ref={ref}
|
|
118
|
-
className={cn(
|
|
119
|
-
'relative flex w-full cursor-default select-none items-center rounded-button py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-muted focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
120
|
-
className
|
|
121
|
-
)}
|
|
122
|
-
{...props}
|
|
123
|
-
>
|
|
124
|
-
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
125
|
-
<SelectPrimitive.ItemIndicator>
|
|
126
|
-
<Check className="h-4 w-4" />
|
|
127
|
-
</SelectPrimitive.ItemIndicator>
|
|
128
|
-
</span>
|
|
129
|
-
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
130
|
-
</SelectPrimitive.Item>
|
|
131
|
-
))
|
|
132
|
-
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
133
|
-
|
|
134
|
-
const SelectSeparator = React.forwardRef<
|
|
135
|
-
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
136
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
137
|
-
>(({ className, ...props }, ref) => (
|
|
138
|
-
<SelectPrimitive.Separator
|
|
139
|
-
ref={ref}
|
|
140
|
-
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
|
141
|
-
{...props}
|
|
142
|
-
/>
|
|
143
|
-
))
|
|
144
|
-
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
|
145
|
-
|
|
146
|
-
export {
|
|
147
|
-
Select,
|
|
148
|
-
SelectGroup,
|
|
149
|
-
SelectValue,
|
|
150
|
-
SelectTrigger,
|
|
151
|
-
SelectContent,
|
|
152
|
-
SelectLabel,
|
|
153
|
-
SelectItem,
|
|
154
|
-
SelectSeparator,
|
|
155
|
-
SelectScrollUpButton,
|
|
156
|
-
SelectScrollDownButton,
|
|
157
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
|
3
|
-
|
|
4
|
-
import { cn } from '../utils/cn'
|
|
5
|
-
|
|
6
|
-
const Separator = React.forwardRef<
|
|
7
|
-
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
8
|
-
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
9
|
-
>(
|
|
10
|
-
(
|
|
11
|
-
{ className, orientation = 'horizontal', decorative = true, ...props },
|
|
12
|
-
ref
|
|
13
|
-
) => (
|
|
14
|
-
<SeparatorPrimitive.Root
|
|
15
|
-
ref={ref}
|
|
16
|
-
decorative={decorative}
|
|
17
|
-
orientation={orientation}
|
|
18
|
-
className={cn(
|
|
19
|
-
'shrink-0 bg-border',
|
|
20
|
-
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
21
|
-
className
|
|
22
|
-
)}
|
|
23
|
-
{...props}
|
|
24
|
-
/>
|
|
25
|
-
)
|
|
26
|
-
)
|
|
27
|
-
Separator.displayName = SeparatorPrimitive.Root.displayName
|
|
28
|
-
|
|
29
|
-
export { Separator }
|