@fayz-ai/ui 0.8.3 → 0.8.4
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/dist/{chunk-XIRJGVDK.cjs → chunk-7ZQQZD4Q.cjs} +34 -37
- package/dist/{chunk-ID2HQK2Q.js.map → chunk-7ZQQZD4Q.cjs.map} +1 -1
- package/dist/{chunk-SVVKUMSL.cjs → chunk-B3BTZDTA.cjs} +10 -42
- package/dist/chunk-B3BTZDTA.cjs.map +1 -0
- package/dist/{chunk-O5F4LLSL.js → chunk-DYMBFHD7.js} +8 -7
- package/dist/chunk-DYMBFHD7.js.map +1 -0
- package/dist/{chunk-BVF2KPTT.js → chunk-G6OKZ7WL.js} +30 -3
- package/dist/chunk-G6OKZ7WL.js.map +1 -0
- package/dist/{chunk-A7O7KGNL.js → chunk-M34ACV5E.js} +23 -4
- package/dist/chunk-M34ACV5E.js.map +1 -0
- package/dist/{chunk-WF3STNDX.cjs → chunk-MUGRP5DD.cjs} +53 -34
- package/dist/chunk-MUGRP5DD.cjs.map +1 -0
- package/dist/{chunk-MI6FOB4O.cjs → chunk-NZV47HWO.cjs} +31 -2
- package/dist/chunk-NZV47HWO.cjs.map +1 -0
- package/dist/{chunk-2GDQUARM.js → chunk-R4LPSXTT.js} +8 -38
- package/dist/chunk-R4LPSXTT.js.map +1 -0
- package/dist/{chunk-TDGSY5W6.cjs → chunk-R4SQ2GVD.cjs} +8 -7
- package/dist/chunk-R4SQ2GVD.cjs.map +1 -0
- package/dist/{chunk-ID2HQK2Q.js → chunk-VVQ45ZDZ.js} +35 -38
- package/dist/chunk-VVQ45ZDZ.js.map +1 -0
- package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
- package/dist/dashboard/index.cjs +26 -26
- package/dist/dashboard/index.js +2 -2
- package/dist/index.cjs +137 -137
- package/dist/index.js +5 -5
- package/dist/layout/Sidebar.d.ts +4 -0
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/index.cjs +19 -19
- package/dist/layout/index.js +1 -1
- package/dist/primitives/index.cjs +83 -83
- package/dist/primitives/index.js +2 -2
- package/dist/primitives/search-select.d.ts +3 -1
- package/dist/primitives/search-select.d.ts.map +1 -1
- package/dist/theme/index.cjs +8 -8
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -1
- package/package.json +3 -3
- package/src/dashboard/OnboardingWidget.tsx +32 -1
- package/src/layout/Sidebar.tsx +69 -29
- package/src/primitives/search-select.tsx +12 -4
- package/src/theme/index.ts +9 -6
- package/dist/chunk-2GDQUARM.js.map +0 -1
- package/dist/chunk-A7O7KGNL.js.map +0 -1
- package/dist/chunk-BVF2KPTT.js.map +0 -1
- package/dist/chunk-MI6FOB4O.cjs.map +0 -1
- package/dist/chunk-O5F4LLSL.js.map +0 -1
- package/dist/chunk-SVVKUMSL.cjs.map +0 -1
- package/dist/chunk-TDGSY5W6.cjs.map +0 -1
- package/dist/chunk-WF3STNDX.cjs.map +0 -1
- package/dist/chunk-XIRJGVDK.cjs.map +0 -1
package/dist/theme/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ThemeProvider, createFayzTheme, createTheme, darkTheme, fayzThemePresets, lightTheme, useTheme } from '../chunk-
|
|
1
|
+
export { ThemeProvider, createFayzTheme, createTheme, darkTheme, fayzThemePresets, lightTheme, useTheme } from '../chunk-DYMBFHD7.js';
|
|
2
2
|
export { fayzUiPreset } from '../chunk-IWBXYV4U.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"fayz": {
|
|
4
4
|
"status": "beta"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.8.
|
|
6
|
+
"version": "0.8.4",
|
|
7
7
|
"description": "Fayz SDK UI — Radix + Tailwind component library with CRUD engine and layout shells",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.cjs",
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"sonner": "^1.5.0",
|
|
85
85
|
"tailwind-merge": "^2.4.0",
|
|
86
86
|
"zustand": "^4.5.0",
|
|
87
|
-
"@fayz-ai/
|
|
88
|
-
"@fayz-ai/
|
|
87
|
+
"@fayz-ai/auth": "^0.8.4",
|
|
88
|
+
"@fayz-ai/core": "^0.8.4"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@types/react": "^18.3.0",
|
|
@@ -3,6 +3,7 @@ import { Check, Circle } from 'lucide-react'
|
|
|
3
3
|
import { cn } from '../utils/cn'
|
|
4
4
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../primitives/card'
|
|
5
5
|
import { Button } from '../primitives/button'
|
|
6
|
+
import { Skeleton } from '../primitives/skeleton'
|
|
6
7
|
import { renderIcon } from './icon'
|
|
7
8
|
import type { IconRef } from './types'
|
|
8
9
|
|
|
@@ -30,6 +31,33 @@ export interface OnboardingWidgetProps {
|
|
|
30
31
|
className?: string
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/** Placeholder mirroring the widget's layout while the async checks resolve. */
|
|
35
|
+
function OnboardingSkeleton({ rows, className }: { rows: number; className?: string }) {
|
|
36
|
+
return (
|
|
37
|
+
<Card className={className} aria-busy="true">
|
|
38
|
+
<CardHeader>
|
|
39
|
+
<Skeleton className="h-5 w-40" />
|
|
40
|
+
<Skeleton className="mt-1.5 h-3.5 w-64 max-w-full" />
|
|
41
|
+
<div className="mt-2 flex items-center gap-3">
|
|
42
|
+
<Skeleton className="h-1.5 flex-1 rounded-full" />
|
|
43
|
+
<Skeleton className="h-3 w-8" />
|
|
44
|
+
</div>
|
|
45
|
+
</CardHeader>
|
|
46
|
+
<CardContent className="space-y-1 pt-0">
|
|
47
|
+
{Array.from({ length: Math.max(rows, 1) }).map((_, i) => (
|
|
48
|
+
<div key={i} className="flex items-center gap-3 px-2 py-2.5">
|
|
49
|
+
<Skeleton className="h-7 w-7 shrink-0 rounded-full" />
|
|
50
|
+
<div className="min-w-0 flex-1 space-y-1.5">
|
|
51
|
+
<Skeleton className="h-3.5 w-1/2" />
|
|
52
|
+
<Skeleton className="h-3 w-3/4" />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
))}
|
|
56
|
+
</CardContent>
|
|
57
|
+
</Card>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
33
61
|
/** Getting-started checklist ("first steps to set up") with progress. */
|
|
34
62
|
export function OnboardingWidget({
|
|
35
63
|
title = 'Getting started', subtitle, steps, onNavigate, hideWhenComplete = true, className,
|
|
@@ -58,7 +86,10 @@ export function OnboardingWidget({
|
|
|
58
86
|
|
|
59
87
|
const doneCount = steps.filter((s) => completed[s.id]).length
|
|
60
88
|
const allDone = doneCount === steps.length
|
|
61
|
-
|
|
89
|
+
// Hold the card's real shape while the checks settle. Painting the resolved
|
|
90
|
+
// checklist here would flash 0/N at an already-onboarded tenant, and painting
|
|
91
|
+
// nothing collapsed the row and shifted the dashboard on resolve.
|
|
92
|
+
if (!resolved) return <OnboardingSkeleton rows={steps.length} className={className} />
|
|
62
93
|
if (allDone && hideWhenComplete) return null
|
|
63
94
|
|
|
64
95
|
const runAction = (action: OnboardingStepInput['action']) => {
|
package/src/layout/Sidebar.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import * as React from 'react'
|
|
|
2
2
|
import * as Tooltip from '@radix-ui/react-tooltip'
|
|
3
3
|
import {
|
|
4
4
|
Crown,
|
|
5
|
+
ExternalLink,
|
|
5
6
|
Home,
|
|
6
7
|
Users,
|
|
7
8
|
Settings,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
ShoppingBag,
|
|
26
27
|
Target,
|
|
27
28
|
Wrench,
|
|
29
|
+
Truck,
|
|
28
30
|
ClipboardList,
|
|
29
31
|
ListChecks,
|
|
30
32
|
Briefcase,
|
|
@@ -114,6 +116,10 @@ export interface NavigationItem {
|
|
|
114
116
|
* not entitle the feature. The item stays visible with a small Crown badge
|
|
115
117
|
* and, on click, leads to the plan's UpgradePrompt. */
|
|
116
118
|
premium?: boolean
|
|
119
|
+
/** Sales-channel / external link: when set, the item opens this URL in a new
|
|
120
|
+
* tab instead of navigating an internal route. Rendered as an anchor with an
|
|
121
|
+
* external-link glyph. `route` is ignored for matching when `href` is set. */
|
|
122
|
+
href?: string
|
|
117
123
|
children?: NavigationItem[]
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -162,7 +168,7 @@ export interface SidebarProps {
|
|
|
162
168
|
|
|
163
169
|
const ICON_MAP: Record<string, LucideIcon> = {
|
|
164
170
|
Home, Users, Settings, CreditCard, Bell, Calendar, CalendarClock, Package, Activity, BarChart3,
|
|
165
|
-
FileText, Mail, DollarSign, Megaphone, ShoppingCart, Target, Wrench,
|
|
171
|
+
FileText, Mail, DollarSign, Megaphone, ShoppingCart, Target, Wrench, Truck,
|
|
166
172
|
Receipt, ShoppingBag, ClipboardList, ListChecks, Briefcase, UserCog, BookOpen, BookOpenCheck, MessageCircle, Globe,
|
|
167
173
|
Percent, Tag, Tags, Camera, UtensilsCrossed, MapPin, Map, Handshake, Contact,
|
|
168
174
|
Building2, Filter, Plus, List, ListPlus, Search, Shield, Ban, Clock, Dog, Cat, PawPrint, Heart, FolderOpen, LayoutTemplate, LeafyGreen, Apple, Egg, Wheat,
|
|
@@ -199,33 +205,46 @@ function NavItem({
|
|
|
199
205
|
onNavigate: (route: string) => void
|
|
200
206
|
}) {
|
|
201
207
|
const Icon = getIcon(item.icon)
|
|
208
|
+
const isExternal = !!item.href
|
|
209
|
+
|
|
210
|
+
const className = cn(
|
|
211
|
+
'flex w-full items-center gap-3 rounded-md px-2.5 py-1.5 text-[13px] font-medium transition-colors',
|
|
212
|
+
'hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground',
|
|
213
|
+
isActive
|
|
214
|
+
? 'bg-sidebar-accent font-semibold text-sidebar-accent-foreground shadow-sm'
|
|
215
|
+
: 'text-sidebar-foreground/80',
|
|
216
|
+
collapsed && 'justify-center px-2',
|
|
217
|
+
)
|
|
202
218
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
onClick={() => onNavigate(item.route)}
|
|
206
|
-
className={cn(
|
|
207
|
-
'flex w-full items-center gap-3 rounded-md px-2.5 py-1.5 text-[13px] font-medium transition-colors',
|
|
208
|
-
'hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground',
|
|
209
|
-
isActive
|
|
210
|
-
? 'bg-sidebar-accent font-semibold text-sidebar-accent-foreground shadow-sm'
|
|
211
|
-
: 'text-sidebar-foreground/80',
|
|
212
|
-
collapsed && 'justify-center px-2',
|
|
213
|
-
)}
|
|
214
|
-
>
|
|
219
|
+
const inner = (
|
|
220
|
+
<>
|
|
215
221
|
<Icon className="h-4 w-4 shrink-0" />
|
|
216
222
|
{!collapsed && (
|
|
217
223
|
<>
|
|
218
224
|
<span className="flex-1 truncate text-left">{item.label}</span>
|
|
225
|
+
{isExternal && (
|
|
226
|
+
<ExternalLink className="ml-auto h-3.5 w-3.5 shrink-0 opacity-50" aria-hidden />
|
|
227
|
+
)}
|
|
219
228
|
{item.premium && (
|
|
220
|
-
<Crown className=
|
|
229
|
+
<Crown className={cn('h-3.5 w-3.5 shrink-0 text-amber-500', !isExternal && 'ml-auto')} aria-hidden />
|
|
221
230
|
)}
|
|
222
231
|
{item.badge !== undefined && (
|
|
223
|
-
<span className=
|
|
232
|
+
<span className={cn('rounded-full bg-primary px-2 py-0.5 text-xs font-semibold text-primary-foreground', !isExternal && !item.premium && 'ml-auto')}>
|
|
224
233
|
{item.badge}
|
|
225
234
|
</span>
|
|
226
235
|
)}
|
|
227
236
|
</>
|
|
228
237
|
)}
|
|
238
|
+
</>
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const content = isExternal ? (
|
|
242
|
+
<a href={item.href} target="_blank" rel="noopener noreferrer" className={className}>
|
|
243
|
+
{inner}
|
|
244
|
+
</a>
|
|
245
|
+
) : (
|
|
246
|
+
<button onClick={() => onNavigate(item.route)} className={className}>
|
|
247
|
+
{inner}
|
|
229
248
|
</button>
|
|
230
249
|
)
|
|
231
250
|
|
|
@@ -342,19 +361,32 @@ function NavGroup({
|
|
|
342
361
|
{item.children?.map((child) => {
|
|
343
362
|
const ChildIcon = getIcon(child.icon)
|
|
344
363
|
const isActive = child.route === activeRoute
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
)}
|
|
355
|
-
>
|
|
364
|
+
const childExternal = !!child.href
|
|
365
|
+
const childClass = cn(
|
|
366
|
+
'flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors',
|
|
367
|
+
isActive
|
|
368
|
+
? 'font-medium text-sidebar-accent-foreground'
|
|
369
|
+
: 'text-sidebar-muted hover:text-sidebar-accent-foreground'
|
|
370
|
+
)
|
|
371
|
+
const childInner = (
|
|
372
|
+
<>
|
|
356
373
|
<ChildIcon className="h-3.5 w-3.5 shrink-0" />
|
|
357
|
-
<span className="truncate">{child.label}</span>
|
|
374
|
+
<span className="flex-1 truncate text-left">{child.label}</span>
|
|
375
|
+
{childExternal && (
|
|
376
|
+
<ExternalLink className="h-3 w-3 shrink-0 opacity-50" aria-hidden />
|
|
377
|
+
)}
|
|
378
|
+
{child.premium && (
|
|
379
|
+
<Crown className="h-3 w-3 shrink-0 text-amber-500" aria-hidden />
|
|
380
|
+
)}
|
|
381
|
+
</>
|
|
382
|
+
)
|
|
383
|
+
return childExternal ? (
|
|
384
|
+
<a key={child.id} href={child.href} target="_blank" rel="noopener noreferrer" className={childClass}>
|
|
385
|
+
{childInner}
|
|
386
|
+
</a>
|
|
387
|
+
) : (
|
|
388
|
+
<button key={child.id} onClick={() => onNavigate(child.route)} className={childClass}>
|
|
389
|
+
{childInner}
|
|
358
390
|
</button>
|
|
359
391
|
)
|
|
360
392
|
})}
|
|
@@ -422,7 +454,13 @@ export function Sidebar({
|
|
|
422
454
|
// node so a context change can snapshot the outgoing rail and animate it out.
|
|
423
455
|
const navBody = (
|
|
424
456
|
<>
|
|
425
|
-
{topContent &&
|
|
457
|
+
{topContent && (
|
|
458
|
+
<div className="mb-4 space-y-2">
|
|
459
|
+
{React.isValidElement(topContent)
|
|
460
|
+
? React.cloneElement(topContent as React.ReactElement<any>, { collapsed })
|
|
461
|
+
: topContent}
|
|
462
|
+
</div>
|
|
463
|
+
)}
|
|
426
464
|
{mainItems.length > 0 && (
|
|
427
465
|
<div className="space-y-1">
|
|
428
466
|
{sectionHeading(sectionLabels?.main)}
|
|
@@ -509,7 +547,9 @@ export function Sidebar({
|
|
|
509
547
|
<nav
|
|
510
548
|
className={cn(
|
|
511
549
|
'relative flex-1 p-2',
|
|
512
|
-
|
|
550
|
+
// scrollbar-none: the rail still scrolls, but without the native
|
|
551
|
+
// scrollbar gutter eating into the nav items.
|
|
552
|
+
outgoing ? 'overflow-hidden' : 'overflow-y-auto scrollbar-none',
|
|
513
553
|
)}
|
|
514
554
|
>
|
|
515
555
|
{outgoing && (
|
|
@@ -43,7 +43,9 @@ export interface SearchSelectProps {
|
|
|
43
43
|
onCreate?: (query: string) => void | Promise<void>
|
|
44
44
|
/** Custom render for each option */
|
|
45
45
|
renderOption?: (option: SearchSelectOption) => React.ReactNode
|
|
46
|
-
/** Load initial results when dropdown opens (calls onSearch(''))
|
|
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). */
|
|
47
49
|
loadOnOpen?: boolean
|
|
48
50
|
/** Additional className for the container */
|
|
49
51
|
className?: string
|
|
@@ -69,7 +71,7 @@ export function SearchSelect({
|
|
|
69
71
|
createLabel = 'Add',
|
|
70
72
|
onCreate,
|
|
71
73
|
renderOption,
|
|
72
|
-
loadOnOpen,
|
|
74
|
+
loadOnOpen = true,
|
|
73
75
|
className,
|
|
74
76
|
disabled,
|
|
75
77
|
}: SearchSelectProps) {
|
|
@@ -162,6 +164,10 @@ export function SearchSelect({
|
|
|
162
164
|
if (disabled || creating) return
|
|
163
165
|
setOpen(true)
|
|
164
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([])
|
|
165
171
|
setTimeout(() => inputRef.current?.focus(), 0)
|
|
166
172
|
}
|
|
167
173
|
|
|
@@ -177,7 +183,9 @@ export function SearchSelect({
|
|
|
177
183
|
}, [results])
|
|
178
184
|
|
|
179
185
|
const hasValue = !!value && !!selectedLabel
|
|
180
|
-
|
|
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)
|
|
181
189
|
|
|
182
190
|
return (
|
|
183
191
|
<div ref={containerRef} className={cn('relative', className)}>
|
|
@@ -246,7 +254,7 @@ export function SearchSelect({
|
|
|
246
254
|
</div>
|
|
247
255
|
) : results.length === 0 ? (
|
|
248
256
|
<div className="px-4 py-4 text-center">
|
|
249
|
-
<p className="text-xs text-muted-foreground">No results for "{query}"</p>
|
|
257
|
+
<p className="text-xs text-muted-foreground">{query ? `No results for "${query}"` : 'No results'}</p>
|
|
250
258
|
{allowCreate && onCreate && (
|
|
251
259
|
<button
|
|
252
260
|
onClick={() => handleCreate(query)}
|
package/src/theme/index.ts
CHANGED
|
@@ -60,16 +60,19 @@ export function useTheme() {
|
|
|
60
60
|
export function createTheme(overrides: CreateThemeOptions): ThemeTokens {
|
|
61
61
|
const colors = { ...lightTheme.colors, ...overrides.colors }
|
|
62
62
|
|
|
63
|
-
// Brand shorthand: auto-derive primary, ring, accent from single HSL
|
|
63
|
+
// Brand shorthand: auto-derive primary, ring, accent from single HSL.
|
|
64
|
+
// Explicit `colors.*` overrides win — the shorthand is a default, not a
|
|
65
|
+
// mandate, so an app can pin any one of these without dropping the rest.
|
|
64
66
|
if (overrides.brand) {
|
|
65
|
-
|
|
66
|
-
colors.
|
|
67
|
-
colors.
|
|
67
|
+
const given = overrides.colors ?? {}
|
|
68
|
+
if (given.primary == null) colors.primary = overrides.brand
|
|
69
|
+
if (given.primaryForeground == null) colors.primaryForeground = '0 0% 100%'
|
|
70
|
+
if (given.ring == null) colors.ring = overrides.brand
|
|
68
71
|
const parts = overrides.brand.split(' ')
|
|
69
72
|
if (parts.length >= 3) {
|
|
70
73
|
const hue = (parseFloat(parts[0]) - 50 + 360) % 360
|
|
71
|
-
colors.accent = `${hue} ${parts[1]} ${parts[2]}`
|
|
72
|
-
colors.accentForeground = '0 0% 100%'
|
|
74
|
+
if (given.accent == null) colors.accent = `${hue} ${parts[1]} ${parts[2]}`
|
|
75
|
+
if (given.accentForeground == null) colors.accentForeground = '0 0% 100%'
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
|