@timbal-ai/timbal-react 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +35 -5
  3. package/dist/app.cjs +2291 -741
  4. package/dist/app.d.cts +5 -2
  5. package/dist/app.d.ts +5 -2
  6. package/dist/app.esm.js +58 -5
  7. package/dist/button-CIKzUrJI.d.cts +18 -0
  8. package/dist/button-CIKzUrJI.d.ts +18 -0
  9. package/dist/chart-artifact-C2m891nx.d.cts +756 -0
  10. package/dist/chart-artifact-CqqhdSR9.d.ts +756 -0
  11. package/dist/{chat-CWtQWDtJ.d.cts → chat-Bed4FQSl.d.cts} +10 -0
  12. package/dist/{chat-CWtQWDtJ.d.ts → chat-Bed4FQSl.d.ts} +10 -0
  13. package/dist/chat.cjs +876 -562
  14. package/dist/chat.d.cts +3 -3
  15. package/dist/chat.d.ts +3 -3
  16. package/dist/chat.esm.js +3 -3
  17. package/dist/{chunk-4TCJQSIX.esm.js → chunk-2XZ3S4OP.esm.js} +14 -3
  18. package/dist/{chunk-WLTW56MC.esm.js → chunk-3WCG6ZRL.esm.js} +2 -2
  19. package/dist/chunk-7U2N6XZA.esm.js +2296 -0
  20. package/dist/{chunk-OVHR7J3J.esm.js → chunk-EQC5JEDZ.esm.js} +38 -11
  21. package/dist/{chunk-YJQLLFKP.esm.js → chunk-RY3LB3JN.esm.js} +817 -507
  22. package/dist/{chunk-IYENDIRY.esm.js → chunk-TDIJHV4I.esm.js} +1 -1
  23. package/dist/{chunk-OFHLFNJH.esm.js → chunk-Z27GBSOT.esm.js} +3 -1
  24. package/dist/index.cjs +2596 -1019
  25. package/dist/index.d.cts +8 -7
  26. package/dist/index.d.ts +8 -7
  27. package/dist/index.esm.js +57 -7
  28. package/dist/{layout-CQWngNQ7.d.ts → layout-BTJyU8wd.d.ts} +1 -1
  29. package/dist/{layout-B9VayJhZ.d.cts → layout-C2G-FcER.d.cts} +1 -1
  30. package/dist/studio.cjs +1131 -788
  31. package/dist/studio.d.cts +2 -2
  32. package/dist/studio.d.ts +2 -2
  33. package/dist/studio.esm.js +6 -6
  34. package/dist/styles.css +8 -4
  35. package/dist/{timbal-v2-button-F4-z7m33.d.ts → timbal-v2-button-CNfdwGq4.d.cts} +1 -1
  36. package/dist/{timbal-v2-button-F4-z7m33.d.cts → timbal-v2-button-CNfdwGq4.d.ts} +1 -1
  37. package/dist/ui.cjs +12 -3
  38. package/dist/ui.d.cts +5 -16
  39. package/dist/ui.d.ts +5 -16
  40. package/dist/ui.esm.js +2 -2
  41. package/dist/{welcome--80i_O0p.d.cts → welcome-COOb05a5.d.cts} +5 -4
  42. package/dist/{welcome-BOizSp5h.d.ts → welcome-DE08m9ca.d.ts} +5 -4
  43. package/package.json +2 -1
  44. package/vite/local-dev.mjs +45 -3
  45. package/dist/chart-artifact-C71dk4xI.d.ts +0 -329
  46. package/dist/chart-artifact-CPEpOmtV.d.cts +0 -329
  47. package/dist/chunk-M4V6Q6XO.esm.js +0 -1082
