@fayz-ai/ui 0.8.3 → 0.8.5
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/ChartWidgetInner-GDE5PBTR.js +46 -0
- package/dist/ChartWidgetInner-GDE5PBTR.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-ID2HQK2Q.js → chunk-KV3MHB6N.js} +116 -213
- package/dist/chunk-KV3MHB6N.js.map +1 -0
- package/dist/{chunk-A7O7KGNL.js → chunk-OUPJDGKE.js} +27 -43
- package/dist/chunk-OUPJDGKE.js.map +1 -0
- package/dist/chunk-PVIB724L.js +161 -0
- package/dist/chunk-PVIB724L.js.map +1 -0
- package/dist/{chunk-O5F4LLSL.js → chunk-VDAYIQX4.js} +208 -8
- package/dist/chunk-VDAYIQX4.js.map +1 -0
- package/dist/{chunk-2GDQUARM.js → chunk-YLQGUJAQ.js} +24 -42
- package/dist/chunk-YLQGUJAQ.js.map +1 -0
- package/dist/dashboard/ChartWidget.d.ts.map +1 -1
- package/dist/dashboard/ChartWidgetInner.d.ts +11 -0
- package/dist/dashboard/ChartWidgetInner.d.ts.map +1 -0
- package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -2
- package/dist/icons.d.ts +8 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +3 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/layout/Sidebar.d.ts +5 -2
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/index.js +2 -1
- package/dist/preset.cjs +201 -11
- package/dist/preset.cjs.map +1 -1
- package/dist/preset.js +201 -1
- package/dist/preset.js.map +1 -1
- package/dist/primitives/date-picker.d.ts +7 -1
- package/dist/primitives/date-picker.d.ts.map +1 -1
- 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/tailwind.cjs +199 -2
- package/dist/tailwind.cjs.map +1 -1
- package/dist/tailwind.js +198 -1
- package/dist/tailwind.js.map +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -2
- package/package.json +24 -22
- package/src/dashboard/ChartWidget.tsx +8 -65
- package/src/dashboard/ChartWidgetInner.tsx +83 -0
- package/src/dashboard/OnboardingWidget.tsx +32 -1
- package/src/dashboard/icon.ts +2 -2
- package/src/icons.ts +66 -0
- package/src/index.ts +4 -0
- package/src/layout/Sidebar.tsx +70 -136
- package/src/primitives/date-picker.tsx +30 -4
- 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-2P7T6QQK.cjs +0 -206
- package/dist/chunk-2P7T6QQK.cjs.map +0 -1
- package/dist/chunk-A7O7KGNL.js.map +0 -1
- package/dist/chunk-BVF2KPTT.js.map +0 -1
- package/dist/chunk-H6OULCHF.cjs +0 -81
- package/dist/chunk-H6OULCHF.cjs.map +0 -1
- package/dist/chunk-ID2HQK2Q.js.map +0 -1
- package/dist/chunk-IWBXYV4U.js +0 -203
- package/dist/chunk-IWBXYV4U.js.map +0 -1
- package/dist/chunk-MI6FOB4O.cjs +0 -351
- package/dist/chunk-MI6FOB4O.cjs.map +0 -1
- package/dist/chunk-O5F4LLSL.js.map +0 -1
- package/dist/chunk-OW73RZOU.cjs +0 -313
- package/dist/chunk-OW73RZOU.cjs.map +0 -1
- package/dist/chunk-SVVKUMSL.cjs +0 -1733
- package/dist/chunk-SVVKUMSL.cjs.map +0 -1
- package/dist/chunk-TDGSY5W6.cjs +0 -474
- package/dist/chunk-TDGSY5W6.cjs.map +0 -1
- package/dist/chunk-WF3STNDX.cjs +0 -682
- package/dist/chunk-WF3STNDX.cjs.map +0 -1
- package/dist/chunk-XIRJGVDK.cjs +0 -1826
- package/dist/chunk-XIRJGVDK.cjs.map +0 -1
- package/dist/dashboard/index.cjs +0 -106
- package/dist/dashboard/index.cjs.map +0 -1
- package/dist/index.cjs +0 -650
- package/dist/index.cjs.map +0 -1
- package/dist/layout/index.cjs +0 -98
- package/dist/layout/index.cjs.map +0 -1
- package/dist/primitives/index.cjs +0 -363
- package/dist/primitives/index.cjs.map +0 -1
- package/dist/theme/index.cjs +0 -41
- package/dist/theme/index.cjs.map +0 -1
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
ResponsiveContainer,
|
|
4
|
-
LineChart, Line,
|
|
5
|
-
BarChart, Bar,
|
|
6
|
-
AreaChart, Area,
|
|
7
|
-
PieChart, Pie, Cell,
|
|
8
|
-
XAxis, YAxis, CartesianGrid, Tooltip, Legend,
|
|
9
|
-
} from 'recharts'
|
|
10
2
|
import { Card, CardContent, CardHeader, CardTitle } from '../primitives/card'
|
|
11
3
|
import { renderIcon } from './icon'
|
|
12
4
|
import type { ChartSeries, ChartType, IconRef } from './types'
|
|
13
5
|
|
|
14
|
-
/** Theme-driven palette (HSL triplet CSS vars defined in styles.css). */
|
|
15
|
-
const PALETTE = [
|
|
16
|
-
'hsl(var(--info))',
|
|
17
|
-
'hsl(var(--magic))',
|
|
18
|
-
'hsl(var(--success))',
|
|
19
|
-
'hsl(var(--warning))',
|
|
20
|
-
'hsl(var(--destructive))',
|
|
21
|
-
]
|
|
22
|
-
const AXIS = 'hsl(var(--muted-foreground))'
|
|
23
|
-
const GRID = 'hsl(var(--border))'
|
|
24
|
-
|
|
25
6
|
export interface ChartWidgetProps {
|
|
26
7
|
type: ChartType
|
|
27
8
|
/** Rows of data; each key referenced by a series.dataKey. */
|
|
@@ -37,12 +18,15 @@ export interface ChartWidgetProps {
|
|
|
37
18
|
className?: string
|
|
38
19
|
}
|
|
39
20
|
|
|
21
|
+
// The recharts body is lazy so recharts ships as an async chunk instead of in
|
|
22
|
+
// every consumer's entry bundle; the Card shell renders immediately and the
|
|
23
|
+
// fallback reserves the chart's height to avoid layout shift.
|
|
24
|
+
const ChartWidgetInner = React.lazy(() => import('./ChartWidgetInner'))
|
|
25
|
+
|
|
40
26
|
/** recharts-backed chart in a Card. Replaces the hand-rolled CSS bar charts. */
|
|
41
27
|
export function ChartWidget({
|
|
42
28
|
type, data, series, categoryKey = 'name', title, icon, headerAction, height = 240, className,
|
|
43
29
|
}: ChartWidgetProps) {
|
|
44
|
-
const color = (i: number, explicit?: string) => explicit ?? PALETTE[i % PALETTE.length]
|
|
45
|
-
|
|
46
30
|
return (
|
|
47
31
|
<Card className={className}>
|
|
48
32
|
{(title || headerAction) && (
|
|
@@ -57,50 +41,9 @@ export function ChartWidget({
|
|
|
57
41
|
</CardHeader>
|
|
58
42
|
)}
|
|
59
43
|
<CardContent className="p-4 pt-2">
|
|
60
|
-
<
|
|
61
|
-
{type
|
|
62
|
-
|
|
63
|
-
<Tooltip />
|
|
64
|
-
<Pie data={data} dataKey={series[0]?.dataKey ?? 'value'} nameKey={categoryKey} innerRadius={50} outerRadius={80} paddingAngle={2}>
|
|
65
|
-
{data.map((_, i) => <Cell key={i} fill={color(i, series[0]?.color)} />)}
|
|
66
|
-
</Pie>
|
|
67
|
-
<Legend />
|
|
68
|
-
</PieChart>
|
|
69
|
-
) : type === 'bar' ? (
|
|
70
|
-
<BarChart data={data}>
|
|
71
|
-
<CartesianGrid strokeDasharray="3 3" stroke={GRID} vertical={false} />
|
|
72
|
-
<XAxis dataKey={categoryKey} stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} />
|
|
73
|
-
<YAxis stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} width={36} />
|
|
74
|
-
<Tooltip cursor={{ fill: GRID, opacity: 0.3 }} />
|
|
75
|
-
{series.length > 1 && <Legend />}
|
|
76
|
-
{series.map((s, i) => (
|
|
77
|
-
<Bar key={s.dataKey} dataKey={s.dataKey} name={s.label ?? s.dataKey} fill={color(i, s.color)} radius={[4, 4, 0, 0]} />
|
|
78
|
-
))}
|
|
79
|
-
</BarChart>
|
|
80
|
-
) : type === 'area' ? (
|
|
81
|
-
<AreaChart data={data}>
|
|
82
|
-
<CartesianGrid strokeDasharray="3 3" stroke={GRID} vertical={false} />
|
|
83
|
-
<XAxis dataKey={categoryKey} stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} />
|
|
84
|
-
<YAxis stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} width={36} />
|
|
85
|
-
<Tooltip />
|
|
86
|
-
{series.length > 1 && <Legend />}
|
|
87
|
-
{series.map((s, i) => (
|
|
88
|
-
<Area key={s.dataKey} dataKey={s.dataKey} name={s.label ?? s.dataKey} stroke={color(i, s.color)} fill={color(i, s.color)} fillOpacity={0.15} />
|
|
89
|
-
))}
|
|
90
|
-
</AreaChart>
|
|
91
|
-
) : (
|
|
92
|
-
<LineChart data={data}>
|
|
93
|
-
<CartesianGrid strokeDasharray="3 3" stroke={GRID} vertical={false} />
|
|
94
|
-
<XAxis dataKey={categoryKey} stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} />
|
|
95
|
-
<YAxis stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} width={36} />
|
|
96
|
-
<Tooltip />
|
|
97
|
-
{series.length > 1 && <Legend />}
|
|
98
|
-
{series.map((s, i) => (
|
|
99
|
-
<Line key={s.dataKey} dataKey={s.dataKey} name={s.label ?? s.dataKey} stroke={color(i, s.color)} strokeWidth={2} dot={false} />
|
|
100
|
-
))}
|
|
101
|
-
</LineChart>
|
|
102
|
-
)}
|
|
103
|
-
</ResponsiveContainer>
|
|
44
|
+
<React.Suspense fallback={<div style={{ height }} />}>
|
|
45
|
+
<ChartWidgetInner type={type} data={data} series={series} categoryKey={categoryKey} height={height} />
|
|
46
|
+
</React.Suspense>
|
|
104
47
|
</CardContent>
|
|
105
48
|
</Card>
|
|
106
49
|
)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ResponsiveContainer,
|
|
4
|
+
LineChart, Line,
|
|
5
|
+
BarChart, Bar,
|
|
6
|
+
AreaChart, Area,
|
|
7
|
+
PieChart, Pie, Cell,
|
|
8
|
+
XAxis, YAxis, CartesianGrid, Tooltip, Legend,
|
|
9
|
+
} from 'recharts'
|
|
10
|
+
import type { ChartSeries, ChartType } from './types'
|
|
11
|
+
|
|
12
|
+
/** Theme-driven palette (HSL triplet CSS vars defined in styles.css). */
|
|
13
|
+
const PALETTE = [
|
|
14
|
+
'hsl(var(--info))',
|
|
15
|
+
'hsl(var(--magic))',
|
|
16
|
+
'hsl(var(--success))',
|
|
17
|
+
'hsl(var(--warning))',
|
|
18
|
+
'hsl(var(--destructive))',
|
|
19
|
+
]
|
|
20
|
+
const AXIS = 'hsl(var(--muted-foreground))'
|
|
21
|
+
const GRID = 'hsl(var(--border))'
|
|
22
|
+
|
|
23
|
+
export interface ChartWidgetInnerProps {
|
|
24
|
+
type: ChartType
|
|
25
|
+
data: Array<Record<string, string | number>>
|
|
26
|
+
series: ChartSeries[]
|
|
27
|
+
categoryKey: string
|
|
28
|
+
height: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// recharts-backed chart body. Default export + own module so ChartWidget can
|
|
32
|
+
// React.lazy it — recharts (~400 KB min) then lives in an async chunk instead
|
|
33
|
+
// of every consumer's entry bundle.
|
|
34
|
+
export default function ChartWidgetInner({ type, data, series, categoryKey, height }: ChartWidgetInnerProps) {
|
|
35
|
+
const color = (i: number, explicit?: string) => explicit ?? PALETTE[i % PALETTE.length]
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<ResponsiveContainer width="100%" height={height}>
|
|
39
|
+
{type === 'pie' ? (
|
|
40
|
+
<PieChart>
|
|
41
|
+
<Tooltip />
|
|
42
|
+
<Pie data={data} dataKey={series[0]?.dataKey ?? 'value'} nameKey={categoryKey} innerRadius={50} outerRadius={80} paddingAngle={2}>
|
|
43
|
+
{data.map((_, i) => <Cell key={i} fill={color(i, series[0]?.color)} />)}
|
|
44
|
+
</Pie>
|
|
45
|
+
<Legend />
|
|
46
|
+
</PieChart>
|
|
47
|
+
) : type === 'bar' ? (
|
|
48
|
+
<BarChart data={data}>
|
|
49
|
+
<CartesianGrid strokeDasharray="3 3" stroke={GRID} vertical={false} />
|
|
50
|
+
<XAxis dataKey={categoryKey} stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} />
|
|
51
|
+
<YAxis stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} width={36} />
|
|
52
|
+
<Tooltip cursor={{ fill: GRID, opacity: 0.3 }} />
|
|
53
|
+
{series.length > 1 && <Legend />}
|
|
54
|
+
{series.map((s, i) => (
|
|
55
|
+
<Bar key={s.dataKey} dataKey={s.dataKey} name={s.label ?? s.dataKey} fill={color(i, s.color)} radius={[4, 4, 0, 0]} />
|
|
56
|
+
))}
|
|
57
|
+
</BarChart>
|
|
58
|
+
) : type === 'area' ? (
|
|
59
|
+
<AreaChart data={data}>
|
|
60
|
+
<CartesianGrid strokeDasharray="3 3" stroke={GRID} vertical={false} />
|
|
61
|
+
<XAxis dataKey={categoryKey} stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} />
|
|
62
|
+
<YAxis stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} width={36} />
|
|
63
|
+
<Tooltip />
|
|
64
|
+
{series.length > 1 && <Legend />}
|
|
65
|
+
{series.map((s, i) => (
|
|
66
|
+
<Area key={s.dataKey} dataKey={s.dataKey} name={s.label ?? s.dataKey} stroke={color(i, s.color)} fill={color(i, s.color)} fillOpacity={0.15} />
|
|
67
|
+
))}
|
|
68
|
+
</AreaChart>
|
|
69
|
+
) : (
|
|
70
|
+
<LineChart data={data}>
|
|
71
|
+
<CartesianGrid strokeDasharray="3 3" stroke={GRID} vertical={false} />
|
|
72
|
+
<XAxis dataKey={categoryKey} stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} />
|
|
73
|
+
<YAxis stroke={AXIS} fontSize={12} tickLine={false} axisLine={false} width={36} />
|
|
74
|
+
<Tooltip />
|
|
75
|
+
{series.length > 1 && <Legend />}
|
|
76
|
+
{series.map((s, i) => (
|
|
77
|
+
<Line key={s.dataKey} dataKey={s.dataKey} name={s.label ?? s.dataKey} stroke={color(i, s.color)} strokeWidth={2} dot={false} />
|
|
78
|
+
))}
|
|
79
|
+
</LineChart>
|
|
80
|
+
)}
|
|
81
|
+
</ResponsiveContainer>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -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/dashboard/icon.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import
|
|
2
|
+
import { getLucideIcon } from '../icons'
|
|
3
3
|
import type { IconRef } from './types'
|
|
4
4
|
|
|
5
5
|
/** Resolve an IconRef (lucide name or component) to a renderable element. */
|
|
6
6
|
export function renderIcon(icon: IconRef | undefined, className = 'h-4 w-4'): React.ReactNode {
|
|
7
7
|
if (!icon) return null
|
|
8
8
|
if (typeof icon === 'string') {
|
|
9
|
-
const Comp = (
|
|
9
|
+
const Comp = getLucideIcon(icon)
|
|
10
10
|
return Comp ? React.createElement(Comp, { className }) : null
|
|
11
11
|
}
|
|
12
12
|
return React.createElement(icon, { className })
|
package/src/icons.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react'
|
|
2
|
+
import {
|
|
3
|
+
Activity, AlertTriangle, Apple, ArrowDownCircle, ArrowDownRight, ArrowLeftRight, ArrowUpCircle, ArrowUpRight,
|
|
4
|
+
Award, BadgeDollarSign, Ban, Banknote, BarChart3, Bell, Bike, BookOpen,
|
|
5
|
+
BookOpenCheck, Box, Boxes, Briefcase, Building2, Calendar, CalendarCheck, CalendarCheck2,
|
|
6
|
+
CalendarClock, CalendarDays, CalendarPlus, CalendarX, Camera, Cat, Check, CheckCircle,
|
|
7
|
+
CheckSquare, ChefHat, ChevronsUpDown, Circle, CircleCheck, CircleDollarSign, Clapperboard, ClipboardList,
|
|
8
|
+
Clock, Clock3, Command, Compass, Contact, CreditCard, Disc3, Dog,
|
|
9
|
+
DollarSign, DoorOpen, Egg, ExternalLink, Eye, FileCheck2, FileDown,
|
|
10
|
+
FileText, Filter, Flame, FolderOpen, Globe, GraduationCap, Handshake, Heart,
|
|
11
|
+
Home, Image, Inbox, Instagram, Landmark, Layers, LayoutDashboard, LayoutTemplate,
|
|
12
|
+
Leaf, LeafyGreen, Link, List, ListChecks, ListMusic, ListPlus, Lock,
|
|
13
|
+
Mail, Map, MapPin, Megaphone, MessageCircle, MessageSquare, Mic, MoreHorizontal,
|
|
14
|
+
MousePointerClick, Music, Newspaper, Package, PackageCheck, Palette, PartyPopper, PawPrint,
|
|
15
|
+
Pencil, Percent, Phone, PieChart, Plug, Plus, PlusCircle, Puzzle, Radio,
|
|
16
|
+
Receipt, Repeat, RotateCcw, Ruler, Search, Send, Settings, Shield,
|
|
17
|
+
ShieldCheck, ShoppingBag, ShoppingCart, Sigma, SlidersHorizontal, Sparkles, Star, Tag,
|
|
18
|
+
Tags, Target, ToggleLeft, TreePalm, TrendingDown, TrendingUp, Trophy, Truck,
|
|
19
|
+
User, UserCheck, UserCog, UserPlus, UserX, Users, UsersRound, Utensils,
|
|
20
|
+
UtensilsCrossed, Wallet, Warehouse, Wheat, Workflow, Wrench, X, XCircle,
|
|
21
|
+
Zap,
|
|
22
|
+
} from 'lucide-react'
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Central lucide icon registry. The SDK's manifest-driven surfaces resolve
|
|
26
|
+
// icons by string name; a namespace import (`import * as LucideIcons`) defeats
|
|
27
|
+
// tree-shaking and drags all ~1,500 icons (~500 KB min) into every bundle, so
|
|
28
|
+
// the SDK ships this curated set instead — the union of every icon name the
|
|
29
|
+
// SDK, plugins, and app templates reference. Apps using names outside the set
|
|
30
|
+
// call registerIcons({ Name: Component }) with their own named imports.
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const registry: Record<string, LucideIcon> = {
|
|
34
|
+
Activity, AlertTriangle, Apple, ArrowDownCircle, ArrowDownRight, ArrowLeftRight, ArrowUpCircle, ArrowUpRight,
|
|
35
|
+
Award, BadgeDollarSign, Ban, Banknote, BarChart3, Bell, Bike, BookOpen,
|
|
36
|
+
BookOpenCheck, Box, Boxes, Briefcase, Building2, Calendar, CalendarCheck, CalendarCheck2,
|
|
37
|
+
CalendarClock, CalendarDays, CalendarPlus, CalendarX, Camera, Cat, Check, CheckCircle,
|
|
38
|
+
CheckSquare, ChefHat, ChevronsUpDown, Circle, CircleCheck, CircleDollarSign, Clapperboard, ClipboardList,
|
|
39
|
+
Clock, Clock3, Command, Compass, Contact, CreditCard, Disc3, Dog,
|
|
40
|
+
DollarSign, DoorOpen, Egg, ExternalLink, Eye, FileCheck2, FileDown,
|
|
41
|
+
FileText, Filter, Flame, FolderOpen, Globe, GraduationCap, Handshake, Heart,
|
|
42
|
+
Home, Image, Inbox, Instagram, Landmark, Layers, LayoutDashboard, LayoutTemplate,
|
|
43
|
+
Leaf, LeafyGreen, Link, List, ListChecks, ListMusic, ListPlus, Lock,
|
|
44
|
+
Mail, Map, MapPin, Megaphone, MessageCircle, MessageSquare, Mic, MoreHorizontal,
|
|
45
|
+
MousePointerClick, Music, Newspaper, Package, PackageCheck, Palette, PartyPopper, PawPrint,
|
|
46
|
+
Pencil, Percent, Phone, PieChart, Plug, Plus, PlusCircle, Puzzle, Radio,
|
|
47
|
+
Receipt, Repeat, RotateCcw, Ruler, Search, Send, Settings, Shield,
|
|
48
|
+
ShieldCheck, ShoppingBag, ShoppingCart, Sigma, SlidersHorizontal, Sparkles, Star, Tag,
|
|
49
|
+
Tags, Target, ToggleLeft, TreePalm, TrendingDown, TrendingUp, Trophy, Truck,
|
|
50
|
+
User, UserCheck, UserCog, UserPlus, UserX, Users, UsersRound, Utensils,
|
|
51
|
+
UtensilsCrossed, Wallet, Warehouse, Wheat, Workflow, Wrench, X, XCircle,
|
|
52
|
+
Zap,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Add or override icons resolvable by string name (e.g. from app manifests). */
|
|
56
|
+
export function registerIcons(icons: Record<string, LucideIcon>): void {
|
|
57
|
+
Object.assign(registry, icons)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Resolve a lucide icon by name; undefined when not registered. */
|
|
61
|
+
export function getLucideIcon(name: string): LucideIcon | undefined {
|
|
62
|
+
return registry[name]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The registered icon map (live reference — do not mutate directly). */
|
|
66
|
+
export const ICON_MAP = registry
|
package/src/index.ts
CHANGED
|
@@ -228,3 +228,7 @@ export {
|
|
|
228
228
|
type ChartType,
|
|
229
229
|
type ChartSeries,
|
|
230
230
|
} from './dashboard'
|
|
231
|
+
|
|
232
|
+
// Central lucide icon registry — curated tree-shakable set + escape hatch for
|
|
233
|
+
// apps that need icon names outside it (registerIcons({ Name: Component })).
|
|
234
|
+
export { registerIcons, getLucideIcon } from './icons'
|
package/src/layout/Sidebar.tsx
CHANGED
|
@@ -1,104 +1,10 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import * as Tooltip from '@radix-ui/react-tooltip'
|
|
3
3
|
import {
|
|
4
|
-
Crown,
|
|
5
|
-
Home,
|
|
6
|
-
Users,
|
|
7
|
-
Settings,
|
|
8
|
-
CreditCard,
|
|
9
|
-
Bell,
|
|
10
|
-
Calendar,
|
|
11
|
-
CalendarClock,
|
|
12
|
-
Package,
|
|
13
|
-
Activity,
|
|
14
|
-
BarChart3,
|
|
15
|
-
FileText,
|
|
16
|
-
Mail,
|
|
17
|
-
PanelLeftClose,
|
|
18
|
-
PanelLeft,
|
|
19
|
-
Search,
|
|
20
|
-
Shield,
|
|
21
|
-
DollarSign,
|
|
22
|
-
Megaphone,
|
|
23
|
-
Receipt,
|
|
24
|
-
ShoppingCart,
|
|
25
|
-
ShoppingBag,
|
|
26
|
-
Target,
|
|
27
|
-
Wrench,
|
|
28
|
-
ClipboardList,
|
|
29
|
-
ListChecks,
|
|
30
|
-
Briefcase,
|
|
31
|
-
UserCog,
|
|
32
|
-
BookOpen,
|
|
33
|
-
BookOpenCheck,
|
|
34
|
-
MessageCircle,
|
|
35
|
-
Globe,
|
|
36
|
-
Percent,
|
|
37
|
-
Tag,
|
|
38
|
-
Tags,
|
|
39
|
-
Camera,
|
|
40
|
-
UtensilsCrossed,
|
|
41
|
-
MapPin,
|
|
42
|
-
Map,
|
|
43
|
-
Handshake,
|
|
44
|
-
Contact,
|
|
45
|
-
Building2,
|
|
46
|
-
Filter,
|
|
47
|
-
Plus,
|
|
48
|
-
List,
|
|
49
|
-
ListPlus,
|
|
50
|
-
ChevronDown,
|
|
51
|
-
Ban,
|
|
52
|
-
Clock,
|
|
53
|
-
Dog,
|
|
54
|
-
Cat,
|
|
55
|
-
PawPrint,
|
|
56
|
-
Heart,
|
|
57
|
-
FolderOpen,
|
|
58
|
-
LayoutTemplate,
|
|
59
|
-
LeafyGreen,
|
|
60
|
-
Apple,
|
|
61
|
-
Egg,
|
|
62
|
-
Wheat,
|
|
63
|
-
PartyPopper,
|
|
64
|
-
Radio,
|
|
65
|
-
CalendarDays,
|
|
66
|
-
CalendarCheck2,
|
|
67
|
-
CalendarX,
|
|
68
|
-
BadgeDollarSign,
|
|
69
|
-
Banknote,
|
|
70
|
-
Landmark,
|
|
71
|
-
CircleDollarSign,
|
|
72
|
-
Layers,
|
|
73
|
-
Music,
|
|
74
|
-
Eye,
|
|
75
|
-
ListMusic,
|
|
76
|
-
Disc3,
|
|
77
|
-
UsersRound,
|
|
78
|
-
Mic,
|
|
79
|
-
Zap,
|
|
80
|
-
Award,
|
|
81
|
-
LayoutDashboard,
|
|
82
|
-
MessageSquare,
|
|
83
|
-
Inbox,
|
|
84
|
-
Star,
|
|
85
|
-
TrendingUp,
|
|
86
|
-
GraduationCap,
|
|
87
|
-
Repeat,
|
|
88
|
-
UserCheck,
|
|
89
|
-
UserPlus,
|
|
90
|
-
UserX,
|
|
91
|
-
Workflow,
|
|
92
|
-
Boxes,
|
|
93
|
-
Clock3,
|
|
94
|
-
FileCheck2,
|
|
95
|
-
User,
|
|
96
|
-
Palette,
|
|
97
|
-
ShieldCheck,
|
|
98
|
-
SlidersHorizontal,
|
|
99
|
-
Puzzle,
|
|
4
|
+
Bell, ChevronDown, Crown, ExternalLink, Home, PanelLeft, PanelLeftClose, Search, Settings, User,
|
|
100
5
|
type LucideIcon,
|
|
101
6
|
} from 'lucide-react'
|
|
7
|
+
import { ICON_MAP } from '../icons'
|
|
102
8
|
import { cn } from '../utils/cn'
|
|
103
9
|
import { useLayoutStore } from '../stores/layout.store'
|
|
104
10
|
|
|
@@ -114,6 +20,10 @@ export interface NavigationItem {
|
|
|
114
20
|
* not entitle the feature. The item stays visible with a small Crown badge
|
|
115
21
|
* and, on click, leads to the plan's UpgradePrompt. */
|
|
116
22
|
premium?: boolean
|
|
23
|
+
/** Sales-channel / external link: when set, the item opens this URL in a new
|
|
24
|
+
* tab instead of navigating an internal route. Rendered as an anchor with an
|
|
25
|
+
* external-link glyph. `route` is ignored for matching when `href` is set. */
|
|
26
|
+
href?: string
|
|
117
27
|
children?: NavigationItem[]
|
|
118
28
|
}
|
|
119
29
|
|
|
@@ -160,18 +70,8 @@ export interface SidebarProps {
|
|
|
160
70
|
navDirection?: 'forward' | 'back'
|
|
161
71
|
}
|
|
162
72
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
FileText, Mail, DollarSign, Megaphone, ShoppingCart, Target, Wrench,
|
|
166
|
-
Receipt, ShoppingBag, ClipboardList, ListChecks, Briefcase, UserCog, BookOpen, BookOpenCheck, MessageCircle, Globe,
|
|
167
|
-
Percent, Tag, Tags, Camera, UtensilsCrossed, MapPin, Map, Handshake, Contact,
|
|
168
|
-
Building2, Filter, Plus, List, ListPlus, Search, Shield, Ban, Clock, Dog, Cat, PawPrint, Heart, FolderOpen, LayoutTemplate, LeafyGreen, Apple, Egg, Wheat,
|
|
169
|
-
PartyPopper, Radio, CalendarDays, CalendarCheck2, CalendarX, BadgeDollarSign, Banknote, Landmark, CircleDollarSign, Layers, Music, Eye, ListMusic, Disc3, UsersRound, Mic,
|
|
170
|
-
Zap, Award, LayoutDashboard, MessageSquare, Inbox, Star, TrendingUp, UserCheck, UserPlus, UserX, Workflow, Boxes, Clock3, FileCheck2,
|
|
171
|
-
GraduationCap, Repeat,
|
|
172
|
-
User, Palette, ShieldCheck, SlidersHorizontal, Puzzle,
|
|
173
|
-
}
|
|
174
|
-
|
|
73
|
+
// ICON_MAP now lives in ../icons (central curated registry, shared by every
|
|
74
|
+
// string-name icon lookup in the SDK); re-exported here for compat.
|
|
175
75
|
export { ICON_MAP }
|
|
176
76
|
|
|
177
77
|
/** Kept in sync with --saas-rail-duration in styles.css. */
|
|
@@ -199,33 +99,46 @@ function NavItem({
|
|
|
199
99
|
onNavigate: (route: string) => void
|
|
200
100
|
}) {
|
|
201
101
|
const Icon = getIcon(item.icon)
|
|
102
|
+
const isExternal = !!item.href
|
|
103
|
+
|
|
104
|
+
const className = cn(
|
|
105
|
+
'flex w-full items-center gap-3 rounded-md px-2.5 py-1.5 text-[13px] font-medium transition-colors',
|
|
106
|
+
'hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground',
|
|
107
|
+
isActive
|
|
108
|
+
? 'bg-sidebar-accent font-semibold text-sidebar-accent-foreground shadow-sm'
|
|
109
|
+
: 'text-sidebar-foreground/80',
|
|
110
|
+
collapsed && 'justify-center px-2',
|
|
111
|
+
)
|
|
202
112
|
|
|
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
|
-
>
|
|
113
|
+
const inner = (
|
|
114
|
+
<>
|
|
215
115
|
<Icon className="h-4 w-4 shrink-0" />
|
|
216
116
|
{!collapsed && (
|
|
217
117
|
<>
|
|
218
118
|
<span className="flex-1 truncate text-left">{item.label}</span>
|
|
119
|
+
{isExternal && (
|
|
120
|
+
<ExternalLink className="ml-auto h-3.5 w-3.5 shrink-0 opacity-50" aria-hidden />
|
|
121
|
+
)}
|
|
219
122
|
{item.premium && (
|
|
220
|
-
<Crown className=
|
|
123
|
+
<Crown className={cn('h-3.5 w-3.5 shrink-0 text-amber-500', !isExternal && 'ml-auto')} aria-hidden />
|
|
221
124
|
)}
|
|
222
125
|
{item.badge !== undefined && (
|
|
223
|
-
<span className=
|
|
126
|
+
<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
127
|
{item.badge}
|
|
225
128
|
</span>
|
|
226
129
|
)}
|
|
227
130
|
</>
|
|
228
131
|
)}
|
|
132
|
+
</>
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const content = isExternal ? (
|
|
136
|
+
<a href={item.href} target="_blank" rel="noopener noreferrer" className={className}>
|
|
137
|
+
{inner}
|
|
138
|
+
</a>
|
|
139
|
+
) : (
|
|
140
|
+
<button onClick={() => onNavigate(item.route)} className={className}>
|
|
141
|
+
{inner}
|
|
229
142
|
</button>
|
|
230
143
|
)
|
|
231
144
|
|
|
@@ -342,19 +255,32 @@ function NavGroup({
|
|
|
342
255
|
{item.children?.map((child) => {
|
|
343
256
|
const ChildIcon = getIcon(child.icon)
|
|
344
257
|
const isActive = child.route === activeRoute
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
)}
|
|
355
|
-
>
|
|
258
|
+
const childExternal = !!child.href
|
|
259
|
+
const childClass = cn(
|
|
260
|
+
'flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors',
|
|
261
|
+
isActive
|
|
262
|
+
? 'font-medium text-sidebar-accent-foreground'
|
|
263
|
+
: 'text-sidebar-muted hover:text-sidebar-accent-foreground'
|
|
264
|
+
)
|
|
265
|
+
const childInner = (
|
|
266
|
+
<>
|
|
356
267
|
<ChildIcon className="h-3.5 w-3.5 shrink-0" />
|
|
357
|
-
<span className="truncate">{child.label}</span>
|
|
268
|
+
<span className="flex-1 truncate text-left">{child.label}</span>
|
|
269
|
+
{childExternal && (
|
|
270
|
+
<ExternalLink className="h-3 w-3 shrink-0 opacity-50" aria-hidden />
|
|
271
|
+
)}
|
|
272
|
+
{child.premium && (
|
|
273
|
+
<Crown className="h-3 w-3 shrink-0 text-amber-500" aria-hidden />
|
|
274
|
+
)}
|
|
275
|
+
</>
|
|
276
|
+
)
|
|
277
|
+
return childExternal ? (
|
|
278
|
+
<a key={child.id} href={child.href} target="_blank" rel="noopener noreferrer" className={childClass}>
|
|
279
|
+
{childInner}
|
|
280
|
+
</a>
|
|
281
|
+
) : (
|
|
282
|
+
<button key={child.id} onClick={() => onNavigate(child.route)} className={childClass}>
|
|
283
|
+
{childInner}
|
|
358
284
|
</button>
|
|
359
285
|
)
|
|
360
286
|
})}
|
|
@@ -422,7 +348,13 @@ export function Sidebar({
|
|
|
422
348
|
// node so a context change can snapshot the outgoing rail and animate it out.
|
|
423
349
|
const navBody = (
|
|
424
350
|
<>
|
|
425
|
-
{topContent &&
|
|
351
|
+
{topContent && (
|
|
352
|
+
<div className="mb-4 space-y-2">
|
|
353
|
+
{React.isValidElement(topContent)
|
|
354
|
+
? React.cloneElement(topContent as React.ReactElement<any>, { collapsed })
|
|
355
|
+
: topContent}
|
|
356
|
+
</div>
|
|
357
|
+
)}
|
|
426
358
|
{mainItems.length > 0 && (
|
|
427
359
|
<div className="space-y-1">
|
|
428
360
|
{sectionHeading(sectionLabels?.main)}
|
|
@@ -509,7 +441,9 @@ export function Sidebar({
|
|
|
509
441
|
<nav
|
|
510
442
|
className={cn(
|
|
511
443
|
'relative flex-1 p-2',
|
|
512
|
-
|
|
444
|
+
// scrollbar-none: the rail still scrolls, but without the native
|
|
445
|
+
// scrollbar gutter eating into the nav items.
|
|
446
|
+
outgoing ? 'overflow-hidden' : 'overflow-y-auto scrollbar-none',
|
|
513
447
|
)}
|
|
514
448
|
>
|
|
515
449
|
{outgoing && (
|
|
@@ -28,6 +28,17 @@ function formatDate(date: Date, locale: string) {
|
|
|
28
28
|
return date.toLocaleDateString(toDateLocale(locale), { month: 'short', day: 'numeric', year: 'numeric' })
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** Toolbar label: the year is noise while you're inside it, and two of these sit
|
|
32
|
+
* side by side in a range — it only earns its space on another year. */
|
|
33
|
+
function formatDateCompact(date: Date, locale: string) {
|
|
34
|
+
const sameYear = date.getFullYear() === new Date().getFullYear()
|
|
35
|
+
return date.toLocaleDateString(toDateLocale(locale), {
|
|
36
|
+
month: 'short',
|
|
37
|
+
day: 'numeric',
|
|
38
|
+
...(sameYear ? {} : { year: 'numeric' }),
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
function toDateStr(d: Date) {
|
|
32
43
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
33
44
|
}
|
|
@@ -42,13 +53,24 @@ interface DatePickerProps {
|
|
|
42
53
|
value: string
|
|
43
54
|
onChange: (value: string) => void
|
|
44
55
|
className?: string
|
|
56
|
+
/**
|
|
57
|
+
* `input` (default) is the form-field trigger. `toolbar` is the 26px pill that
|
|
58
|
+
* matches SegmentedControl/Button `sm`, for filter bars where a full-height
|
|
59
|
+
* form field towers over everything beside it.
|
|
60
|
+
*/
|
|
61
|
+
variant?: 'input' | 'toolbar'
|
|
45
62
|
/** Open the dropdown immediately on mount */
|
|
46
63
|
defaultOpen?: boolean
|
|
47
64
|
/** Called when the dropdown opens or closes */
|
|
48
65
|
onOpenChange?: (open: boolean) => void
|
|
49
66
|
}
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
const TRIGGER_VARIANT: Record<'input' | 'toolbar', string> = {
|
|
69
|
+
input: 'rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm w-full',
|
|
70
|
+
toolbar: 'h-[26px] rounded-button border border-border bg-card shadow-button px-2.5 text-xs font-medium',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function DatePicker({ value, onChange, className, variant = 'input', defaultOpen, onOpenChange }: DatePickerProps) {
|
|
52
74
|
const t = useTranslation()
|
|
53
75
|
const locale = getCurrentLocale()
|
|
54
76
|
const [open, setOpen] = useState(defaultOpen ?? false)
|
|
@@ -129,9 +151,13 @@ export function DatePicker({ value, onChange, className, defaultOpen, onOpenChan
|
|
|
129
151
|
<div ref={ref} className={`relative ${className ?? ''}`}>
|
|
130
152
|
{/* Trigger */}
|
|
131
153
|
<button ref={triggerRef} type="button" onClick={() => setOpen((p) => !p)}
|
|
132
|
-
className=
|
|
133
|
-
<Calendar className=
|
|
134
|
-
<span
|
|
154
|
+
className={`flex items-center gap-2 text-left transition-colors hover:bg-muted/30 ${TRIGGER_VARIANT[variant]}`}>
|
|
155
|
+
<Calendar className={`shrink-0 text-muted-foreground ${variant === 'toolbar' ? 'h-3 w-3' : 'h-3.5 w-3.5'}`} />
|
|
156
|
+
<span className="whitespace-nowrap">
|
|
157
|
+
{value
|
|
158
|
+
? (variant === 'toolbar' ? formatDateCompact : formatDate)(selectedDate, locale)
|
|
159
|
+
: t('common.selectDate')}
|
|
160
|
+
</span>
|
|
135
161
|
</button>
|
|
136
162
|
|
|
137
163
|
{/* Dropdown — portal to escape overflow containers */}
|