@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.
- 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 +4 -13
- 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/styles.css +0 -461
- 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
|
@@ -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} “<span className="font-medium">{value.trim()}</span>”</span>
|
|
209
|
-
</div>
|
|
210
|
-
)}
|
|
211
|
-
</div>,
|
|
212
|
-
document.body,
|
|
213
|
-
)}
|
|
214
|
-
</div>
|
|
215
|
-
)
|
|
216
|
-
}
|
package/src/primitives/badge.tsx
DELETED
|
@@ -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 }
|
package/src/primitives/card.tsx
DELETED
|
@@ -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
|
-
}
|