@@ -0,0 +1,2296 @@
1
+ import {
2
+ SIDEBAR_INSET_PX_EXPANDED,
3
+ ShellInsetProvider,
4
+ studioChromeShellStyle,
5
+ studioSidebarWidthTransition
6
+ } from "./chunk-Z27GBSOT.esm.js";
7
+ import {
8
+ ChartArtifactView,
9
+ LineAreaChart,
10
+ Thread,
11
+ TimbalRuntimeProvider,
12
+ monotoneAreaPath,
13
+ monotoneLinePath,
14
+ studioIntegrationCardClass,
15
+ studioSearchChromeClass,
16
+ studioSecondaryChromeClass,
17
+ studioTopbarPillHeightClass,
18
+ toNum
19
+ } from "./chunk-RY3LB3JN.esm.js";
20
+ import {
21
+ PillSegmentedTabs
22
+ } from "./chunk-TDIJHV4I.esm.js";
23
+ import {
24
+ Button,
25
+ Dialog,
26
+ DialogContent,
27
+ DialogTitle,
28
+ TIMBAL_V2_ELEVATED_SURFACE,
29
+ TIMBAL_V2_LOGO_TILE,
30
+ TIMBAL_V2_SWITCH_THUMB,
31
+ TIMBAL_V2_SWITCH_TRACK_OFF,
32
+ TimbalV2Button,
33
+ cn
34
+ } from "./chunk-2XZ3S4OP.esm.js";
35
+
36
+ // src/app/agent-instructions.ts
37
+ var APP_KIT_AGENT_INSTRUCTIONS = `
38
+ ## App kit (@timbal-ai/timbal-react/app)
39
+
40
+ Build **dashboard and operations UIs** with React components. Import from \`@timbal-ai/timbal-react/app\` (or the main package entry if your app already uses it).
41
+
42
+ ### Creative freedom (read this first)
43
+
44
+ You are **not** required to copy any example layout, page title, section order, or visual theme.
45
+
46
+ - **Do** invent layouts that fit the user's domain (CRM, inventory, billing, internal tools, etc.).
47
+ - **Do** pick only the components you need; skip shell, sidebar, or copilot when the task does not need them.
48
+ - **Do** vary density, grid columns, navigation patterns, and copy \u2014 as long as you follow the **design guidelines** below.
49
+ - **Do not** treat \`examples/app-kit/reference/\` as a template to clone (no default "Operations" dashboard with sidebar + three KPI tiles + SubNav + table unless the user asked for that).
50
+ - **Do** use \`examples/app-kit/recipes/\` as **short grammar examples** (one concern per file), not as a full app blueprint.
51
+
52
+ When in doubt: compose from the **component menu** + **guidelines**, then adapt creatively to the request.
53
+
54
+ ### Module layout (source folders)
55
+
56
+ Presentational groups \u2014 import from the package root, not from these paths:
57
+
58
+ | Folder | Components |
59
+ |--------|------------|
60
+ | \`data/\` | \`MetricRow\`, \`MetricChartCard\`, \`MetricTile\`, \`DataTable\`, \`FilterBar\`, \`ChartPanel\` |
61
+ | \`integrations/\` | \`IntegrationCard\`, \`ConnectionRow\`, \`ConnectionRowList\`, \`IntegrationsEmptyState\`, \`PlanBadge\` |
62
+ | \`settings/\` | \`SettingsSection\`, \`FieldRow\`, \`DangerZone\`, \`FloatingUnsavedChangesBar\` |
63
+ | \`surfaces/\` | \`StatTile\`, \`InfoCard\`, \`ResourceCard\`, \`DescriptionList\`, \`ExpandableSection\`, \`StatusDot\`, \`StatusBadge\`, \`EmptyState\` |
64
+ | \`layout/\` | \`AppShell\`, \`Page\`, \`Section\` |
65
+ | \`charts\` (re-exported) | \`LineAreaChart\`, \`Sparkline\`, \`CHART_PALETTE\` |
66
+
67
+ Also re-exported: \`Button\`, \`TimbalChat\`, \`ChartArtifactView\`, \`APP_KIT_AGENT_INSTRUCTIONS\`.
68
+
69
+ ### Design guidelines (required)
70
+
71
+ | Area | Rule |
72
+ |------|------|
73
+ | **Copilot** | Use \`AppCopilotProvider\` for page context (\`useAppCopilotContext\`). Copilot is a **floating overlay** via \`AppShell\` \`chat={<AppChatPanel />}\` \u2014 not a sidebar column that shrinks main content. |
74
+ | **Chat panel** | \`AppChatPanel\` only; \`Thread\` uses \`variant="panel"\` internally. Dismiss with **X**; trigger is a **text-only** pill (e.g. "Assistant") \u2014 **no** MessageSquare or chat icons on the shell trigger. |
75
+ | **Context** | Do not show raw JSON context in the panel header; keep context in \`AppCopilotProvider\` only. |
76
+ | **Theming** | Use semantic Tailwind tokens (\`bg-background\`, \`text-foreground\`, \`border-border\`, \`bg-elevated-from\`, etc.) from the host app's \`styles.css\`. Optional: \`import "@timbal-ai/timbal-react/styles.css"\`. |
77
+ | **Layout chrome** | \`Page\` \u2192 \`Section\` for main content hierarchy. \`AppShellTopbar\` for global actions (auth, theme). |
78
+ | **Data** | Prefer \`DataTable\` with typed \`columns\` / \`rows\` / \`getRowKey\`; use \`ChartPanel\` with \`ChartArtifact\` for charts. |
79
+ | **Modals** | Use \`AppConfirmDialog\` for destructive/export confirmations. |
80
+ | **Metrics** | Overview KPIs \u2192 \`MetricRow\` or \`MetricChartCard\` (not four separate heavy cards). Values use **normal** font weight, not bold. |
81
+ | **Integrations** | Catalog \u2192 \`IntegrationCard\` grid; connected list \u2192 \`ConnectionRow\` inside \`ConnectionRowList\`. Footer CTAs: \`Button variant="secondary"\`. |
82
+ | **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). |
83
+
84
+ ### Accessibility (required)
85
+
86
+ | Area | Rule |
87
+ |------|------|
88
+ | **Headings** | Use \`Page\` / \`Section\` titles for hierarchy. Card titles inside premade components are already \`h3\`/\`h4\`. |
89
+ | **Selectable metrics** | \`MetricChartCard\` / \`MetricRow\` tiles are buttons with \`aria-pressed\`. Pass \`metricsAriaLabel\` when the default "Metrics" is too vague. |
90
+ | **Charts** | \`LineAreaChart\` exposes \`role="img"\` + \`aria-label\`; \`MetricChartCard\` updates the chart label when the active metric changes (\`aria-live\` on the plot region). |
91
+ | **Integration cards** | Whole-card click \u2192 \`onClick\` only (no nested footer button). With footer \`action\`, render a static \`article\` \u2014 do not wrap the CTA in a card button. Pass \`ariaLabel\` when \`name\` is not plain text. |
92
+ | **Lists** | Wrap \`ConnectionRow\` in \`ConnectionRowList\` (\`role="list"\`); rows expose \`role="listitem"\`. |
93
+ | **Status** | Pair \`StatusDot\` / \`StatusBadge\` with visible text \u2014 do not rely on color alone. |
94
+ | **Forms** | Use \`Field*\` components; errors use \`role="alert"\`. |
95
+ | **Custom labels** | \`ariaLabel\` props exist on \`MetricTile\`, \`IntegrationCard\`, \`ConnectionRow\`, \`ResourceCard\` when slots are icons or rich nodes. |
96
+
97
+ ### Component menu
98
+
99
+ | Component | Use for |
100
+ |-----------|---------|
101
+ | \`AppShell\` | Shell: optional \`sidebar\`, \`topbar\`, main \`children\`, optional floating \`chat\`. Props: \`chatTriggerLabel\`, \`chatCollapsible\`, \`chatWidth\`, \`chatHeight\`, controlled \`chatOpen\`. |
102
+ | \`AppShellTopbar\` | Full-width top bar: \`start\`, \`actions\` slots. |
103
+ | \`AppCopilotProvider\` | React context for copilot-aware tools (page, filters, selection, etc.). |
104
+ | \`AppChatPanel\` | Floating thread: \`workforceId\`, \`welcome\`, \`debug\`. |
105
+ | \`useAppShellChat\` | Custom open/close trigger when \`hideChatTrigger\` on shell. |
106
+ | \`Page\` | Page title, description, \`breadcrumbs\`, \`actions\`, children. |
107
+ | \`Section\` | Titled block inside a page. |
108
+ | \`SubNav\` | In-page tabs: \`items\`, \`activeId\`, \`onChange\`. |
109
+ | \`Breadcrumbs\` | Trail: \`items: [{ label, href? }]\`. |
110
+ | \`Button\` | Actions \u2014 \`variant="secondary"\` for catalog/secondary CTAs; \`variant="default"\` for primary. |
111
+ | \`StatTile\` | Single KPI in its own card (grid of scattered stats). Prefer \`MetricRow\` for a unified overview strip. |
112
+ | \`StatusBadge\` | Status pill: \`tone\` (\`success\`, \`warn\`, \u2026), children. |
113
+ | \`FilterBar\` | Horizontal filter row (wraps \`SearchInput\`, buttons, etc.). |
114
+ | \`SearchInput\` | Filter field with consistent app styling. |
115
+ | \`DataTable\` | Sortable table: \`columns\`, \`rows\`, \`getRowKey\`, optional \`sort\` / \`onSortChange\`, \`emptyTitle\`, \`showRowCount\`, \`caption\` for screen readers. |
116
+ | \`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\`. |
117
+ | \`FieldInput\`, \`FieldTextarea\`, \`FieldSelect\`, \`FieldSwitch\` | Settings-style forms with labels and hints. |
118
+ | \`FormSection\` | Grouped form block. |
119
+ | \`AppConfirmDialog\` | Confirm/cancel modal: \`open\`, \`onOpenChange\`, \`title\`, \`description\`, \`onConfirm\`. |
120
+ | \`SurfaceCard\`, \`EmptyState\` | Generic surfaces when needed. |
121
+ | \`TimbalChat\` | Re-export if you need chat outside \`AppChatPanel\`. |
122
+
123
+ #### Charts & metrics
124
+
125
+ | Component | Use for |
126
+ |-----------|---------|
127
+ | \`LineAreaChart\` | Chart engine. Props: \`data\`, \`xKey\`, \`series\`, \`variant\` (\`"area"\`), \`layout\` (\`"flush"\`), \`height\`, \`ariaLabel\`, \`formatX\`, \`formatValue\`. |
128
+ | \`Sparkline\` | Tiny inline trend (table cells): \`data\`, \`color\`, \`area\`. |
129
+ | \`MetricTile\` | Low-level KPI cell \u2014 prefer \`MetricRow\` / \`MetricChartCard\` instead of hand-wiring tiles. |
130
+ | \`MetricRow\` | KPI strip in one elevated card (no chart). Props: \`metrics: [{ id, label, value, unit?, trend? }]\`, optional \`onMetricChange\`, \`metricsAriaLabel\`. |
131
+ | \`MetricChartCard\` | KPI strip + flush chart; tile click swaps series. Same metrics shape + \`data\` per metric. Default chart height 300. |
132
+
133
+ #### Settings
134
+
135
+ | Component | Use for |
136
+ |-----------|---------|
137
+ | \`SettingsSection\` | Two-column settings block: \`title\` + \`description\` rail on the left, controls on the right. |
138
+ | \`FieldRow\` | Labeled control row: \`label\`, \`description\`, \`inline\` (right-aligned control for switches). |
139
+ | \`DangerZone\` + \`DangerZoneAction\` | Destructive-actions container with destructive border. |
140
+ | \`FloatingUnsavedChangesBar\` | Portaled discard/save pill: \`visible\`, \`onDiscard\`, \`onSave\`, \`isSaving\`. |
141
+
142
+ #### Integrations & resources
143
+
144
+ | Component | Use for |
145
+ |-----------|---------|
146
+ | \`IntegrationCard\` | Catalog tile: \`logo\`, \`name\`, \`description\`, \`badge\`, \`status\`, footer \`action\` **or** whole-card \`onClick\` (never both). |
147
+ | \`ConnectionRow\` | One connected provider row: \`logo\`, \`name\`, \`meta\`, \`badge\`, \`action\`. |
148
+ | \`ConnectionRowList\` | Wrapper for rows (\`role="list"\`) \u2014 use instead of raw class strings. |
149
+ | \`IntegrationsEmptyState\` | Empty catalog hero: \`icon\`, \`title\`, \`description\`, \`action\`. |
150
+ | \`PlanBadge\` | Neutral tier chip on catalog cards. |
151
+ | \`ResourceCard\` | Project/agent/dataset card on elevated surface + logo tile: \`media\`, \`title\`, \`subtitle\`, optional \`badge\`, \`footer\` (\`StatusDot\`), \`action\` (\`Sparkline\`). |
152
+
153
+ #### Surfaces & details
154
+
155
+ | Component | Use for |
156
+ |-----------|---------|
157
+ | \`InfoCard\` | Soft callout: \`icon\`, \`title\`, body, \`action\`, \`tone\` (\`info\`/\`success\`/\`warn\`/\`danger\`). |
158
+ | \`DescriptionList\` | Read-only key/value metadata: \`items: [{ label, value }]\`, optional \`stacked\`. |
159
+ | \`ExpandableSection\` | Collapsible block: \`title\`, \`icon\`, \`count\`, animated body (\`aria-expanded\` + \`aria-controls\`). |
160
+ | \`StatusDot\` | Status indicator dot: \`tone\`, \`label\`, \`pulse\`. |
161
+
162
+ Studio chrome (\`StudioSidebar\`, \`ModeToggle\`, \u2026) lives in \`@timbal-ai/timbal-react/studio\` \u2014 optional, not required for every dashboard.
163
+
164
+ ### Recipe index (\`examples/app-kit/recipes/\`)
165
+
166
+ | Recipe file | Components to study |
167
+ |-------------|---------------------|
168
+ | \`metrics-row.tsx\` | \`Page\`, \`MetricRow\` |
169
+ | \`analytics-card.tsx\` | \`MetricChartCard\`, \`Button\` |
170
+ | \`integrations-grid.tsx\` | \`IntegrationCard\`, \`ConnectionRowList\`, \`PlanBadge\` |
171
+ | \`table-with-filters.tsx\` | \`FilterBar\`, \`DataTable\` |
172
+ | \`settings-page.tsx\` | \`SettingsSection\`, \`DangerZone\`, \`FloatingUnsavedChangesBar\` |
173
+ | \`resource-gallery.tsx\` | \`ResourceCard\`, \`StatusDot\`, \`Sparkline\` |
174
+ | \`charts-panel.tsx\` | \`ChartPanel\`, \`ChartArtifact\` |
175
+ | \`copilot-overlay.tsx\` | \`AppShell\`, \`AppChatPanel\` |
176
+
177
+ ### Typical compositions
178
+
179
+ - **Metrics overview** \u2014 \`MetricRow\` or \`MetricChartCard\` (not four isolated stat cards with bold numbers).
180
+ - **Analytics** \u2014 \`MetricChartCard\`; header action: \`Button variant="secondary" size="sm"\`.
181
+ - **Table workspace** \u2014 \`Page\` + \`FilterBar\` + \`DataTable\` (+ \`StatusBadge\` / \`StatusDot\` in cells).
182
+ - **Settings** \u2014 \`Page\` + \`SettingsSection\`s + \`DangerZone\` + \`FloatingUnsavedChangesBar\`.
183
+ - **Integrations** \u2014 grid of \`IntegrationCard\`; \`ConnectionRowList\` for connected providers; \`IntegrationsEmptyState\` when empty.
184
+ - **Resource gallery** \u2014 grid of \`ResourceCard\`.
185
+ - **Copilot-assisted app** \u2014 \`AppCopilotProvider\` + \`AppShell\` with \`chat={<AppChatPanel workforceId="\u2026" />}\`.
186
+
187
+ ### Example imports
188
+
189
+ \`\`\`tsx
190
+ import {
191
+ AppShell,
192
+ AppCopilotProvider,
193
+ AppChatPanel,
194
+ Page,
195
+ Section,
196
+ MetricRow,
197
+ MetricChartCard,
198
+ IntegrationCard,
199
+ ConnectionRow,
200
+ ConnectionRowList,
201
+ Button,
202
+ DataTable,
203
+ FilterBar,
204
+ } from "@timbal-ai/timbal-react/app";
205
+ \`\`\`
206
+
207
+ ### Examples in this repo (for humans/tools)
208
+
209
+ | Path | Purpose |
210
+ |------|---------|
211
+ | \`examples/app-kit/recipes/*\` | **Recipes** \u2014 one pattern each (~20\u201380 lines). Use for capability, not layout. |
212
+ | \`examples/app-kit/reference/operations-dashboard.tsx\` | **Reference only** \u2014 full wired app; do not treat as the default generated layout. |
213
+
214
+ ### Rules
215
+
216
+ - Prefer stable props documented above; avoid undocumented \`design/*\` class exports (\`connectionRowListClass\` is exported but \`ConnectionRowList\` is preferred).
217
+ - Match the user's domain language in titles and labels.
218
+ - For rich in-chat widgets, use **artifacts** (\`ARTIFACT_AGENT_INSTRUCTIONS\`) \u2014 app kit is for the **host application shell**.
219
+ `.trim();
220
+
221
+ // src/design/app-classes.ts
222
+ var appPageColumnClass = "mx-auto w-full max-w-6xl px-4 md:px-6";
223
+ var appShellTopbarInsetClass = "w-full px-4 md:px-6";
224
+ var appShellInsetTopClass = "pt-4 md:pt-6";
225
+ var appShellTopbarRowClass = cn(
226
+ studioTopbarPillHeightClass,
227
+ "flex w-full items-center justify-between gap-2"
228
+ );
229
+ var appShellTopbarStickyClass = cn(
230
+ "sticky top-0 z-20 shrink-0 bg-background pb-2",
231
+ appShellInsetTopClass
232
+ );
233
+ var appPageHeaderClass = cn(
234
+ "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
235
+ "pb-4 pt-2"
236
+ );
237
+ var appSectionClass = "flex flex-col gap-4 py-4";
238
+ var appSectionTitleClass = "text-lg font-semibold text-foreground";
239
+ var appSectionDescriptionClass = "text-sm text-muted-foreground";
240
+ var appSurfaceCardClass = cn(
241
+ studioIntegrationCardClass,
242
+ "p-4 md:p-5"
243
+ );
244
+ var appStatTileClass = cn(
245
+ appSurfaceCardClass,
246
+ "flex flex-col gap-1 px-4 py-3 shadow-none"
247
+ );
248
+ var appStatValueClass = "text-2xl font-normal tracking-tight text-foreground tabular-nums";
249
+ var appStatLabelClass = "text-xs font-normal text-muted-foreground";
250
+ var appFilterBarClass = cn(
251
+ "flex flex-wrap items-center gap-2",
252
+ studioTopbarPillHeightClass
253
+ );
254
+ var appSearchInputClass = cn(studioSearchChromeClass, "text-sm");
255
+ var appBreadcrumbsClass = "flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground";
256
+ var appBreadcrumbLinkClass = "transition-colors hover:text-foreground";
257
+ var appFieldClass = "flex flex-col gap-1.5";
258
+ var appFieldLabelClass = "text-sm font-medium text-foreground";
259
+ var appFieldHintClass = "text-xs text-muted-foreground";
260
+ var appInputClass = cn(
261
+ studioSecondaryChromeClass,
262
+ "h-10 w-full rounded-lg px-3 text-sm text-foreground outline-none",
263
+ "placeholder:text-muted-foreground/70",
264
+ "focus-visible:ring-2 focus-visible:ring-foreground/10"
265
+ );
266
+ var appEmptyStateClass = cn(
267
+ appSurfaceCardClass,
268
+ "flex flex-col items-center justify-center gap-2 py-12 text-center"
269
+ );
270
+ var appEmptyStateTitleClass = "text-base font-medium text-foreground";
271
+ var appEmptyStateDescriptionClass = "max-w-sm text-sm text-muted-foreground";
272
+ var appChartPanelClass = cn(appSurfaceCardClass, "flex flex-col gap-3");
273
+
274
+ // src/app/layout/app-shell-chat-context.tsx
275
+ import { createContext, useContext } from "react";
276
+ var AppShellChatContext = createContext(null);
277
+ var AppShellChatProvider = AppShellChatContext.Provider;
278
+ function useAppShellChat() {
279
+ return useContext(AppShellChatContext);
280
+ }
281
+
282
+ // src/app/layout/AppShell.tsx
283
+ import { motion, useReducedMotion } from "motion/react";
284
+ import { useCallback, useState } from "react";
285
+ import { jsx, jsxs } from "react/jsx-runtime";
286
+ var floatingTriggerClass = cn(
287
+ "aui-app-shell-chat-trigger-fixed fixed z-50 rounded-full px-5 py-2.5 text-sm font-medium shadow-card-elevated",
288
+ "bg-primary text-primary-foreground transition-colors hover:bg-primary/90",
289
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
290
+ "bottom-6 right-6 max-sm:bottom-4 max-sm:right-4"
291
+ );
292
+ var floatingPanelClass = cn(
293
+ "aui-app-shell-chat-float fixed z-50 flex flex-col overflow-hidden rounded-2xl border border-border/60 shadow-card-elevated",
294
+ "bg-card/85 backdrop-blur-xl supports-backdrop-filter:bg-card/75",
295
+ // Mobile: stretch between insets (no fixed width — avoids a wide empty strip on the right)
296
+ "max-sm:inset-x-3 max-sm:top-3 max-sm:bottom-3 max-sm:w-auto",
297
+ "sm:top-6 sm:right-6 sm:bottom-6 sm:w-[var(--app-shell-chat-width)] sm:max-w-[calc(100vw-3rem)]"
298
+ );
299
+ var AppShellBody = ({
300
+ sidebar,
301
+ topbarContent,
302
+ mainClassName,
303
+ insetPaddingPx,
304
+ insetExpanded,
305
+ children
306
+ }) => {
307
+ const reducedMotion = useReducedMotion();
308
+ const layoutDirection = insetExpanded ? "expand" : "collapse";
309
+ const layoutTransition = studioSidebarWidthTransition(
310
+ !!reducedMotion,
311
+ layoutDirection
312
+ );
313
+ const insetPadding = sidebar ? insetPaddingPx : 0;
314
+ return /* @__PURE__ */ jsx(
315
+ motion.div,
316
+ {
317
+ className: "aui-app-shell-body relative z-10 flex min-h-0 min-w-0 flex-1 flex-col",
318
+ initial: false,
319
+ animate: { paddingLeft: insetPadding },
320
+ transition: layoutTransition,
321
+ children: /* @__PURE__ */ jsxs(
322
+ "div",
323
+ {
324
+ className: cn(
325
+ "aui-app-shell-scroll flex min-h-0 flex-1 flex-col overflow-y-auto",
326
+ !topbarContent && appShellInsetTopClass
327
+ ),
328
+ children: [
329
+ topbarContent ? /* @__PURE__ */ jsx("header", { className: cn("aui-app-shell-topbar-region", appShellTopbarStickyClass), children: /* @__PURE__ */ jsx("div", { className: appShellTopbarInsetClass, children: topbarContent }) }) : null,
330
+ /* @__PURE__ */ jsx("main", { className: cn("aui-app-shell-main min-w-0 flex-1", mainClassName), children })
331
+ ]
332
+ }
333
+ )
334
+ }
335
+ );
336
+ };
337
+ var AppShell = ({
338
+ sidebar,
339
+ topbar,
340
+ header,
341
+ children,
342
+ chat,
343
+ chatWidth = "24rem",
344
+ chatHeight,
345
+ chatOpen: chatOpenProp,
346
+ defaultChatOpen = false,
347
+ onChatOpenChange,
348
+ chatCollapsible = true,
349
+ chatTriggerLabel = "Assistant",
350
+ hideChatTrigger = false,
351
+ className,
352
+ mainClassName
353
+ }) => {
354
+ const topbarContent = topbar ?? header;
355
+ const hasChat = Boolean(chat);
356
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultChatOpen);
357
+ const isChatControlled = chatOpenProp !== void 0;
358
+ const chatOpen = isChatControlled ? chatOpenProp : uncontrolledOpen;
359
+ const setChatOpen = useCallback(
360
+ (open) => {
361
+ if (!isChatControlled) {
362
+ setUncontrolledOpen(open);
363
+ }
364
+ onChatOpenChange?.(open);
365
+ },
366
+ [isChatControlled, onChatOpenChange]
367
+ );
368
+ const toggleChat = useCallback(() => {
369
+ setChatOpen(!chatOpen);
370
+ }, [chatOpen, setChatOpen]);
371
+ const [insetPaddingPx, setInsetPaddingPx] = useState(
372
+ sidebar ? SIDEBAR_INSET_PX_EXPANDED : 0
373
+ );
374
+ const reportShellInset = useCallback((insetPx) => {
375
+ setInsetPaddingPx(insetPx);
376
+ }, []);
377
+ const insetExpanded = insetPaddingPx >= SIDEBAR_INSET_PX_EXPANDED;
378
+ const shellBody = /* @__PURE__ */ jsx(
379
+ AppShellBody,
380
+ {
381
+ sidebar,
382
+ topbarContent,
383
+ mainClassName,
384
+ insetPaddingPx,
385
+ insetExpanded,
386
+ children
387
+ }
388
+ );
389
+ const tree = /* @__PURE__ */ jsx(ShellInsetProvider, { value: sidebar ? reportShellInset : null, children: /* @__PURE__ */ jsxs(
390
+ "div",
391
+ {
392
+ className: cn(
393
+ "aui-app-shell relative flex h-dvh overflow-hidden bg-background",
394
+ className
395
+ ),
396
+ style: studioChromeShellStyle,
397
+ children: [
398
+ sidebar,
399
+ shellBody,
400
+ hasChat && chatOpen ? /* @__PURE__ */ jsx(
401
+ "div",
402
+ {
403
+ className: floatingPanelClass,
404
+ style: {
405
+ ["--app-shell-chat-width"]: chatWidth,
406
+ ...chatHeight ? { height: chatHeight } : void 0
407
+ },
408
+ role: "dialog",
409
+ "aria-label": typeof chatTriggerLabel === "string" ? chatTriggerLabel : "Assistant",
410
+ children: chat
411
+ }
412
+ ) : null,
413
+ hasChat && chatCollapsible && !chatOpen && !hideChatTrigger ? /* @__PURE__ */ jsx(
414
+ "button",
415
+ {
416
+ type: "button",
417
+ className: floatingTriggerClass,
418
+ onClick: () => setChatOpen(true),
419
+ "aria-expanded": false,
420
+ children: chatTriggerLabel
421
+ }
422
+ ) : null
423
+ ]
424
+ }
425
+ ) });
426
+ if (!hasChat) {
427
+ return tree;
428
+ }
429
+ return /* @__PURE__ */ jsx(
430
+ AppShellChatProvider,
431
+ {
432
+ value: {
433
+ open: chatOpen,
434
+ setOpen: setChatOpen,
435
+ toggle: toggleChat,
436
+ collapsible: chatCollapsible
437
+ },
438
+ children: tree
439
+ }
440
+ );
441
+ };
442
+
443
+ // src/app/layout/AppShellTopbar.tsx
444
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
445
+ var AppShellTopbar = ({
446
+ start,
447
+ actions,
448
+ children,
449
+ className
450
+ }) => {
451
+ return /* @__PURE__ */ jsxs2("div", { className: cn("aui-app-shell-topbar", appShellTopbarRowClass, className), children: [
452
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
453
+ start,
454
+ children
455
+ ] }),
456
+ actions ? /* @__PURE__ */ jsx2("div", { className: "aui-app-shell-topbar-actions flex shrink-0 items-center gap-2", children: actions }) : null
457
+ ] });
458
+ };
459
+
460
+ // src/app/layout/AppShellChatTrigger.tsx
461
+ import { jsx as jsx3 } from "react/jsx-runtime";
462
+ var floatingPositionClass = "fixed bottom-6 right-6 z-50 max-sm:bottom-4 max-sm:right-4";
463
+ var AppShellChatTrigger = ({
464
+ className,
465
+ label = "Assistant",
466
+ placement = "inline"
467
+ }) => {
468
+ const shellChat = useAppShellChat();
469
+ if (!shellChat || shellChat.open) return null;
470
+ return /* @__PURE__ */ jsx3(
471
+ TimbalV2Button,
472
+ {
473
+ type: "button",
474
+ variant: placement === "floating" ? "primary" : "secondary",
475
+ size: "sm",
476
+ className: cn(
477
+ "aui-app-shell-chat-trigger",
478
+ placement === "floating" && floatingPositionClass,
479
+ placement === "floating" && "shadow-card-elevated",
480
+ className
481
+ ),
482
+ onClick: () => shellChat.setOpen(true),
483
+ "aria-expanded": false,
484
+ children: label
485
+ }
486
+ );
487
+ };
488
+
489
+ // src/app/layout/PageHeader.tsx
490
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
491
+ var PageHeader = ({
492
+ title,
493
+ description,
494
+ actions,
495
+ className
496
+ }) => {
497
+ return /* @__PURE__ */ jsxs3("header", { className: cn("aui-app-page-header", appPageHeaderClass, className), children: [
498
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
499
+ /* @__PURE__ */ jsx4("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: title }),
500
+ description ? /* @__PURE__ */ jsx4("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null
501
+ ] }),
502
+ actions ? /* @__PURE__ */ jsx4("div", { className: "aui-app-page-header-actions flex shrink-0 flex-wrap items-center gap-2", children: actions }) : null
503
+ ] });
504
+ };
505
+
506
+ // src/app/layout/Page.tsx
507
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
508
+ var Page = ({
509
+ children,
510
+ breadcrumbs,
511
+ className,
512
+ ...headerProps
513
+ }) => {
514
+ return /* @__PURE__ */ jsxs4("div", { className: cn("aui-app-page", appPageColumnClass, className), children: [
515
+ breadcrumbs,
516
+ /* @__PURE__ */ jsx5(PageHeader, { ...headerProps }),
517
+ children
518
+ ] });
519
+ };
520
+
521
+ // src/app/layout/Section.tsx
522
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
523
+ var Section = ({
524
+ title,
525
+ description,
526
+ children,
527
+ className
528
+ }) => {
529
+ return /* @__PURE__ */ jsxs5("section", { className: cn("aui-app-section", appSectionClass, className), children: [
530
+ title ? /* @__PURE__ */ jsx6("h2", { className: appSectionTitleClass, children: title }) : null,
531
+ description ? /* @__PURE__ */ jsx6("p", { className: appSectionDescriptionClass, children: description }) : null,
532
+ children
533
+ ] });
534
+ };
535
+
536
+ // src/app/copilot/app-copilot-context.tsx
537
+ import { createContext as createContext2, useContext as useContext2 } from "react";
538
+ import { jsx as jsx7 } from "react/jsx-runtime";
539
+ var AppCopilotContext = createContext2(null);
540
+ var AppCopilotProvider = ({
541
+ value,
542
+ children
543
+ }) => {
544
+ return /* @__PURE__ */ jsx7(AppCopilotContext.Provider, { value, children });
545
+ };
546
+ function useAppCopilotContext() {
547
+ return useContext2(AppCopilotContext) ?? {};
548
+ }
549
+
550
+ // src/app/chat/AppChatPanel.tsx
551
+ import { XIcon } from "lucide-react";
552
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
553
+ var shellClass = "aui-app-chat-panel flex h-full min-h-0 flex-col overflow-hidden";
554
+ var chromeClass = cn(
555
+ "aui-app-chat-panel-chrome relative z-20 flex min-h-10 shrink-0 items-center justify-end px-2 pt-2"
556
+ );
557
+ var closeButtonClass = cn(
558
+ "aui-app-chat-panel-close flex size-8 shrink-0 items-center justify-center rounded-md",
559
+ "text-muted-foreground transition-colors hover:bg-foreground/5 hover:text-foreground",
560
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
561
+ );
562
+ var bodyClass = cn(
563
+ "aui-app-chat-panel-body relative min-h-0 flex-1 overflow-hidden",
564
+ "[&_.aui-thread-root]:h-full",
565
+ "[&_.aui-thread-viewport]:scrollbar-thin",
566
+ "[&_.aui-thread-viewport]:[scrollbar-color:var(--border)_transparent]",
567
+ // Reserve the scrollbar gutter on BOTH edges so the composer + messages
568
+ // stay symmetric (otherwise the right-side scrollbar adds a phantom inset).
569
+ "[&_.aui-thread-viewport]:[scrollbar-gutter:stable_both-edges]",
570
+ // Tighter symmetric horizontal inset for panel + composer
571
+ "[&_.aui-thread-viewport]:!px-2",
572
+ "[&_.aui-thread-viewport]:!pt-1",
573
+ "[&_.aui-user-message-root]:!px-0",
574
+ "[&_.aui-user-message-root]:!pe-1",
575
+ "[&_.aui-composer-input]:!px-2",
576
+ "[&_.aui-composer-action-wrapper]:!px-2"
577
+ );
578
+ var AppChatPanel = ({
579
+ className,
580
+ workforceId,
581
+ baseUrl,
582
+ fetch,
583
+ attachments,
584
+ attachmentsUploadUrl,
585
+ attachmentsAccept,
586
+ debug,
587
+ welcome,
588
+ suggestions,
589
+ showWelcomeSuggestions,
590
+ composerPlaceholder,
591
+ components,
592
+ artifacts,
593
+ onArtifactEvent,
594
+ ...rest
595
+ }) => {
596
+ const shellChat = useAppShellChat();
597
+ return /* @__PURE__ */ jsxs6("div", { className: cn(shellClass, className), children: [
598
+ shellChat?.collapsible ? /* @__PURE__ */ jsx8("div", { className: chromeClass, children: /* @__PURE__ */ jsx8(
599
+ "button",
600
+ {
601
+ type: "button",
602
+ className: closeButtonClass,
603
+ onClick: () => shellChat.setOpen(false),
604
+ "aria-label": "Close assistant",
605
+ children: /* @__PURE__ */ jsx8(XIcon, { className: "size-4", "aria-hidden": true })
606
+ }
607
+ ) }) : null,
608
+ /* @__PURE__ */ jsx8("div", { className: bodyClass, children: /* @__PURE__ */ jsx8(
609
+ TimbalRuntimeProvider,
610
+ {
611
+ workforceId,
612
+ baseUrl,
613
+ fetch,
614
+ attachments,
615
+ attachmentsUploadUrl,
616
+ attachmentsAccept,
617
+ debug,
618
+ children: /* @__PURE__ */ jsx8(
619
+ Thread,
620
+ {
621
+ variant: "panel",
622
+ className: "aui-app-chat-panel-thread",
623
+ welcome,
624
+ suggestions,
625
+ showWelcomeSuggestions,
626
+ composerPlaceholder,
627
+ components,
628
+ artifacts,
629
+ onArtifactEvent,
630
+ ...rest
631
+ }
632
+ )
633
+ }
634
+ ) })
635
+ ] });
636
+ };
637
+
638
+ // src/app/surfaces/SurfaceCard.tsx
639
+ import { jsx as jsx9 } from "react/jsx-runtime";
640
+ var SurfaceCard = ({ children, className }) => {
641
+ return /* @__PURE__ */ jsx9("div", { className: cn("aui-app-surface-card", appSurfaceCardClass, className), children });
642
+ };
643
+
644
+ // src/app/surfaces/StatTile.tsx
645
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
646
+ var StatTile = ({ label, value, hint, className }) => {
647
+ return /* @__PURE__ */ jsxs7("div", { className: cn("aui-app-stat-tile", appStatTileClass, className), children: [
648
+ /* @__PURE__ */ jsx10("span", { className: appStatLabelClass, children: label }),
649
+ /* @__PURE__ */ jsx10("span", { className: appStatValueClass, children: value }),
650
+ hint ? /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", children: hint }) : null
651
+ ] });
652
+ };
653
+
654
+ // src/app/surfaces/EmptyState.tsx
655
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
656
+ var EmptyState = ({
657
+ title,
658
+ description,
659
+ action,
660
+ className
661
+ }) => {
662
+ return /* @__PURE__ */ jsxs8("div", { className: cn("aui-app-empty-state", appEmptyStateClass, className), children: [
663
+ /* @__PURE__ */ jsx11("p", { className: appEmptyStateTitleClass, children: title }),
664
+ description ? /* @__PURE__ */ jsx11("p", { className: appEmptyStateDescriptionClass, children: description }) : null,
665
+ action
666
+ ] });
667
+ };
668
+
669
+ // src/app/surfaces/StatusBadge.tsx
670
+ import { jsx as jsx12 } from "react/jsx-runtime";
671
+ var statusBadgeToneClass = {
672
+ default: "bg-muted text-foreground",
673
+ primary: "bg-primary/10 text-primary",
674
+ success: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
675
+ warn: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
676
+ muted: "bg-muted/80 text-muted-foreground"
677
+ };
678
+ var StatusBadge = ({
679
+ children,
680
+ tone = "default",
681
+ className
682
+ }) => {
683
+ return /* @__PURE__ */ jsx12(
684
+ "span",
685
+ {
686
+ className: cn(
687
+ "aui-app-status-badge inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
688
+ statusBadgeToneClass[tone],
689
+ className
690
+ ),
691
+ children
692
+ }
693
+ );
694
+ };
695
+
696
+ // src/app/surfaces/AppConfirmDialog.tsx
697
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
698
+ var bodyClass2 = "flex flex-col gap-4 p-6";
699
+ var titleClass = "pr-8";
700
+ var actionsClass = "flex flex-wrap justify-end gap-2";
701
+ var AppConfirmDialog = ({
702
+ open,
703
+ onOpenChange,
704
+ title,
705
+ description,
706
+ confirmLabel = "Confirm",
707
+ cancelLabel = "Cancel",
708
+ onConfirm,
709
+ destructive = false,
710
+ className
711
+ }) => {
712
+ return /* @__PURE__ */ jsx13(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx13(
713
+ DialogContent,
714
+ {
715
+ className: cn("gap-0 p-0 sm:max-w-md", className),
716
+ children: /* @__PURE__ */ jsxs9("div", { className: bodyClass2, children: [
717
+ /* @__PURE__ */ jsx13(DialogTitle, { className: titleClass, children: title }),
718
+ description ? /* @__PURE__ */ jsx13("p", { className: "text-sm text-muted-foreground", children: description }) : null,
719
+ /* @__PURE__ */ jsxs9("div", { className: actionsClass, children: [
720
+ /* @__PURE__ */ jsx13(
721
+ TimbalV2Button,
722
+ {
723
+ type: "button",
724
+ variant: "secondary",
725
+ size: "sm",
726
+ onClick: () => onOpenChange(false),
727
+ children: cancelLabel
728
+ }
729
+ ),
730
+ /* @__PURE__ */ jsx13(
731
+ TimbalV2Button,
732
+ {
733
+ type: "button",
734
+ variant: destructive ? "destructive" : "primary",
735
+ size: "sm",
736
+ onClick: () => {
737
+ onConfirm();
738
+ onOpenChange(false);
739
+ },
740
+ children: confirmLabel
741
+ }
742
+ )
743
+ ] })
744
+ ] })
745
+ }
746
+ ) });
747
+ };
748
+
749
+ // src/app/surfaces/InfoCard.tsx
750
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
751
+ var toneClass = {
752
+ neutral: "border-border bg-muted/40",
753
+ info: "border-primary/25 bg-primary/5",
754
+ success: "border-emerald-500/25 bg-emerald-500/5",
755
+ warn: "border-amber-500/25 bg-amber-500/5",
756
+ danger: "border-destructive/25 bg-destructive/5"
757
+ };
758
+ var InfoCard = ({
759
+ title,
760
+ children,
761
+ icon,
762
+ action,
763
+ tone = "neutral",
764
+ className
765
+ }) => /* @__PURE__ */ jsxs10(
766
+ "div",
767
+ {
768
+ className: cn(
769
+ "flex items-start gap-3 rounded-xl border p-4",
770
+ toneClass[tone],
771
+ className
772
+ ),
773
+ children: [
774
+ icon ? /* @__PURE__ */ jsx14("span", { className: "mt-0.5 shrink-0 text-muted-foreground", children: icon }) : null,
775
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
776
+ title ? /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-foreground", children: title }) : null,
777
+ children ? /* @__PURE__ */ jsx14("div", { className: cn("text-sm text-muted-foreground", title && "mt-1"), children }) : null
778
+ ] }),
779
+ action ? /* @__PURE__ */ jsx14("div", { className: "shrink-0", children: action }) : null
780
+ ]
781
+ }
782
+ );
783
+
784
+ // src/app/surfaces/StatusDot.tsx
785
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
786
+ var dotClass = {
787
+ online: "bg-emerald-500",
788
+ busy: "bg-amber-500",
789
+ offline: "bg-muted-foreground/50",
790
+ error: "bg-destructive",
791
+ neutral: "bg-muted-foreground"
792
+ };
793
+ var StatusDot = ({
794
+ tone = "neutral",
795
+ label,
796
+ pulse = false,
797
+ className
798
+ }) => /* @__PURE__ */ jsxs11("span", { className: cn("inline-flex items-center gap-1.5", className), children: [
799
+ /* @__PURE__ */ jsxs11("span", { className: "relative flex size-2", children: [
800
+ pulse ? /* @__PURE__ */ jsx15(
801
+ "span",
802
+ {
803
+ className: cn(
804
+ "absolute inline-flex size-full animate-ping rounded-full opacity-60",
805
+ dotClass[tone]
806
+ )
807
+ }
808
+ ) : null,
809
+ /* @__PURE__ */ jsx15("span", { className: cn("relative inline-flex size-2 rounded-full", dotClass[tone]) })
810
+ ] }),
811
+ label ? /* @__PURE__ */ jsx15("span", { className: "text-xs text-muted-foreground", children: label }) : null
812
+ ] });
813
+
814
+ // src/app/surfaces/DescriptionList.tsx
815
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
816
+ var DescriptionList = ({
817
+ items,
818
+ stacked = false,
819
+ className
820
+ }) => /* @__PURE__ */ jsx16(
821
+ "dl",
822
+ {
823
+ className: cn(
824
+ "divide-y divide-border rounded-xl border border-border bg-card",
825
+ className
826
+ ),
827
+ children: items.map((item, i) => /* @__PURE__ */ jsxs12(
828
+ "div",
829
+ {
830
+ className: cn(
831
+ "px-4 py-3",
832
+ stacked ? "flex flex-col gap-0.5" : "flex items-center justify-between gap-4"
833
+ ),
834
+ children: [
835
+ /* @__PURE__ */ jsx16("dt", { className: "text-sm text-muted-foreground", children: item.label }),
836
+ /* @__PURE__ */ jsx16(
837
+ "dd",
838
+ {
839
+ className: cn(
840
+ "text-sm text-foreground",
841
+ !stacked && "text-right tabular-nums"
842
+ ),
843
+ children: item.value
844
+ }
845
+ )
846
+ ]
847
+ },
848
+ i
849
+ ))
850
+ }
851
+ );
852
+
853
+ // src/app/surfaces/ExpandableSection.tsx
854
+ import { useId, useState as useState2 } from "react";
855
+ import { AnimatePresence, motion as motion2, useReducedMotion as useReducedMotion2 } from "motion/react";
856
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
857
+ var Chevron = ({ open }) => /* @__PURE__ */ jsx17(
858
+ "svg",
859
+ {
860
+ viewBox: "0 0 24 24",
861
+ className: cn(
862
+ "size-4 text-muted-foreground transition-transform duration-200",
863
+ open && "rotate-180"
864
+ ),
865
+ fill: "none",
866
+ stroke: "currentColor",
867
+ strokeWidth: 2,
868
+ strokeLinecap: "round",
869
+ strokeLinejoin: "round",
870
+ "aria-hidden": true,
871
+ children: /* @__PURE__ */ jsx17("path", { d: "m6 9 6 6 6-6" })
872
+ }
873
+ );
874
+ var ExpandableSection = ({
875
+ title,
876
+ icon,
877
+ count,
878
+ children,
879
+ open: openProp,
880
+ defaultOpen = false,
881
+ onOpenChange,
882
+ className
883
+ }) => {
884
+ const reduceMotion = useReducedMotion2();
885
+ const panelId = useId();
886
+ const [internalOpen, setInternalOpen] = useState2(defaultOpen);
887
+ const open = openProp ?? internalOpen;
888
+ const toggle = () => {
889
+ if (openProp == null) setInternalOpen((o) => !o);
890
+ onOpenChange?.(!open);
891
+ };
892
+ return /* @__PURE__ */ jsxs13("div", { className: cn("border-b border-border last:border-0", className), children: [
893
+ /* @__PURE__ */ jsxs13(
894
+ "button",
895
+ {
896
+ type: "button",
897
+ onClick: toggle,
898
+ "aria-expanded": open,
899
+ "aria-controls": panelId,
900
+ 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",
901
+ children: [
902
+ /* @__PURE__ */ jsxs13("span", { className: "flex min-w-0 items-center gap-3", children: [
903
+ icon ? /* @__PURE__ */ jsx17("span", { className: "flex size-8 items-center justify-center rounded-lg border border-border bg-muted text-muted-foreground", children: icon }) : null,
904
+ /* @__PURE__ */ jsx17("span", { className: "truncate text-sm font-medium text-foreground", children: title }),
905
+ count != null ? /* @__PURE__ */ jsx17("span", { className: "rounded-full border border-border bg-muted px-2 py-0.5 text-xs text-muted-foreground", children: count }) : null
906
+ ] }),
907
+ /* @__PURE__ */ jsx17(Chevron, { open })
908
+ ]
909
+ }
910
+ ),
911
+ /* @__PURE__ */ jsx17(AnimatePresence, { initial: false, children: open ? /* @__PURE__ */ jsx17(
912
+ motion2.div,
913
+ {
914
+ id: panelId,
915
+ initial: reduceMotion ? void 0 : { height: 0, opacity: 0 },
916
+ animate: { height: "auto", opacity: 1 },
917
+ exit: reduceMotion ? void 0 : { height: 0, opacity: 0 },
918
+ transition: { duration: 0.2, ease: "easeOut" },
919
+ className: "overflow-hidden",
920
+ children: /* @__PURE__ */ jsx17("div", { className: "bg-muted/20", children })
921
+ },
922
+ "body"
923
+ ) : null })
924
+ ] });
925
+ };
926
+
927
+ // src/app/surfaces/ResourceCard.tsx
928
+ import { Fragment, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
929
+ var resourceCardShellClass = cn(
930
+ "flex min-h-[8.5rem] flex-col rounded-2xl p-4 text-left font-normal",
931
+ TIMBAL_V2_ELEVATED_SURFACE
932
+ );
933
+ var mediaShellClass = cn(
934
+ "flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-xl text-sm font-normal text-foreground",
935
+ TIMBAL_V2_LOGO_TILE
936
+ );
937
+ var resourceCardInteractiveClass = cn(
938
+ resourceCardShellClass,
939
+ "cursor-pointer bg-transparent hover:bg-transparent active:bg-transparent",
940
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
941
+ );
942
+ var ResourceCard = ({
943
+ title,
944
+ subtitle,
945
+ media,
946
+ badge,
947
+ footer,
948
+ action,
949
+ onClick,
950
+ ariaLabel,
951
+ className
952
+ }) => {
953
+ const body = /* @__PURE__ */ jsxs14(Fragment, { children: [
954
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-start gap-3", children: [
955
+ media ? /* @__PURE__ */ jsx18("span", { className: mediaShellClass, children: media }) : null,
956
+ /* @__PURE__ */ jsxs14("div", { className: "min-w-0 flex-1 pt-0.5", children: [
957
+ /* @__PURE__ */ jsx18("p", { className: "truncate text-sm font-normal leading-snug text-foreground", children: title }),
958
+ subtitle ? /* @__PURE__ */ jsx18("p", { className: "mt-1 line-clamp-2 text-xs font-normal text-muted-foreground", children: subtitle }) : null
959
+ ] }),
960
+ badge ? /* @__PURE__ */ jsx18("span", { className: "shrink-0 pt-0.5", children: badge }) : null
961
+ ] }),
962
+ footer || action ? /* @__PURE__ */ jsxs14("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: [
963
+ /* @__PURE__ */ jsx18("span", { className: "min-w-0 truncate", children: footer }),
964
+ action ? /* @__PURE__ */ jsx18("span", { className: "shrink-0 opacity-80", children: action }) : null
965
+ ] }) : null
966
+ ] });
967
+ if (onClick) {
968
+ return /* @__PURE__ */ jsx18("button", { type: "button", onClick, "aria-label": ariaLabel, className: cn(resourceCardInteractiveClass, className), children: body });
969
+ }
970
+ return /* @__PURE__ */ jsx18("article", { className: cn(resourceCardShellClass, className), children: body });
971
+ };
972
+
973
+ // src/app/settings/SettingsSection.tsx
974
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
975
+ var SettingsSectionHeader = ({
976
+ title,
977
+ description,
978
+ className
979
+ }) => /* @__PURE__ */ jsxs15("div", { className: cn("flex flex-col", className), children: [
980
+ /* @__PURE__ */ jsx19("h3", { className: "text-[17px] font-medium leading-tight text-foreground", children: title }),
981
+ description ? /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null
982
+ ] });
983
+ var SettingsSection = ({
984
+ title,
985
+ description,
986
+ descriptionFooter,
987
+ children,
988
+ noBorderTop = false,
989
+ className
990
+ }) => /* @__PURE__ */ jsxs15(
991
+ "section",
992
+ {
993
+ className: cn(
994
+ "grid grid-cols-1 gap-y-4 lg:grid-cols-[minmax(200px,280px)_minmax(0,1fr)] lg:gap-x-12 lg:gap-y-0",
995
+ noBorderTop ? "pb-6" : "border-t border-border py-6",
996
+ className
997
+ ),
998
+ children: [
999
+ /* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
1000
+ /* @__PURE__ */ jsx19("h2", { className: "text-sm font-medium text-foreground", children: title }),
1001
+ description ? /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null,
1002
+ descriptionFooter ? /* @__PURE__ */ jsx19("div", { className: "mt-3 min-w-0", children: descriptionFooter }) : null
1003
+ ] }),
1004
+ /* @__PURE__ */ jsx19("div", { className: "min-w-0 space-y-3", children })
1005
+ ]
1006
+ }
1007
+ );
1008
+
1009
+ // src/app/settings/FieldRow.tsx
1010
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
1011
+ var FieldRow = ({
1012
+ label,
1013
+ children,
1014
+ description,
1015
+ inline = false,
1016
+ htmlFor,
1017
+ className
1018
+ }) => {
1019
+ if (inline) {
1020
+ return /* @__PURE__ */ jsxs16(
1021
+ "div",
1022
+ {
1023
+ className: cn(
1024
+ "flex items-center justify-between gap-4 rounded-lg border border-border bg-card px-3.5 py-3",
1025
+ className
1026
+ ),
1027
+ children: [
1028
+ /* @__PURE__ */ jsxs16("div", { className: "min-w-0", children: [
1029
+ /* @__PURE__ */ jsx20(
1030
+ "label",
1031
+ {
1032
+ htmlFor,
1033
+ className: "block text-sm font-medium text-foreground",
1034
+ children: label
1035
+ }
1036
+ ),
1037
+ description ? /* @__PURE__ */ jsx20("p", { className: "mt-0.5 text-xs text-muted-foreground", children: description }) : null
1038
+ ] }),
1039
+ /* @__PURE__ */ jsx20("div", { className: "shrink-0", children })
1040
+ ]
1041
+ }
1042
+ );
1043
+ }
1044
+ return /* @__PURE__ */ jsxs16("div", { className: cn("flex flex-col gap-1.5", className), children: [
1045
+ /* @__PURE__ */ jsx20("label", { htmlFor, className: "text-sm font-medium text-foreground", children: label }),
1046
+ children,
1047
+ description ? /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: description }) : null
1048
+ ] });
1049
+ };
1050
+
1051
+ // src/app/settings/FloatingUnsavedChangesBar.tsx
1052
+ import { useEffect, useState as useState3 } from "react";
1053
+ import { createPortal } from "react-dom";
1054
+ import { AnimatePresence as AnimatePresence2, motion as motion3, useReducedMotion as useReducedMotion3 } from "motion/react";
1055
+ import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
1056
+ var FloatingUnsavedChangesBar = ({
1057
+ visible,
1058
+ message = "Unsaved changes",
1059
+ discardLabel = "Discard",
1060
+ saveLabel = "Save changes",
1061
+ isSaving = false,
1062
+ saveDisabled = false,
1063
+ onDiscard,
1064
+ onSave,
1065
+ ariaLabel = "Unsaved changes",
1066
+ className
1067
+ }) => {
1068
+ const reduceMotion = useReducedMotion3();
1069
+ const [mounted, setMounted] = useState3(false);
1070
+ useEffect(() => setMounted(true), []);
1071
+ if (!mounted || typeof document === "undefined") return null;
1072
+ return createPortal(
1073
+ /* @__PURE__ */ jsx21(AnimatePresence2, { children: visible ? /* @__PURE__ */ jsx21("div", { className: "pointer-events-none fixed inset-x-0 bottom-5 z-50 flex justify-center px-4", children: /* @__PURE__ */ jsxs17(
1074
+ motion3.div,
1075
+ {
1076
+ role: "region",
1077
+ "aria-label": ariaLabel,
1078
+ initial: reduceMotion ? { opacity: 0 } : { opacity: 0, y: 28, scale: 0.94 },
1079
+ animate: { opacity: 1, y: 0, scale: 1 },
1080
+ exit: reduceMotion ? { opacity: 0 } : { opacity: 0, y: 18, scale: 0.96 },
1081
+ transition: { type: "spring", stiffness: 420, damping: 32 },
1082
+ className: cn(
1083
+ "pointer-events-auto inline-flex max-w-[calc(100vw-2rem)] items-center gap-3 rounded-full border border-border bg-card py-1.5 pl-4 pr-1.5 shadow-card-elevated",
1084
+ className
1085
+ ),
1086
+ children: [
1087
+ /* @__PURE__ */ jsx21("span", { className: "text-sm text-muted-foreground", children: message }),
1088
+ /* @__PURE__ */ jsxs17("span", { className: "flex items-center gap-1.5", children: [
1089
+ /* @__PURE__ */ jsx21(Button, { variant: "ghost", size: "sm", onClick: onDiscard, disabled: isSaving, children: discardLabel }),
1090
+ /* @__PURE__ */ jsx21(Button, { size: "sm", onClick: onSave, disabled: saveDisabled || isSaving, children: isSaving ? "Saving\u2026" : saveLabel })
1091
+ ] })
1092
+ ]
1093
+ }
1094
+ ) }) : null }),
1095
+ document.body
1096
+ );
1097
+ };
1098
+
1099
+ // src/app/settings/DangerZone.tsx
1100
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
1101
+ var DangerZoneAction = ({
1102
+ title,
1103
+ description,
1104
+ action,
1105
+ className
1106
+ }) => /* @__PURE__ */ jsxs18(
1107
+ "div",
1108
+ {
1109
+ className: cn(
1110
+ "flex flex-col gap-3 px-4 py-3.5 sm:flex-row sm:items-center sm:justify-between",
1111
+ className
1112
+ ),
1113
+ children: [
1114
+ /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1115
+ /* @__PURE__ */ jsx22("p", { className: "text-sm font-medium text-foreground", children: title }),
1116
+ description ? /* @__PURE__ */ jsx22("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
1117
+ ] }),
1118
+ /* @__PURE__ */ jsx22("div", { className: "shrink-0", children: action })
1119
+ ]
1120
+ }
1121
+ );
1122
+ var DangerZone = ({
1123
+ title = "Danger zone",
1124
+ description,
1125
+ children,
1126
+ className
1127
+ }) => /* @__PURE__ */ jsxs18(
1128
+ "section",
1129
+ {
1130
+ className: cn(
1131
+ "overflow-hidden rounded-xl border border-destructive/30",
1132
+ className
1133
+ ),
1134
+ children: [
1135
+ (title || description) && /* @__PURE__ */ jsxs18("header", { className: "border-b border-destructive/20 bg-destructive/5 px-4 py-3", children: [
1136
+ title ? /* @__PURE__ */ jsx22("h3", { className: "text-sm font-semibold text-destructive", children: title }) : null,
1137
+ description ? /* @__PURE__ */ jsx22("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
1138
+ ] }),
1139
+ /* @__PURE__ */ jsx22("div", { className: "divide-y divide-border bg-card", children })
1140
+ ]
1141
+ }
1142
+ );
1143
+
1144
+ // src/app/integrations/IntegrationCard.tsx
1145
+ import { useId as useId2 } from "react";
1146
+ import { Fragment as Fragment2, jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1147
+ var INTEGRATION_CATALOG_CARD_HEIGHT_CLASS = "h-[12.25rem] min-h-[12.25rem] max-h-[12.25rem]";
1148
+ var statusLabel = {
1149
+ available: null,
1150
+ connected: "Connected",
1151
+ disabled: "Disabled",
1152
+ locked: "Locked"
1153
+ };
1154
+ var catalogCardShellClass = cn(
1155
+ "group relative box-border flex flex-col overflow-hidden rounded-2xl px-4 pb-4 pt-4 text-left font-normal",
1156
+ INTEGRATION_CATALOG_CARD_HEIGHT_CLASS,
1157
+ TIMBAL_V2_ELEVATED_SURFACE,
1158
+ "transition-opacity duration-200 ease-out"
1159
+ );
1160
+ var catalogCardInteractiveClass = cn(
1161
+ catalogCardShellClass,
1162
+ "cursor-pointer bg-transparent hover:bg-transparent active:bg-transparent",
1163
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1164
+ );
1165
+ var catalogCardMutedClass = cn(
1166
+ "border-border/60 saturate-[0.72]",
1167
+ "from-muted/80 to-muted/50 dark:border-white/[0.06] dark:from-white/[0.04] dark:to-white/[0.02]"
1168
+ );
1169
+ var logoShellClass = cn(
1170
+ "relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-xl",
1171
+ TIMBAL_V2_LOGO_TILE
1172
+ );
1173
+ var IntegrationCard = ({
1174
+ name,
1175
+ description,
1176
+ logo,
1177
+ badge,
1178
+ status = "available",
1179
+ action,
1180
+ onClick,
1181
+ ariaLabel,
1182
+ className
1183
+ }) => {
1184
+ const titleId = useId2();
1185
+ const locked = status === "locked";
1186
+ const dimmed = status === "disabled" || locked;
1187
+ const body = /* @__PURE__ */ jsxs19("div", { className: "flex h-full min-h-0 flex-col", children: [
1188
+ /* @__PURE__ */ jsxs19("div", { className: "flex shrink-0 items-start gap-3 pr-2", children: [
1189
+ logo ? /* @__PURE__ */ jsx23("span", { className: logoShellClass, "aria-hidden": Boolean(ariaLabel), children: logo }) : null,
1190
+ /* @__PURE__ */ jsx23("div", { className: "min-w-0 flex-1 pt-0.5", children: /* @__PURE__ */ jsxs19("div", { className: "flex items-start justify-between gap-2", children: [
1191
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0", children: [
1192
+ /* @__PURE__ */ jsx23(
1193
+ "h4",
1194
+ {
1195
+ id: onClick && !action ? void 0 : titleId,
1196
+ className: "truncate text-sm font-normal leading-snug text-foreground",
1197
+ children: name
1198
+ }
1199
+ ),
1200
+ statusLabel[status] ? /* @__PURE__ */ jsx23("p", { className: "mt-0.5 text-xs text-muted-foreground", children: statusLabel[status] }) : null
1201
+ ] }),
1202
+ badge ? /* @__PURE__ */ jsx23("span", { className: "shrink-0", children: badge }) : null
1203
+ ] }) })
1204
+ ] }),
1205
+ description ? /* @__PURE__ */ jsx23(
1206
+ "p",
1207
+ {
1208
+ className: cn(
1209
+ "mt-3 line-clamp-3 shrink-0 text-sm leading-relaxed text-muted-foreground",
1210
+ dimmed && "text-muted-foreground/80"
1211
+ ),
1212
+ children: description
1213
+ }
1214
+ ) : null,
1215
+ action ? /* @__PURE__ */ jsxs19(Fragment2, { children: [
1216
+ /* @__PURE__ */ jsx23("div", { className: "min-h-0 flex-1", "aria-hidden": true }),
1217
+ /* @__PURE__ */ jsx23("div", { className: "relative mt-3 shrink-0", children: action })
1218
+ ] }) : null
1219
+ ] });
1220
+ const shellClass3 = cn(
1221
+ catalogCardShellClass,
1222
+ dimmed && catalogCardMutedClass,
1223
+ locked && "cursor-default opacity-75",
1224
+ className
1225
+ );
1226
+ if (onClick && !action) {
1227
+ return /* @__PURE__ */ jsx23(
1228
+ "button",
1229
+ {
1230
+ type: "button",
1231
+ onClick,
1232
+ disabled: locked,
1233
+ "aria-label": ariaLabel,
1234
+ className: cn(
1235
+ catalogCardInteractiveClass,
1236
+ dimmed && catalogCardMutedClass,
1237
+ locked && "cursor-default opacity-75",
1238
+ className
1239
+ ),
1240
+ children: body
1241
+ }
1242
+ );
1243
+ }
1244
+ return /* @__PURE__ */ jsx23("article", { className: shellClass3, "aria-labelledby": titleId, children: body });
1245
+ };
1246
+
1247
+ // src/app/integrations/IntegrationsEmptyState.tsx
1248
+ import { useId as useId3 } from "react";
1249
+ import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
1250
+ var IntegrationsEmptyState = ({
1251
+ title = "No integrations yet",
1252
+ description = "Connect a provider to start syncing data and powering your workforce.",
1253
+ icon,
1254
+ action,
1255
+ className
1256
+ }) => {
1257
+ const titleId = useId3();
1258
+ return /* @__PURE__ */ jsxs20(
1259
+ "section",
1260
+ {
1261
+ className: cn(
1262
+ "flex flex-col items-center justify-center gap-3 rounded-2xl px-6 py-14 text-center",
1263
+ TIMBAL_V2_ELEVATED_SURFACE,
1264
+ className
1265
+ ),
1266
+ "aria-labelledby": titleId,
1267
+ children: [
1268
+ icon ? /* @__PURE__ */ jsx24(
1269
+ "span",
1270
+ {
1271
+ className: cn(
1272
+ "flex size-14 items-center justify-center overflow-hidden rounded-2xl",
1273
+ TIMBAL_V2_LOGO_TILE,
1274
+ "text-muted-foreground"
1275
+ ),
1276
+ "aria-hidden": true,
1277
+ children: icon
1278
+ }
1279
+ ) : null,
1280
+ /* @__PURE__ */ jsx24("h3", { id: titleId, className: "text-base font-normal text-foreground", children: title }),
1281
+ description ? /* @__PURE__ */ jsx24("p", { className: "max-w-sm text-sm text-muted-foreground", children: description }) : null,
1282
+ action ? /* @__PURE__ */ jsx24("div", { className: "mt-1", children: action }) : null
1283
+ ]
1284
+ }
1285
+ );
1286
+ };
1287
+
1288
+ // src/app/integrations/PlanBadge.tsx
1289
+ import { jsx as jsx25 } from "react/jsx-runtime";
1290
+ 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";
1291
+ var PlanBadge = ({ children, className }) => /* @__PURE__ */ jsx25("span", { className: cn(planBadgeClass, className), children });
1292
+
1293
+ // src/app/integrations/ConnectionRow.tsx
1294
+ import { Fragment as Fragment3, jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
1295
+ var logoShellClass2 = cn(
1296
+ "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-lg",
1297
+ TIMBAL_V2_LOGO_TILE
1298
+ );
1299
+ var ConnectionRow = ({
1300
+ name,
1301
+ logo,
1302
+ meta,
1303
+ badge,
1304
+ action,
1305
+ onClick,
1306
+ ariaLabel,
1307
+ className
1308
+ }) => {
1309
+ const inner = /* @__PURE__ */ jsxs21(Fragment3, { children: [
1310
+ logo ? /* @__PURE__ */ jsx26("span", { className: logoShellClass2, children: logo }) : null,
1311
+ /* @__PURE__ */ jsxs21("div", { className: "min-w-0 flex-1", children: [
1312
+ /* @__PURE__ */ jsx26("p", { className: "truncate text-sm font-normal text-foreground", children: name }),
1313
+ meta ? /* @__PURE__ */ jsx26("p", { className: "truncate text-xs text-muted-foreground", children: meta }) : null
1314
+ ] }),
1315
+ badge ? /* @__PURE__ */ jsx26("span", { className: "shrink-0", children: badge }) : null,
1316
+ action ? /* @__PURE__ */ jsx26("span", { className: "shrink-0", children: action }) : null
1317
+ ] });
1318
+ const rowClass2 = cn(
1319
+ "flex w-full items-center gap-3 px-4 py-3 text-left",
1320
+ onClick && "cursor-pointer bg-transparent hover:bg-transparent active:bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-foreground/10",
1321
+ className
1322
+ );
1323
+ if (onClick) {
1324
+ return /* @__PURE__ */ jsx26(
1325
+ "button",
1326
+ {
1327
+ type: "button",
1328
+ role: "listitem",
1329
+ onClick,
1330
+ "aria-label": ariaLabel,
1331
+ className: rowClass2,
1332
+ children: inner
1333
+ }
1334
+ );
1335
+ }
1336
+ return /* @__PURE__ */ jsx26("div", { role: "listitem", className: rowClass2, children: inner });
1337
+ };
1338
+ var connectionRowListClass = cn(
1339
+ "overflow-hidden rounded-2xl",
1340
+ TIMBAL_V2_ELEVATED_SURFACE
1341
+ );
1342
+
1343
+ // src/app/integrations/ConnectionRowList.tsx
1344
+ import { jsx as jsx27 } from "react/jsx-runtime";
1345
+ var ConnectionRowList = ({
1346
+ children,
1347
+ "aria-label": ariaLabel = "Connected integrations",
1348
+ className
1349
+ }) => /* @__PURE__ */ jsx27(
1350
+ "div",
1351
+ {
1352
+ role: "list",
1353
+ "aria-label": ariaLabel,
1354
+ className: cn(connectionRowListClass, "divide-y divide-border", className),
1355
+ children
1356
+ }
1357
+ );
1358
+
1359
+ // src/app/navigation/SubNav.tsx
1360
+ import { jsx as jsx28 } from "react/jsx-runtime";
1361
+ var SubNav = ({
1362
+ items,
1363
+ activeId,
1364
+ onChange,
1365
+ className,
1366
+ "aria-label": ariaLabel = "Section navigation",
1367
+ layoutId
1368
+ }) => {
1369
+ return /* @__PURE__ */ jsx28("nav", { className: cn("aui-app-sub-nav", className), "aria-label": ariaLabel, children: /* @__PURE__ */ jsx28(
1370
+ PillSegmentedTabs,
1371
+ {
1372
+ value: activeId,
1373
+ onChange,
1374
+ tabs: items.map((item) => ({ key: item.id, label: item.label })),
1375
+ trackVariant: "flush",
1376
+ layoutId: layoutId ?? "app-sub-nav-segmented",
1377
+ "aria-label": ariaLabel
1378
+ }
1379
+ ) });
1380
+ };
1381
+
1382
+ // src/app/navigation/Breadcrumbs.tsx
1383
+ import { jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
1384
+ var Breadcrumbs = ({ items, className }) => {
1385
+ return /* @__PURE__ */ jsx29("nav", { className: cn("aui-app-breadcrumbs", appBreadcrumbsClass, className), "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx29("ol", { className: "flex flex-wrap items-center gap-1.5", children: items.map((item, index) => {
1386
+ const isLast = index === items.length - 1;
1387
+ return /* @__PURE__ */ jsxs22("li", { className: "inline-flex items-center gap-1.5", children: [
1388
+ index > 0 ? /* @__PURE__ */ jsx29("span", { className: "text-muted-foreground/50", "aria-hidden": true, children: "/" }) : null,
1389
+ isLast ? /* @__PURE__ */ jsx29("span", { className: "text-foreground", "aria-current": "page", children: item.label }) : item.href ? /* @__PURE__ */ jsx29("a", { href: item.href, className: appBreadcrumbLinkClass, children: item.label }) : /* @__PURE__ */ jsx29(
1390
+ "button",
1391
+ {
1392
+ type: "button",
1393
+ className: appBreadcrumbLinkClass,
1394
+ onClick: item.onClick,
1395
+ children: item.label
1396
+ }
1397
+ )
1398
+ ] }, index);
1399
+ }) }) });
1400
+ };
1401
+
1402
+ // src/app/forms/Field.tsx
1403
+ import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
1404
+ var Field = ({
1405
+ label,
1406
+ hint,
1407
+ error,
1408
+ children,
1409
+ className,
1410
+ htmlFor
1411
+ }) => {
1412
+ return /* @__PURE__ */ jsxs23("div", { className: cn("aui-app-field", appFieldClass, className), children: [
1413
+ /* @__PURE__ */ jsx30("label", { className: appFieldLabelClass, htmlFor, children: label }),
1414
+ children,
1415
+ hint && !error ? /* @__PURE__ */ jsx30("p", { className: appFieldHintClass, children: hint }) : null,
1416
+ error ? /* @__PURE__ */ jsx30("p", { className: "text-xs text-destructive", role: "alert", children: error }) : null
1417
+ ] });
1418
+ };
1419
+ var FieldInput = ({
1420
+ label,
1421
+ hint,
1422
+ error,
1423
+ fieldClassName,
1424
+ className,
1425
+ id,
1426
+ ...inputProps
1427
+ }) => {
1428
+ const inputId = id ?? inputProps.name;
1429
+ return /* @__PURE__ */ jsx30(
1430
+ Field,
1431
+ {
1432
+ label,
1433
+ hint,
1434
+ error,
1435
+ htmlFor: inputId,
1436
+ className: fieldClassName,
1437
+ children: /* @__PURE__ */ jsx30(
1438
+ "input",
1439
+ {
1440
+ id: inputId,
1441
+ className: cn(appInputClass, className),
1442
+ "aria-invalid": error ? true : void 0,
1443
+ ...inputProps
1444
+ }
1445
+ )
1446
+ }
1447
+ );
1448
+ };
1449
+
1450
+ // src/app/forms/FieldTextarea.tsx
1451
+ import { jsx as jsx31 } from "react/jsx-runtime";
1452
+ var textareaClass = cn(
1453
+ appInputClass,
1454
+ "min-h-[5.5rem] resize-y py-2.5 leading-relaxed"
1455
+ );
1456
+ var FieldTextarea = ({
1457
+ label,
1458
+ hint,
1459
+ error,
1460
+ fieldClassName,
1461
+ className,
1462
+ id,
1463
+ ...props
1464
+ }) => {
1465
+ const textareaId = id ?? props.name;
1466
+ return /* @__PURE__ */ jsx31(
1467
+ Field,
1468
+ {
1469
+ label,
1470
+ hint,
1471
+ error,
1472
+ htmlFor: textareaId,
1473
+ className: fieldClassName,
1474
+ children: /* @__PURE__ */ jsx31(
1475
+ "textarea",
1476
+ {
1477
+ id: textareaId,
1478
+ className: cn(textareaClass, className),
1479
+ "aria-invalid": error ? true : void 0,
1480
+ ...props
1481
+ }
1482
+ )
1483
+ }
1484
+ );
1485
+ };
1486
+
1487
+ // src/app/forms/FieldSelect.tsx
1488
+ import { ChevronDownIcon } from "lucide-react";
1489
+ import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
1490
+ var selectWrapClass = "relative";
1491
+ var selectClass = cn(
1492
+ appInputClass,
1493
+ "appearance-none pr-9"
1494
+ );
1495
+ var FieldSelect = ({
1496
+ label,
1497
+ hint,
1498
+ error,
1499
+ fieldClassName,
1500
+ className,
1501
+ children,
1502
+ id,
1503
+ ...props
1504
+ }) => {
1505
+ const selectId = id ?? props.name;
1506
+ return /* @__PURE__ */ jsx32(
1507
+ Field,
1508
+ {
1509
+ label,
1510
+ hint,
1511
+ error,
1512
+ htmlFor: selectId,
1513
+ className: fieldClassName,
1514
+ children: /* @__PURE__ */ jsxs24("div", { className: selectWrapClass, children: [
1515
+ /* @__PURE__ */ jsx32(
1516
+ "select",
1517
+ {
1518
+ id: selectId,
1519
+ className: cn(selectClass, className),
1520
+ "aria-invalid": error ? true : void 0,
1521
+ ...props,
1522
+ children
1523
+ }
1524
+ ),
1525
+ /* @__PURE__ */ jsx32(
1526
+ ChevronDownIcon,
1527
+ {
1528
+ className: "pointer-events-none absolute top-1/2 right-3 size-4 -translate-y-1/2 text-muted-foreground",
1529
+ "aria-hidden": true
1530
+ }
1531
+ )
1532
+ ] })
1533
+ }
1534
+ );
1535
+ };
1536
+
1537
+ // src/app/forms/FieldSwitch.tsx
1538
+ import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
1539
+ var trackClass = cn(
1540
+ "relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-[background,box-shadow,border-color] duration-200",
1541
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-foreground/10",
1542
+ "peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
1543
+ TIMBAL_V2_SWITCH_TRACK_OFF,
1544
+ "peer-checked:border-foreground/15 peer-checked:from-primary-fill-from peer-checked:to-primary-fill-to peer-checked:shadow-card",
1545
+ "peer-checked:[&>span]:translate-x-4"
1546
+ );
1547
+ var thumbClass = cn(
1548
+ "pointer-events-none inline-block size-4 shrink-0 translate-x-0.5 rounded-full transition-transform",
1549
+ TIMBAL_V2_SWITCH_THUMB
1550
+ );
1551
+ var FieldSwitch = ({
1552
+ label,
1553
+ description,
1554
+ className,
1555
+ id,
1556
+ ...props
1557
+ }) => {
1558
+ const inputId = id ?? props.name ?? "switch";
1559
+ return /* @__PURE__ */ jsxs25(
1560
+ "label",
1561
+ {
1562
+ className: cn(
1563
+ "aui-app-field-switch flex cursor-pointer items-start gap-3",
1564
+ className
1565
+ ),
1566
+ htmlFor: inputId,
1567
+ children: [
1568
+ /* @__PURE__ */ jsxs25("span", { className: "relative mt-0.5", children: [
1569
+ /* @__PURE__ */ jsx33(
1570
+ "input",
1571
+ {
1572
+ id: inputId,
1573
+ type: "checkbox",
1574
+ role: "switch",
1575
+ className: "peer sr-only",
1576
+ ...props
1577
+ }
1578
+ ),
1579
+ /* @__PURE__ */ jsx33("span", { className: trackClass, "aria-hidden": true, children: /* @__PURE__ */ jsx33("span", { className: thumbClass }) })
1580
+ ] }),
1581
+ /* @__PURE__ */ jsxs25("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
1582
+ /* @__PURE__ */ jsx33("span", { className: "text-sm font-medium text-foreground", children: label }),
1583
+ description ? /* @__PURE__ */ jsx33("span", { className: "text-xs text-muted-foreground", children: description }) : null
1584
+ ] })
1585
+ ]
1586
+ }
1587
+ );
1588
+ };
1589
+
1590
+ // src/app/forms/SearchInput.tsx
1591
+ import { SearchIcon } from "lucide-react";
1592
+ import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
1593
+ var SearchInput = ({
1594
+ className,
1595
+ placeholder = "Search\u2026",
1596
+ ...props
1597
+ }) => {
1598
+ return /* @__PURE__ */ jsxs26(
1599
+ "label",
1600
+ {
1601
+ className: cn(
1602
+ "aui-app-search-input inline-flex min-w-[12rem] items-center gap-2",
1603
+ appSearchInputClass,
1604
+ className
1605
+ ),
1606
+ children: [
1607
+ /* @__PURE__ */ jsx34(SearchIcon, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
1608
+ /* @__PURE__ */ jsx34(
1609
+ "input",
1610
+ {
1611
+ type: "search",
1612
+ placeholder,
1613
+ className: "min-w-0 flex-1 border-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground/70",
1614
+ ...props
1615
+ }
1616
+ )
1617
+ ]
1618
+ }
1619
+ );
1620
+ };
1621
+
1622
+ // src/app/forms/FormSection.tsx
1623
+ import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
1624
+ var FormSection = ({ title, children, className }) => {
1625
+ return /* @__PURE__ */ jsxs27("fieldset", { className: cn("aui-app-form-section", appSectionClass, "border-0 p-0", className), children: [
1626
+ title ? /* @__PURE__ */ jsx35("legend", { className: cn(appSectionTitleClass, "mb-3 px-0"), children: title }) : null,
1627
+ /* @__PURE__ */ jsx35("div", { className: "flex flex-col gap-4", children })
1628
+ ] });
1629
+ };
1630
+
1631
+ // src/app/data/FilterBar.tsx
1632
+ import { jsx as jsx36 } from "react/jsx-runtime";
1633
+ var FilterBar = ({ children, className }) => {
1634
+ return /* @__PURE__ */ jsx36(
1635
+ "div",
1636
+ {
1637
+ className: cn("aui-app-filter-bar", appFilterBarClass, className),
1638
+ role: "toolbar",
1639
+ "aria-label": "Filters",
1640
+ children
1641
+ }
1642
+ );
1643
+ };
1644
+
1645
+ // src/app/data/DataTable.tsx
1646
+ import { useMemo, useState as useState4 } from "react";
1647
+ import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon } from "lucide-react";
1648
+ import { jsx as jsx37, jsxs as jsxs28 } from "react/jsx-runtime";
1649
+ var shellClass2 = "overflow-hidden rounded-xl border border-border bg-card shadow-card";
1650
+ var tableClass = "w-full border-collapse bg-transparent text-sm";
1651
+ 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";
1652
+ var bodyCellClass = "border-b border-border/40 bg-transparent px-4 py-2.5 text-foreground";
1653
+ var rowClass = "bg-transparent transition-colors hover:bg-foreground/[0.03] data-[clickable=true]:cursor-pointer";
1654
+ var footCellClass = "border-t border-border/60 bg-transparent px-4 py-2.5 text-xs text-muted-foreground";
1655
+ var footInnerClass = "flex flex-wrap items-center gap-2";
1656
+ var emptyCellClass = "bg-transparent px-4 py-10 text-center text-sm text-muted-foreground";
1657
+ 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";
1658
+ 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";
1659
+ var alignClass = {
1660
+ left: "text-left",
1661
+ center: "text-center",
1662
+ right: "text-right"
1663
+ };
1664
+ function compareSortValues(a, b) {
1665
+ if (a == null && b == null) return 0;
1666
+ if (a == null) return 1;
1667
+ if (b == null) return -1;
1668
+ if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
1669
+ if (typeof a === "number" && typeof b === "number") return a - b;
1670
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
1671
+ return String(a).localeCompare(String(b), void 0, { sensitivity: "base" });
1672
+ }
1673
+ function nextSort(current, columnId) {
1674
+ if (current?.columnId !== columnId) {
1675
+ return { columnId, direction: "asc" };
1676
+ }
1677
+ if (current.direction === "asc") {
1678
+ return { columnId, direction: "desc" };
1679
+ }
1680
+ return null;
1681
+ }
1682
+ function SortIndicator({
1683
+ active,
1684
+ direction
1685
+ }) {
1686
+ const iconClass = "size-3.5 shrink-0 opacity-60 group-hover:opacity-100";
1687
+ if (!active) {
1688
+ return /* @__PURE__ */ jsx37(ArrowUpDownIcon, { className: iconClass, "aria-hidden": true });
1689
+ }
1690
+ if (direction === "desc") {
1691
+ return /* @__PURE__ */ jsx37(ArrowDownIcon, { className: iconClass, "aria-hidden": true });
1692
+ }
1693
+ return /* @__PURE__ */ jsx37(ArrowUpIcon, { className: iconClass, "aria-hidden": true });
1694
+ }
1695
+ function DataTable({
1696
+ columns,
1697
+ rows,
1698
+ getRowKey,
1699
+ emptyTitle = "No data",
1700
+ emptyDescription,
1701
+ emptyMode = "replace",
1702
+ className,
1703
+ sort: sortProp,
1704
+ defaultSort = null,
1705
+ onSortChange,
1706
+ showRowCount = false,
1707
+ rowCountLabel,
1708
+ footer,
1709
+ onRowClick,
1710
+ stickyHeader = false,
1711
+ dense = false,
1712
+ caption
1713
+ }) {
1714
+ const [uncontrolledSort, setUncontrolledSort] = useState4(
1715
+ defaultSort
1716
+ );
1717
+ const isSortControlled = sortProp !== void 0;
1718
+ const sort = isSortControlled ? sortProp : uncontrolledSort;
1719
+ const setSort = (next) => {
1720
+ if (!isSortControlled) {
1721
+ setUncontrolledSort(next);
1722
+ }
1723
+ onSortChange?.(next);
1724
+ };
1725
+ const sortedRows = useMemo(() => {
1726
+ if (!sort) return rows;
1727
+ const column = columns.find((col) => col.id === sort.columnId);
1728
+ if (!column?.sortable) return rows;
1729
+ const getValue = column.sortValue ?? ((row) => {
1730
+ const rendered = column.cell(row);
1731
+ if (rendered === null || rendered === void 0 || typeof rendered === "string" || typeof rendered === "number" || typeof rendered === "boolean") {
1732
+ return rendered;
1733
+ }
1734
+ return String(rendered);
1735
+ });
1736
+ return [...rows].sort((a, b) => {
1737
+ const cmp = compareSortValues(getValue(a), getValue(b));
1738
+ return sort.direction === "asc" ? cmp : -cmp;
1739
+ });
1740
+ }, [columns, rows, sort]);
1741
+ const cellPad = dense ? "px-3 py-2" : void 0;
1742
+ const headPad = dense ? "px-3 py-2" : void 0;
1743
+ if (rows.length === 0 && emptyMode === "replace") {
1744
+ return /* @__PURE__ */ jsx37(EmptyState, { title: emptyTitle, description: emptyDescription, className });
1745
+ }
1746
+ const rowCountText = rowCountLabel?.(sortedRows.length) ?? `${sortedRows.length} row${sortedRows.length === 1 ? "" : "s"}`;
1747
+ const hasFoot = Boolean((showRowCount || footer) && sortedRows.length > 0);
1748
+ return /* @__PURE__ */ jsx37("div", { className: cn("aui-app-data-table", shellClass2, className), children: /* @__PURE__ */ jsx37("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs28("table", { className: tableClass, children: [
1749
+ caption ? /* @__PURE__ */ jsx37("caption", { className: "sr-only", children: caption }) : null,
1750
+ /* @__PURE__ */ jsx37("thead", { className: cn(stickyHeader && stickyHeadClass), children: /* @__PURE__ */ jsx37("tr", { children: columns.map((col) => {
1751
+ const isSorted = sort?.columnId === col.id;
1752
+ const ariaSort = col.sortable ? isSorted ? sort.direction === "asc" ? "ascending" : "descending" : "none" : void 0;
1753
+ const headerContent = col.sortable ? /* @__PURE__ */ jsxs28(
1754
+ "button",
1755
+ {
1756
+ type: "button",
1757
+ className: sortButtonClass,
1758
+ onClick: () => setSort(nextSort(sort, col.id)),
1759
+ children: [
1760
+ /* @__PURE__ */ jsx37("span", { className: "truncate", children: col.header }),
1761
+ /* @__PURE__ */ jsx37(SortIndicator, { active: Boolean(isSorted), direction: sort?.direction })
1762
+ ]
1763
+ }
1764
+ ) : col.header;
1765
+ return /* @__PURE__ */ jsx37(
1766
+ "th",
1767
+ {
1768
+ scope: "col",
1769
+ "aria-sort": ariaSort,
1770
+ className: cn(
1771
+ headCellClass,
1772
+ headPad,
1773
+ col.align && alignClass[col.align],
1774
+ col.headerClassName
1775
+ ),
1776
+ children: headerContent
1777
+ },
1778
+ col.id
1779
+ );
1780
+ }) }) }),
1781
+ /* @__PURE__ */ jsx37("tbody", { className: cn(!hasFoot && "[&_tr:last-child_td]:border-b-0"), children: sortedRows.length === 0 ? /* @__PURE__ */ jsx37("tr", { children: /* @__PURE__ */ jsx37("td", { colSpan: columns.length, className: emptyCellClass, children: /* @__PURE__ */ jsxs28("div", { className: "flex flex-col items-center gap-1", children: [
1782
+ /* @__PURE__ */ jsx37("p", { className: "font-medium text-foreground", children: emptyTitle }),
1783
+ emptyDescription ? /* @__PURE__ */ jsx37("p", { className: "max-w-sm text-muted-foreground", children: emptyDescription }) : null
1784
+ ] }) }) }) : sortedRows.map((row) => /* @__PURE__ */ jsx37(
1785
+ "tr",
1786
+ {
1787
+ className: rowClass,
1788
+ "data-clickable": onRowClick ? "true" : void 0,
1789
+ onClick: onRowClick ? () => onRowClick(row) : void 0,
1790
+ onKeyDown: onRowClick ? (event) => {
1791
+ if (event.key === "Enter" || event.key === " ") {
1792
+ event.preventDefault();
1793
+ onRowClick(row);
1794
+ }
1795
+ } : void 0,
1796
+ tabIndex: onRowClick ? 0 : void 0,
1797
+ role: onRowClick ? "button" : void 0,
1798
+ children: columns.map((col) => /* @__PURE__ */ jsx37(
1799
+ "td",
1800
+ {
1801
+ className: cn(
1802
+ bodyCellClass,
1803
+ cellPad,
1804
+ col.align && alignClass[col.align],
1805
+ col.className
1806
+ ),
1807
+ children: col.cell(row)
1808
+ },
1809
+ col.id
1810
+ ))
1811
+ },
1812
+ getRowKey(row)
1813
+ )) }),
1814
+ hasFoot ? /* @__PURE__ */ jsx37("tfoot", { children: /* @__PURE__ */ jsx37("tr", { children: /* @__PURE__ */ jsx37("td", { colSpan: columns.length, className: footCellClass, children: /* @__PURE__ */ jsxs28(
1815
+ "div",
1816
+ {
1817
+ className: cn(
1818
+ footInnerClass,
1819
+ showRowCount && footer ? "justify-between" : "justify-start"
1820
+ ),
1821
+ children: [
1822
+ showRowCount ? /* @__PURE__ */ jsx37("span", { children: rowCountText }) : null,
1823
+ footer
1824
+ ]
1825
+ }
1826
+ ) }) }) }) : null
1827
+ ] }) }) });
1828
+ }
1829
+
1830
+ // src/app/data/ChartPanel.tsx
1831
+ import { useId as useId4 } from "react";
1832
+
1833
+ // src/app/data/metrics-shared.tsx
1834
+ import { jsx as jsx38, jsxs as jsxs29 } from "react/jsx-runtime";
1835
+ var metricCardShellClass = cn(
1836
+ studioIntegrationCardClass,
1837
+ "aui-app-metric-card shadow-none",
1838
+ "flex flex-col overflow-hidden"
1839
+ );
1840
+ var metricCardHeaderClass = "flex items-start justify-between gap-3 px-4 pb-1 pt-3";
1841
+ var metricTilesRowClass = "grid w-full min-w-0";
1842
+ var metricChartRegionClass = "relative min-h-0 w-full border-t border-border/40 pt-2";
1843
+ var metricChartPlotRegionClass = "relative min-h-0 w-full border-t border-border/40 px-0 pt-5 pb-3";
1844
+ var metricCellDividerClass = "border-r border-border/40";
1845
+ var MetricCardHeader = ({
1846
+ title,
1847
+ titleId,
1848
+ description,
1849
+ actions
1850
+ }) => {
1851
+ if (!title && !description && !actions) return null;
1852
+ return /* @__PURE__ */ jsxs29("header", { className: metricCardHeaderClass, children: [
1853
+ /* @__PURE__ */ jsxs29("div", { className: "min-w-0", children: [
1854
+ title ? /* @__PURE__ */ jsx38("h3", { id: titleId, className: "text-base font-normal text-foreground", children: title }) : null,
1855
+ description ? /* @__PURE__ */ jsx38("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
1856
+ ] }),
1857
+ actions ? /* @__PURE__ */ jsx38("div", { className: "shrink-0", children: actions }) : null
1858
+ ] });
1859
+ };
1860
+ function metricTilesGridColsClass(n) {
1861
+ switch (n) {
1862
+ case 1:
1863
+ return "grid-cols-1";
1864
+ case 2:
1865
+ return "grid-cols-2";
1866
+ case 3:
1867
+ return "grid-cols-3";
1868
+ case 5:
1869
+ return "grid-cols-2 sm:grid-cols-5";
1870
+ case 6:
1871
+ return "grid-cols-2 sm:grid-cols-3 lg:grid-cols-6";
1872
+ default:
1873
+ return "grid-cols-2 md:grid-cols-4";
1874
+ }
1875
+ }
1876
+
1877
+ // src/app/data/ChartPanel.tsx
1878
+ import { jsx as jsx39, jsxs as jsxs30 } from "react/jsx-runtime";
1879
+ var ChartPanel = ({
1880
+ title,
1881
+ description,
1882
+ artifact,
1883
+ children,
1884
+ actions,
1885
+ height = 300,
1886
+ className
1887
+ }) => {
1888
+ const titleId = useId4();
1889
+ const resolvedTitle = title ?? artifact?.title;
1890
+ const hasHeader = Boolean(resolvedTitle || description || actions);
1891
+ const body = children ?? (artifact ? /* @__PURE__ */ jsx39(ChartArtifactView, { artifact, embedded: true, height }) : null);
1892
+ return /* @__PURE__ */ jsxs30(
1893
+ "section",
1894
+ {
1895
+ className: cn(metricCardShellClass, "aui-app-chart-panel", className),
1896
+ "aria-labelledby": resolvedTitle ? titleId : void 0,
1897
+ children: [
1898
+ /* @__PURE__ */ jsx39(
1899
+ MetricCardHeader,
1900
+ {
1901
+ title: resolvedTitle,
1902
+ titleId,
1903
+ description,
1904
+ actions
1905
+ }
1906
+ ),
1907
+ /* @__PURE__ */ jsx39(
1908
+ "div",
1909
+ {
1910
+ className: cn(
1911
+ "relative min-h-0 w-full",
1912
+ hasHeader ? metricChartPlotRegionClass : "pt-2 pb-3"
1913
+ ),
1914
+ children: body ?? /* @__PURE__ */ jsx39(
1915
+ "div",
1916
+ {
1917
+ className: "flex items-center justify-center text-sm font-normal text-muted-foreground",
1918
+ style: { minHeight: height },
1919
+ role: "status",
1920
+ children: "No chart"
1921
+ }
1922
+ )
1923
+ }
1924
+ )
1925
+ ]
1926
+ }
1927
+ );
1928
+ };
1929
+
1930
+ // src/app/data/MetricTile.tsx
1931
+ import { Fragment as Fragment4, jsx as jsx40, jsxs as jsxs31 } from "react/jsx-runtime";
1932
+ var trendToneClass = {
1933
+ up: "border-border/80 bg-muted/40 text-muted-foreground",
1934
+ down: "border-border/80 bg-muted/40 text-muted-foreground",
1935
+ neutral: "border-border/80 bg-muted/30 text-muted-foreground"
1936
+ };
1937
+ var metricTileBaseClass = "relative flex min-w-0 flex-1 flex-col gap-1 px-4 py-3 text-left font-normal";
1938
+ var metricTileInteractiveClass = cn(
1939
+ metricTileBaseClass,
1940
+ "bg-transparent hover:bg-transparent active:bg-transparent",
1941
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-foreground/10"
1942
+ );
1943
+ var MetricTile = ({
1944
+ label,
1945
+ value,
1946
+ unit,
1947
+ trend,
1948
+ trendTone = "neutral",
1949
+ active = false,
1950
+ showDivider = false,
1951
+ onSelect,
1952
+ ariaLabel,
1953
+ className
1954
+ }) => {
1955
+ const content = /* @__PURE__ */ jsxs31(Fragment4, { children: [
1956
+ active ? /* @__PURE__ */ jsx40(
1957
+ "span",
1958
+ {
1959
+ "aria-hidden": true,
1960
+ className: "absolute inset-x-0 bottom-0 h-0.5 bg-foreground dark:bg-white"
1961
+ }
1962
+ ) : null,
1963
+ /* @__PURE__ */ jsx40("span", { className: "text-xs font-normal text-muted-foreground", children: label }),
1964
+ /* @__PURE__ */ jsxs31("span", { className: "flex items-center gap-2", children: [
1965
+ /* @__PURE__ */ jsxs31("span", { className: "flex items-baseline gap-1", children: [
1966
+ /* @__PURE__ */ jsx40("span", { className: "text-2xl font-normal tracking-tight text-foreground tabular-nums", children: value }),
1967
+ unit ? /* @__PURE__ */ jsx40("span", { className: "text-xs font-normal text-muted-foreground", children: unit }) : null
1968
+ ] }),
1969
+ trend ? /* @__PURE__ */ jsx40(
1970
+ "span",
1971
+ {
1972
+ className: cn(
1973
+ "rounded-full border px-1.5 py-0.5 text-xs font-normal",
1974
+ trendToneClass[trendTone]
1975
+ ),
1976
+ children: trend
1977
+ }
1978
+ ) : null
1979
+ ] })
1980
+ ] });
1981
+ const divider = showDivider ? metricCellDividerClass : void 0;
1982
+ if (onSelect) {
1983
+ return /* @__PURE__ */ jsx40(
1984
+ "button",
1985
+ {
1986
+ type: "button",
1987
+ onClick: onSelect,
1988
+ "aria-pressed": active,
1989
+ "aria-label": ariaLabel,
1990
+ className: cn(metricTileInteractiveClass, divider, className),
1991
+ children: content
1992
+ }
1993
+ );
1994
+ }
1995
+ return /* @__PURE__ */ jsx40("div", { className: cn(metricTileBaseClass, divider, className), children: content });
1996
+ };
1997
+
1998
+ // src/app/data/MetricRow.tsx
1999
+ import { useId as useId5, useState as useState5 } from "react";
2000
+ import { jsx as jsx41, jsxs as jsxs32 } from "react/jsx-runtime";
2001
+ var MetricRow = ({
2002
+ title,
2003
+ description,
2004
+ actions,
2005
+ metrics,
2006
+ activeMetricId,
2007
+ defaultActiveMetricId,
2008
+ onMetricChange,
2009
+ metricsAriaLabel = "Metrics",
2010
+ className
2011
+ }) => {
2012
+ const titleId = useId5();
2013
+ const selectable = onMetricChange != null || activeMetricId != null;
2014
+ const [internalId, setInternalId] = useState5(
2015
+ defaultActiveMetricId ?? metrics[0]?.id
2016
+ );
2017
+ const activeId = activeMetricId ?? internalId;
2018
+ const select = (id) => {
2019
+ if (activeMetricId == null) setInternalId(id);
2020
+ onMetricChange?.(id);
2021
+ };
2022
+ return /* @__PURE__ */ jsxs32(
2023
+ "section",
2024
+ {
2025
+ className: cn(metricCardShellClass, className),
2026
+ "aria-labelledby": title ? titleId : void 0,
2027
+ children: [
2028
+ /* @__PURE__ */ jsx41(
2029
+ MetricCardHeader,
2030
+ {
2031
+ title,
2032
+ titleId,
2033
+ description,
2034
+ actions
2035
+ }
2036
+ ),
2037
+ /* @__PURE__ */ jsx41(
2038
+ "div",
2039
+ {
2040
+ role: selectable ? "group" : void 0,
2041
+ "aria-label": selectable ? metricsAriaLabel : void 0,
2042
+ className: cn(
2043
+ metricTilesRowClass,
2044
+ metricTilesGridColsClass(metrics.length),
2045
+ (title || description || actions) && "mt-3"
2046
+ ),
2047
+ children: metrics.map((m, index) => /* @__PURE__ */ jsx41(
2048
+ MetricTile,
2049
+ {
2050
+ label: m.label,
2051
+ value: m.value,
2052
+ unit: m.unit,
2053
+ trend: m.trend,
2054
+ trendTone: m.trendTone,
2055
+ active: selectable && m.id === activeId,
2056
+ showDivider: index < metrics.length - 1,
2057
+ onSelect: selectable ? () => select(m.id) : void 0
2058
+ },
2059
+ m.id
2060
+ ))
2061
+ }
2062
+ )
2063
+ ]
2064
+ }
2065
+ );
2066
+ };
2067
+
2068
+ // src/app/data/MetricChartCard.tsx
2069
+ import { useId as useId6, useState as useState6 } from "react";
2070
+ import { jsx as jsx42, jsxs as jsxs33 } from "react/jsx-runtime";
2071
+ var MetricChartCard = ({
2072
+ title,
2073
+ description,
2074
+ actions,
2075
+ metrics,
2076
+ activeMetricId,
2077
+ defaultActiveMetricId,
2078
+ onMetricChange,
2079
+ xKey = "date",
2080
+ variant = "area",
2081
+ height = 300,
2082
+ formatX,
2083
+ formatValue,
2084
+ emptyLabel = "No data yet",
2085
+ metricsAriaLabel = "Metrics",
2086
+ className
2087
+ }) => {
2088
+ const titleId = useId6();
2089
+ const [internalId, setInternalId] = useState6(
2090
+ defaultActiveMetricId ?? metrics[0]?.id
2091
+ );
2092
+ const activeId = activeMetricId ?? internalId;
2093
+ const active = metrics.find((m) => m.id === activeId) ?? metrics[0];
2094
+ const select = (id) => {
2095
+ if (activeMetricId == null) setInternalId(id);
2096
+ onMetricChange?.(id);
2097
+ };
2098
+ const hasHeader = Boolean(title || description || actions);
2099
+ const chartAriaLabel = typeof active?.label === "string" ? `${active.label} over time` : "Metric chart";
2100
+ return /* @__PURE__ */ jsxs33(
2101
+ "section",
2102
+ {
2103
+ className: cn(metricCardShellClass, className),
2104
+ "aria-labelledby": title ? titleId : void 0,
2105
+ children: [
2106
+ /* @__PURE__ */ jsx42(
2107
+ MetricCardHeader,
2108
+ {
2109
+ title,
2110
+ titleId,
2111
+ description,
2112
+ actions
2113
+ }
2114
+ ),
2115
+ /* @__PURE__ */ jsx42(
2116
+ "div",
2117
+ {
2118
+ role: "group",
2119
+ "aria-label": metricsAriaLabel,
2120
+ className: cn(
2121
+ metricTilesRowClass,
2122
+ metricTilesGridColsClass(metrics.length),
2123
+ hasHeader && "mt-3"
2124
+ ),
2125
+ children: metrics.map((m, index) => /* @__PURE__ */ jsx42(
2126
+ MetricTile,
2127
+ {
2128
+ label: m.label,
2129
+ value: m.value,
2130
+ unit: m.unit,
2131
+ trend: m.trend,
2132
+ trendTone: m.trendTone,
2133
+ active: m.id === active?.id,
2134
+ showDivider: index < metrics.length - 1,
2135
+ onSelect: () => select(m.id)
2136
+ },
2137
+ m.id
2138
+ ))
2139
+ }
2140
+ ),
2141
+ /* @__PURE__ */ jsx42("div", { className: metricChartRegionClass, "aria-live": "polite", "aria-atomic": "true", children: active?.data && active.data.length > 0 ? /* @__PURE__ */ jsx42(
2142
+ LineAreaChart,
2143
+ {
2144
+ data: active.data,
2145
+ xKey,
2146
+ series: [
2147
+ {
2148
+ dataKey: active.dataKey ?? "value",
2149
+ label: typeof active.label === "string" ? active.label : active.id,
2150
+ color: active.color
2151
+ }
2152
+ ],
2153
+ variant,
2154
+ layout: "flush",
2155
+ height,
2156
+ formatX,
2157
+ formatValue,
2158
+ ariaLabel: chartAriaLabel
2159
+ },
2160
+ active.id
2161
+ ) : /* @__PURE__ */ jsx42(
2162
+ "div",
2163
+ {
2164
+ className: "flex w-full items-center justify-center text-sm font-normal text-muted-foreground",
2165
+ style: { height },
2166
+ role: "status",
2167
+ children: emptyLabel
2168
+ }
2169
+ ) })
2170
+ ]
2171
+ }
2172
+ );
2173
+ };
2174
+
2175
+ // src/charts/sparkline.tsx
2176
+ import { useId as useId7 } from "react";
2177
+ import { Fragment as Fragment5, jsx as jsx43, jsxs as jsxs34 } from "react/jsx-runtime";
2178
+ var Sparkline = ({
2179
+ data,
2180
+ dataKey = "value",
2181
+ color = "var(--primary, #6366f1)",
2182
+ area = true,
2183
+ width = 96,
2184
+ height = 28,
2185
+ strokeWidth = 1.5,
2186
+ className,
2187
+ ariaLabel = "Trend"
2188
+ }) => {
2189
+ const uid = useId7();
2190
+ const values = data.map((d) => typeof d === "number" ? d : toNum(d[dataKey]));
2191
+ if (values.length === 0) {
2192
+ return /* @__PURE__ */ jsx43("span", { className: cn("inline-block", className), style: { width, height } });
2193
+ }
2194
+ const pad = strokeWidth + 1;
2195
+ const min = Math.min(...values);
2196
+ const max = Math.max(...values);
2197
+ const range = max - min || 1;
2198
+ const innerW = width - pad * 2;
2199
+ const innerH = height - pad * 2;
2200
+ const points = values.map((v, i) => ({
2201
+ x: pad + (values.length > 1 ? i / (values.length - 1) * innerW : innerW / 2),
2202
+ y: pad + innerH - (v - min) / range * innerH
2203
+ }));
2204
+ return /* @__PURE__ */ jsxs34(
2205
+ "svg",
2206
+ {
2207
+ width,
2208
+ height,
2209
+ viewBox: `0 0 ${width} ${height}`,
2210
+ className: cn("block", className),
2211
+ role: "img",
2212
+ "aria-label": ariaLabel,
2213
+ preserveAspectRatio: "none",
2214
+ children: [
2215
+ area && /* @__PURE__ */ jsxs34(Fragment5, { children: [
2216
+ /* @__PURE__ */ jsx43("defs", { children: /* @__PURE__ */ jsxs34("linearGradient", { id: `${uid}-spark`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
2217
+ /* @__PURE__ */ jsx43("stop", { offset: "0%", style: { stopColor: color, stopOpacity: 0.25 } }),
2218
+ /* @__PURE__ */ jsx43("stop", { offset: "100%", style: { stopColor: color, stopOpacity: 0 } })
2219
+ ] }) }),
2220
+ /* @__PURE__ */ jsx43("path", { d: monotoneAreaPath(points, height - pad), fill: `url(#${uid}-spark)` })
2221
+ ] }),
2222
+ /* @__PURE__ */ jsx43(
2223
+ "path",
2224
+ {
2225
+ d: monotoneLinePath(points),
2226
+ fill: "none",
2227
+ stroke: color,
2228
+ strokeWidth,
2229
+ strokeLinecap: "round",
2230
+ strokeLinejoin: "round"
2231
+ }
2232
+ )
2233
+ ]
2234
+ }
2235
+ );
2236
+ };
2237
+
2238
+ export {
2239
+ APP_KIT_AGENT_INSTRUCTIONS,
2240
+ appPageColumnClass,
2241
+ appShellTopbarInsetClass,
2242
+ appShellInsetTopClass,
2243
+ appSurfaceCardClass,
2244
+ appStatTileClass,
2245
+ appFilterBarClass,
2246
+ appSearchInputClass,
2247
+ useAppShellChat,
2248
+ AppShell,
2249
+ AppShellTopbar,
2250
+ AppShellChatTrigger,
2251
+ PageHeader,
2252
+ Page,
2253
+ Section,
2254
+ AppCopilotProvider,
2255
+ useAppCopilotContext,
2256
+ AppChatPanel,
2257
+ SurfaceCard,
2258
+ StatTile,
2259
+ EmptyState,
2260
+ StatusBadge,
2261
+ AppConfirmDialog,
2262
+ InfoCard,
2263
+ StatusDot,
2264
+ DescriptionList,
2265
+ ExpandableSection,
2266
+ ResourceCard,
2267
+ SettingsSectionHeader,
2268
+ SettingsSection,
2269
+ FieldRow,
2270
+ FloatingUnsavedChangesBar,
2271
+ DangerZoneAction,
2272
+ DangerZone,
2273
+ INTEGRATION_CATALOG_CARD_HEIGHT_CLASS,
2274
+ IntegrationCard,
2275
+ IntegrationsEmptyState,
2276
+ PlanBadge,
2277
+ ConnectionRow,
2278
+ connectionRowListClass,
2279
+ ConnectionRowList,
2280
+ SubNav,
2281
+ Breadcrumbs,
2282
+ Field,
2283
+ FieldInput,
2284
+ FieldTextarea,
2285
+ FieldSelect,
2286
+ FieldSwitch,
2287
+ SearchInput,
2288
+ FormSection,
2289
+ FilterBar,
2290
+ DataTable,
2291
+ ChartPanel,
2292
+ MetricTile,
2293
+ MetricRow,
2294
+ MetricChartCard,
2295
+ Sparkline
2296
+ };