@timbal-ai/timbal-react 0.8.2 → 1.1.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/CHANGELOG.md +49 -0
- package/README.md +100 -6
- package/dist/app.cjs +2538 -1262
- package/dist/app.d.cts +11 -6
- package/dist/app.d.ts +11 -6
- package/dist/app.esm.js +43 -6
- package/dist/{button-ClSgD6OF.d.cts → button-BoyX5pM_.d.cts} +1 -1
- package/dist/{button-ClSgD6OF.d.ts → button-BoyX5pM_.d.ts} +1 -1
- package/dist/{chart-artifact-Bl67kre7.d.ts → chart-artifact-BZp7nmaf.d.ts} +430 -14
- package/dist/{chart-artifact-BzcvblDe.d.cts → chart-artifact-CX-rh9nq.d.cts} +430 -14
- package/dist/{chat-Bed4FQSl.d.cts → chat-DCms8pJ_.d.cts} +31 -4
- package/dist/{chat-Bed4FQSl.d.ts → chat-DCms8pJ_.d.ts} +31 -4
- package/dist/chat.cjs +1111 -776
- package/dist/chat.d.cts +1 -1
- package/dist/chat.d.ts +1 -1
- package/dist/chat.esm.js +3 -3
- package/dist/{chunk-QVAUCVQA.esm.js → chunk-4AKJ6FKE.esm.js} +277 -4
- package/dist/chunk-6HWMJNZT.esm.js +3439 -0
- package/dist/{chunk-VWHHKAHN.esm.js → chunk-FRZOEYBO.esm.js} +4 -4
- package/dist/chunk-JEAUF54A.esm.js +52 -0
- package/dist/{chunk-OISVICYF.esm.js → chunk-P3KDAYX6.esm.js} +1 -1
- package/dist/{chunk-6YVKCVEP.esm.js → chunk-TK2AGIME.esm.js} +1106 -298
- package/dist/{chunk-CFU3YDTV.esm.js → chunk-XCM3V6RK.esm.js} +5 -5
- package/dist/{chunk-5ZKLPWVN.esm.js → chunk-YXZ22OJN.esm.js} +849 -667
- package/dist/index.cjs +6070 -1605
- package/dist/index.d.cts +8 -6
- package/dist/index.d.ts +8 -6
- package/dist/index.esm.js +427 -11
- package/dist/pill-segmented-tabs-Ba5q0feL.d.cts +500 -0
- package/dist/pill-segmented-tabs-Ba5q0feL.d.ts +500 -0
- package/dist/studio.cjs +1333 -998
- package/dist/studio.d.cts +2 -2
- package/dist/studio.d.ts +2 -2
- package/dist/studio.esm.js +5 -5
- package/dist/styles.css +220 -0
- package/dist/ui.cjs +3592 -89
- package/dist/ui.d.cts +72 -96
- package/dist/ui.d.ts +72 -96
- package/dist/ui.esm.js +400 -6
- package/dist/{welcome-COOb05a5.d.cts → welcome-CRqOPKMp.d.cts} +1 -1
- package/dist/{welcome-DE08m9ca.d.ts → welcome-DlHUa3OL.d.ts} +1 -1
- package/package.json +9 -3
- package/dist/chunk-P4SN7M67.esm.js +0 -435
|
@@ -13,14 +13,16 @@ import {
|
|
|
13
13
|
monotoneAreaPath,
|
|
14
14
|
monotoneLinePath,
|
|
15
15
|
studioIntegrationCardClass,
|
|
16
|
-
studioSearchChromeClass,
|
|
17
|
-
studioSecondaryChromeClass,
|
|
18
16
|
studioTopbarPillHeightClass,
|
|
19
17
|
toNum
|
|
20
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-YXZ22OJN.esm.js";
|
|
19
|
+
import {
|
|
20
|
+
Checkbox,
|
|
21
|
+
Skeleton
|
|
22
|
+
} from "./chunk-JEAUF54A.esm.js";
|
|
21
23
|
import {
|
|
22
24
|
PillSegmentedTabs
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-P3KDAYX6.esm.js";
|
|
24
26
|
import {
|
|
25
27
|
Button,
|
|
26
28
|
Dialog,
|
|
@@ -31,10 +33,195 @@ import {
|
|
|
31
33
|
TIMBAL_V2_SWITCH_THUMB,
|
|
32
34
|
TIMBAL_V2_SWITCH_TRACK_OFF,
|
|
33
35
|
TimbalV2Button,
|
|
34
|
-
cn
|
|
35
|
-
|
|
36
|
+
cn,
|
|
37
|
+
controlClass
|
|
38
|
+
} from "./chunk-4AKJ6FKE.esm.js";
|
|
39
|
+
|
|
40
|
+
// src/design/ui-vocabulary.ts
|
|
41
|
+
var SEMANTIC_COLOR_TOKENS = [
|
|
42
|
+
// shadcn-style base tokens
|
|
43
|
+
"background",
|
|
44
|
+
"foreground",
|
|
45
|
+
"card",
|
|
46
|
+
"card-foreground",
|
|
47
|
+
"popover",
|
|
48
|
+
"popover-foreground",
|
|
49
|
+
"primary",
|
|
50
|
+
"primary-foreground",
|
|
51
|
+
"secondary",
|
|
52
|
+
"secondary-foreground",
|
|
53
|
+
"muted",
|
|
54
|
+
"muted-foreground",
|
|
55
|
+
"accent",
|
|
56
|
+
"accent-foreground",
|
|
57
|
+
"destructive",
|
|
58
|
+
"destructive-foreground",
|
|
59
|
+
"border",
|
|
60
|
+
"input",
|
|
61
|
+
"ring",
|
|
62
|
+
// sidebar scope
|
|
63
|
+
"sidebar",
|
|
64
|
+
"sidebar-foreground",
|
|
65
|
+
"sidebar-primary",
|
|
66
|
+
"sidebar-primary-foreground",
|
|
67
|
+
"sidebar-accent",
|
|
68
|
+
"sidebar-accent-foreground",
|
|
69
|
+
"sidebar-border",
|
|
70
|
+
"sidebar-ring",
|
|
71
|
+
// timbal chrome extensions
|
|
72
|
+
"elevated-from",
|
|
73
|
+
"elevated-to",
|
|
74
|
+
"modal-from",
|
|
75
|
+
"modal-to",
|
|
76
|
+
"playground-from",
|
|
77
|
+
"playground-via",
|
|
78
|
+
"playground-to",
|
|
79
|
+
"composer-bg",
|
|
80
|
+
"composer-border",
|
|
81
|
+
"composer-border-focus",
|
|
82
|
+
"bubble-user",
|
|
83
|
+
"bubble-user-foreground",
|
|
84
|
+
"code-block-bg",
|
|
85
|
+
"code-header-bg"
|
|
86
|
+
];
|
|
87
|
+
var RESERVED_GRADIENT_TOKENS = [
|
|
88
|
+
"primary-fill-from",
|
|
89
|
+
"primary-fill-to",
|
|
90
|
+
"primary-fill-hover-from",
|
|
91
|
+
"primary-fill-hover-to",
|
|
92
|
+
"primary-fill-active-from",
|
|
93
|
+
"primary-fill-active-to",
|
|
94
|
+
"secondary-fill-hover-from",
|
|
95
|
+
"secondary-fill-hover-to",
|
|
96
|
+
"secondary-fill-active-from",
|
|
97
|
+
"secondary-fill-active-to",
|
|
98
|
+
"destructive-fill-hover-from",
|
|
99
|
+
"destructive-fill-hover-to",
|
|
100
|
+
"destructive-fill-active-from",
|
|
101
|
+
"destructive-fill-active-to",
|
|
102
|
+
"ghost-fill-hover",
|
|
103
|
+
"ghost-fill-active",
|
|
104
|
+
"elevated-from",
|
|
105
|
+
"elevated-to",
|
|
106
|
+
"modal-from",
|
|
107
|
+
"modal-to",
|
|
108
|
+
"playground-from",
|
|
109
|
+
"playground-via",
|
|
110
|
+
"playground-to"
|
|
111
|
+
];
|
|
112
|
+
var TAILWIND_PALETTE_COLORS = [
|
|
113
|
+
"slate",
|
|
114
|
+
"gray",
|
|
115
|
+
"zinc",
|
|
116
|
+
"neutral",
|
|
117
|
+
"stone",
|
|
118
|
+
"red",
|
|
119
|
+
"orange",
|
|
120
|
+
"amber",
|
|
121
|
+
"yellow",
|
|
122
|
+
"lime",
|
|
123
|
+
"green",
|
|
124
|
+
"emerald",
|
|
125
|
+
"teal",
|
|
126
|
+
"cyan",
|
|
127
|
+
"sky",
|
|
128
|
+
"blue",
|
|
129
|
+
"indigo",
|
|
130
|
+
"violet",
|
|
131
|
+
"purple",
|
|
132
|
+
"fuchsia",
|
|
133
|
+
"pink",
|
|
134
|
+
"rose"
|
|
135
|
+
];
|
|
136
|
+
var COLOR_UTILITY_PREFIXES = [
|
|
137
|
+
"bg",
|
|
138
|
+
"text",
|
|
139
|
+
"border",
|
|
140
|
+
"ring",
|
|
141
|
+
"from",
|
|
142
|
+
"via",
|
|
143
|
+
"to",
|
|
144
|
+
"fill",
|
|
145
|
+
"stroke",
|
|
146
|
+
"decoration",
|
|
147
|
+
"outline",
|
|
148
|
+
"shadow",
|
|
149
|
+
"divide",
|
|
150
|
+
"accent",
|
|
151
|
+
"caret"
|
|
152
|
+
];
|
|
153
|
+
var SLOP_BUDGETS = {
|
|
154
|
+
/** Max decorative/standalone icons rendered in a single generated file. */
|
|
155
|
+
maxIconsPerView: 6,
|
|
156
|
+
/** Max consecutive list rows separated by an explicit border/divider before
|
|
157
|
+
* it reads as a "ruled table" — prefer spacing or zebra instead. */
|
|
158
|
+
maxRowDividers: 2
|
|
159
|
+
};
|
|
160
|
+
var HOUSE_RULES = [
|
|
161
|
+
{
|
|
162
|
+
id: "semantic-color",
|
|
163
|
+
rule: "Color only through semantic tokens \u2014 never a raw palette color, hex, or oklch literal.",
|
|
164
|
+
why: "The theme generator owns every color; hardcoding breaks dark mode and rebranding.",
|
|
165
|
+
slop: `<span className="text-blue-600 bg-green-50">`,
|
|
166
|
+
good: `<span className="text-primary bg-muted">`
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "no-decorative-icons",
|
|
170
|
+
rule: "Icons must earn their place (action, nav, or status). Never add an icon beside a label that already says the thing.",
|
|
171
|
+
why: "An icon on every tile/card is the #1 tell of generated slop.",
|
|
172
|
+
slop: `<StatTile label={<><BarChart2 /> Revenue</>} value="$95k" />`,
|
|
173
|
+
good: `<StatTile label="Revenue" value="$95k" />`
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "neutral-trend",
|
|
177
|
+
rule: "Don't put a colored trend pill on every metric. Use a trend only when the delta is the point, and keep it muted.",
|
|
178
|
+
why: "Loud green/red pills everywhere are noise, not signal.",
|
|
179
|
+
slop: `<MetricTile trend="+8%" className="text-green-500" />`,
|
|
180
|
+
good: `<MetricTile label="Win rate" value="50%" />`
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: "values-normal-weight",
|
|
184
|
+
rule: "Metric values use normal font weight, not bold.",
|
|
185
|
+
why: "Giant bold numbers read as a template; normal weight reads as a product.",
|
|
186
|
+
slop: `<span className="text-3xl font-bold tabular-nums">$322k</span>`,
|
|
187
|
+
good: `<span className="text-2xl font-normal tabular-nums">$322k</span>`
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "no-card-in-card",
|
|
191
|
+
rule: "Don't nest a bordered card inside another bordered card. Group with spacing or a Section instead.",
|
|
192
|
+
why: "Card-in-card doubles borders and shadows for no information gain."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "no-row-dividers",
|
|
196
|
+
rule: "Don't put a divider between every list row. Use spacing or zebra striping.",
|
|
197
|
+
why: "A rule under every row turns a clean list into a dense ledger."
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "no-data-gradient",
|
|
201
|
+
rule: "Gradients are reserved for chrome (composer, elevated surface, playground). Never on a data card, tile, or table.",
|
|
202
|
+
why: "Gradient stat cards are the canonical 'AI dashboard' look."
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "compose-from-blocks",
|
|
206
|
+
rule: "Build from premade blocks (MetricRow, MetricChartCard, DataTable, IntegrationCard). Drop to raw primitives only when no block fits.",
|
|
207
|
+
why: "Slop appears the moment generation falls below the curated block layer."
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "use-kit-controls",
|
|
211
|
+
rule: "Use the kit's controls (SearchInput, Select, DropdownMenu, FieldInput, FieldSelect) \u2014 never hand-roll an input/trigger surface (`border-input rounded-* bg-\u2026`).",
|
|
212
|
+
why: "Hand-rolled controls drift from the shared control-surface skin and look foreign next to kit controls.",
|
|
213
|
+
slop: `<button className="rounded-lg border border-input bg-transparent px-3 h-9">`,
|
|
214
|
+
good: `<SelectTrigger><SelectValue /></SelectTrigger>`
|
|
215
|
+
}
|
|
216
|
+
];
|
|
36
217
|
|
|
37
218
|
// src/app/agent-instructions.ts
|
|
219
|
+
var ANTI_SLOP_CHECKLIST = HOUSE_RULES.map((r) => {
|
|
220
|
+
const pair = r.slop && r.good ? `
|
|
221
|
+
- slop: \`${r.slop}\`
|
|
222
|
+
- good: \`${r.good}\`` : "";
|
|
223
|
+
return `- **${r.id}** \u2014 ${r.rule} (${r.why})${pair}`;
|
|
224
|
+
}).join("\n");
|
|
38
225
|
var APP_KIT_AGENT_INSTRUCTIONS = `
|
|
39
226
|
## App kit (@timbal-ai/timbal-react/app)
|
|
40
227
|
|
|
@@ -58,12 +245,12 @@ Presentational groups \u2014 import from the package root, not from these paths:
|
|
|
58
245
|
|
|
59
246
|
| Folder | Components |
|
|
60
247
|
|--------|------------|
|
|
61
|
-
| \`data/\` | \`MetricRow\`, \`MetricChartCard\`, \`MetricTile\`, \`DataTable\`, \`FilterBar\`, \`ChartPanel\` |
|
|
248
|
+
| \`data/\` | \`MetricRow\`, \`MetricChartCard\`, \`MetricTile\`, \`DataTable\`, \`FilterBar\`, \`FilterField\`, \`ChartPanel\` |
|
|
62
249
|
| \`integrations/\` | \`IntegrationCard\`, \`ConnectionRow\`, \`ConnectionRowList\`, \`IntegrationsEmptyState\`, \`PlanBadge\` |
|
|
63
250
|
| \`settings/\` | \`SettingsSection\`, \`FieldRow\`, \`DangerZone\`, \`FloatingUnsavedChangesBar\` |
|
|
64
251
|
| \`surfaces/\` | \`StatTile\`, \`InfoCard\`, \`ResourceCard\`, \`DescriptionList\`, \`ExpandableSection\`, \`StatusDot\`, \`StatusBadge\`, \`EmptyState\` |
|
|
65
252
|
| \`layout/\` | \`AppShell\`, \`Page\`, \`Section\` |
|
|
66
|
-
| \`charts\` (re-exported) | \`LineAreaChart\`, \`Sparkline\`, \`CHART_PALETTE\` |
|
|
253
|
+
| \`charts\` (re-exported) | \`LineAreaChart\`, \`PieChart\`, \`RadialChart\`, \`RadarChart\`, \`Sparkline\`, \`CHART_PALETTE\` |
|
|
67
254
|
|
|
68
255
|
Also re-exported: \`Button\`, \`TimbalChat\`, \`ChartArtifactView\`, \`APP_KIT_AGENT_INSTRUCTIONS\`.
|
|
69
256
|
|
|
@@ -78,11 +265,19 @@ Theming helpers (import from the package root or \`/app\`): \`createTimbalTheme\
|
|
|
78
265
|
| **Context** | Do not show raw JSON context in the panel header; keep context in \`AppCopilotProvider\` only. |
|
|
79
266
|
| **Theming** | Use semantic Tailwind tokens (\`bg-background\`, \`text-foreground\`, \`border-border\`, \`bg-elevated-from\`, etc.) from the host app's \`styles.css\`. To rebrand, **never hand-author OKLCH** \u2014 call \`createTimbalTheme({ brand })\` + \`themeToCss\`/\`applyTimbalTheme\`, or apply a catalog preset (\`TIMBAL_THEME_PRESETS\` / \`applyThemePreset\`). To offer styles, render \`ThemePresetGallery\`. See \`THEME_AGENT_INSTRUCTIONS\`. |
|
|
80
267
|
| **Layout chrome** | \`Page\` \u2192 \`Section\` for main content hierarchy. \`AppShellTopbar\` for global actions (auth, theme). |
|
|
81
|
-
| **Data** | Prefer \`DataTable\` with typed \`columns\` / \`rows\` / \`getRowKey\`; use \`ChartPanel\` with \`ChartArtifact\` for charts. |
|
|
268
|
+
| **Data** | Prefer \`DataTable\` with typed \`columns\` / \`rows\` / \`getRowKey\`; use \`ChartPanel\` with a \`ChartArtifact\` for charts (set \`chartType\` + options \u2014 see the chart catalog). Chart colors come from the theme \`--chart-1..6\` tokens; pass \`series[].color\` / \`colors\` only to override, never raw hex on every series. |
|
|
82
269
|
| **Modals** | Use \`AppConfirmDialog\` for destructive/export confirmations. |
|
|
83
270
|
| **Metrics** | Overview KPIs \u2192 \`MetricRow\` or \`MetricChartCard\` (not four separate heavy cards). Values use **normal** font weight, not bold. |
|
|
84
271
|
| **Integrations** | Catalog \u2192 \`IntegrationCard\` grid; connected list \u2192 \`ConnectionRow\` inside \`ConnectionRowList\`. Footer CTAs: \`Button variant="secondary"\`. |
|
|
85
|
-
| **Anti-slop** | No loud green/red trend pills on every tile; no \`bg-card\` flat grids when platform chrome exists; avoid recycling demo names ("Operations", mock workforce lists). |
|
|
272
|
+
| **Anti-slop** | Follow the **anti-slop checklist** below. No loud green/red trend pills on every tile; no \`bg-card\` flat grids when platform chrome exists; avoid recycling demo names ("Operations", mock workforce lists). |
|
|
273
|
+
|
|
274
|
+
### Anti-slop checklist (required \u2014 output is linted against this)
|
|
275
|
+
|
|
276
|
+
Generated UIs are checked by \`lintGeneratedUi\` and rejected on any error. Self-review against these before returning code (icon budget: ${SLOP_BUDGETS.maxIconsPerView} per view; at most ${SLOP_BUDGETS.maxRowDividers} ruled rows before it reads as a ledger):
|
|
277
|
+
|
|
278
|
+
${ANTI_SLOP_CHECKLIST}
|
|
279
|
+
|
|
280
|
+
The cause of slop is dropping **below** the curated block layer into raw primitives + free Tailwind. Stay on the blocks; reach for primitives only when no block fits, and even then keep colors on semantic tokens.
|
|
86
281
|
|
|
87
282
|
### Accessibility (required)
|
|
88
283
|
|
|
@@ -108,15 +303,17 @@ Theming helpers (import from the package root or \`/app\`): \`createTimbalTheme\
|
|
|
108
303
|
| \`useAppShellChat\` | Custom open/close trigger when \`hideChatTrigger\` on shell. |
|
|
109
304
|
| \`Page\` | Page title, description, \`breadcrumbs\`, \`actions\`, children. |
|
|
110
305
|
| \`Section\` | Titled block inside a page. |
|
|
111
|
-
| \`SubNav\` |
|
|
306
|
+
| \`SubNav\` | **Section switcher** (Overview / Reports pill bar): \`items\`, \`activeId\`, \`onChange\`. Never use Radix/shadcn \`Tabs\` \u2014 it is not in this package. Switch panels with state or the router. |
|
|
307
|
+
| **Menus** | **Select** = short list, no search. **Combobox** = searchable (same trigger as Select). **Command** only inside \`PopoverContent variant="list"\` or Combobox \u2014 never padded default Popover. See \`examples/app-kit/src/recipes/primitives-catalog.ts\`. |
|
|
112
308
|
| \`Breadcrumbs\` | Trail: \`items: [{ label, href? }]\`. |
|
|
113
309
|
| \`Button\` | Actions \u2014 \`variant="secondary"\` for catalog/secondary CTAs; \`variant="default"\` for primary. |
|
|
114
310
|
| \`StatTile\` | Single KPI in its own card (grid of scattered stats). Prefer \`MetricRow\` for a unified overview strip. |
|
|
115
|
-
| \`StatusBadge\` | Status pill: \`tone\` (\`success
|
|
116
|
-
| \`FilterBar\` | Horizontal filter row
|
|
311
|
+
| \`StatusBadge\` | Status pill: \`tone\` (\`default\`\\|\`primary\`\\|\`success\`\\|\`warn\`\\|\`danger\`\\|\`muted\`), children. Use \`danger\` for critical/error severity. |
|
|
312
|
+
| \`FilterBar\` | Horizontal filter row \u2014 bottom-aligns controls. Mix \`SearchInput\` with labeled \`FilterField\` + \`Select\` (or \`Field\` + \`Select\`); labels sit above, control baselines match. |
|
|
313
|
+
| \`FilterField\` | Optional label wrapper for a filter control inside \`FilterBar\` (severity, status, \u2026). Omit \`label\` for search-only fields. |
|
|
117
314
|
| \`SearchInput\` | Filter field with consistent app styling. |
|
|
118
|
-
| \`DataTable\` | Sortable table: \`columns\`, \`rows\`, \`getRowKey\`, optional \`sort\` / \`onSortChange\`, \`emptyTitle\`, \`showRowCount\`, \`caption\` for
|
|
119
|
-
| \`ChartPanel\` | Same shell as \`MetricChartCard\`: title row (\`px-4 pt-4\`), flush plot (\`pt-2\` only). Pass \`title\` + \`artifact\` (omit \`artifact.title\` to avoid duplicates) or \`children\`. |
|
|
315
|
+
| \`DataTable\` | Sortable table: \`columns\`, \`rows\`, \`getRowKey\`, optional \`sort\` / \`onSortChange\`, \`emptyTitle\`, \`showRowCount\`, \`caption\`. **Scales:** \`pageSize\` (built-in client pager), \`selectable\` + \`onSelectionChange\` (checkbox column for bulk actions), \`loading\` (skeleton rows). \`onRowClick\` for row \u2192 detail (open a \`Sheet\`). |
|
|
316
|
+
| \`ChartPanel\` | Same shell as \`MetricChartCard\`: title row (\`px-4 pt-4\`), flush plot (\`pt-2\` only). Pass \`title\` + \`artifact\` (omit \`artifact.title\` to avoid duplicates) or \`children\`. \`loading\` renders a plot-height skeleton. |
|
|
120
317
|
| \`FieldInput\`, \`FieldTextarea\`, \`FieldSelect\`, \`FieldSwitch\` | Settings-style forms with labels and hints. |
|
|
121
318
|
| \`FormSection\` | Grouped form block. |
|
|
122
319
|
| \`AppConfirmDialog\` | Confirm/cancel modal: \`open\`, \`onOpenChange\`, \`title\`, \`description\`, \`onConfirm\`. |
|
|
@@ -125,13 +322,18 @@ Theming helpers (import from the package root or \`/app\`): \`createTimbalTheme\
|
|
|
125
322
|
|
|
126
323
|
#### Charts & metrics
|
|
127
324
|
|
|
325
|
+
Charts run on **recharts** with shadcn \`ChartContainer\` / \`ChartTooltipContent\` chrome (see \`src/ui/chart.tsx\`). Series colors default to \`--chart-1..6\`; override those CSS tokens to rebrand every chart.
|
|
326
|
+
|
|
128
327
|
| Component | Use for |
|
|
129
328
|
|-----------|---------|
|
|
130
|
-
| \`LineAreaChart\` |
|
|
329
|
+
| \`LineAreaChart\` | Cartesian engine (shadcn-style chrome). Props: \`data\`, \`xKey\`, \`series: [{ dataKey, label?, color? }]\`, \`variant\` (\`area\`\\|\`line\`\\|\`bar\`), \`orientation\` (\`horizontal\` for horizontal bars), \`stacked\`, \`curve\` (\`monotone\`\\|\`linear\`\\|\`step\`), \`dots\`, \`gridLines\`, \`tooltipIndicator\` (\`dot\`\\|\`line\`\\|\`dashed\`), \`layout\` (\`flush\`), \`height\`, \`showLegend\`, \`formatX\`, \`formatValue\`, \`ariaLabel\`. |
|
|
330
|
+
| \`PieChart\` | Pie / donut: \`data\`, \`nameKey\`, \`dataKey\`, \`innerRadius\` (>0 = donut), \`centerValue\`/\`centerLabel\` (donut hole KPI), \`showLabels\`, \`colors\`. |
|
|
331
|
+
| \`RadialChart\` | Concentric progress rings: \`data\`, \`nameKey\`, \`dataKey\`, \`maxValue\`, \`centerValue\`/\`centerLabel\`. Good for gauges / share-of-target. |
|
|
332
|
+
| \`RadarChart\` | Spider chart (\u22653 axes): \`data\`, \`nameKey\`, \`series\`, \`maxValue\`. Compare a few metrics across entities. |
|
|
131
333
|
| \`Sparkline\` | Tiny inline trend (table cells): \`data\`, \`color\`, \`area\`. |
|
|
132
334
|
| \`MetricTile\` | Low-level KPI cell \u2014 prefer \`MetricRow\` / \`MetricChartCard\` instead of hand-wiring tiles. |
|
|
133
|
-
| \`MetricRow\` | KPI strip in one elevated card (no chart). Props: \`metrics: [{ id, label, value, unit?, trend? }]\`, optional \`onMetricChange\`, \`metricsAriaLabel
|
|
134
|
-
| \`MetricChartCard\` | KPI strip + flush chart; tile click swaps series. Same metrics shape + \`data\` per metric. Default chart height 300. |
|
|
335
|
+
| \`MetricRow\` | KPI strip in one elevated card (no chart). Props: \`metrics: [{ id, label, value, unit?, trend?, trendTone? }]\`, optional \`onMetricChange\`, \`metricsAriaLabel\`, \`loading\` (skeleton tiles). |
|
|
336
|
+
| \`MetricChartCard\` | KPI strip + flush chart; tile click swaps series. Same metrics shape + \`data\` per metric. Default chart height 300. \`loading\` renders skeleton tiles + chart. |
|
|
135
337
|
|
|
136
338
|
#### Settings
|
|
137
339
|
|
|
@@ -164,19 +366,37 @@ Theming helpers (import from the package root or \`/app\`): \`createTimbalTheme\
|
|
|
164
366
|
|
|
165
367
|
Studio chrome (\`StudioSidebar\`, \`ModeToggle\`, \u2026) lives in \`@timbal-ai/timbal-react/studio\` \u2014 optional, not required for every dashboard.
|
|
166
368
|
|
|
167
|
-
###
|
|
369
|
+
### Block recipes \u2014 compose these (don't clone wholesale)
|
|
370
|
+
|
|
371
|
+
Ready-made **section patterns** assembled from the components above. Each is a composition to rebuild in your own domain with your data \u2014 **not** an importable component. Reach for a block before dropping to raw primitives.
|
|
168
372
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
373
|
+
**Settings**
|
|
374
|
+
- **Project settings** \u2014 General / Usage / Danger sections; the floating save bar appears on first edit. Compose \`SettingsSection\` + \`FieldInput\`/\`FieldSwitch\` + \`FieldRow\` + \`InfoCard\` + \`DangerZone\` + \`FloatingUnsavedChangesBar\`.
|
|
375
|
+
- **Settings form** \u2014 compact stacked form for one concern (profile, billing). Compose \`FormSection\` + \`FieldInput\`/\`FieldSelect\`/\`FieldTextarea\`.
|
|
376
|
+
|
|
377
|
+
**Data & metrics**
|
|
378
|
+
- **Metrics row** \u2014 KPI strip in one elevated card. Compose \`MetricRow\` + \`MetricTile\`.
|
|
379
|
+
- **Analytics card** \u2014 selectable KPI tiles driving a shared chart. Compose \`MetricChartCard\` + \`LineAreaChart\`.
|
|
380
|
+
- **Charts panel** \u2014 embedded chart artifact. Compose \`ChartPanel\` + \`ChartArtifactView\`.
|
|
381
|
+
- **Chart catalog** \u2014 every chart kind (stacked area, multi-line, step, bar, stacked + horizontal bar, donut, radial, radar) in \`ChartPanel\` cards. Pick a \`chartType\` + options on a \`ChartArtifact\`; theme via \`--chart-N\`.
|
|
382
|
+
- **Table + filters** \u2014 \`FilterBar\` above a sortable \`DataTable\` (+ \`StatusBadge\` in cells).
|
|
383
|
+
|
|
384
|
+
**Collections**
|
|
385
|
+
- **Integrations grid** \u2014 connector catalog + connected list. Compose \`IntegrationCard\` + \`PlanBadge\` + \`ConnectionRowList\` (\`IntegrationsEmptyState\` when empty).
|
|
386
|
+
- **Resource gallery** \u2014 project / agent / dataset cards. Compose \`ResourceCard\` + \`StatusDot\` + \`Sparkline\`.
|
|
387
|
+
|
|
388
|
+
**Overlays & flows** (animate automatically)
|
|
389
|
+
- **Confirm & destructive** \u2014 confirm/cancel modal or destructive alert. Compose \`AppConfirmDialog\` (or \`AlertDialog\`) + \`Button\`; never hand-roll a \`Dialog\` for confirms.
|
|
390
|
+
- **Detail sheet** \u2014 slide-over edit panel without leaving the list. Compose \`Sheet\` + \`Field*\` + \`Button\` + \`Separator\`.
|
|
391
|
+
|
|
392
|
+
**States & auth**
|
|
393
|
+
- **Empty states** \u2014 no-data / no-results / first-run. Compose \`EmptyState\` + \`Card\` + \`Button\`.
|
|
394
|
+
- **Sign-in card** \u2014 centered auth entry. Compose \`Card\` + \`Input\` + \`Label\` + \`Button\`.
|
|
395
|
+
|
|
396
|
+
**Shells & theming**
|
|
397
|
+
- **Minimal shell** \u2014 \`AppShell\` + \`Page\` (no sidebar/chat).
|
|
398
|
+
- **Copilot overlay** \u2014 \`AppShell\` + floating \`AppChatPanel\`.
|
|
399
|
+
- **Theme presets** \u2014 \`ThemePresetGallery\` + \`applyTimbalTheme\` (never hand-author OKLCH).
|
|
180
400
|
|
|
181
401
|
### Typical compositions
|
|
182
402
|
|
|
@@ -187,6 +407,7 @@ Studio chrome (\`StudioSidebar\`, \`ModeToggle\`, \u2026) lives in \`@timbal-ai/
|
|
|
187
407
|
- **Integrations** \u2014 grid of \`IntegrationCard\`; \`ConnectionRowList\` for connected providers; \`IntegrationsEmptyState\` when empty.
|
|
188
408
|
- **Resource gallery** \u2014 grid of \`ResourceCard\`.
|
|
189
409
|
- **Copilot-assisted app** \u2014 \`AppCopilotProvider\` + \`AppShell\` with \`chat={<AppChatPanel workforceId="\u2026" />}\`.
|
|
410
|
+
- **Motion is automatic** \u2014 Dialog, AlertDialog, Sheet, Popover, DropdownMenu, Select, Tooltip, Toast, and Accordion/Collapsible animate out of the box (fade/zoom/slide/height) via the engine inlined in \`styles.css\`. Do not add a separate animation library or hand-write \`@keyframes\`.
|
|
190
411
|
|
|
191
412
|
### Example imports
|
|
192
413
|
|
|
@@ -205,6 +426,7 @@ import {
|
|
|
205
426
|
Button,
|
|
206
427
|
DataTable,
|
|
207
428
|
FilterBar,
|
|
429
|
+
FilterField,
|
|
208
430
|
} from "@timbal-ai/timbal-react/app";
|
|
209
431
|
\`\`\`
|
|
210
432
|
|
|
@@ -222,6 +444,211 @@ import {
|
|
|
222
444
|
- For rich in-chat widgets, use **artifacts** (\`ARTIFACT_AGENT_INSTRUCTIONS\`) \u2014 app kit is for the **host application shell**.
|
|
223
445
|
`.trim();
|
|
224
446
|
|
|
447
|
+
// src/design/ui-lint.ts
|
|
448
|
+
var PALETTE_GROUP = TAILWIND_PALETTE_COLORS.join("|");
|
|
449
|
+
var PREFIX_GROUP = COLOR_UTILITY_PREFIXES.join("|");
|
|
450
|
+
var RAW_COLOR_RE = new RegExp(
|
|
451
|
+
`(?:^|[\\s"'\`:])(?:[a-z-]+:)*(?:${PREFIX_GROUP})-(?:${PALETTE_GROUP})-\\d{2,3}(?:/\\d{1,3})?`,
|
|
452
|
+
"g"
|
|
453
|
+
);
|
|
454
|
+
var COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|\b(?:oklch|rgba?|hsla?)\s*\(/g;
|
|
455
|
+
var INLINE_STYLE_COLOR_RE = /style=\{\{[^}]*\b(?:color|background|backgroundColor|borderColor|fill|stroke)\b/;
|
|
456
|
+
var BOLD_VALUE_RE = /text-(?:xl|2xl|3xl|4xl|5xl|6xl)[^"'`]*\bfont-(?:bold|extrabold|black|semibold)|font-(?:bold|extrabold|black|semibold)[^"'`]*text-(?:xl|2xl|3xl|4xl|5xl|6xl)/;
|
|
457
|
+
var GRADIENT_RE = /\bbg-(?:gradient|linear|radial|conic)-/;
|
|
458
|
+
var GRADIENT_DIRECTIONS = /* @__PURE__ */ new Set([
|
|
459
|
+
"t",
|
|
460
|
+
"tr",
|
|
461
|
+
"r",
|
|
462
|
+
"br",
|
|
463
|
+
"b",
|
|
464
|
+
"bl",
|
|
465
|
+
"l",
|
|
466
|
+
"tl"
|
|
467
|
+
]);
|
|
468
|
+
var ICON_IMPORT_RE = /from\s+["']lucide-react["']/;
|
|
469
|
+
var RAW_CONTROL_SURFACE_RE = /\bborder-input\b/;
|
|
470
|
+
var RESERVED_GRADIENT_SET = new Set(RESERVED_GRADIENT_TOKENS);
|
|
471
|
+
function stripVariants(util) {
|
|
472
|
+
return util.replace(/^(?:[a-z-]+:)*/, "");
|
|
473
|
+
}
|
|
474
|
+
function isCommentOrImport(line) {
|
|
475
|
+
const t = line.trim();
|
|
476
|
+
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("import ") || t.startsWith("export ");
|
|
477
|
+
}
|
|
478
|
+
function lintGeneratedUi(source, options = {}) {
|
|
479
|
+
const maxIcons = options.maxIconsPerView ?? SLOP_BUDGETS.maxIconsPerView;
|
|
480
|
+
const maxRowDividers = options.maxRowDividers ?? SLOP_BUDGETS.maxRowDividers;
|
|
481
|
+
const findings = [];
|
|
482
|
+
const lines = source.split("\n");
|
|
483
|
+
let usesLucide = false;
|
|
484
|
+
let iconUsageCount = 0;
|
|
485
|
+
let dividerRunCount = 0;
|
|
486
|
+
const lucideNames = /* @__PURE__ */ new Set();
|
|
487
|
+
for (let i = 0; i < lines.length; i++) {
|
|
488
|
+
const line = lines[i];
|
|
489
|
+
const lineNo = i + 1;
|
|
490
|
+
if (ICON_IMPORT_RE.test(line)) {
|
|
491
|
+
usesLucide = true;
|
|
492
|
+
const named = line.match(/\{([^}]*)\}/);
|
|
493
|
+
if (named) {
|
|
494
|
+
for (const raw of named[1].split(",")) {
|
|
495
|
+
const name = raw.trim().split(/\s+as\s+/)[0].trim();
|
|
496
|
+
if (name) lucideNames.add(name);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (isCommentOrImport(line)) continue;
|
|
502
|
+
const rawColors = line.match(RAW_COLOR_RE);
|
|
503
|
+
if (rawColors) {
|
|
504
|
+
for (const m of rawColors) {
|
|
505
|
+
findings.push({
|
|
506
|
+
rule: "raw-color",
|
|
507
|
+
severity: "error",
|
|
508
|
+
line: lineNo,
|
|
509
|
+
message: "Hardcoded palette color. Use a semantic token (text-primary, bg-muted, border-border, text-muted-foreground, \u2026) so dark mode and rebranding work.",
|
|
510
|
+
snippet: m.trim().replace(/^["'`:\s]+/, "")
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const literals = line.match(COLOR_LITERAL_RE);
|
|
515
|
+
if (literals) {
|
|
516
|
+
findings.push({
|
|
517
|
+
rule: "color-literal",
|
|
518
|
+
severity: "error",
|
|
519
|
+
line: lineNo,
|
|
520
|
+
message: "Hardcoded color literal. Colors must come from the theme generator (createTimbalTheme) and semantic tokens \u2014 never inline hex/oklch/rgb.",
|
|
521
|
+
snippet: line.trim().slice(0, 120)
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
if (INLINE_STYLE_COLOR_RE.test(line)) {
|
|
525
|
+
findings.push({
|
|
526
|
+
rule: "inline-style-color",
|
|
527
|
+
severity: "error",
|
|
528
|
+
line: lineNo,
|
|
529
|
+
message: "Inline style color. Move color to a semantic Tailwind token on className.",
|
|
530
|
+
snippet: line.trim().slice(0, 120)
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (RAW_CONTROL_SURFACE_RE.test(line)) {
|
|
534
|
+
findings.push({
|
|
535
|
+
rule: "raw-control-surface",
|
|
536
|
+
severity: "warn",
|
|
537
|
+
line: lineNo,
|
|
538
|
+
message: "Hand-rolled control surface (border-input). Use a kit control \u2014 SearchInput, Select, DropdownMenu, FieldInput, FieldSelect \u2014 so it matches every other control.",
|
|
539
|
+
snippet: line.trim().slice(0, 120)
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
if (BOLD_VALUE_RE.test(line)) {
|
|
543
|
+
findings.push({
|
|
544
|
+
rule: "bold-metric",
|
|
545
|
+
severity: "warn",
|
|
546
|
+
line: lineNo,
|
|
547
|
+
message: "Bold large value. House style: metric values use font-normal, not bold \u2014 bold giant numbers read as a template.",
|
|
548
|
+
snippet: line.trim().slice(0, 120)
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
if (GRADIENT_RE.test(line)) {
|
|
552
|
+
const fromTo = line.match(
|
|
553
|
+
new RegExp(`(?:from|via|to)-([a-z-]+)`, "g")
|
|
554
|
+
);
|
|
555
|
+
const colorStops = (fromTo ?? []).map((u) => stripVariants(u).replace(/^(?:from|via|to)-/, "")).filter((token) => !GRADIENT_DIRECTIONS.has(token));
|
|
556
|
+
const allReserved = colorStops.length > 0 && colorStops.every((token) => RESERVED_GRADIENT_SET.has(token));
|
|
557
|
+
if (!allReserved) {
|
|
558
|
+
findings.push({
|
|
559
|
+
rule: "data-gradient",
|
|
560
|
+
severity: "warn",
|
|
561
|
+
line: lineNo,
|
|
562
|
+
message: "Gradient outside chrome. Gradients are reserved for buttons / elevated / modal / playground \u2014 never a data card, tile, or table.",
|
|
563
|
+
snippet: line.trim().slice(0, 120)
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (/\b(?:border-t|border-b|divide-y)\b/.test(line)) {
|
|
568
|
+
dividerRunCount++;
|
|
569
|
+
if (dividerRunCount === maxRowDividers + 1) {
|
|
570
|
+
findings.push({
|
|
571
|
+
rule: "row-divider",
|
|
572
|
+
severity: "warn",
|
|
573
|
+
line: lineNo,
|
|
574
|
+
message: "Divider on every row. Prefer spacing (gap-*) or zebra striping over a rule under each list item.",
|
|
575
|
+
snippet: line.trim().slice(0, 120)
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
} else if (line.trim() !== "" && !line.includes("className")) {
|
|
579
|
+
if (!/^\s*[)>}/]/.test(line)) dividerRunCount = 0;
|
|
580
|
+
}
|
|
581
|
+
if (usesLucide && lucideNames.size > 0) {
|
|
582
|
+
for (const name of lucideNames) {
|
|
583
|
+
const usage = new RegExp(`<${name}\\b`, "g");
|
|
584
|
+
const hits = line.match(usage);
|
|
585
|
+
if (hits) iconUsageCount += hits.length;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (usesLucide && iconUsageCount > maxIcons) {
|
|
590
|
+
findings.push({
|
|
591
|
+
rule: "icon-spam",
|
|
592
|
+
severity: "warn",
|
|
593
|
+
line: 1,
|
|
594
|
+
message: `Too many icons (${iconUsageCount} > ${maxIcons}). Icons should mark actions/nav/status \u2014 not decorate every label, tile, and card.`,
|
|
595
|
+
snippet: `${iconUsageCount} lucide-react icon usages`
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
const effectiveErrors = findings.filter(
|
|
599
|
+
(f) => f.severity === "error" || options.strict && f.severity === "warn"
|
|
600
|
+
).length;
|
|
601
|
+
return {
|
|
602
|
+
findings,
|
|
603
|
+
errorCount: findings.filter((f) => f.severity === "error").length,
|
|
604
|
+
warnCount: findings.filter((f) => f.severity === "warn").length,
|
|
605
|
+
ok: effectiveErrors === 0
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function formatLintReport(findings) {
|
|
609
|
+
if (findings.length === 0) return "";
|
|
610
|
+
const lines = findings.slice().sort((a, b) => a.line - b.line).map((f) => {
|
|
611
|
+
const tag = f.severity === "error" ? "ERROR" : "warn ";
|
|
612
|
+
return ` ${tag} L${f.line} [${f.rule}] ${f.message}
|
|
613
|
+
\u2192 ${f.snippet}`;
|
|
614
|
+
});
|
|
615
|
+
const errs = findings.filter((f) => f.severity === "error").length;
|
|
616
|
+
const warns = findings.filter((f) => f.severity === "warn").length;
|
|
617
|
+
return `Anti-slop review: ${errs} error(s), ${warns} warning(s)
|
|
618
|
+
${lines.join("\n")}`;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/design/ui-review.ts
|
|
622
|
+
function reviewGeneratedUi(source, options = {}) {
|
|
623
|
+
const lint = lintGeneratedUi(source, options);
|
|
624
|
+
const report = formatLintReport(lint.findings);
|
|
625
|
+
if (lint.ok) {
|
|
626
|
+
return { lint, passed: true, report, revisionPrompt: null };
|
|
627
|
+
}
|
|
628
|
+
const revisionPrompt = [
|
|
629
|
+
"The generated UI failed the Timbal anti-slop review. Fix every issue below, then return the corrected code only.",
|
|
630
|
+
"",
|
|
631
|
+
report,
|
|
632
|
+
"",
|
|
633
|
+
"Rules: colors come only from semantic tokens (text-primary, bg-muted, border-border, text-muted-foreground, \u2026) \u2014 never palette colors, hex, or oklch. Icons mark actions/nav/status, not decoration. Metric values use font-normal. No gradients on data surfaces. No divider under every row. Do not change anything that already passed."
|
|
634
|
+
].join("\n");
|
|
635
|
+
return { lint, passed: false, report, revisionPrompt };
|
|
636
|
+
}
|
|
637
|
+
var UI_REVIEW_AGENT_INSTRUCTIONS = `
|
|
638
|
+
## Self-review before returning UI (anti-slop)
|
|
639
|
+
|
|
640
|
+
Before you output any generated UI code, silently re-read it and fix anything that matches the slop checklist \u2014 this is the same rubric an automated linter applies, so output that fails it will be rejected and sent back:
|
|
641
|
+
|
|
642
|
+
- **No hardcoded colors.** Every color is a semantic token (\`text-primary\`, \`bg-muted\`, \`border-border\`, \`text-muted-foreground\`, \`bg-destructive\`, \u2026). No \`text-blue-600\`, no \`#hex\`, no \`oklch(...)\`, no \`style={{ color }}\`.
|
|
643
|
+
- **No decorative icons.** An icon must mark an action, nav target, or status. Remove icons that sit beside a label that already says the thing. Aim for very few icons per view.
|
|
644
|
+
- **Muted, sparse trends.** No colored up/down pill on every metric. Show a trend only when the change is the point.
|
|
645
|
+
- **Normal-weight values.** Metric numbers use \`font-normal\`, never \`font-bold\` at large sizes.
|
|
646
|
+
- **No card-in-card, no per-row dividers, no gradients on data surfaces.** Group with spacing/Sections; reserve gradients for chrome.
|
|
647
|
+
- **Compose from blocks.** Prefer \`MetricRow\` / \`MetricChartCard\` / \`DataTable\` / \`IntegrationCard\` over hand-assembled primitives.
|
|
648
|
+
|
|
649
|
+
If a check fails, fix it and re-read once more. Only return code that would pass clean.
|
|
650
|
+
`.trim();
|
|
651
|
+
|
|
225
652
|
// src/design/oklch.ts
|
|
226
653
|
var clamp = (n, min, max) => Math.min(max, Math.max(min, n));
|
|
227
654
|
var round = (n, digits) => {
|
|
@@ -1109,22 +1536,14 @@ var appStatTileClass = cn(
|
|
|
1109
1536
|
);
|
|
1110
1537
|
var appStatValueClass = "text-2xl font-normal tracking-tight text-foreground tabular-nums";
|
|
1111
1538
|
var appStatLabelClass = "text-xs font-normal text-muted-foreground";
|
|
1112
|
-
var appFilterBarClass =
|
|
1113
|
-
|
|
1114
|
-
studioTopbarPillHeightClass
|
|
1115
|
-
);
|
|
1116
|
-
var appSearchInputClass = cn(studioSearchChromeClass, "text-sm");
|
|
1539
|
+
var appFilterBarClass = "flex flex-wrap items-end gap-2";
|
|
1540
|
+
var appSearchInputClass = controlClass({}, "inline-flex items-center gap-2");
|
|
1117
1541
|
var appBreadcrumbsClass = "flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground";
|
|
1118
1542
|
var appBreadcrumbLinkClass = "transition-colors hover:text-foreground";
|
|
1119
1543
|
var appFieldClass = "flex flex-col gap-1.5";
|
|
1120
1544
|
var appFieldLabelClass = "text-sm font-medium text-foreground";
|
|
1121
1545
|
var appFieldHintClass = "text-xs text-muted-foreground";
|
|
1122
|
-
var appInputClass =
|
|
1123
|
-
studioSecondaryChromeClass,
|
|
1124
|
-
"h-10 w-full rounded-lg px-3 text-sm text-foreground outline-none",
|
|
1125
|
-
"placeholder:text-muted-foreground/70",
|
|
1126
|
-
"focus-visible:ring-2 focus-visible:ring-foreground/10"
|
|
1127
|
-
);
|
|
1546
|
+
var appInputClass = controlClass({}, "w-full");
|
|
1128
1547
|
var appEmptyStateClass = cn(
|
|
1129
1548
|
appSurfaceCardClass,
|
|
1130
1549
|
"flex flex-col items-center justify-center gap-2 py-12 text-center"
|
|
@@ -1141,9 +1560,23 @@ function useAppShellChat() {
|
|
|
1141
1560
|
return useContext(AppShellChatContext);
|
|
1142
1561
|
}
|
|
1143
1562
|
|
|
1563
|
+
// src/app/layout/app-shell-nav-context.tsx
|
|
1564
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
1565
|
+
var AppShellNavContext = createContext2(null);
|
|
1566
|
+
var AppShellNavProvider = AppShellNavContext.Provider;
|
|
1567
|
+
function useAppShellNav() {
|
|
1568
|
+
return useContext2(AppShellNavContext) ?? {
|
|
1569
|
+
open: false,
|
|
1570
|
+
setOpen: () => {
|
|
1571
|
+
},
|
|
1572
|
+
toggle: () => {
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1144
1577
|
// src/app/layout/AppShell.tsx
|
|
1145
1578
|
import { motion, useReducedMotion } from "motion/react";
|
|
1146
|
-
import { useCallback, useState } from "react";
|
|
1579
|
+
import { useCallback, useMemo, useState } from "react";
|
|
1147
1580
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1148
1581
|
var floatingTriggerClass = cn(
|
|
1149
1582
|
"aui-app-shell-chat-trigger-fixed fixed z-50 rounded-full px-5 py-2.5 text-sm font-medium shadow-card-elevated",
|
|
@@ -1210,11 +1643,29 @@ var AppShell = ({
|
|
|
1210
1643
|
chatCollapsible = true,
|
|
1211
1644
|
chatTriggerLabel = "Assistant",
|
|
1212
1645
|
hideChatTrigger = false,
|
|
1646
|
+
navOpen: navOpenProp,
|
|
1647
|
+
defaultNavOpen = false,
|
|
1648
|
+
onNavOpenChange,
|
|
1213
1649
|
className,
|
|
1214
1650
|
mainClassName
|
|
1215
1651
|
}) => {
|
|
1216
1652
|
const topbarContent = topbar ?? header;
|
|
1217
1653
|
const hasChat = Boolean(chat);
|
|
1654
|
+
const [uncontrolledNavOpen, setUncontrolledNavOpen] = useState(defaultNavOpen);
|
|
1655
|
+
const isNavControlled = navOpenProp !== void 0;
|
|
1656
|
+
const navOpen = isNavControlled ? navOpenProp : uncontrolledNavOpen;
|
|
1657
|
+
const setNavOpen = useCallback(
|
|
1658
|
+
(open) => {
|
|
1659
|
+
if (!isNavControlled) setUncontrolledNavOpen(open);
|
|
1660
|
+
onNavOpenChange?.(open);
|
|
1661
|
+
},
|
|
1662
|
+
[isNavControlled, onNavOpenChange]
|
|
1663
|
+
);
|
|
1664
|
+
const toggleNav = useCallback(() => setNavOpen(!navOpen), [navOpen, setNavOpen]);
|
|
1665
|
+
const navControls = useMemo(
|
|
1666
|
+
() => ({ open: navOpen, setOpen: setNavOpen, toggle: toggleNav }),
|
|
1667
|
+
[navOpen, setNavOpen, toggleNav]
|
|
1668
|
+
);
|
|
1218
1669
|
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultChatOpen);
|
|
1219
1670
|
const isChatControlled = chatOpenProp !== void 0;
|
|
1220
1671
|
const chatOpen = isChatControlled ? chatOpenProp : uncontrolledOpen;
|
|
@@ -1258,6 +1709,15 @@ var AppShell = ({
|
|
|
1258
1709
|
style: studioChromeShellStyle,
|
|
1259
1710
|
children: [
|
|
1260
1711
|
sidebar,
|
|
1712
|
+
sidebar && navOpen ? /* @__PURE__ */ jsx5(
|
|
1713
|
+
"button",
|
|
1714
|
+
{
|
|
1715
|
+
type: "button",
|
|
1716
|
+
"aria-label": "Close navigation",
|
|
1717
|
+
onClick: () => setNavOpen(false),
|
|
1718
|
+
className: "fixed inset-0 z-40 bg-foreground/30 backdrop-blur-[2px] md:hidden"
|
|
1719
|
+
}
|
|
1720
|
+
) : null,
|
|
1261
1721
|
shellBody,
|
|
1262
1722
|
hasChat && chatOpen ? /* @__PURE__ */ jsx5(
|
|
1263
1723
|
"div",
|
|
@@ -1285,8 +1745,9 @@ var AppShell = ({
|
|
|
1285
1745
|
]
|
|
1286
1746
|
}
|
|
1287
1747
|
) });
|
|
1748
|
+
const withNav = /* @__PURE__ */ jsx5(AppShellNavProvider, { value: navControls, children: tree });
|
|
1288
1749
|
if (!hasChat) {
|
|
1289
|
-
return
|
|
1750
|
+
return withNav;
|
|
1290
1751
|
}
|
|
1291
1752
|
return /* @__PURE__ */ jsx5(
|
|
1292
1753
|
AppShellChatProvider,
|
|
@@ -1297,7 +1758,7 @@ var AppShell = ({
|
|
|
1297
1758
|
toggle: toggleChat,
|
|
1298
1759
|
collapsible: chatCollapsible
|
|
1299
1760
|
},
|
|
1300
|
-
children:
|
|
1761
|
+
children: withNav
|
|
1301
1762
|
}
|
|
1302
1763
|
);
|
|
1303
1764
|
};
|
|
@@ -1348,8 +1809,33 @@ var AppShellChatTrigger = ({
|
|
|
1348
1809
|
);
|
|
1349
1810
|
};
|
|
1350
1811
|
|
|
1812
|
+
// src/app/layout/AppShellSidebarTrigger.tsx
|
|
1813
|
+
import { MenuIcon } from "lucide-react";
|
|
1814
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1815
|
+
var AppShellSidebarTrigger = ({
|
|
1816
|
+
label = "Open navigation",
|
|
1817
|
+
className
|
|
1818
|
+
}) => {
|
|
1819
|
+
const nav = useAppShellNav();
|
|
1820
|
+
return /* @__PURE__ */ jsx8(
|
|
1821
|
+
"button",
|
|
1822
|
+
{
|
|
1823
|
+
type: "button",
|
|
1824
|
+
onClick: nav.toggle,
|
|
1825
|
+
"aria-label": label,
|
|
1826
|
+
"aria-expanded": nav.open,
|
|
1827
|
+
className: cn(
|
|
1828
|
+
"inline-flex size-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-foreground/[0.04] hover:text-foreground md:hidden",
|
|
1829
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/10",
|
|
1830
|
+
className
|
|
1831
|
+
),
|
|
1832
|
+
children: /* @__PURE__ */ jsx8(MenuIcon, { className: "size-5", "aria-hidden": true })
|
|
1833
|
+
}
|
|
1834
|
+
);
|
|
1835
|
+
};
|
|
1836
|
+
|
|
1351
1837
|
// src/app/layout/PageHeader.tsx
|
|
1352
|
-
import { jsx as
|
|
1838
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1353
1839
|
var PageHeader = ({
|
|
1354
1840
|
title,
|
|
1355
1841
|
description,
|
|
@@ -1358,15 +1844,15 @@ var PageHeader = ({
|
|
|
1358
1844
|
}) => {
|
|
1359
1845
|
return /* @__PURE__ */ jsxs7("header", { className: cn("aui-app-page-header", appPageHeaderClass, className), children: [
|
|
1360
1846
|
/* @__PURE__ */ jsxs7("div", { className: "min-w-0", children: [
|
|
1361
|
-
/* @__PURE__ */
|
|
1362
|
-
description ? /* @__PURE__ */
|
|
1847
|
+
/* @__PURE__ */ jsx9("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: title }),
|
|
1848
|
+
description ? /* @__PURE__ */ jsx9("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null
|
|
1363
1849
|
] }),
|
|
1364
|
-
actions ? /* @__PURE__ */
|
|
1850
|
+
actions ? /* @__PURE__ */ jsx9("div", { className: "aui-app-page-header-actions flex shrink-0 flex-wrap items-center gap-2", children: actions }) : null
|
|
1365
1851
|
] });
|
|
1366
1852
|
};
|
|
1367
1853
|
|
|
1368
1854
|
// src/app/layout/Page.tsx
|
|
1369
|
-
import { jsx as
|
|
1855
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1370
1856
|
var Page = ({
|
|
1371
1857
|
children,
|
|
1372
1858
|
breadcrumbs,
|
|
@@ -1375,13 +1861,13 @@ var Page = ({
|
|
|
1375
1861
|
}) => {
|
|
1376
1862
|
return /* @__PURE__ */ jsxs8("div", { className: cn("aui-app-page", appPageColumnClass, className), children: [
|
|
1377
1863
|
breadcrumbs,
|
|
1378
|
-
/* @__PURE__ */
|
|
1864
|
+
/* @__PURE__ */ jsx10(PageHeader, { ...headerProps }),
|
|
1379
1865
|
children
|
|
1380
1866
|
] });
|
|
1381
1867
|
};
|
|
1382
1868
|
|
|
1383
1869
|
// src/app/layout/Section.tsx
|
|
1384
|
-
import { jsx as
|
|
1870
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1385
1871
|
var Section = ({
|
|
1386
1872
|
title,
|
|
1387
1873
|
description,
|
|
@@ -1389,29 +1875,29 @@ var Section = ({
|
|
|
1389
1875
|
className
|
|
1390
1876
|
}) => {
|
|
1391
1877
|
return /* @__PURE__ */ jsxs9("section", { className: cn("aui-app-section", appSectionClass, className), children: [
|
|
1392
|
-
title ? /* @__PURE__ */
|
|
1393
|
-
description ? /* @__PURE__ */
|
|
1878
|
+
title ? /* @__PURE__ */ jsx11("h2", { className: appSectionTitleClass, children: title }) : null,
|
|
1879
|
+
description ? /* @__PURE__ */ jsx11("p", { className: appSectionDescriptionClass, children: description }) : null,
|
|
1394
1880
|
children
|
|
1395
1881
|
] });
|
|
1396
1882
|
};
|
|
1397
1883
|
|
|
1398
1884
|
// src/app/copilot/app-copilot-context.tsx
|
|
1399
|
-
import { createContext as
|
|
1400
|
-
import { jsx as
|
|
1401
|
-
var AppCopilotContext =
|
|
1885
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
1886
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
1887
|
+
var AppCopilotContext = createContext3(null);
|
|
1402
1888
|
var AppCopilotProvider = ({
|
|
1403
1889
|
value,
|
|
1404
1890
|
children
|
|
1405
1891
|
}) => {
|
|
1406
|
-
return /* @__PURE__ */
|
|
1892
|
+
return /* @__PURE__ */ jsx12(AppCopilotContext.Provider, { value, children });
|
|
1407
1893
|
};
|
|
1408
1894
|
function useAppCopilotContext() {
|
|
1409
|
-
return
|
|
1895
|
+
return useContext3(AppCopilotContext) ?? {};
|
|
1410
1896
|
}
|
|
1411
1897
|
|
|
1412
1898
|
// src/app/chat/AppChatPanel.tsx
|
|
1413
1899
|
import { XIcon } from "lucide-react";
|
|
1414
|
-
import { jsx as
|
|
1900
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1415
1901
|
var shellClass = "aui-app-chat-panel flex h-full min-h-0 flex-col overflow-hidden";
|
|
1416
1902
|
var chromeClass = cn(
|
|
1417
1903
|
"aui-app-chat-panel-chrome relative z-20 flex min-h-10 shrink-0 items-center justify-end px-2 pt-2"
|
|
@@ -1457,17 +1943,17 @@ var AppChatPanel = ({
|
|
|
1457
1943
|
}) => {
|
|
1458
1944
|
const shellChat = useAppShellChat();
|
|
1459
1945
|
return /* @__PURE__ */ jsxs10("div", { className: cn(shellClass, className), children: [
|
|
1460
|
-
shellChat?.collapsible ? /* @__PURE__ */
|
|
1946
|
+
shellChat?.collapsible ? /* @__PURE__ */ jsx13("div", { className: chromeClass, children: /* @__PURE__ */ jsx13(
|
|
1461
1947
|
"button",
|
|
1462
1948
|
{
|
|
1463
1949
|
type: "button",
|
|
1464
1950
|
className: closeButtonClass,
|
|
1465
1951
|
onClick: () => shellChat.setOpen(false),
|
|
1466
1952
|
"aria-label": "Close assistant",
|
|
1467
|
-
children: /* @__PURE__ */
|
|
1953
|
+
children: /* @__PURE__ */ jsx13(XIcon, { className: "size-4", "aria-hidden": true })
|
|
1468
1954
|
}
|
|
1469
1955
|
) }) : null,
|
|
1470
|
-
/* @__PURE__ */
|
|
1956
|
+
/* @__PURE__ */ jsx13("div", { className: bodyClass, children: /* @__PURE__ */ jsx13(
|
|
1471
1957
|
TimbalRuntimeProvider,
|
|
1472
1958
|
{
|
|
1473
1959
|
workforceId,
|
|
@@ -1477,7 +1963,7 @@ var AppChatPanel = ({
|
|
|
1477
1963
|
attachmentsUploadUrl,
|
|
1478
1964
|
attachmentsAccept,
|
|
1479
1965
|
debug,
|
|
1480
|
-
children: /* @__PURE__ */
|
|
1966
|
+
children: /* @__PURE__ */ jsx13(
|
|
1481
1967
|
Thread,
|
|
1482
1968
|
{
|
|
1483
1969
|
variant: "panel",
|
|
@@ -1498,23 +1984,23 @@ var AppChatPanel = ({
|
|
|
1498
1984
|
};
|
|
1499
1985
|
|
|
1500
1986
|
// src/app/surfaces/SurfaceCard.tsx
|
|
1501
|
-
import { jsx as
|
|
1987
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1502
1988
|
var SurfaceCard = ({ children, className }) => {
|
|
1503
|
-
return /* @__PURE__ */
|
|
1989
|
+
return /* @__PURE__ */ jsx14("div", { className: cn("aui-app-surface-card", appSurfaceCardClass, className), children });
|
|
1504
1990
|
};
|
|
1505
1991
|
|
|
1506
1992
|
// src/app/surfaces/StatTile.tsx
|
|
1507
|
-
import { jsx as
|
|
1993
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1508
1994
|
var StatTile = ({ label, value, hint, className }) => {
|
|
1509
1995
|
return /* @__PURE__ */ jsxs11("div", { className: cn("aui-app-stat-tile", appStatTileClass, className), children: [
|
|
1510
|
-
/* @__PURE__ */
|
|
1511
|
-
/* @__PURE__ */
|
|
1512
|
-
hint ? /* @__PURE__ */
|
|
1996
|
+
/* @__PURE__ */ jsx15("span", { className: appStatLabelClass, children: label }),
|
|
1997
|
+
/* @__PURE__ */ jsx15("span", { className: appStatValueClass, children: value }),
|
|
1998
|
+
hint ? /* @__PURE__ */ jsx15("span", { className: "text-xs text-muted-foreground", children: hint }) : null
|
|
1513
1999
|
] });
|
|
1514
2000
|
};
|
|
1515
2001
|
|
|
1516
2002
|
// src/app/surfaces/EmptyState.tsx
|
|
1517
|
-
import { jsx as
|
|
2003
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1518
2004
|
var EmptyState = ({
|
|
1519
2005
|
title,
|
|
1520
2006
|
description,
|
|
@@ -1522,19 +2008,20 @@ var EmptyState = ({
|
|
|
1522
2008
|
className
|
|
1523
2009
|
}) => {
|
|
1524
2010
|
return /* @__PURE__ */ jsxs12("div", { className: cn("aui-app-empty-state", appEmptyStateClass, className), children: [
|
|
1525
|
-
/* @__PURE__ */
|
|
1526
|
-
description ? /* @__PURE__ */
|
|
2011
|
+
/* @__PURE__ */ jsx16("p", { className: appEmptyStateTitleClass, children: title }),
|
|
2012
|
+
description ? /* @__PURE__ */ jsx16("p", { className: appEmptyStateDescriptionClass, children: description }) : null,
|
|
1527
2013
|
action
|
|
1528
2014
|
] });
|
|
1529
2015
|
};
|
|
1530
2016
|
|
|
1531
2017
|
// src/app/surfaces/StatusBadge.tsx
|
|
1532
|
-
import { jsx as
|
|
2018
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1533
2019
|
var statusBadgeToneClass = {
|
|
1534
2020
|
default: "bg-muted text-foreground",
|
|
1535
2021
|
primary: "bg-primary/10 text-primary",
|
|
1536
2022
|
success: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
|
|
1537
2023
|
warn: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
|
2024
|
+
danger: "bg-destructive/10 text-destructive",
|
|
1538
2025
|
muted: "bg-muted/80 text-muted-foreground"
|
|
1539
2026
|
};
|
|
1540
2027
|
var StatusBadge = ({
|
|
@@ -1542,7 +2029,7 @@ var StatusBadge = ({
|
|
|
1542
2029
|
tone = "default",
|
|
1543
2030
|
className
|
|
1544
2031
|
}) => {
|
|
1545
|
-
return /* @__PURE__ */
|
|
2032
|
+
return /* @__PURE__ */ jsx17(
|
|
1546
2033
|
"span",
|
|
1547
2034
|
{
|
|
1548
2035
|
className: cn(
|
|
@@ -1556,7 +2043,7 @@ var StatusBadge = ({
|
|
|
1556
2043
|
};
|
|
1557
2044
|
|
|
1558
2045
|
// src/app/surfaces/AppConfirmDialog.tsx
|
|
1559
|
-
import { jsx as
|
|
2046
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1560
2047
|
var bodyClass2 = "flex flex-col gap-4 p-6";
|
|
1561
2048
|
var titleClass = "pr-8";
|
|
1562
2049
|
var actionsClass = "flex flex-wrap justify-end gap-2";
|
|
@@ -1571,15 +2058,15 @@ var AppConfirmDialog = ({
|
|
|
1571
2058
|
destructive = false,
|
|
1572
2059
|
className
|
|
1573
2060
|
}) => {
|
|
1574
|
-
return /* @__PURE__ */
|
|
2061
|
+
return /* @__PURE__ */ jsx18(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx18(
|
|
1575
2062
|
DialogContent,
|
|
1576
2063
|
{
|
|
1577
2064
|
className: cn("gap-0 p-0 sm:max-w-md", className),
|
|
1578
2065
|
children: /* @__PURE__ */ jsxs13("div", { className: bodyClass2, children: [
|
|
1579
|
-
/* @__PURE__ */
|
|
1580
|
-
description ? /* @__PURE__ */
|
|
2066
|
+
/* @__PURE__ */ jsx18(DialogTitle, { className: titleClass, children: title }),
|
|
2067
|
+
description ? /* @__PURE__ */ jsx18("p", { className: "text-sm text-muted-foreground", children: description }) : null,
|
|
1581
2068
|
/* @__PURE__ */ jsxs13("div", { className: actionsClass, children: [
|
|
1582
|
-
/* @__PURE__ */
|
|
2069
|
+
/* @__PURE__ */ jsx18(
|
|
1583
2070
|
TimbalV2Button,
|
|
1584
2071
|
{
|
|
1585
2072
|
type: "button",
|
|
@@ -1589,7 +2076,7 @@ var AppConfirmDialog = ({
|
|
|
1589
2076
|
children: cancelLabel
|
|
1590
2077
|
}
|
|
1591
2078
|
),
|
|
1592
|
-
/* @__PURE__ */
|
|
2079
|
+
/* @__PURE__ */ jsx18(
|
|
1593
2080
|
TimbalV2Button,
|
|
1594
2081
|
{
|
|
1595
2082
|
type: "button",
|
|
@@ -1609,7 +2096,7 @@ var AppConfirmDialog = ({
|
|
|
1609
2096
|
};
|
|
1610
2097
|
|
|
1611
2098
|
// src/app/surfaces/InfoCard.tsx
|
|
1612
|
-
import { jsx as
|
|
2099
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1613
2100
|
var toneClass = {
|
|
1614
2101
|
neutral: "border-border bg-muted/40",
|
|
1615
2102
|
info: "border-primary/25 bg-primary/5",
|
|
@@ -1633,18 +2120,18 @@ var InfoCard = ({
|
|
|
1633
2120
|
className
|
|
1634
2121
|
),
|
|
1635
2122
|
children: [
|
|
1636
|
-
icon ? /* @__PURE__ */
|
|
2123
|
+
icon ? /* @__PURE__ */ jsx19("span", { className: "mt-0.5 shrink-0 text-muted-foreground", children: icon }) : null,
|
|
1637
2124
|
/* @__PURE__ */ jsxs14("div", { className: "min-w-0 flex-1", children: [
|
|
1638
|
-
title ? /* @__PURE__ */
|
|
1639
|
-
children ? /* @__PURE__ */
|
|
2125
|
+
title ? /* @__PURE__ */ jsx19("p", { className: "text-sm font-medium text-foreground", children: title }) : null,
|
|
2126
|
+
children ? /* @__PURE__ */ jsx19("div", { className: cn("text-sm text-muted-foreground", title && "mt-1"), children }) : null
|
|
1640
2127
|
] }),
|
|
1641
|
-
action ? /* @__PURE__ */
|
|
2128
|
+
action ? /* @__PURE__ */ jsx19("div", { className: "shrink-0", children: action }) : null
|
|
1642
2129
|
]
|
|
1643
2130
|
}
|
|
1644
2131
|
);
|
|
1645
2132
|
|
|
1646
2133
|
// src/app/surfaces/StatusDot.tsx
|
|
1647
|
-
import { jsx as
|
|
2134
|
+
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1648
2135
|
var dotClass = {
|
|
1649
2136
|
online: "bg-emerald-500",
|
|
1650
2137
|
busy: "bg-amber-500",
|
|
@@ -1659,7 +2146,7 @@ var StatusDot = ({
|
|
|
1659
2146
|
className
|
|
1660
2147
|
}) => /* @__PURE__ */ jsxs15("span", { className: cn("inline-flex items-center gap-1.5", className), children: [
|
|
1661
2148
|
/* @__PURE__ */ jsxs15("span", { className: "relative flex size-2", children: [
|
|
1662
|
-
pulse ? /* @__PURE__ */
|
|
2149
|
+
pulse ? /* @__PURE__ */ jsx20(
|
|
1663
2150
|
"span",
|
|
1664
2151
|
{
|
|
1665
2152
|
className: cn(
|
|
@@ -1668,18 +2155,18 @@ var StatusDot = ({
|
|
|
1668
2155
|
)
|
|
1669
2156
|
}
|
|
1670
2157
|
) : null,
|
|
1671
|
-
/* @__PURE__ */
|
|
2158
|
+
/* @__PURE__ */ jsx20("span", { className: cn("relative inline-flex size-2 rounded-full", dotClass[tone]) })
|
|
1672
2159
|
] }),
|
|
1673
|
-
label ? /* @__PURE__ */
|
|
2160
|
+
label ? /* @__PURE__ */ jsx20("span", { className: "text-xs text-muted-foreground", children: label }) : null
|
|
1674
2161
|
] });
|
|
1675
2162
|
|
|
1676
2163
|
// src/app/surfaces/DescriptionList.tsx
|
|
1677
|
-
import { jsx as
|
|
2164
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1678
2165
|
var DescriptionList = ({
|
|
1679
2166
|
items,
|
|
1680
2167
|
stacked = false,
|
|
1681
2168
|
className
|
|
1682
|
-
}) => /* @__PURE__ */
|
|
2169
|
+
}) => /* @__PURE__ */ jsx21(
|
|
1683
2170
|
"dl",
|
|
1684
2171
|
{
|
|
1685
2172
|
className: cn(
|
|
@@ -1694,8 +2181,8 @@ var DescriptionList = ({
|
|
|
1694
2181
|
stacked ? "flex flex-col gap-0.5" : "flex items-center justify-between gap-4"
|
|
1695
2182
|
),
|
|
1696
2183
|
children: [
|
|
1697
|
-
/* @__PURE__ */
|
|
1698
|
-
/* @__PURE__ */
|
|
2184
|
+
/* @__PURE__ */ jsx21("dt", { className: "text-sm text-muted-foreground", children: item.label }),
|
|
2185
|
+
/* @__PURE__ */ jsx21(
|
|
1699
2186
|
"dd",
|
|
1700
2187
|
{
|
|
1701
2188
|
className: cn(
|
|
@@ -1715,8 +2202,8 @@ var DescriptionList = ({
|
|
|
1715
2202
|
// src/app/surfaces/ExpandableSection.tsx
|
|
1716
2203
|
import { useId, useState as useState2 } from "react";
|
|
1717
2204
|
import { AnimatePresence, motion as motion2, useReducedMotion as useReducedMotion2 } from "motion/react";
|
|
1718
|
-
import { jsx as
|
|
1719
|
-
var Chevron = ({ open }) => /* @__PURE__ */
|
|
2205
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2206
|
+
var Chevron = ({ open }) => /* @__PURE__ */ jsx22(
|
|
1720
2207
|
"svg",
|
|
1721
2208
|
{
|
|
1722
2209
|
viewBox: "0 0 24 24",
|
|
@@ -1730,7 +2217,7 @@ var Chevron = ({ open }) => /* @__PURE__ */ jsx21(
|
|
|
1730
2217
|
strokeLinecap: "round",
|
|
1731
2218
|
strokeLinejoin: "round",
|
|
1732
2219
|
"aria-hidden": true,
|
|
1733
|
-
children: /* @__PURE__ */
|
|
2220
|
+
children: /* @__PURE__ */ jsx22("path", { d: "m6 9 6 6 6-6" })
|
|
1734
2221
|
}
|
|
1735
2222
|
);
|
|
1736
2223
|
var ExpandableSection = ({
|
|
@@ -1762,15 +2249,15 @@ var ExpandableSection = ({
|
|
|
1762
2249
|
className: "flex w-full items-center justify-between gap-3 bg-transparent px-4 py-3 text-left hover:bg-transparent active:bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-foreground/10",
|
|
1763
2250
|
children: [
|
|
1764
2251
|
/* @__PURE__ */ jsxs17("span", { className: "flex min-w-0 items-center gap-3", children: [
|
|
1765
|
-
icon ? /* @__PURE__ */
|
|
1766
|
-
/* @__PURE__ */
|
|
1767
|
-
count != null ? /* @__PURE__ */
|
|
2252
|
+
icon ? /* @__PURE__ */ jsx22("span", { className: "flex size-8 items-center justify-center rounded-lg border border-border bg-muted text-muted-foreground", children: icon }) : null,
|
|
2253
|
+
/* @__PURE__ */ jsx22("span", { className: "truncate text-sm font-medium text-foreground", children: title }),
|
|
2254
|
+
count != null ? /* @__PURE__ */ jsx22("span", { className: "rounded-full border border-border bg-muted px-2 py-0.5 text-xs text-muted-foreground", children: count }) : null
|
|
1768
2255
|
] }),
|
|
1769
|
-
/* @__PURE__ */
|
|
2256
|
+
/* @__PURE__ */ jsx22(Chevron, { open })
|
|
1770
2257
|
]
|
|
1771
2258
|
}
|
|
1772
2259
|
),
|
|
1773
|
-
/* @__PURE__ */
|
|
2260
|
+
/* @__PURE__ */ jsx22(AnimatePresence, { initial: false, children: open ? /* @__PURE__ */ jsx22(
|
|
1774
2261
|
motion2.div,
|
|
1775
2262
|
{
|
|
1776
2263
|
id: panelId,
|
|
@@ -1779,7 +2266,7 @@ var ExpandableSection = ({
|
|
|
1779
2266
|
exit: reduceMotion ? void 0 : { height: 0, opacity: 0 },
|
|
1780
2267
|
transition: { duration: 0.2, ease: "easeOut" },
|
|
1781
2268
|
className: "overflow-hidden",
|
|
1782
|
-
children: /* @__PURE__ */
|
|
2269
|
+
children: /* @__PURE__ */ jsx22("div", { className: "bg-muted/20", children })
|
|
1783
2270
|
},
|
|
1784
2271
|
"body"
|
|
1785
2272
|
) : null })
|
|
@@ -1787,7 +2274,7 @@ var ExpandableSection = ({
|
|
|
1787
2274
|
};
|
|
1788
2275
|
|
|
1789
2276
|
// src/app/surfaces/ResourceCard.tsx
|
|
1790
|
-
import { Fragment as Fragment3, jsx as
|
|
2277
|
+
import { Fragment as Fragment3, jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1791
2278
|
var resourceCardShellClass = cn(
|
|
1792
2279
|
"flex min-h-[8.5rem] flex-col rounded-2xl p-4 text-left font-normal",
|
|
1793
2280
|
TIMBAL_V2_ELEVATED_SURFACE
|
|
@@ -1814,33 +2301,33 @@ var ResourceCard = ({
|
|
|
1814
2301
|
}) => {
|
|
1815
2302
|
const body = /* @__PURE__ */ jsxs18(Fragment3, { children: [
|
|
1816
2303
|
/* @__PURE__ */ jsxs18("div", { className: "flex items-start gap-3", children: [
|
|
1817
|
-
media ? /* @__PURE__ */
|
|
2304
|
+
media ? /* @__PURE__ */ jsx23("span", { className: mediaShellClass, children: media }) : null,
|
|
1818
2305
|
/* @__PURE__ */ jsxs18("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
1819
|
-
/* @__PURE__ */
|
|
1820
|
-
subtitle ? /* @__PURE__ */
|
|
2306
|
+
/* @__PURE__ */ jsx23("p", { className: "truncate text-sm font-normal leading-snug text-foreground", children: title }),
|
|
2307
|
+
subtitle ? /* @__PURE__ */ jsx23("p", { className: "mt-1 line-clamp-2 text-xs font-normal text-muted-foreground", children: subtitle }) : null
|
|
1821
2308
|
] }),
|
|
1822
|
-
badge ? /* @__PURE__ */
|
|
2309
|
+
badge ? /* @__PURE__ */ jsx23("span", { className: "shrink-0 pt-0.5", children: badge }) : null
|
|
1823
2310
|
] }),
|
|
1824
2311
|
footer || action ? /* @__PURE__ */ jsxs18("div", { className: "mt-auto flex items-center justify-between gap-3 border-t border-border/40 pt-3 text-xs font-normal text-muted-foreground", children: [
|
|
1825
|
-
/* @__PURE__ */
|
|
1826
|
-
action ? /* @__PURE__ */
|
|
2312
|
+
/* @__PURE__ */ jsx23("span", { className: "min-w-0 truncate", children: footer }),
|
|
2313
|
+
action ? /* @__PURE__ */ jsx23("span", { className: "shrink-0 opacity-80", children: action }) : null
|
|
1827
2314
|
] }) : null
|
|
1828
2315
|
] });
|
|
1829
2316
|
if (onClick) {
|
|
1830
|
-
return /* @__PURE__ */
|
|
2317
|
+
return /* @__PURE__ */ jsx23("button", { type: "button", onClick, "aria-label": ariaLabel, className: cn(resourceCardInteractiveClass, className), children: body });
|
|
1831
2318
|
}
|
|
1832
|
-
return /* @__PURE__ */
|
|
2319
|
+
return /* @__PURE__ */ jsx23("article", { className: cn(resourceCardShellClass, className), children: body });
|
|
1833
2320
|
};
|
|
1834
2321
|
|
|
1835
2322
|
// src/app/settings/SettingsSection.tsx
|
|
1836
|
-
import { jsx as
|
|
2323
|
+
import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1837
2324
|
var SettingsSectionHeader = ({
|
|
1838
2325
|
title,
|
|
1839
2326
|
description,
|
|
1840
2327
|
className
|
|
1841
2328
|
}) => /* @__PURE__ */ jsxs19("div", { className: cn("flex flex-col", className), children: [
|
|
1842
|
-
/* @__PURE__ */
|
|
1843
|
-
description ? /* @__PURE__ */
|
|
2329
|
+
/* @__PURE__ */ jsx24("h3", { className: "text-[17px] font-medium leading-tight text-foreground", children: title }),
|
|
2330
|
+
description ? /* @__PURE__ */ jsx24("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null
|
|
1844
2331
|
] });
|
|
1845
2332
|
var SettingsSection = ({
|
|
1846
2333
|
title,
|
|
@@ -1859,17 +2346,17 @@ var SettingsSection = ({
|
|
|
1859
2346
|
),
|
|
1860
2347
|
children: [
|
|
1861
2348
|
/* @__PURE__ */ jsxs19("div", { className: "min-w-0", children: [
|
|
1862
|
-
/* @__PURE__ */
|
|
1863
|
-
description ? /* @__PURE__ */
|
|
1864
|
-
descriptionFooter ? /* @__PURE__ */
|
|
2349
|
+
/* @__PURE__ */ jsx24("h2", { className: "text-sm font-medium text-foreground", children: title }),
|
|
2350
|
+
description ? /* @__PURE__ */ jsx24("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null,
|
|
2351
|
+
descriptionFooter ? /* @__PURE__ */ jsx24("div", { className: "mt-3 min-w-0", children: descriptionFooter }) : null
|
|
1865
2352
|
] }),
|
|
1866
|
-
/* @__PURE__ */
|
|
2353
|
+
/* @__PURE__ */ jsx24("div", { className: "min-w-0 space-y-3", children })
|
|
1867
2354
|
]
|
|
1868
2355
|
}
|
|
1869
2356
|
);
|
|
1870
2357
|
|
|
1871
2358
|
// src/app/settings/FieldRow.tsx
|
|
1872
|
-
import { jsx as
|
|
2359
|
+
import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1873
2360
|
var FieldRow = ({
|
|
1874
2361
|
label,
|
|
1875
2362
|
children,
|
|
@@ -1888,7 +2375,7 @@ var FieldRow = ({
|
|
|
1888
2375
|
),
|
|
1889
2376
|
children: [
|
|
1890
2377
|
/* @__PURE__ */ jsxs20("div", { className: "min-w-0", children: [
|
|
1891
|
-
/* @__PURE__ */
|
|
2378
|
+
/* @__PURE__ */ jsx25(
|
|
1892
2379
|
"label",
|
|
1893
2380
|
{
|
|
1894
2381
|
htmlFor,
|
|
@@ -1896,17 +2383,17 @@ var FieldRow = ({
|
|
|
1896
2383
|
children: label
|
|
1897
2384
|
}
|
|
1898
2385
|
),
|
|
1899
|
-
description ? /* @__PURE__ */
|
|
2386
|
+
description ? /* @__PURE__ */ jsx25("p", { className: "mt-0.5 text-xs text-muted-foreground", children: description }) : null
|
|
1900
2387
|
] }),
|
|
1901
|
-
/* @__PURE__ */
|
|
2388
|
+
/* @__PURE__ */ jsx25("div", { className: "shrink-0", children })
|
|
1902
2389
|
]
|
|
1903
2390
|
}
|
|
1904
2391
|
);
|
|
1905
2392
|
}
|
|
1906
2393
|
return /* @__PURE__ */ jsxs20("div", { className: cn("flex flex-col gap-1.5", className), children: [
|
|
1907
|
-
/* @__PURE__ */
|
|
2394
|
+
/* @__PURE__ */ jsx25("label", { htmlFor, className: "text-sm font-medium text-foreground", children: label }),
|
|
1908
2395
|
children,
|
|
1909
|
-
description ? /* @__PURE__ */
|
|
2396
|
+
description ? /* @__PURE__ */ jsx25("p", { className: "text-xs text-muted-foreground", children: description }) : null
|
|
1910
2397
|
] });
|
|
1911
2398
|
};
|
|
1912
2399
|
|
|
@@ -1914,7 +2401,7 @@ var FieldRow = ({
|
|
|
1914
2401
|
import { useEffect, useState as useState3 } from "react";
|
|
1915
2402
|
import { createPortal } from "react-dom";
|
|
1916
2403
|
import { AnimatePresence as AnimatePresence2, motion as motion3, useReducedMotion as useReducedMotion3 } from "motion/react";
|
|
1917
|
-
import { jsx as
|
|
2404
|
+
import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1918
2405
|
var FloatingUnsavedChangesBar = ({
|
|
1919
2406
|
visible,
|
|
1920
2407
|
message = "Unsaved changes",
|
|
@@ -1932,7 +2419,7 @@ var FloatingUnsavedChangesBar = ({
|
|
|
1932
2419
|
useEffect(() => setMounted(true), []);
|
|
1933
2420
|
if (!mounted || typeof document === "undefined") return null;
|
|
1934
2421
|
return createPortal(
|
|
1935
|
-
/* @__PURE__ */
|
|
2422
|
+
/* @__PURE__ */ jsx26(AnimatePresence2, { children: visible ? /* @__PURE__ */ jsx26("div", { className: "pointer-events-none fixed inset-x-0 bottom-5 z-50 flex justify-center px-4", children: /* @__PURE__ */ jsxs21(
|
|
1936
2423
|
motion3.div,
|
|
1937
2424
|
{
|
|
1938
2425
|
role: "region",
|
|
@@ -1946,10 +2433,10 @@ var FloatingUnsavedChangesBar = ({
|
|
|
1946
2433
|
className
|
|
1947
2434
|
),
|
|
1948
2435
|
children: [
|
|
1949
|
-
/* @__PURE__ */
|
|
2436
|
+
/* @__PURE__ */ jsx26("span", { className: "text-sm text-muted-foreground", children: message }),
|
|
1950
2437
|
/* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1.5", children: [
|
|
1951
|
-
/* @__PURE__ */
|
|
1952
|
-
/* @__PURE__ */
|
|
2438
|
+
/* @__PURE__ */ jsx26(Button, { variant: "ghost", size: "sm", onClick: onDiscard, disabled: isSaving, children: discardLabel }),
|
|
2439
|
+
/* @__PURE__ */ jsx26(Button, { size: "sm", onClick: onSave, disabled: saveDisabled || isSaving, children: isSaving ? "Saving\u2026" : saveLabel })
|
|
1953
2440
|
] })
|
|
1954
2441
|
]
|
|
1955
2442
|
}
|
|
@@ -1959,7 +2446,7 @@ var FloatingUnsavedChangesBar = ({
|
|
|
1959
2446
|
};
|
|
1960
2447
|
|
|
1961
2448
|
// src/app/settings/DangerZone.tsx
|
|
1962
|
-
import { jsx as
|
|
2449
|
+
import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1963
2450
|
var DangerZoneAction = ({
|
|
1964
2451
|
title,
|
|
1965
2452
|
description,
|
|
@@ -1974,10 +2461,10 @@ var DangerZoneAction = ({
|
|
|
1974
2461
|
),
|
|
1975
2462
|
children: [
|
|
1976
2463
|
/* @__PURE__ */ jsxs22("div", { className: "min-w-0", children: [
|
|
1977
|
-
/* @__PURE__ */
|
|
1978
|
-
description ? /* @__PURE__ */
|
|
2464
|
+
/* @__PURE__ */ jsx27("p", { className: "text-sm font-medium text-foreground", children: title }),
|
|
2465
|
+
description ? /* @__PURE__ */ jsx27("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
|
|
1979
2466
|
] }),
|
|
1980
|
-
/* @__PURE__ */
|
|
2467
|
+
/* @__PURE__ */ jsx27("div", { className: "shrink-0", children: action })
|
|
1981
2468
|
]
|
|
1982
2469
|
}
|
|
1983
2470
|
);
|
|
@@ -1995,17 +2482,17 @@ var DangerZone = ({
|
|
|
1995
2482
|
),
|
|
1996
2483
|
children: [
|
|
1997
2484
|
(title || description) && /* @__PURE__ */ jsxs22("header", { className: "border-b border-destructive/20 bg-destructive/5 px-4 py-3", children: [
|
|
1998
|
-
title ? /* @__PURE__ */
|
|
1999
|
-
description ? /* @__PURE__ */
|
|
2485
|
+
title ? /* @__PURE__ */ jsx27("h3", { className: "text-sm font-semibold text-destructive", children: title }) : null,
|
|
2486
|
+
description ? /* @__PURE__ */ jsx27("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
|
|
2000
2487
|
] }),
|
|
2001
|
-
/* @__PURE__ */
|
|
2488
|
+
/* @__PURE__ */ jsx27("div", { className: "divide-y divide-border bg-card", children })
|
|
2002
2489
|
]
|
|
2003
2490
|
}
|
|
2004
2491
|
);
|
|
2005
2492
|
|
|
2006
2493
|
// src/app/integrations/IntegrationCard.tsx
|
|
2007
2494
|
import { useId as useId2 } from "react";
|
|
2008
|
-
import { Fragment as Fragment4, jsx as
|
|
2495
|
+
import { Fragment as Fragment4, jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2009
2496
|
var INTEGRATION_CATALOG_CARD_HEIGHT_CLASS = "h-[12.25rem] min-h-[12.25rem] max-h-[12.25rem]";
|
|
2010
2497
|
var statusLabel = {
|
|
2011
2498
|
available: null,
|
|
@@ -2048,10 +2535,10 @@ var IntegrationCard = ({
|
|
|
2048
2535
|
const dimmed = status === "disabled" || locked;
|
|
2049
2536
|
const body = /* @__PURE__ */ jsxs23("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
2050
2537
|
/* @__PURE__ */ jsxs23("div", { className: "flex shrink-0 items-start gap-3 pr-2", children: [
|
|
2051
|
-
logo ? /* @__PURE__ */
|
|
2052
|
-
/* @__PURE__ */
|
|
2538
|
+
logo ? /* @__PURE__ */ jsx28("span", { className: logoShellClass, "aria-hidden": Boolean(ariaLabel), children: logo }) : null,
|
|
2539
|
+
/* @__PURE__ */ jsx28("div", { className: "min-w-0 flex-1 pt-0.5", children: /* @__PURE__ */ jsxs23("div", { className: "flex items-start justify-between gap-2", children: [
|
|
2053
2540
|
/* @__PURE__ */ jsxs23("div", { className: "min-w-0", children: [
|
|
2054
|
-
/* @__PURE__ */
|
|
2541
|
+
/* @__PURE__ */ jsx28(
|
|
2055
2542
|
"h4",
|
|
2056
2543
|
{
|
|
2057
2544
|
id: onClick && !action ? void 0 : titleId,
|
|
@@ -2059,12 +2546,12 @@ var IntegrationCard = ({
|
|
|
2059
2546
|
children: name
|
|
2060
2547
|
}
|
|
2061
2548
|
),
|
|
2062
|
-
statusLabel[status] ? /* @__PURE__ */
|
|
2549
|
+
statusLabel[status] ? /* @__PURE__ */ jsx28("p", { className: "mt-0.5 text-xs text-muted-foreground", children: statusLabel[status] }) : null
|
|
2063
2550
|
] }),
|
|
2064
|
-
badge ? /* @__PURE__ */
|
|
2551
|
+
badge ? /* @__PURE__ */ jsx28("span", { className: "shrink-0", children: badge }) : null
|
|
2065
2552
|
] }) })
|
|
2066
2553
|
] }),
|
|
2067
|
-
description ? /* @__PURE__ */
|
|
2554
|
+
description ? /* @__PURE__ */ jsx28(
|
|
2068
2555
|
"p",
|
|
2069
2556
|
{
|
|
2070
2557
|
className: cn(
|
|
@@ -2075,8 +2562,8 @@ var IntegrationCard = ({
|
|
|
2075
2562
|
}
|
|
2076
2563
|
) : null,
|
|
2077
2564
|
action ? /* @__PURE__ */ jsxs23(Fragment4, { children: [
|
|
2078
|
-
/* @__PURE__ */
|
|
2079
|
-
/* @__PURE__ */
|
|
2565
|
+
/* @__PURE__ */ jsx28("div", { className: "min-h-0 flex-1", "aria-hidden": true }),
|
|
2566
|
+
/* @__PURE__ */ jsx28("div", { className: "relative mt-3 shrink-0", children: action })
|
|
2080
2567
|
] }) : null
|
|
2081
2568
|
] });
|
|
2082
2569
|
const shellClass3 = cn(
|
|
@@ -2086,7 +2573,7 @@ var IntegrationCard = ({
|
|
|
2086
2573
|
className
|
|
2087
2574
|
);
|
|
2088
2575
|
if (onClick && !action) {
|
|
2089
|
-
return /* @__PURE__ */
|
|
2576
|
+
return /* @__PURE__ */ jsx28(
|
|
2090
2577
|
"button",
|
|
2091
2578
|
{
|
|
2092
2579
|
type: "button",
|
|
@@ -2103,12 +2590,12 @@ var IntegrationCard = ({
|
|
|
2103
2590
|
}
|
|
2104
2591
|
);
|
|
2105
2592
|
}
|
|
2106
|
-
return /* @__PURE__ */
|
|
2593
|
+
return /* @__PURE__ */ jsx28("article", { className: shellClass3, "aria-labelledby": titleId, children: body });
|
|
2107
2594
|
};
|
|
2108
2595
|
|
|
2109
2596
|
// src/app/integrations/IntegrationsEmptyState.tsx
|
|
2110
2597
|
import { useId as useId3 } from "react";
|
|
2111
|
-
import { jsx as
|
|
2598
|
+
import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2112
2599
|
var IntegrationsEmptyState = ({
|
|
2113
2600
|
title = "No integrations yet",
|
|
2114
2601
|
description = "Connect a provider to start syncing data and powering your workforce.",
|
|
@@ -2127,7 +2614,7 @@ var IntegrationsEmptyState = ({
|
|
|
2127
2614
|
),
|
|
2128
2615
|
"aria-labelledby": titleId,
|
|
2129
2616
|
children: [
|
|
2130
|
-
icon ? /* @__PURE__ */
|
|
2617
|
+
icon ? /* @__PURE__ */ jsx29(
|
|
2131
2618
|
"span",
|
|
2132
2619
|
{
|
|
2133
2620
|
className: cn(
|
|
@@ -2139,21 +2626,21 @@ var IntegrationsEmptyState = ({
|
|
|
2139
2626
|
children: icon
|
|
2140
2627
|
}
|
|
2141
2628
|
) : null,
|
|
2142
|
-
/* @__PURE__ */
|
|
2143
|
-
description ? /* @__PURE__ */
|
|
2144
|
-
action ? /* @__PURE__ */
|
|
2629
|
+
/* @__PURE__ */ jsx29("h3", { id: titleId, className: "text-base font-normal text-foreground", children: title }),
|
|
2630
|
+
description ? /* @__PURE__ */ jsx29("p", { className: "max-w-sm text-sm text-muted-foreground", children: description }) : null,
|
|
2631
|
+
action ? /* @__PURE__ */ jsx29("div", { className: "mt-1", children: action }) : null
|
|
2145
2632
|
]
|
|
2146
2633
|
}
|
|
2147
2634
|
);
|
|
2148
2635
|
};
|
|
2149
2636
|
|
|
2150
2637
|
// src/app/integrations/PlanBadge.tsx
|
|
2151
|
-
import { jsx as
|
|
2638
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
2152
2639
|
var planBadgeClass = "inline-flex h-5 max-w-full shrink-0 items-center rounded-md border border-border bg-muted/90 px-2 text-[11px] font-normal text-muted-foreground dark:border-white/10 dark:bg-white/5 dark:text-muted-foreground";
|
|
2153
|
-
var PlanBadge = ({ children, className }) => /* @__PURE__ */
|
|
2640
|
+
var PlanBadge = ({ children, className }) => /* @__PURE__ */ jsx30("span", { className: cn(planBadgeClass, className), children });
|
|
2154
2641
|
|
|
2155
2642
|
// src/app/integrations/ConnectionRow.tsx
|
|
2156
|
-
import { Fragment as Fragment5, jsx as
|
|
2643
|
+
import { Fragment as Fragment5, jsx as jsx31, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2157
2644
|
var logoShellClass2 = cn(
|
|
2158
2645
|
"flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-lg",
|
|
2159
2646
|
TIMBAL_V2_LOGO_TILE
|
|
@@ -2169,13 +2656,13 @@ var ConnectionRow = ({
|
|
|
2169
2656
|
className
|
|
2170
2657
|
}) => {
|
|
2171
2658
|
const inner = /* @__PURE__ */ jsxs25(Fragment5, { children: [
|
|
2172
|
-
logo ? /* @__PURE__ */
|
|
2659
|
+
logo ? /* @__PURE__ */ jsx31("span", { className: logoShellClass2, children: logo }) : null,
|
|
2173
2660
|
/* @__PURE__ */ jsxs25("div", { className: "min-w-0 flex-1", children: [
|
|
2174
|
-
/* @__PURE__ */
|
|
2175
|
-
meta ? /* @__PURE__ */
|
|
2661
|
+
/* @__PURE__ */ jsx31("p", { className: "truncate text-sm font-normal text-foreground", children: name }),
|
|
2662
|
+
meta ? /* @__PURE__ */ jsx31("p", { className: "truncate text-xs text-muted-foreground", children: meta }) : null
|
|
2176
2663
|
] }),
|
|
2177
|
-
badge ? /* @__PURE__ */
|
|
2178
|
-
action ? /* @__PURE__ */
|
|
2664
|
+
badge ? /* @__PURE__ */ jsx31("span", { className: "shrink-0", children: badge }) : null,
|
|
2665
|
+
action ? /* @__PURE__ */ jsx31("span", { className: "shrink-0", children: action }) : null
|
|
2179
2666
|
] });
|
|
2180
2667
|
const rowClass2 = cn(
|
|
2181
2668
|
"flex w-full items-center gap-3 px-4 py-3 text-left",
|
|
@@ -2183,7 +2670,7 @@ var ConnectionRow = ({
|
|
|
2183
2670
|
className
|
|
2184
2671
|
);
|
|
2185
2672
|
if (onClick) {
|
|
2186
|
-
return /* @__PURE__ */
|
|
2673
|
+
return /* @__PURE__ */ jsx31(
|
|
2187
2674
|
"button",
|
|
2188
2675
|
{
|
|
2189
2676
|
type: "button",
|
|
@@ -2195,7 +2682,7 @@ var ConnectionRow = ({
|
|
|
2195
2682
|
}
|
|
2196
2683
|
);
|
|
2197
2684
|
}
|
|
2198
|
-
return /* @__PURE__ */
|
|
2685
|
+
return /* @__PURE__ */ jsx31("div", { role: "listitem", className: rowClass2, children: inner });
|
|
2199
2686
|
};
|
|
2200
2687
|
var connectionRowListClass = cn(
|
|
2201
2688
|
"overflow-hidden rounded-2xl",
|
|
@@ -2203,12 +2690,12 @@ var connectionRowListClass = cn(
|
|
|
2203
2690
|
);
|
|
2204
2691
|
|
|
2205
2692
|
// src/app/integrations/ConnectionRowList.tsx
|
|
2206
|
-
import { jsx as
|
|
2693
|
+
import { jsx as jsx32 } from "react/jsx-runtime";
|
|
2207
2694
|
var ConnectionRowList = ({
|
|
2208
2695
|
children,
|
|
2209
2696
|
"aria-label": ariaLabel = "Connected integrations",
|
|
2210
2697
|
className
|
|
2211
|
-
}) => /* @__PURE__ */
|
|
2698
|
+
}) => /* @__PURE__ */ jsx32(
|
|
2212
2699
|
"div",
|
|
2213
2700
|
{
|
|
2214
2701
|
role: "list",
|
|
@@ -2219,7 +2706,7 @@ var ConnectionRowList = ({
|
|
|
2219
2706
|
);
|
|
2220
2707
|
|
|
2221
2708
|
// src/app/navigation/SubNav.tsx
|
|
2222
|
-
import { jsx as
|
|
2709
|
+
import { jsx as jsx33 } from "react/jsx-runtime";
|
|
2223
2710
|
var SubNav = ({
|
|
2224
2711
|
items,
|
|
2225
2712
|
activeId,
|
|
@@ -2228,7 +2715,7 @@ var SubNav = ({
|
|
|
2228
2715
|
"aria-label": ariaLabel = "Section navigation",
|
|
2229
2716
|
layoutId
|
|
2230
2717
|
}) => {
|
|
2231
|
-
return /* @__PURE__ */
|
|
2718
|
+
return /* @__PURE__ */ jsx33("nav", { className: cn("aui-app-sub-nav", className), "aria-label": ariaLabel, children: /* @__PURE__ */ jsx33(
|
|
2232
2719
|
PillSegmentedTabs,
|
|
2233
2720
|
{
|
|
2234
2721
|
value: activeId,
|
|
@@ -2242,13 +2729,13 @@ var SubNav = ({
|
|
|
2242
2729
|
};
|
|
2243
2730
|
|
|
2244
2731
|
// src/app/navigation/Breadcrumbs.tsx
|
|
2245
|
-
import { jsx as
|
|
2732
|
+
import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2246
2733
|
var Breadcrumbs = ({ items, className }) => {
|
|
2247
|
-
return /* @__PURE__ */
|
|
2734
|
+
return /* @__PURE__ */ jsx34("nav", { className: cn("aui-app-breadcrumbs", appBreadcrumbsClass, className), "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx34("ol", { className: "flex flex-wrap items-center gap-1.5", children: items.map((item, index) => {
|
|
2248
2735
|
const isLast = index === items.length - 1;
|
|
2249
2736
|
return /* @__PURE__ */ jsxs26("li", { className: "inline-flex items-center gap-1.5", children: [
|
|
2250
|
-
index > 0 ? /* @__PURE__ */
|
|
2251
|
-
isLast ? /* @__PURE__ */
|
|
2737
|
+
index > 0 ? /* @__PURE__ */ jsx34("span", { className: "text-muted-foreground/50", "aria-hidden": true, children: "/" }) : null,
|
|
2738
|
+
isLast ? /* @__PURE__ */ jsx34("span", { className: "text-foreground", "aria-current": "page", children: item.label }) : item.href ? /* @__PURE__ */ jsx34("a", { href: item.href, className: appBreadcrumbLinkClass, children: item.label }) : /* @__PURE__ */ jsx34(
|
|
2252
2739
|
"button",
|
|
2253
2740
|
{
|
|
2254
2741
|
type: "button",
|
|
@@ -2262,7 +2749,7 @@ var Breadcrumbs = ({ items, className }) => {
|
|
|
2262
2749
|
};
|
|
2263
2750
|
|
|
2264
2751
|
// src/app/forms/Field.tsx
|
|
2265
|
-
import { jsx as
|
|
2752
|
+
import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2266
2753
|
var Field = ({
|
|
2267
2754
|
label,
|
|
2268
2755
|
hint,
|
|
@@ -2272,10 +2759,10 @@ var Field = ({
|
|
|
2272
2759
|
htmlFor
|
|
2273
2760
|
}) => {
|
|
2274
2761
|
return /* @__PURE__ */ jsxs27("div", { className: cn("aui-app-field", appFieldClass, className), children: [
|
|
2275
|
-
/* @__PURE__ */
|
|
2762
|
+
/* @__PURE__ */ jsx35("label", { className: appFieldLabelClass, htmlFor, children: label }),
|
|
2276
2763
|
children,
|
|
2277
|
-
hint && !error ? /* @__PURE__ */
|
|
2278
|
-
error ? /* @__PURE__ */
|
|
2764
|
+
hint && !error ? /* @__PURE__ */ jsx35("p", { className: appFieldHintClass, children: hint }) : null,
|
|
2765
|
+
error ? /* @__PURE__ */ jsx35("p", { className: "text-xs text-destructive", role: "alert", children: error }) : null
|
|
2279
2766
|
] });
|
|
2280
2767
|
};
|
|
2281
2768
|
var FieldInput = ({
|
|
@@ -2288,7 +2775,7 @@ var FieldInput = ({
|
|
|
2288
2775
|
...inputProps
|
|
2289
2776
|
}) => {
|
|
2290
2777
|
const inputId = id ?? inputProps.name;
|
|
2291
|
-
return /* @__PURE__ */
|
|
2778
|
+
return /* @__PURE__ */ jsx35(
|
|
2292
2779
|
Field,
|
|
2293
2780
|
{
|
|
2294
2781
|
label,
|
|
@@ -2296,7 +2783,7 @@ var FieldInput = ({
|
|
|
2296
2783
|
error,
|
|
2297
2784
|
htmlFor: inputId,
|
|
2298
2785
|
className: fieldClassName,
|
|
2299
|
-
children: /* @__PURE__ */
|
|
2786
|
+
children: /* @__PURE__ */ jsx35(
|
|
2300
2787
|
"input",
|
|
2301
2788
|
{
|
|
2302
2789
|
id: inputId,
|
|
@@ -2310,7 +2797,7 @@ var FieldInput = ({
|
|
|
2310
2797
|
};
|
|
2311
2798
|
|
|
2312
2799
|
// src/app/forms/FieldTextarea.tsx
|
|
2313
|
-
import { jsx as
|
|
2800
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
2314
2801
|
var textareaClass = cn(
|
|
2315
2802
|
appInputClass,
|
|
2316
2803
|
"min-h-[5.5rem] resize-y py-2.5 leading-relaxed"
|
|
@@ -2325,7 +2812,7 @@ var FieldTextarea = ({
|
|
|
2325
2812
|
...props
|
|
2326
2813
|
}) => {
|
|
2327
2814
|
const textareaId = id ?? props.name;
|
|
2328
|
-
return /* @__PURE__ */
|
|
2815
|
+
return /* @__PURE__ */ jsx36(
|
|
2329
2816
|
Field,
|
|
2330
2817
|
{
|
|
2331
2818
|
label,
|
|
@@ -2333,7 +2820,7 @@ var FieldTextarea = ({
|
|
|
2333
2820
|
error,
|
|
2334
2821
|
htmlFor: textareaId,
|
|
2335
2822
|
className: fieldClassName,
|
|
2336
|
-
children: /* @__PURE__ */
|
|
2823
|
+
children: /* @__PURE__ */ jsx36(
|
|
2337
2824
|
"textarea",
|
|
2338
2825
|
{
|
|
2339
2826
|
id: textareaId,
|
|
@@ -2348,7 +2835,7 @@ var FieldTextarea = ({
|
|
|
2348
2835
|
|
|
2349
2836
|
// src/app/forms/FieldSelect.tsx
|
|
2350
2837
|
import { ChevronDownIcon } from "lucide-react";
|
|
2351
|
-
import { jsx as
|
|
2838
|
+
import { jsx as jsx37, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2352
2839
|
var selectWrapClass = "relative";
|
|
2353
2840
|
var selectClass = cn(
|
|
2354
2841
|
appInputClass,
|
|
@@ -2365,7 +2852,7 @@ var FieldSelect = ({
|
|
|
2365
2852
|
...props
|
|
2366
2853
|
}) => {
|
|
2367
2854
|
const selectId = id ?? props.name;
|
|
2368
|
-
return /* @__PURE__ */
|
|
2855
|
+
return /* @__PURE__ */ jsx37(
|
|
2369
2856
|
Field,
|
|
2370
2857
|
{
|
|
2371
2858
|
label,
|
|
@@ -2374,7 +2861,7 @@ var FieldSelect = ({
|
|
|
2374
2861
|
htmlFor: selectId,
|
|
2375
2862
|
className: fieldClassName,
|
|
2376
2863
|
children: /* @__PURE__ */ jsxs28("div", { className: selectWrapClass, children: [
|
|
2377
|
-
/* @__PURE__ */
|
|
2864
|
+
/* @__PURE__ */ jsx37(
|
|
2378
2865
|
"select",
|
|
2379
2866
|
{
|
|
2380
2867
|
id: selectId,
|
|
@@ -2384,7 +2871,7 @@ var FieldSelect = ({
|
|
|
2384
2871
|
children
|
|
2385
2872
|
}
|
|
2386
2873
|
),
|
|
2387
|
-
/* @__PURE__ */
|
|
2874
|
+
/* @__PURE__ */ jsx37(
|
|
2388
2875
|
ChevronDownIcon,
|
|
2389
2876
|
{
|
|
2390
2877
|
className: "pointer-events-none absolute top-1/2 right-3 size-4 -translate-y-1/2 text-muted-foreground",
|
|
@@ -2397,7 +2884,7 @@ var FieldSelect = ({
|
|
|
2397
2884
|
};
|
|
2398
2885
|
|
|
2399
2886
|
// src/app/forms/FieldSwitch.tsx
|
|
2400
|
-
import { jsx as
|
|
2887
|
+
import { jsx as jsx38, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
2401
2888
|
var trackClass = cn(
|
|
2402
2889
|
"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-[background,box-shadow,border-color] duration-200",
|
|
2403
2890
|
"peer-focus-visible:ring-2 peer-focus-visible:ring-foreground/10",
|
|
@@ -2428,7 +2915,7 @@ var FieldSwitch = ({
|
|
|
2428
2915
|
htmlFor: inputId,
|
|
2429
2916
|
children: [
|
|
2430
2917
|
/* @__PURE__ */ jsxs29("span", { className: "relative mt-0.5", children: [
|
|
2431
|
-
/* @__PURE__ */
|
|
2918
|
+
/* @__PURE__ */ jsx38(
|
|
2432
2919
|
"input",
|
|
2433
2920
|
{
|
|
2434
2921
|
id: inputId,
|
|
@@ -2438,11 +2925,11 @@ var FieldSwitch = ({
|
|
|
2438
2925
|
...props
|
|
2439
2926
|
}
|
|
2440
2927
|
),
|
|
2441
|
-
/* @__PURE__ */
|
|
2928
|
+
/* @__PURE__ */ jsx38("span", { className: trackClass, "aria-hidden": true, children: /* @__PURE__ */ jsx38("span", { className: thumbClass }) })
|
|
2442
2929
|
] }),
|
|
2443
2930
|
/* @__PURE__ */ jsxs29("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
|
|
2444
|
-
/* @__PURE__ */
|
|
2445
|
-
description ? /* @__PURE__ */
|
|
2931
|
+
/* @__PURE__ */ jsx38("span", { className: "text-sm font-medium text-foreground", children: label }),
|
|
2932
|
+
description ? /* @__PURE__ */ jsx38("span", { className: "text-xs text-muted-foreground", children: description }) : null
|
|
2446
2933
|
] })
|
|
2447
2934
|
]
|
|
2448
2935
|
}
|
|
@@ -2451,7 +2938,7 @@ var FieldSwitch = ({
|
|
|
2451
2938
|
|
|
2452
2939
|
// src/app/forms/SearchInput.tsx
|
|
2453
2940
|
import { SearchIcon } from "lucide-react";
|
|
2454
|
-
import { jsx as
|
|
2941
|
+
import { jsx as jsx39, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
2455
2942
|
var SearchInput = ({
|
|
2456
2943
|
className,
|
|
2457
2944
|
placeholder = "Search\u2026",
|
|
@@ -2466,8 +2953,8 @@ var SearchInput = ({
|
|
|
2466
2953
|
className
|
|
2467
2954
|
),
|
|
2468
2955
|
children: [
|
|
2469
|
-
/* @__PURE__ */
|
|
2470
|
-
/* @__PURE__ */
|
|
2956
|
+
/* @__PURE__ */ jsx39(SearchIcon, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
2957
|
+
/* @__PURE__ */ jsx39(
|
|
2471
2958
|
"input",
|
|
2472
2959
|
{
|
|
2473
2960
|
type: "search",
|
|
@@ -2482,18 +2969,18 @@ var SearchInput = ({
|
|
|
2482
2969
|
};
|
|
2483
2970
|
|
|
2484
2971
|
// src/app/forms/FormSection.tsx
|
|
2485
|
-
import { jsx as
|
|
2972
|
+
import { jsx as jsx40, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
2486
2973
|
var FormSection = ({ title, children, className }) => {
|
|
2487
2974
|
return /* @__PURE__ */ jsxs31("fieldset", { className: cn("aui-app-form-section", appSectionClass, "border-0 p-0", className), children: [
|
|
2488
|
-
title ? /* @__PURE__ */
|
|
2489
|
-
/* @__PURE__ */
|
|
2975
|
+
title ? /* @__PURE__ */ jsx40("legend", { className: cn(appSectionTitleClass, "mb-3 px-0"), children: title }) : null,
|
|
2976
|
+
/* @__PURE__ */ jsx40("div", { className: "flex flex-col gap-4", children })
|
|
2490
2977
|
] });
|
|
2491
2978
|
};
|
|
2492
2979
|
|
|
2493
2980
|
// src/app/data/FilterBar.tsx
|
|
2494
|
-
import { jsx as
|
|
2981
|
+
import { jsx as jsx41 } from "react/jsx-runtime";
|
|
2495
2982
|
var FilterBar = ({ children, className }) => {
|
|
2496
|
-
return /* @__PURE__ */
|
|
2983
|
+
return /* @__PURE__ */ jsx41(
|
|
2497
2984
|
"div",
|
|
2498
2985
|
{
|
|
2499
2986
|
className: cn("aui-app-filter-bar", appFilterBarClass, className),
|
|
@@ -2504,20 +2991,41 @@ var FilterBar = ({ children, className }) => {
|
|
|
2504
2991
|
);
|
|
2505
2992
|
};
|
|
2506
2993
|
|
|
2994
|
+
// src/app/data/FilterField.tsx
|
|
2995
|
+
import { jsx as jsx42, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2996
|
+
var FilterField = ({
|
|
2997
|
+
label,
|
|
2998
|
+
children,
|
|
2999
|
+
className
|
|
3000
|
+
}) => {
|
|
3001
|
+
return /* @__PURE__ */ jsxs32("div", { className: cn("aui-app-filter-field", appFieldClass, className), children: [
|
|
3002
|
+
label ? /* @__PURE__ */ jsx42("span", { className: appFieldLabelClass, children: label }) : null,
|
|
3003
|
+
children
|
|
3004
|
+
] });
|
|
3005
|
+
};
|
|
3006
|
+
|
|
2507
3007
|
// src/app/data/DataTable.tsx
|
|
2508
|
-
import { useMemo, useState as useState4 } from "react";
|
|
2509
|
-
import {
|
|
2510
|
-
|
|
3008
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useState as useState4 } from "react";
|
|
3009
|
+
import {
|
|
3010
|
+
ArrowDownIcon,
|
|
3011
|
+
ArrowUpDownIcon,
|
|
3012
|
+
ArrowUpIcon,
|
|
3013
|
+
ChevronLeftIcon,
|
|
3014
|
+
ChevronRightIcon
|
|
3015
|
+
} from "lucide-react";
|
|
3016
|
+
import { jsx as jsx43, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2511
3017
|
var shellClass2 = "overflow-hidden rounded-xl border border-border bg-card shadow-card";
|
|
2512
3018
|
var tableClass = "w-full border-collapse bg-transparent text-sm";
|
|
2513
3019
|
var headCellClass = "border-b border-border/60 bg-transparent px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground";
|
|
2514
3020
|
var bodyCellClass = "border-b border-border/40 bg-transparent px-4 py-2.5 text-foreground";
|
|
2515
|
-
var rowClass = "bg-transparent transition-colors hover:bg-foreground/[0.03] data-[clickable=true]:cursor-pointer";
|
|
3021
|
+
var rowClass = "bg-transparent transition-colors hover:bg-foreground/[0.03] data-[clickable=true]:cursor-pointer data-[selected=true]:bg-primary/[0.04]";
|
|
2516
3022
|
var footCellClass = "border-t border-border/60 bg-transparent px-4 py-2.5 text-xs text-muted-foreground";
|
|
2517
3023
|
var footInnerClass = "flex flex-wrap items-center gap-2";
|
|
2518
3024
|
var emptyCellClass = "bg-transparent px-4 py-10 text-center text-sm text-muted-foreground";
|
|
2519
3025
|
var sortButtonClass = "group inline-flex min-w-0 items-center gap-1.5 rounded-md -mx-1 px-1 py-0.5 text-left font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/10";
|
|
2520
3026
|
var stickyHeadClass = "sticky top-0 z-[1] bg-card/95 shadow-[0_1px_0_0_hsl(var(--border)/0.5)] backdrop-blur-sm [&_th]:bg-card/95";
|
|
3027
|
+
var selectCellClass = "w-10 px-4 py-2.5 align-middle";
|
|
3028
|
+
var pagerButtonClass = "inline-flex size-7 items-center justify-center rounded-md border border-border bg-transparent text-muted-foreground transition-colors hover:bg-foreground/[0.04] hover:text-foreground disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/10";
|
|
2521
3029
|
var alignClass = {
|
|
2522
3030
|
left: "text-left",
|
|
2523
3031
|
center: "text-center",
|
|
@@ -2547,12 +3055,12 @@ function SortIndicator({
|
|
|
2547
3055
|
}) {
|
|
2548
3056
|
const iconClass = "size-3.5 shrink-0 opacity-60 group-hover:opacity-100";
|
|
2549
3057
|
if (!active) {
|
|
2550
|
-
return /* @__PURE__ */
|
|
3058
|
+
return /* @__PURE__ */ jsx43(ArrowUpDownIcon, { className: iconClass, "aria-hidden": true });
|
|
2551
3059
|
}
|
|
2552
3060
|
if (direction === "desc") {
|
|
2553
|
-
return /* @__PURE__ */
|
|
3061
|
+
return /* @__PURE__ */ jsx43(ArrowDownIcon, { className: iconClass, "aria-hidden": true });
|
|
2554
3062
|
}
|
|
2555
|
-
return /* @__PURE__ */
|
|
3063
|
+
return /* @__PURE__ */ jsx43(ArrowUpIcon, { className: iconClass, "aria-hidden": true });
|
|
2556
3064
|
}
|
|
2557
3065
|
function DataTable({
|
|
2558
3066
|
columns,
|
|
@@ -2571,7 +3079,18 @@ function DataTable({
|
|
|
2571
3079
|
onRowClick,
|
|
2572
3080
|
stickyHeader = false,
|
|
2573
3081
|
dense = false,
|
|
2574
|
-
caption
|
|
3082
|
+
caption,
|
|
3083
|
+
loading = false,
|
|
3084
|
+
loadingRows,
|
|
3085
|
+
selectable = false,
|
|
3086
|
+
selectedKeys: selectedKeysProp,
|
|
3087
|
+
defaultSelectedKeys,
|
|
3088
|
+
onSelectionChange,
|
|
3089
|
+
selectAllAriaLabel = "Select all rows",
|
|
3090
|
+
pageSize,
|
|
3091
|
+
pageIndex: pageIndexProp,
|
|
3092
|
+
defaultPageIndex = 0,
|
|
3093
|
+
onPageChange
|
|
2575
3094
|
}) {
|
|
2576
3095
|
const [uncontrolledSort, setUncontrolledSort] = useState4(
|
|
2577
3096
|
defaultSort
|
|
@@ -2584,7 +3103,28 @@ function DataTable({
|
|
|
2584
3103
|
}
|
|
2585
3104
|
onSortChange?.(next);
|
|
2586
3105
|
};
|
|
2587
|
-
const
|
|
3106
|
+
const [uncontrolledSelected, setUncontrolledSelected] = useState4(
|
|
3107
|
+
defaultSelectedKeys ?? []
|
|
3108
|
+
);
|
|
3109
|
+
const isSelectionControlled = selectedKeysProp !== void 0;
|
|
3110
|
+
const selectedKeys = isSelectionControlled ? selectedKeysProp : uncontrolledSelected;
|
|
3111
|
+
const selectedSet = useMemo2(() => new Set(selectedKeys), [selectedKeys]);
|
|
3112
|
+
const setSelected = (next) => {
|
|
3113
|
+
if (!isSelectionControlled) {
|
|
3114
|
+
setUncontrolledSelected(next);
|
|
3115
|
+
}
|
|
3116
|
+
onSelectionChange?.(next);
|
|
3117
|
+
};
|
|
3118
|
+
const [uncontrolledPage, setUncontrolledPage] = useState4(defaultPageIndex);
|
|
3119
|
+
const isPageControlled = pageIndexProp !== void 0;
|
|
3120
|
+
const rawPageIndex = isPageControlled ? pageIndexProp : uncontrolledPage;
|
|
3121
|
+
const setPage = (next) => {
|
|
3122
|
+
if (!isPageControlled) {
|
|
3123
|
+
setUncontrolledPage(next);
|
|
3124
|
+
}
|
|
3125
|
+
onPageChange?.(next);
|
|
3126
|
+
};
|
|
3127
|
+
const sortedRows = useMemo2(() => {
|
|
2588
3128
|
if (!sort) return rows;
|
|
2589
3129
|
const column = columns.find((col) => col.id === sort.columnId);
|
|
2590
3130
|
if (!column?.sortable) return rows;
|
|
@@ -2600,89 +3140,218 @@ function DataTable({
|
|
|
2600
3140
|
return sort.direction === "asc" ? cmp : -cmp;
|
|
2601
3141
|
});
|
|
2602
3142
|
}, [columns, rows, sort]);
|
|
3143
|
+
const paginated = typeof pageSize === "number" && pageSize > 0;
|
|
3144
|
+
const pageCount = paginated ? Math.max(1, Math.ceil(sortedRows.length / pageSize)) : 1;
|
|
3145
|
+
const pageIndex = Math.min(Math.max(0, rawPageIndex), pageCount - 1);
|
|
3146
|
+
useEffect2(() => {
|
|
3147
|
+
if (!paginated || isPageControlled) return;
|
|
3148
|
+
if (uncontrolledPage > pageCount - 1) {
|
|
3149
|
+
setUncontrolledPage(pageCount - 1);
|
|
3150
|
+
}
|
|
3151
|
+
}, [paginated, isPageControlled, uncontrolledPage, pageCount]);
|
|
3152
|
+
const visibleRows = useMemo2(() => {
|
|
3153
|
+
if (!paginated) return sortedRows;
|
|
3154
|
+
const start = pageIndex * pageSize;
|
|
3155
|
+
return sortedRows.slice(start, start + pageSize);
|
|
3156
|
+
}, [paginated, sortedRows, pageIndex, pageSize]);
|
|
2603
3157
|
const cellPad = dense ? "px-3 py-2" : void 0;
|
|
2604
3158
|
const headPad = dense ? "px-3 py-2" : void 0;
|
|
2605
|
-
|
|
2606
|
-
|
|
3159
|
+
const colSpan = columns.length + (selectable ? 1 : 0);
|
|
3160
|
+
if (!loading && rows.length === 0 && emptyMode === "replace") {
|
|
3161
|
+
return /* @__PURE__ */ jsx43(EmptyState, { title: emptyTitle, description: emptyDescription, className });
|
|
2607
3162
|
}
|
|
3163
|
+
const allKeys = sortedRows.map(getRowKey);
|
|
3164
|
+
const allSelected = allKeys.length > 0 && allKeys.every((k) => selectedSet.has(k));
|
|
3165
|
+
const headerCheckedState = allSelected ? true : selectedSet.size > 0 ? "indeterminate" : false;
|
|
3166
|
+
const toggleAll = () => {
|
|
3167
|
+
if (allSelected) {
|
|
3168
|
+
setSelected([]);
|
|
3169
|
+
} else {
|
|
3170
|
+
setSelected(allKeys);
|
|
3171
|
+
}
|
|
3172
|
+
};
|
|
3173
|
+
const toggleRow = (key) => {
|
|
3174
|
+
const next = new Set(selectedSet);
|
|
3175
|
+
if (next.has(key)) {
|
|
3176
|
+
next.delete(key);
|
|
3177
|
+
} else {
|
|
3178
|
+
next.add(key);
|
|
3179
|
+
}
|
|
3180
|
+
setSelected([...next]);
|
|
3181
|
+
};
|
|
2608
3182
|
const rowCountText = rowCountLabel?.(sortedRows.length) ?? `${sortedRows.length} row${sortedRows.length === 1 ? "" : "s"}`;
|
|
2609
|
-
const
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
3183
|
+
const hasPager = paginated && !loading && sortedRows.length > 0;
|
|
3184
|
+
const hasFoot = (showRowCount || footer || hasPager) && (loading || sortedRows.length > 0);
|
|
3185
|
+
const skeletonCount = loadingRows ?? pageSize ?? 5;
|
|
3186
|
+
return /* @__PURE__ */ jsx43("div", { className: cn("aui-app-data-table", shellClass2, className), children: /* @__PURE__ */ jsx43("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs33("table", { className: tableClass, children: [
|
|
3187
|
+
caption ? /* @__PURE__ */ jsx43("caption", { className: "sr-only", children: caption }) : null,
|
|
3188
|
+
/* @__PURE__ */ jsx43("thead", { className: cn(stickyHeader && stickyHeadClass), children: /* @__PURE__ */ jsxs33("tr", { children: [
|
|
3189
|
+
selectable ? /* @__PURE__ */ jsx43("th", { scope: "col", className: cn(selectCellClass, headPad), children: /* @__PURE__ */ jsx43(
|
|
3190
|
+
Checkbox,
|
|
2617
3191
|
{
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
/* @__PURE__ */ jsx41("span", { className: "truncate", children: col.header }),
|
|
2623
|
-
/* @__PURE__ */ jsx41(SortIndicator, { active: Boolean(isSorted), direction: sort?.direction })
|
|
2624
|
-
]
|
|
3192
|
+
checked: headerCheckedState,
|
|
3193
|
+
onCheckedChange: toggleAll,
|
|
3194
|
+
"aria-label": selectAllAriaLabel,
|
|
3195
|
+
disabled: loading || allKeys.length === 0
|
|
2625
3196
|
}
|
|
2626
|
-
) :
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
"
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
col.id
|
|
2641
|
-
);
|
|
2642
|
-
}) }) }),
|
|
2643
|
-
/* @__PURE__ */ jsx41("tbody", { className: cn(!hasFoot && "[&_tr:last-child_td]:border-b-0"), children: sortedRows.length === 0 ? /* @__PURE__ */ jsx41("tr", { children: /* @__PURE__ */ jsx41("td", { colSpan: columns.length, className: emptyCellClass, children: /* @__PURE__ */ jsxs32("div", { className: "flex flex-col items-center gap-1", children: [
|
|
2644
|
-
/* @__PURE__ */ jsx41("p", { className: "font-medium text-foreground", children: emptyTitle }),
|
|
2645
|
-
emptyDescription ? /* @__PURE__ */ jsx41("p", { className: "max-w-sm text-muted-foreground", children: emptyDescription }) : null
|
|
2646
|
-
] }) }) }) : sortedRows.map((row) => /* @__PURE__ */ jsx41(
|
|
2647
|
-
"tr",
|
|
2648
|
-
{
|
|
2649
|
-
className: rowClass,
|
|
2650
|
-
"data-clickable": onRowClick ? "true" : void 0,
|
|
2651
|
-
onClick: onRowClick ? () => onRowClick(row) : void 0,
|
|
2652
|
-
onKeyDown: onRowClick ? (event) => {
|
|
2653
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
2654
|
-
event.preventDefault();
|
|
2655
|
-
onRowClick(row);
|
|
3197
|
+
) }) : null,
|
|
3198
|
+
columns.map((col) => {
|
|
3199
|
+
const isSorted = sort?.columnId === col.id;
|
|
3200
|
+
const ariaSort = col.sortable ? isSorted ? sort.direction === "asc" ? "ascending" : "descending" : "none" : void 0;
|
|
3201
|
+
const headerContent = col.sortable ? /* @__PURE__ */ jsxs33(
|
|
3202
|
+
"button",
|
|
3203
|
+
{
|
|
3204
|
+
type: "button",
|
|
3205
|
+
className: sortButtonClass,
|
|
3206
|
+
onClick: () => setSort(nextSort(sort, col.id)),
|
|
3207
|
+
children: [
|
|
3208
|
+
/* @__PURE__ */ jsx43("span", { className: "truncate", children: col.header }),
|
|
3209
|
+
/* @__PURE__ */ jsx43(SortIndicator, { active: Boolean(isSorted), direction: sort?.direction })
|
|
3210
|
+
]
|
|
2656
3211
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
children: columns.map((col) => /* @__PURE__ */ jsx41(
|
|
2661
|
-
"td",
|
|
3212
|
+
) : col.header;
|
|
3213
|
+
return /* @__PURE__ */ jsx43(
|
|
3214
|
+
"th",
|
|
2662
3215
|
{
|
|
3216
|
+
scope: "col",
|
|
3217
|
+
"aria-sort": ariaSort,
|
|
2663
3218
|
className: cn(
|
|
2664
|
-
|
|
2665
|
-
|
|
3219
|
+
headCellClass,
|
|
3220
|
+
headPad,
|
|
2666
3221
|
col.align && alignClass[col.align],
|
|
2667
|
-
col.
|
|
3222
|
+
col.headerClassName
|
|
2668
3223
|
),
|
|
2669
|
-
children:
|
|
3224
|
+
children: headerContent
|
|
2670
3225
|
},
|
|
2671
3226
|
col.id
|
|
2672
|
-
)
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
)
|
|
2676
|
-
|
|
3227
|
+
);
|
|
3228
|
+
})
|
|
3229
|
+
] }) }),
|
|
3230
|
+
/* @__PURE__ */ jsx43("tbody", { className: cn(!hasFoot && "[&_tr:last-child_td]:border-b-0"), children: loading ? Array.from({ length: skeletonCount }).map((_, rowIdx) => /* @__PURE__ */ jsxs33("tr", { className: rowClass, "aria-hidden": true, children: [
|
|
3231
|
+
selectable ? /* @__PURE__ */ jsx43("td", { className: cn(selectCellClass, cellPad), children: /* @__PURE__ */ jsx43(Skeleton, { className: "size-4 rounded-[4px]" }) }) : null,
|
|
3232
|
+
columns.map((col) => /* @__PURE__ */ jsx43(
|
|
3233
|
+
"td",
|
|
3234
|
+
{
|
|
3235
|
+
className: cn(
|
|
3236
|
+
bodyCellClass,
|
|
3237
|
+
cellPad,
|
|
3238
|
+
col.align && alignClass[col.align],
|
|
3239
|
+
col.className
|
|
3240
|
+
),
|
|
3241
|
+
children: /* @__PURE__ */ jsx43(Skeleton, { className: "h-4 w-[60%]" })
|
|
3242
|
+
},
|
|
3243
|
+
col.id
|
|
3244
|
+
))
|
|
3245
|
+
] }, `skeleton-${rowIdx}`)) : visibleRows.length === 0 ? /* @__PURE__ */ jsx43("tr", { children: /* @__PURE__ */ jsx43("td", { colSpan, className: emptyCellClass, children: /* @__PURE__ */ jsxs33("div", { className: "flex flex-col items-center gap-1", children: [
|
|
3246
|
+
/* @__PURE__ */ jsx43("p", { className: "font-medium text-foreground", children: emptyTitle }),
|
|
3247
|
+
emptyDescription ? /* @__PURE__ */ jsx43("p", { className: "max-w-sm text-muted-foreground", children: emptyDescription }) : null
|
|
3248
|
+
] }) }) }) : visibleRows.map((row) => {
|
|
3249
|
+
const key = getRowKey(row);
|
|
3250
|
+
const isSelected = selectedSet.has(key);
|
|
3251
|
+
return /* @__PURE__ */ jsxs33(
|
|
3252
|
+
"tr",
|
|
3253
|
+
{
|
|
3254
|
+
className: rowClass,
|
|
3255
|
+
"data-clickable": onRowClick ? "true" : void 0,
|
|
3256
|
+
"data-selected": isSelected ? "true" : void 0,
|
|
3257
|
+
onClick: onRowClick ? () => onRowClick(row) : void 0,
|
|
3258
|
+
onKeyDown: onRowClick ? (event) => {
|
|
3259
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
3260
|
+
event.preventDefault();
|
|
3261
|
+
onRowClick(row);
|
|
3262
|
+
}
|
|
3263
|
+
} : void 0,
|
|
3264
|
+
tabIndex: onRowClick ? 0 : void 0,
|
|
3265
|
+
role: onRowClick ? "button" : void 0,
|
|
3266
|
+
children: [
|
|
3267
|
+
selectable ? /* @__PURE__ */ jsx43(
|
|
3268
|
+
"td",
|
|
3269
|
+
{
|
|
3270
|
+
className: cn(selectCellClass, cellPad),
|
|
3271
|
+
onClick: (event) => event.stopPropagation(),
|
|
3272
|
+
children: /* @__PURE__ */ jsx43(
|
|
3273
|
+
Checkbox,
|
|
3274
|
+
{
|
|
3275
|
+
checked: isSelected,
|
|
3276
|
+
onCheckedChange: () => toggleRow(key),
|
|
3277
|
+
"aria-label": `Select row`
|
|
3278
|
+
}
|
|
3279
|
+
)
|
|
3280
|
+
}
|
|
3281
|
+
) : null,
|
|
3282
|
+
columns.map((col) => /* @__PURE__ */ jsx43(
|
|
3283
|
+
"td",
|
|
3284
|
+
{
|
|
3285
|
+
className: cn(
|
|
3286
|
+
bodyCellClass,
|
|
3287
|
+
cellPad,
|
|
3288
|
+
col.align && alignClass[col.align],
|
|
3289
|
+
col.className
|
|
3290
|
+
),
|
|
3291
|
+
children: col.cell(row)
|
|
3292
|
+
},
|
|
3293
|
+
col.id
|
|
3294
|
+
))
|
|
3295
|
+
]
|
|
3296
|
+
},
|
|
3297
|
+
key
|
|
3298
|
+
);
|
|
3299
|
+
}) }),
|
|
3300
|
+
hasFoot ? /* @__PURE__ */ jsx43("tfoot", { children: /* @__PURE__ */ jsx43("tr", { children: /* @__PURE__ */ jsx43("td", { colSpan, className: footCellClass, children: /* @__PURE__ */ jsxs33(
|
|
2677
3301
|
"div",
|
|
2678
3302
|
{
|
|
2679
3303
|
className: cn(
|
|
2680
3304
|
footInnerClass,
|
|
2681
|
-
showRowCount
|
|
3305
|
+
(showRowCount || footer || hasPager) && "justify-between"
|
|
2682
3306
|
),
|
|
2683
3307
|
children: [
|
|
2684
|
-
|
|
2685
|
-
|
|
3308
|
+
/* @__PURE__ */ jsxs33("div", { className: footInnerClass, children: [
|
|
3309
|
+
showRowCount ? /* @__PURE__ */ jsxs33("span", { children: [
|
|
3310
|
+
rowCountText,
|
|
3311
|
+
selectable && selectedSet.size > 0 ? ` \xB7 ${selectedSet.size} selected` : null
|
|
3312
|
+
] }) : selectable && selectedSet.size > 0 ? /* @__PURE__ */ jsxs33("span", { children: [
|
|
3313
|
+
selectedSet.size,
|
|
3314
|
+
" selected"
|
|
3315
|
+
] }) : null,
|
|
3316
|
+
footer
|
|
3317
|
+
] }),
|
|
3318
|
+
hasPager ? /* @__PURE__ */ jsxs33("div", { className: "flex items-center gap-2", children: [
|
|
3319
|
+
/* @__PURE__ */ jsxs33("span", { className: "tabular-nums", children: [
|
|
3320
|
+
pageIndex * pageSize + 1,
|
|
3321
|
+
"\u2013",
|
|
3322
|
+
Math.min(
|
|
3323
|
+
(pageIndex + 1) * pageSize,
|
|
3324
|
+
sortedRows.length
|
|
3325
|
+
),
|
|
3326
|
+
" ",
|
|
3327
|
+
"of ",
|
|
3328
|
+
sortedRows.length
|
|
3329
|
+
] }),
|
|
3330
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex items-center gap-1", children: [
|
|
3331
|
+
/* @__PURE__ */ jsx43(
|
|
3332
|
+
"button",
|
|
3333
|
+
{
|
|
3334
|
+
type: "button",
|
|
3335
|
+
className: pagerButtonClass,
|
|
3336
|
+
onClick: () => setPage(pageIndex - 1),
|
|
3337
|
+
disabled: pageIndex <= 0,
|
|
3338
|
+
"aria-label": "Previous page",
|
|
3339
|
+
children: /* @__PURE__ */ jsx43(ChevronLeftIcon, { className: "size-4", "aria-hidden": true })
|
|
3340
|
+
}
|
|
3341
|
+
),
|
|
3342
|
+
/* @__PURE__ */ jsx43(
|
|
3343
|
+
"button",
|
|
3344
|
+
{
|
|
3345
|
+
type: "button",
|
|
3346
|
+
className: pagerButtonClass,
|
|
3347
|
+
onClick: () => setPage(pageIndex + 1),
|
|
3348
|
+
disabled: pageIndex >= pageCount - 1,
|
|
3349
|
+
"aria-label": "Next page",
|
|
3350
|
+
children: /* @__PURE__ */ jsx43(ChevronRightIcon, { className: "size-4", "aria-hidden": true })
|
|
3351
|
+
}
|
|
3352
|
+
)
|
|
3353
|
+
] })
|
|
3354
|
+
] }) : null
|
|
2686
3355
|
]
|
|
2687
3356
|
}
|
|
2688
3357
|
) }) }) }) : null
|
|
@@ -2691,7 +3360,7 @@ function DataTable({
|
|
|
2691
3360
|
|
|
2692
3361
|
// src/app/data/ChartPanel.tsx
|
|
2693
3362
|
import { useId as useId4 } from "react";
|
|
2694
|
-
import { jsx as
|
|
3363
|
+
import { jsx as jsx44, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
2695
3364
|
var ChartPanel = ({
|
|
2696
3365
|
title,
|
|
2697
3366
|
description,
|
|
@@ -2699,19 +3368,20 @@ var ChartPanel = ({
|
|
|
2699
3368
|
children,
|
|
2700
3369
|
actions,
|
|
2701
3370
|
height = 300,
|
|
3371
|
+
loading = false,
|
|
2702
3372
|
className
|
|
2703
3373
|
}) => {
|
|
2704
3374
|
const titleId = useId4();
|
|
2705
3375
|
const resolvedTitle = title ?? artifact?.title;
|
|
2706
3376
|
const hasHeader = Boolean(resolvedTitle || description || actions);
|
|
2707
|
-
const body = children ?? (artifact ? /* @__PURE__ */
|
|
2708
|
-
return /* @__PURE__ */
|
|
3377
|
+
const body = loading ? /* @__PURE__ */ jsx44(Skeleton, { className: "w-full rounded-lg", style: { height }, "aria-hidden": true }) : children ?? (artifact ? /* @__PURE__ */ jsx44(ChartArtifactView, { artifact, embedded: true, height }) : null);
|
|
3378
|
+
return /* @__PURE__ */ jsxs34(
|
|
2709
3379
|
"section",
|
|
2710
3380
|
{
|
|
2711
3381
|
className: cn(metricCardShellClass, "aui-app-chart-panel", className),
|
|
2712
3382
|
"aria-labelledby": resolvedTitle ? titleId : void 0,
|
|
2713
3383
|
children: [
|
|
2714
|
-
/* @__PURE__ */
|
|
3384
|
+
/* @__PURE__ */ jsx44(
|
|
2715
3385
|
MetricCardHeader,
|
|
2716
3386
|
{
|
|
2717
3387
|
title: resolvedTitle,
|
|
@@ -2720,14 +3390,14 @@ var ChartPanel = ({
|
|
|
2720
3390
|
actions
|
|
2721
3391
|
}
|
|
2722
3392
|
),
|
|
2723
|
-
/* @__PURE__ */
|
|
3393
|
+
/* @__PURE__ */ jsx44(
|
|
2724
3394
|
"div",
|
|
2725
3395
|
{
|
|
2726
3396
|
className: cn(
|
|
2727
3397
|
"relative min-h-0 w-full",
|
|
2728
3398
|
hasHeader ? metricChartPlotRegionClass : "pt-2 pb-3"
|
|
2729
3399
|
),
|
|
2730
|
-
children: body ?? /* @__PURE__ */
|
|
3400
|
+
children: body ?? /* @__PURE__ */ jsx44(
|
|
2731
3401
|
"div",
|
|
2732
3402
|
{
|
|
2733
3403
|
className: "flex items-center justify-center text-sm font-normal text-muted-foreground",
|
|
@@ -2745,7 +3415,7 @@ var ChartPanel = ({
|
|
|
2745
3415
|
|
|
2746
3416
|
// src/app/data/MetricRow.tsx
|
|
2747
3417
|
import { useId as useId5, useState as useState5 } from "react";
|
|
2748
|
-
import { jsx as
|
|
3418
|
+
import { jsx as jsx45, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
2749
3419
|
var MetricRow = ({
|
|
2750
3420
|
title,
|
|
2751
3421
|
description,
|
|
@@ -2755,6 +3425,7 @@ var MetricRow = ({
|
|
|
2755
3425
|
defaultActiveMetricId,
|
|
2756
3426
|
onMetricChange,
|
|
2757
3427
|
metricsAriaLabel = "Metrics",
|
|
3428
|
+
loading = false,
|
|
2758
3429
|
className
|
|
2759
3430
|
}) => {
|
|
2760
3431
|
const titleId = useId5();
|
|
@@ -2767,13 +3438,13 @@ var MetricRow = ({
|
|
|
2767
3438
|
if (activeMetricId == null) setInternalId(id);
|
|
2768
3439
|
onMetricChange?.(id);
|
|
2769
3440
|
};
|
|
2770
|
-
return /* @__PURE__ */
|
|
3441
|
+
return /* @__PURE__ */ jsxs35(
|
|
2771
3442
|
"section",
|
|
2772
3443
|
{
|
|
2773
3444
|
className: cn(metricCardShellClass, className),
|
|
2774
3445
|
"aria-labelledby": title ? titleId : void 0,
|
|
2775
3446
|
children: [
|
|
2776
|
-
/* @__PURE__ */
|
|
3447
|
+
/* @__PURE__ */ jsx45(
|
|
2777
3448
|
MetricCardHeader,
|
|
2778
3449
|
{
|
|
2779
3450
|
title,
|
|
@@ -2782,17 +3453,29 @@ var MetricRow = ({
|
|
|
2782
3453
|
actions
|
|
2783
3454
|
}
|
|
2784
3455
|
),
|
|
2785
|
-
/* @__PURE__ */
|
|
3456
|
+
/* @__PURE__ */ jsx45(
|
|
2786
3457
|
"div",
|
|
2787
3458
|
{
|
|
2788
3459
|
role: selectable ? "group" : void 0,
|
|
2789
3460
|
"aria-label": selectable ? metricsAriaLabel : void 0,
|
|
3461
|
+
"aria-busy": loading || void 0,
|
|
2790
3462
|
className: cn(
|
|
2791
3463
|
metricTilesRowClass,
|
|
2792
|
-
metricTilesGridColsClass(metrics.length),
|
|
3464
|
+
metricTilesGridColsClass(loading ? metrics.length || 4 : metrics.length),
|
|
2793
3465
|
(title || description || actions) && "mt-3"
|
|
2794
3466
|
),
|
|
2795
|
-
children: metrics.map((
|
|
3467
|
+
children: loading ? Array.from({ length: metrics.length || 4 }).map((_, index) => /* @__PURE__ */ jsxs35(
|
|
3468
|
+
"div",
|
|
3469
|
+
{
|
|
3470
|
+
className: "flex min-w-0 flex-1 flex-col gap-2 px-4 py-3",
|
|
3471
|
+
"aria-hidden": true,
|
|
3472
|
+
children: [
|
|
3473
|
+
/* @__PURE__ */ jsx45(Skeleton, { className: "h-3 w-20" }),
|
|
3474
|
+
/* @__PURE__ */ jsx45(Skeleton, { className: "h-7 w-24" })
|
|
3475
|
+
]
|
|
3476
|
+
},
|
|
3477
|
+
`skeleton-${index}`
|
|
3478
|
+
)) : metrics.map((m, index) => /* @__PURE__ */ jsx45(
|
|
2796
3479
|
MetricTile,
|
|
2797
3480
|
{
|
|
2798
3481
|
label: m.label,
|
|
@@ -2815,7 +3498,7 @@ var MetricRow = ({
|
|
|
2815
3498
|
|
|
2816
3499
|
// src/app/data/MetricChartCard.tsx
|
|
2817
3500
|
import { useId as useId6, useState as useState6 } from "react";
|
|
2818
|
-
import { jsx as
|
|
3501
|
+
import { jsx as jsx46, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
2819
3502
|
var MetricChartCard = ({
|
|
2820
3503
|
title,
|
|
2821
3504
|
description,
|
|
@@ -2831,6 +3514,7 @@ var MetricChartCard = ({
|
|
|
2831
3514
|
formatValue,
|
|
2832
3515
|
emptyLabel = "No data yet",
|
|
2833
3516
|
metricsAriaLabel = "Metrics",
|
|
3517
|
+
loading = false,
|
|
2834
3518
|
className
|
|
2835
3519
|
}) => {
|
|
2836
3520
|
const titleId = useId6();
|
|
@@ -2845,13 +3529,13 @@ var MetricChartCard = ({
|
|
|
2845
3529
|
};
|
|
2846
3530
|
const hasHeader = Boolean(title || description || actions);
|
|
2847
3531
|
const chartAriaLabel = typeof active?.label === "string" ? `${active.label} over time` : "Metric chart";
|
|
2848
|
-
return /* @__PURE__ */
|
|
3532
|
+
return /* @__PURE__ */ jsxs36(
|
|
2849
3533
|
"section",
|
|
2850
3534
|
{
|
|
2851
3535
|
className: cn(metricCardShellClass, className),
|
|
2852
3536
|
"aria-labelledby": title ? titleId : void 0,
|
|
2853
3537
|
children: [
|
|
2854
|
-
/* @__PURE__ */
|
|
3538
|
+
/* @__PURE__ */ jsx46(
|
|
2855
3539
|
MetricCardHeader,
|
|
2856
3540
|
{
|
|
2857
3541
|
title,
|
|
@@ -2860,17 +3544,29 @@ var MetricChartCard = ({
|
|
|
2860
3544
|
actions
|
|
2861
3545
|
}
|
|
2862
3546
|
),
|
|
2863
|
-
/* @__PURE__ */
|
|
3547
|
+
/* @__PURE__ */ jsx46(
|
|
2864
3548
|
"div",
|
|
2865
3549
|
{
|
|
2866
3550
|
role: "group",
|
|
2867
3551
|
"aria-label": metricsAriaLabel,
|
|
3552
|
+
"aria-busy": loading || void 0,
|
|
2868
3553
|
className: cn(
|
|
2869
3554
|
metricTilesRowClass,
|
|
2870
|
-
metricTilesGridColsClass(metrics.length),
|
|
3555
|
+
metricTilesGridColsClass(loading ? metrics.length || 4 : metrics.length),
|
|
2871
3556
|
hasHeader && "mt-3"
|
|
2872
3557
|
),
|
|
2873
|
-
children: metrics.map((
|
|
3558
|
+
children: loading ? Array.from({ length: metrics.length || 4 }).map((_, index) => /* @__PURE__ */ jsxs36(
|
|
3559
|
+
"div",
|
|
3560
|
+
{
|
|
3561
|
+
className: "flex min-w-0 flex-1 flex-col gap-2 px-4 py-3",
|
|
3562
|
+
"aria-hidden": true,
|
|
3563
|
+
children: [
|
|
3564
|
+
/* @__PURE__ */ jsx46(Skeleton, { className: "h-3 w-20" }),
|
|
3565
|
+
/* @__PURE__ */ jsx46(Skeleton, { className: "h-7 w-24" })
|
|
3566
|
+
]
|
|
3567
|
+
},
|
|
3568
|
+
`skeleton-${index}`
|
|
3569
|
+
)) : metrics.map((m, index) => /* @__PURE__ */ jsx46(
|
|
2874
3570
|
MetricTile,
|
|
2875
3571
|
{
|
|
2876
3572
|
label: m.label,
|
|
@@ -2886,7 +3582,14 @@ var MetricChartCard = ({
|
|
|
2886
3582
|
))
|
|
2887
3583
|
}
|
|
2888
3584
|
),
|
|
2889
|
-
/* @__PURE__ */
|
|
3585
|
+
/* @__PURE__ */ jsx46("div", { className: metricChartRegionClass, "aria-live": "polite", "aria-atomic": "true", children: loading ? /* @__PURE__ */ jsx46(
|
|
3586
|
+
Skeleton,
|
|
3587
|
+
{
|
|
3588
|
+
className: "w-full rounded-lg",
|
|
3589
|
+
style: { height },
|
|
3590
|
+
"aria-hidden": true
|
|
3591
|
+
}
|
|
3592
|
+
) : active?.data && active.data.length > 0 ? /* @__PURE__ */ jsx46(
|
|
2890
3593
|
LineAreaChart,
|
|
2891
3594
|
{
|
|
2892
3595
|
data: active.data,
|
|
@@ -2906,7 +3609,7 @@ var MetricChartCard = ({
|
|
|
2906
3609
|
ariaLabel: chartAriaLabel
|
|
2907
3610
|
},
|
|
2908
3611
|
active.id
|
|
2909
|
-
) : /* @__PURE__ */
|
|
3612
|
+
) : /* @__PURE__ */ jsx46(
|
|
2910
3613
|
"div",
|
|
2911
3614
|
{
|
|
2912
3615
|
className: "flex w-full items-center justify-center text-sm font-normal text-muted-foreground",
|
|
@@ -2920,9 +3623,99 @@ var MetricChartCard = ({
|
|
|
2920
3623
|
);
|
|
2921
3624
|
};
|
|
2922
3625
|
|
|
3626
|
+
// src/hooks/use-live-query.ts
|
|
3627
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useRef, useState as useState7 } from "react";
|
|
3628
|
+
function useInterval(callback, delayMs) {
|
|
3629
|
+
const saved = useRef(callback);
|
|
3630
|
+
useEffect3(() => {
|
|
3631
|
+
saved.current = callback;
|
|
3632
|
+
}, [callback]);
|
|
3633
|
+
useEffect3(() => {
|
|
3634
|
+
if (delayMs === null) return;
|
|
3635
|
+
const id = setInterval(() => saved.current(), delayMs);
|
|
3636
|
+
return () => clearInterval(id);
|
|
3637
|
+
}, [delayMs]);
|
|
3638
|
+
}
|
|
3639
|
+
function useLiveQuery(fetcher, options = {}) {
|
|
3640
|
+
const {
|
|
3641
|
+
intervalMs = null,
|
|
3642
|
+
enabled = true,
|
|
3643
|
+
immediate = true,
|
|
3644
|
+
refetchOnFocus = true
|
|
3645
|
+
} = options;
|
|
3646
|
+
const [data, setData] = useState7(void 0);
|
|
3647
|
+
const [error, setError] = useState7(void 0);
|
|
3648
|
+
const [loading, setLoading] = useState7(enabled);
|
|
3649
|
+
const [refreshing, setRefreshing] = useState7(false);
|
|
3650
|
+
const [lastUpdated, setLastUpdated] = useState7(null);
|
|
3651
|
+
const fetcherRef = useRef(fetcher);
|
|
3652
|
+
useEffect3(() => {
|
|
3653
|
+
fetcherRef.current = fetcher;
|
|
3654
|
+
}, [fetcher]);
|
|
3655
|
+
const mounted = useRef(true);
|
|
3656
|
+
const requestId = useRef(0);
|
|
3657
|
+
const hasData = useRef(false);
|
|
3658
|
+
useEffect3(() => {
|
|
3659
|
+
mounted.current = true;
|
|
3660
|
+
return () => {
|
|
3661
|
+
mounted.current = false;
|
|
3662
|
+
};
|
|
3663
|
+
}, []);
|
|
3664
|
+
const run = useCallback2(() => {
|
|
3665
|
+
const id = ++requestId.current;
|
|
3666
|
+
if (hasData.current) {
|
|
3667
|
+
setRefreshing(true);
|
|
3668
|
+
} else {
|
|
3669
|
+
setLoading(true);
|
|
3670
|
+
}
|
|
3671
|
+
fetcherRef.current().then((result) => {
|
|
3672
|
+
if (!mounted.current || id !== requestId.current) return;
|
|
3673
|
+
setData(result);
|
|
3674
|
+
setError(void 0);
|
|
3675
|
+
setLastUpdated(Date.now());
|
|
3676
|
+
hasData.current = true;
|
|
3677
|
+
}).catch((err) => {
|
|
3678
|
+
if (!mounted.current || id !== requestId.current) return;
|
|
3679
|
+
setError(err);
|
|
3680
|
+
}).finally(() => {
|
|
3681
|
+
if (!mounted.current || id !== requestId.current) return;
|
|
3682
|
+
setLoading(false);
|
|
3683
|
+
setRefreshing(false);
|
|
3684
|
+
});
|
|
3685
|
+
}, []);
|
|
3686
|
+
const refetch = useCallback2(() => {
|
|
3687
|
+
if (!enabled) return;
|
|
3688
|
+
run();
|
|
3689
|
+
}, [enabled, run]);
|
|
3690
|
+
useEffect3(() => {
|
|
3691
|
+
if (!enabled) return;
|
|
3692
|
+
if (immediate) run();
|
|
3693
|
+
}, [enabled, immediate, run]);
|
|
3694
|
+
useEffect3(() => {
|
|
3695
|
+
if (!enabled || intervalMs === null) return;
|
|
3696
|
+
const tick = () => {
|
|
3697
|
+
if (refetchOnFocus && typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
3698
|
+
return;
|
|
3699
|
+
}
|
|
3700
|
+
run();
|
|
3701
|
+
};
|
|
3702
|
+
const handle = setInterval(tick, intervalMs);
|
|
3703
|
+
return () => clearInterval(handle);
|
|
3704
|
+
}, [enabled, intervalMs, refetchOnFocus, run]);
|
|
3705
|
+
useEffect3(() => {
|
|
3706
|
+
if (!enabled || !refetchOnFocus || typeof document === "undefined") return;
|
|
3707
|
+
const onVisible = () => {
|
|
3708
|
+
if (document.visibilityState === "visible") run();
|
|
3709
|
+
};
|
|
3710
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
3711
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
3712
|
+
}, [enabled, refetchOnFocus, run]);
|
|
3713
|
+
return { data, error, loading, refreshing, lastUpdated, refetch };
|
|
3714
|
+
}
|
|
3715
|
+
|
|
2923
3716
|
// src/charts/sparkline.tsx
|
|
2924
3717
|
import { useId as useId7 } from "react";
|
|
2925
|
-
import { Fragment as Fragment6, jsx as
|
|
3718
|
+
import { Fragment as Fragment6, jsx as jsx47, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
2926
3719
|
var Sparkline = ({
|
|
2927
3720
|
data,
|
|
2928
3721
|
dataKey = "value",
|
|
@@ -2937,7 +3730,7 @@ var Sparkline = ({
|
|
|
2937
3730
|
const uid = useId7();
|
|
2938
3731
|
const values = data.map((d) => typeof d === "number" ? d : toNum(d[dataKey]));
|
|
2939
3732
|
if (values.length === 0) {
|
|
2940
|
-
return /* @__PURE__ */
|
|
3733
|
+
return /* @__PURE__ */ jsx47("span", { className: cn("inline-block", className), style: { width, height } });
|
|
2941
3734
|
}
|
|
2942
3735
|
const pad = strokeWidth + 1;
|
|
2943
3736
|
const min = Math.min(...values);
|
|
@@ -2949,7 +3742,7 @@ var Sparkline = ({
|
|
|
2949
3742
|
x: pad + (values.length > 1 ? i / (values.length - 1) * innerW : innerW / 2),
|
|
2950
3743
|
y: pad + innerH - (v - min) / range * innerH
|
|
2951
3744
|
}));
|
|
2952
|
-
return /* @__PURE__ */
|
|
3745
|
+
return /* @__PURE__ */ jsxs37(
|
|
2953
3746
|
"svg",
|
|
2954
3747
|
{
|
|
2955
3748
|
width,
|
|
@@ -2960,14 +3753,14 @@ var Sparkline = ({
|
|
|
2960
3753
|
"aria-label": ariaLabel,
|
|
2961
3754
|
preserveAspectRatio: "none",
|
|
2962
3755
|
children: [
|
|
2963
|
-
area && /* @__PURE__ */
|
|
2964
|
-
/* @__PURE__ */
|
|
2965
|
-
/* @__PURE__ */
|
|
2966
|
-
/* @__PURE__ */
|
|
3756
|
+
area && /* @__PURE__ */ jsxs37(Fragment6, { children: [
|
|
3757
|
+
/* @__PURE__ */ jsx47("defs", { children: /* @__PURE__ */ jsxs37("linearGradient", { id: `${uid}-spark`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
|
|
3758
|
+
/* @__PURE__ */ jsx47("stop", { offset: "0%", style: { stopColor: color, stopOpacity: 0.25 } }),
|
|
3759
|
+
/* @__PURE__ */ jsx47("stop", { offset: "100%", style: { stopColor: color, stopOpacity: 0 } })
|
|
2967
3760
|
] }) }),
|
|
2968
|
-
/* @__PURE__ */
|
|
3761
|
+
/* @__PURE__ */ jsx47("path", { d: monotoneAreaPath(points, height - pad), fill: `url(#${uid}-spark)` })
|
|
2969
3762
|
] }),
|
|
2970
|
-
/* @__PURE__ */
|
|
3763
|
+
/* @__PURE__ */ jsx47(
|
|
2971
3764
|
"path",
|
|
2972
3765
|
{
|
|
2973
3766
|
d: monotoneLinePath(points),
|
|
@@ -2984,7 +3777,17 @@ var Sparkline = ({
|
|
|
2984
3777
|
};
|
|
2985
3778
|
|
|
2986
3779
|
export {
|
|
3780
|
+
SEMANTIC_COLOR_TOKENS,
|
|
3781
|
+
RESERVED_GRADIENT_TOKENS,
|
|
3782
|
+
TAILWIND_PALETTE_COLORS,
|
|
3783
|
+
COLOR_UTILITY_PREFIXES,
|
|
3784
|
+
SLOP_BUDGETS,
|
|
3785
|
+
HOUSE_RULES,
|
|
2987
3786
|
APP_KIT_AGENT_INSTRUCTIONS,
|
|
3787
|
+
lintGeneratedUi,
|
|
3788
|
+
formatLintReport,
|
|
3789
|
+
reviewGeneratedUi,
|
|
3790
|
+
UI_REVIEW_AGENT_INSTRUCTIONS,
|
|
2988
3791
|
createTimbalTheme,
|
|
2989
3792
|
themeToCss,
|
|
2990
3793
|
ensureThemeFontLink,
|
|
@@ -3006,9 +3809,11 @@ export {
|
|
|
3006
3809
|
appFilterBarClass,
|
|
3007
3810
|
appSearchInputClass,
|
|
3008
3811
|
useAppShellChat,
|
|
3812
|
+
useAppShellNav,
|
|
3009
3813
|
AppShell,
|
|
3010
3814
|
AppShellTopbar,
|
|
3011
3815
|
AppShellChatTrigger,
|
|
3816
|
+
AppShellSidebarTrigger,
|
|
3012
3817
|
PageHeader,
|
|
3013
3818
|
Page,
|
|
3014
3819
|
Section,
|
|
@@ -3048,9 +3853,12 @@ export {
|
|
|
3048
3853
|
SearchInput,
|
|
3049
3854
|
FormSection,
|
|
3050
3855
|
FilterBar,
|
|
3856
|
+
FilterField,
|
|
3051
3857
|
DataTable,
|
|
3052
3858
|
ChartPanel,
|
|
3053
3859
|
MetricRow,
|
|
3054
3860
|
MetricChartCard,
|
|
3861
|
+
useInterval,
|
|
3862
|
+
useLiveQuery,
|
|
3055
3863
|
Sparkline
|
|
3056
3864
|
};
|