@timbal-ai/timbal-react 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +24 -5
- package/dist/app.cjs +2282 -738
- package/dist/app.d.cts +4 -1
- package/dist/app.d.ts +4 -1
- package/dist/app.esm.js +58 -5
- package/dist/button-CIKzUrJI.d.cts +18 -0
- package/dist/button-CIKzUrJI.d.ts +18 -0
- package/dist/chart-artifact-BFDz8Tf9.d.ts +756 -0
- package/dist/chart-artifact-bWUa-iSG.d.cts +756 -0
- package/dist/chat.cjs +872 -562
- package/dist/chat.d.cts +2 -2
- package/dist/chat.d.ts +2 -2
- package/dist/chat.esm.js +3 -3
- package/dist/{chunk-4TCJQSIX.esm.js → chunk-2XZ3S4OP.esm.js} +14 -3
- package/dist/chunk-533MK5EA.esm.js +2294 -0
- package/dist/{chunk-OVHR7J3J.esm.js → chunk-7O5VY3TP.esm.js} +38 -11
- package/dist/{chunk-WLTW56MC.esm.js → chunk-N3PYVTY5.esm.js} +2 -2
- package/dist/{chunk-IYENDIRY.esm.js → chunk-TDIJHV4I.esm.js} +1 -1
- package/dist/{chunk-YJQLLFKP.esm.js → chunk-TLUF2RUL.esm.js} +813 -507
- package/dist/{chunk-OFHLFNJH.esm.js → chunk-Z27GBSOT.esm.js} +3 -1
- package/dist/index.cjs +2587 -1016
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.esm.js +57 -7
- package/dist/{layout-CQWngNQ7.d.ts → layout-BTJyU8wd.d.ts} +1 -1
- package/dist/{layout-B9VayJhZ.d.cts → layout-C2G-FcER.d.cts} +1 -1
- package/dist/studio.cjs +1127 -788
- package/dist/studio.d.cts +1 -1
- package/dist/studio.d.ts +1 -1
- package/dist/studio.esm.js +6 -6
- package/dist/{timbal-v2-button-F4-z7m33.d.ts → timbal-v2-button-CNfdwGq4.d.cts} +1 -1
- package/dist/{timbal-v2-button-F4-z7m33.d.cts → timbal-v2-button-CNfdwGq4.d.ts} +1 -1
- package/dist/ui.cjs +12 -3
- package/dist/ui.d.cts +5 -16
- package/dist/ui.d.ts +5 -16
- package/dist/ui.esm.js +2 -2
- package/dist/{welcome-BOizSp5h.d.ts → welcome-BBmB3tl7.d.ts} +4 -3
- package/dist/{welcome--80i_O0p.d.cts → welcome-C89Mgdaw.d.cts} +4 -3
- package/package.json +2 -1
- package/vite/local-dev.mjs +91 -5
- package/dist/chart-artifact-C71dk4xI.d.ts +0 -329
- package/dist/chart-artifact-CPEpOmtV.d.cts +0 -329
- package/dist/chunk-M4V6Q6XO.esm.js +0 -1082
|
@@ -0,0 +1,2294 @@
|
|
|
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-TLUF2RUL.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-12 shrink-0 items-center justify-end",
|
|
556
|
+
"bg-card/90 px-2 pt-3 pb-3 backdrop-blur-sm"
|
|
557
|
+
);
|
|
558
|
+
var closeButtonClass = cn(
|
|
559
|
+
"aui-app-chat-panel-close flex size-8 shrink-0 items-center justify-center rounded-md",
|
|
560
|
+
"text-muted-foreground transition-colors hover:bg-foreground/5 hover:text-foreground",
|
|
561
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
|
|
562
|
+
);
|
|
563
|
+
var bodyClass = cn(
|
|
564
|
+
"aui-app-chat-panel-body relative min-h-0 flex-1 overflow-hidden",
|
|
565
|
+
"[&_.aui-thread-root]:h-full",
|
|
566
|
+
"[&_.aui-thread-viewport]:scrollbar-thin",
|
|
567
|
+
"[&_.aui-thread-viewport]:[scrollbar-color:var(--border)_transparent]",
|
|
568
|
+
// Reserve the scrollbar gutter on BOTH edges so the composer + messages
|
|
569
|
+
// stay symmetric (otherwise the right-side scrollbar adds a phantom inset).
|
|
570
|
+
"[&_.aui-thread-viewport]:[scrollbar-gutter:stable_both-edges]",
|
|
571
|
+
// Tighter symmetric horizontal inset for panel + composer
|
|
572
|
+
"[&_.aui-thread-viewport]:!px-2",
|
|
573
|
+
"[&_.aui-thread-viewport]:!pt-2",
|
|
574
|
+
"[&_.aui-user-message-root]:!px-0",
|
|
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
|
+
composerPlaceholder,
|
|
590
|
+
components,
|
|
591
|
+
artifacts,
|
|
592
|
+
onArtifactEvent,
|
|
593
|
+
...rest
|
|
594
|
+
}) => {
|
|
595
|
+
const shellChat = useAppShellChat();
|
|
596
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn(shellClass, className), children: [
|
|
597
|
+
shellChat?.collapsible ? /* @__PURE__ */ jsx8("div", { className: chromeClass, children: /* @__PURE__ */ jsx8(
|
|
598
|
+
"button",
|
|
599
|
+
{
|
|
600
|
+
type: "button",
|
|
601
|
+
className: closeButtonClass,
|
|
602
|
+
onClick: () => shellChat.setOpen(false),
|
|
603
|
+
"aria-label": "Close assistant",
|
|
604
|
+
children: /* @__PURE__ */ jsx8(XIcon, { className: "size-4", "aria-hidden": true })
|
|
605
|
+
}
|
|
606
|
+
) }) : null,
|
|
607
|
+
/* @__PURE__ */ jsx8("div", { className: bodyClass, children: /* @__PURE__ */ jsx8(
|
|
608
|
+
TimbalRuntimeProvider,
|
|
609
|
+
{
|
|
610
|
+
workforceId,
|
|
611
|
+
baseUrl,
|
|
612
|
+
fetch,
|
|
613
|
+
attachments,
|
|
614
|
+
attachmentsUploadUrl,
|
|
615
|
+
attachmentsAccept,
|
|
616
|
+
debug,
|
|
617
|
+
children: /* @__PURE__ */ jsx8(
|
|
618
|
+
Thread,
|
|
619
|
+
{
|
|
620
|
+
variant: "panel",
|
|
621
|
+
className: "aui-app-chat-panel-thread",
|
|
622
|
+
welcome,
|
|
623
|
+
suggestions,
|
|
624
|
+
composerPlaceholder,
|
|
625
|
+
components,
|
|
626
|
+
artifacts,
|
|
627
|
+
onArtifactEvent,
|
|
628
|
+
...rest
|
|
629
|
+
}
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
) })
|
|
633
|
+
] });
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
// src/app/surfaces/SurfaceCard.tsx
|
|
637
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
638
|
+
var SurfaceCard = ({ children, className }) => {
|
|
639
|
+
return /* @__PURE__ */ jsx9("div", { className: cn("aui-app-surface-card", appSurfaceCardClass, className), children });
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/app/surfaces/StatTile.tsx
|
|
643
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
644
|
+
var StatTile = ({ label, value, hint, className }) => {
|
|
645
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("aui-app-stat-tile", appStatTileClass, className), children: [
|
|
646
|
+
/* @__PURE__ */ jsx10("span", { className: appStatLabelClass, children: label }),
|
|
647
|
+
/* @__PURE__ */ jsx10("span", { className: appStatValueClass, children: value }),
|
|
648
|
+
hint ? /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", children: hint }) : null
|
|
649
|
+
] });
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// src/app/surfaces/EmptyState.tsx
|
|
653
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
654
|
+
var EmptyState = ({
|
|
655
|
+
title,
|
|
656
|
+
description,
|
|
657
|
+
action,
|
|
658
|
+
className
|
|
659
|
+
}) => {
|
|
660
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn("aui-app-empty-state", appEmptyStateClass, className), children: [
|
|
661
|
+
/* @__PURE__ */ jsx11("p", { className: appEmptyStateTitleClass, children: title }),
|
|
662
|
+
description ? /* @__PURE__ */ jsx11("p", { className: appEmptyStateDescriptionClass, children: description }) : null,
|
|
663
|
+
action
|
|
664
|
+
] });
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
// src/app/surfaces/StatusBadge.tsx
|
|
668
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
669
|
+
var statusBadgeToneClass = {
|
|
670
|
+
default: "bg-muted text-foreground",
|
|
671
|
+
primary: "bg-primary/10 text-primary",
|
|
672
|
+
success: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
|
|
673
|
+
warn: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
|
674
|
+
muted: "bg-muted/80 text-muted-foreground"
|
|
675
|
+
};
|
|
676
|
+
var StatusBadge = ({
|
|
677
|
+
children,
|
|
678
|
+
tone = "default",
|
|
679
|
+
className
|
|
680
|
+
}) => {
|
|
681
|
+
return /* @__PURE__ */ jsx12(
|
|
682
|
+
"span",
|
|
683
|
+
{
|
|
684
|
+
className: cn(
|
|
685
|
+
"aui-app-status-badge inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
|
686
|
+
statusBadgeToneClass[tone],
|
|
687
|
+
className
|
|
688
|
+
),
|
|
689
|
+
children
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// src/app/surfaces/AppConfirmDialog.tsx
|
|
695
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
696
|
+
var bodyClass2 = "flex flex-col gap-4 p-6";
|
|
697
|
+
var titleClass = "pr-8";
|
|
698
|
+
var actionsClass = "flex flex-wrap justify-end gap-2";
|
|
699
|
+
var AppConfirmDialog = ({
|
|
700
|
+
open,
|
|
701
|
+
onOpenChange,
|
|
702
|
+
title,
|
|
703
|
+
description,
|
|
704
|
+
confirmLabel = "Confirm",
|
|
705
|
+
cancelLabel = "Cancel",
|
|
706
|
+
onConfirm,
|
|
707
|
+
destructive = false,
|
|
708
|
+
className
|
|
709
|
+
}) => {
|
|
710
|
+
return /* @__PURE__ */ jsx13(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx13(
|
|
711
|
+
DialogContent,
|
|
712
|
+
{
|
|
713
|
+
className: cn("gap-0 p-0 sm:max-w-md", className),
|
|
714
|
+
children: /* @__PURE__ */ jsxs9("div", { className: bodyClass2, children: [
|
|
715
|
+
/* @__PURE__ */ jsx13(DialogTitle, { className: titleClass, children: title }),
|
|
716
|
+
description ? /* @__PURE__ */ jsx13("p", { className: "text-sm text-muted-foreground", children: description }) : null,
|
|
717
|
+
/* @__PURE__ */ jsxs9("div", { className: actionsClass, children: [
|
|
718
|
+
/* @__PURE__ */ jsx13(
|
|
719
|
+
TimbalV2Button,
|
|
720
|
+
{
|
|
721
|
+
type: "button",
|
|
722
|
+
variant: "secondary",
|
|
723
|
+
size: "sm",
|
|
724
|
+
onClick: () => onOpenChange(false),
|
|
725
|
+
children: cancelLabel
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsx13(
|
|
729
|
+
TimbalV2Button,
|
|
730
|
+
{
|
|
731
|
+
type: "button",
|
|
732
|
+
variant: destructive ? "destructive" : "primary",
|
|
733
|
+
size: "sm",
|
|
734
|
+
onClick: () => {
|
|
735
|
+
onConfirm();
|
|
736
|
+
onOpenChange(false);
|
|
737
|
+
},
|
|
738
|
+
children: confirmLabel
|
|
739
|
+
}
|
|
740
|
+
)
|
|
741
|
+
] })
|
|
742
|
+
] })
|
|
743
|
+
}
|
|
744
|
+
) });
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// src/app/surfaces/InfoCard.tsx
|
|
748
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
749
|
+
var toneClass = {
|
|
750
|
+
neutral: "border-border bg-muted/40",
|
|
751
|
+
info: "border-primary/25 bg-primary/5",
|
|
752
|
+
success: "border-emerald-500/25 bg-emerald-500/5",
|
|
753
|
+
warn: "border-amber-500/25 bg-amber-500/5",
|
|
754
|
+
danger: "border-destructive/25 bg-destructive/5"
|
|
755
|
+
};
|
|
756
|
+
var InfoCard = ({
|
|
757
|
+
title,
|
|
758
|
+
children,
|
|
759
|
+
icon,
|
|
760
|
+
action,
|
|
761
|
+
tone = "neutral",
|
|
762
|
+
className
|
|
763
|
+
}) => /* @__PURE__ */ jsxs10(
|
|
764
|
+
"div",
|
|
765
|
+
{
|
|
766
|
+
className: cn(
|
|
767
|
+
"flex items-start gap-3 rounded-xl border p-4",
|
|
768
|
+
toneClass[tone],
|
|
769
|
+
className
|
|
770
|
+
),
|
|
771
|
+
children: [
|
|
772
|
+
icon ? /* @__PURE__ */ jsx14("span", { className: "mt-0.5 shrink-0 text-muted-foreground", children: icon }) : null,
|
|
773
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
|
|
774
|
+
title ? /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-foreground", children: title }) : null,
|
|
775
|
+
children ? /* @__PURE__ */ jsx14("div", { className: cn("text-sm text-muted-foreground", title && "mt-1"), children }) : null
|
|
776
|
+
] }),
|
|
777
|
+
action ? /* @__PURE__ */ jsx14("div", { className: "shrink-0", children: action }) : null
|
|
778
|
+
]
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
// src/app/surfaces/StatusDot.tsx
|
|
783
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
784
|
+
var dotClass = {
|
|
785
|
+
online: "bg-emerald-500",
|
|
786
|
+
busy: "bg-amber-500",
|
|
787
|
+
offline: "bg-muted-foreground/50",
|
|
788
|
+
error: "bg-destructive",
|
|
789
|
+
neutral: "bg-muted-foreground"
|
|
790
|
+
};
|
|
791
|
+
var StatusDot = ({
|
|
792
|
+
tone = "neutral",
|
|
793
|
+
label,
|
|
794
|
+
pulse = false,
|
|
795
|
+
className
|
|
796
|
+
}) => /* @__PURE__ */ jsxs11("span", { className: cn("inline-flex items-center gap-1.5", className), children: [
|
|
797
|
+
/* @__PURE__ */ jsxs11("span", { className: "relative flex size-2", children: [
|
|
798
|
+
pulse ? /* @__PURE__ */ jsx15(
|
|
799
|
+
"span",
|
|
800
|
+
{
|
|
801
|
+
className: cn(
|
|
802
|
+
"absolute inline-flex size-full animate-ping rounded-full opacity-60",
|
|
803
|
+
dotClass[tone]
|
|
804
|
+
)
|
|
805
|
+
}
|
|
806
|
+
) : null,
|
|
807
|
+
/* @__PURE__ */ jsx15("span", { className: cn("relative inline-flex size-2 rounded-full", dotClass[tone]) })
|
|
808
|
+
] }),
|
|
809
|
+
label ? /* @__PURE__ */ jsx15("span", { className: "text-xs text-muted-foreground", children: label }) : null
|
|
810
|
+
] });
|
|
811
|
+
|
|
812
|
+
// src/app/surfaces/DescriptionList.tsx
|
|
813
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
814
|
+
var DescriptionList = ({
|
|
815
|
+
items,
|
|
816
|
+
stacked = false,
|
|
817
|
+
className
|
|
818
|
+
}) => /* @__PURE__ */ jsx16(
|
|
819
|
+
"dl",
|
|
820
|
+
{
|
|
821
|
+
className: cn(
|
|
822
|
+
"divide-y divide-border rounded-xl border border-border bg-card",
|
|
823
|
+
className
|
|
824
|
+
),
|
|
825
|
+
children: items.map((item, i) => /* @__PURE__ */ jsxs12(
|
|
826
|
+
"div",
|
|
827
|
+
{
|
|
828
|
+
className: cn(
|
|
829
|
+
"px-4 py-3",
|
|
830
|
+
stacked ? "flex flex-col gap-0.5" : "flex items-center justify-between gap-4"
|
|
831
|
+
),
|
|
832
|
+
children: [
|
|
833
|
+
/* @__PURE__ */ jsx16("dt", { className: "text-sm text-muted-foreground", children: item.label }),
|
|
834
|
+
/* @__PURE__ */ jsx16(
|
|
835
|
+
"dd",
|
|
836
|
+
{
|
|
837
|
+
className: cn(
|
|
838
|
+
"text-sm text-foreground",
|
|
839
|
+
!stacked && "text-right tabular-nums"
|
|
840
|
+
),
|
|
841
|
+
children: item.value
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
]
|
|
845
|
+
},
|
|
846
|
+
i
|
|
847
|
+
))
|
|
848
|
+
}
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
// src/app/surfaces/ExpandableSection.tsx
|
|
852
|
+
import { useId, useState as useState2 } from "react";
|
|
853
|
+
import { AnimatePresence, motion as motion2, useReducedMotion as useReducedMotion2 } from "motion/react";
|
|
854
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
855
|
+
var Chevron = ({ open }) => /* @__PURE__ */ jsx17(
|
|
856
|
+
"svg",
|
|
857
|
+
{
|
|
858
|
+
viewBox: "0 0 24 24",
|
|
859
|
+
className: cn(
|
|
860
|
+
"size-4 text-muted-foreground transition-transform duration-200",
|
|
861
|
+
open && "rotate-180"
|
|
862
|
+
),
|
|
863
|
+
fill: "none",
|
|
864
|
+
stroke: "currentColor",
|
|
865
|
+
strokeWidth: 2,
|
|
866
|
+
strokeLinecap: "round",
|
|
867
|
+
strokeLinejoin: "round",
|
|
868
|
+
"aria-hidden": true,
|
|
869
|
+
children: /* @__PURE__ */ jsx17("path", { d: "m6 9 6 6 6-6" })
|
|
870
|
+
}
|
|
871
|
+
);
|
|
872
|
+
var ExpandableSection = ({
|
|
873
|
+
title,
|
|
874
|
+
icon,
|
|
875
|
+
count,
|
|
876
|
+
children,
|
|
877
|
+
open: openProp,
|
|
878
|
+
defaultOpen = false,
|
|
879
|
+
onOpenChange,
|
|
880
|
+
className
|
|
881
|
+
}) => {
|
|
882
|
+
const reduceMotion = useReducedMotion2();
|
|
883
|
+
const panelId = useId();
|
|
884
|
+
const [internalOpen, setInternalOpen] = useState2(defaultOpen);
|
|
885
|
+
const open = openProp ?? internalOpen;
|
|
886
|
+
const toggle = () => {
|
|
887
|
+
if (openProp == null) setInternalOpen((o) => !o);
|
|
888
|
+
onOpenChange?.(!open);
|
|
889
|
+
};
|
|
890
|
+
return /* @__PURE__ */ jsxs13("div", { className: cn("border-b border-border last:border-0", className), children: [
|
|
891
|
+
/* @__PURE__ */ jsxs13(
|
|
892
|
+
"button",
|
|
893
|
+
{
|
|
894
|
+
type: "button",
|
|
895
|
+
onClick: toggle,
|
|
896
|
+
"aria-expanded": open,
|
|
897
|
+
"aria-controls": panelId,
|
|
898
|
+
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",
|
|
899
|
+
children: [
|
|
900
|
+
/* @__PURE__ */ jsxs13("span", { className: "flex min-w-0 items-center gap-3", children: [
|
|
901
|
+
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,
|
|
902
|
+
/* @__PURE__ */ jsx17("span", { className: "truncate text-sm font-medium text-foreground", children: title }),
|
|
903
|
+
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
|
|
904
|
+
] }),
|
|
905
|
+
/* @__PURE__ */ jsx17(Chevron, { open })
|
|
906
|
+
]
|
|
907
|
+
}
|
|
908
|
+
),
|
|
909
|
+
/* @__PURE__ */ jsx17(AnimatePresence, { initial: false, children: open ? /* @__PURE__ */ jsx17(
|
|
910
|
+
motion2.div,
|
|
911
|
+
{
|
|
912
|
+
id: panelId,
|
|
913
|
+
initial: reduceMotion ? void 0 : { height: 0, opacity: 0 },
|
|
914
|
+
animate: { height: "auto", opacity: 1 },
|
|
915
|
+
exit: reduceMotion ? void 0 : { height: 0, opacity: 0 },
|
|
916
|
+
transition: { duration: 0.2, ease: "easeOut" },
|
|
917
|
+
className: "overflow-hidden",
|
|
918
|
+
children: /* @__PURE__ */ jsx17("div", { className: "bg-muted/20", children })
|
|
919
|
+
},
|
|
920
|
+
"body"
|
|
921
|
+
) : null })
|
|
922
|
+
] });
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/app/surfaces/ResourceCard.tsx
|
|
926
|
+
import { Fragment, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
927
|
+
var resourceCardShellClass = cn(
|
|
928
|
+
"flex min-h-[8.5rem] flex-col rounded-2xl p-4 text-left font-normal",
|
|
929
|
+
TIMBAL_V2_ELEVATED_SURFACE
|
|
930
|
+
);
|
|
931
|
+
var mediaShellClass = cn(
|
|
932
|
+
"flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-xl text-sm font-normal text-foreground",
|
|
933
|
+
TIMBAL_V2_LOGO_TILE
|
|
934
|
+
);
|
|
935
|
+
var resourceCardInteractiveClass = cn(
|
|
936
|
+
resourceCardShellClass,
|
|
937
|
+
"cursor-pointer bg-transparent hover:bg-transparent active:bg-transparent",
|
|
938
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
939
|
+
);
|
|
940
|
+
var ResourceCard = ({
|
|
941
|
+
title,
|
|
942
|
+
subtitle,
|
|
943
|
+
media,
|
|
944
|
+
badge,
|
|
945
|
+
footer,
|
|
946
|
+
action,
|
|
947
|
+
onClick,
|
|
948
|
+
ariaLabel,
|
|
949
|
+
className
|
|
950
|
+
}) => {
|
|
951
|
+
const body = /* @__PURE__ */ jsxs14(Fragment, { children: [
|
|
952
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-start gap-3", children: [
|
|
953
|
+
media ? /* @__PURE__ */ jsx18("span", { className: mediaShellClass, children: media }) : null,
|
|
954
|
+
/* @__PURE__ */ jsxs14("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
955
|
+
/* @__PURE__ */ jsx18("p", { className: "truncate text-sm font-normal leading-snug text-foreground", children: title }),
|
|
956
|
+
subtitle ? /* @__PURE__ */ jsx18("p", { className: "mt-1 line-clamp-2 text-xs font-normal text-muted-foreground", children: subtitle }) : null
|
|
957
|
+
] }),
|
|
958
|
+
badge ? /* @__PURE__ */ jsx18("span", { className: "shrink-0 pt-0.5", children: badge }) : null
|
|
959
|
+
] }),
|
|
960
|
+
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: [
|
|
961
|
+
/* @__PURE__ */ jsx18("span", { className: "min-w-0 truncate", children: footer }),
|
|
962
|
+
action ? /* @__PURE__ */ jsx18("span", { className: "shrink-0 opacity-80", children: action }) : null
|
|
963
|
+
] }) : null
|
|
964
|
+
] });
|
|
965
|
+
if (onClick) {
|
|
966
|
+
return /* @__PURE__ */ jsx18("button", { type: "button", onClick, "aria-label": ariaLabel, className: cn(resourceCardInteractiveClass, className), children: body });
|
|
967
|
+
}
|
|
968
|
+
return /* @__PURE__ */ jsx18("article", { className: cn(resourceCardShellClass, className), children: body });
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// src/app/settings/SettingsSection.tsx
|
|
972
|
+
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
973
|
+
var SettingsSectionHeader = ({
|
|
974
|
+
title,
|
|
975
|
+
description,
|
|
976
|
+
className
|
|
977
|
+
}) => /* @__PURE__ */ jsxs15("div", { className: cn("flex flex-col", className), children: [
|
|
978
|
+
/* @__PURE__ */ jsx19("h3", { className: "text-[17px] font-medium leading-tight text-foreground", children: title }),
|
|
979
|
+
description ? /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null
|
|
980
|
+
] });
|
|
981
|
+
var SettingsSection = ({
|
|
982
|
+
title,
|
|
983
|
+
description,
|
|
984
|
+
descriptionFooter,
|
|
985
|
+
children,
|
|
986
|
+
noBorderTop = false,
|
|
987
|
+
className
|
|
988
|
+
}) => /* @__PURE__ */ jsxs15(
|
|
989
|
+
"section",
|
|
990
|
+
{
|
|
991
|
+
className: cn(
|
|
992
|
+
"grid grid-cols-1 gap-y-4 lg:grid-cols-[minmax(200px,280px)_minmax(0,1fr)] lg:gap-x-12 lg:gap-y-0",
|
|
993
|
+
noBorderTop ? "pb-6" : "border-t border-border py-6",
|
|
994
|
+
className
|
|
995
|
+
),
|
|
996
|
+
children: [
|
|
997
|
+
/* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
|
|
998
|
+
/* @__PURE__ */ jsx19("h2", { className: "text-sm font-medium text-foreground", children: title }),
|
|
999
|
+
description ? /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null,
|
|
1000
|
+
descriptionFooter ? /* @__PURE__ */ jsx19("div", { className: "mt-3 min-w-0", children: descriptionFooter }) : null
|
|
1001
|
+
] }),
|
|
1002
|
+
/* @__PURE__ */ jsx19("div", { className: "min-w-0 space-y-3", children })
|
|
1003
|
+
]
|
|
1004
|
+
}
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
// src/app/settings/FieldRow.tsx
|
|
1008
|
+
import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1009
|
+
var FieldRow = ({
|
|
1010
|
+
label,
|
|
1011
|
+
children,
|
|
1012
|
+
description,
|
|
1013
|
+
inline = false,
|
|
1014
|
+
htmlFor,
|
|
1015
|
+
className
|
|
1016
|
+
}) => {
|
|
1017
|
+
if (inline) {
|
|
1018
|
+
return /* @__PURE__ */ jsxs16(
|
|
1019
|
+
"div",
|
|
1020
|
+
{
|
|
1021
|
+
className: cn(
|
|
1022
|
+
"flex items-center justify-between gap-4 rounded-lg border border-border bg-card px-3.5 py-3",
|
|
1023
|
+
className
|
|
1024
|
+
),
|
|
1025
|
+
children: [
|
|
1026
|
+
/* @__PURE__ */ jsxs16("div", { className: "min-w-0", children: [
|
|
1027
|
+
/* @__PURE__ */ jsx20(
|
|
1028
|
+
"label",
|
|
1029
|
+
{
|
|
1030
|
+
htmlFor,
|
|
1031
|
+
className: "block text-sm font-medium text-foreground",
|
|
1032
|
+
children: label
|
|
1033
|
+
}
|
|
1034
|
+
),
|
|
1035
|
+
description ? /* @__PURE__ */ jsx20("p", { className: "mt-0.5 text-xs text-muted-foreground", children: description }) : null
|
|
1036
|
+
] }),
|
|
1037
|
+
/* @__PURE__ */ jsx20("div", { className: "shrink-0", children })
|
|
1038
|
+
]
|
|
1039
|
+
}
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
return /* @__PURE__ */ jsxs16("div", { className: cn("flex flex-col gap-1.5", className), children: [
|
|
1043
|
+
/* @__PURE__ */ jsx20("label", { htmlFor, className: "text-sm font-medium text-foreground", children: label }),
|
|
1044
|
+
children,
|
|
1045
|
+
description ? /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: description }) : null
|
|
1046
|
+
] });
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
// src/app/settings/FloatingUnsavedChangesBar.tsx
|
|
1050
|
+
import { useEffect, useState as useState3 } from "react";
|
|
1051
|
+
import { createPortal } from "react-dom";
|
|
1052
|
+
import { AnimatePresence as AnimatePresence2, motion as motion3, useReducedMotion as useReducedMotion3 } from "motion/react";
|
|
1053
|
+
import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1054
|
+
var FloatingUnsavedChangesBar = ({
|
|
1055
|
+
visible,
|
|
1056
|
+
message = "Unsaved changes",
|
|
1057
|
+
discardLabel = "Discard",
|
|
1058
|
+
saveLabel = "Save changes",
|
|
1059
|
+
isSaving = false,
|
|
1060
|
+
saveDisabled = false,
|
|
1061
|
+
onDiscard,
|
|
1062
|
+
onSave,
|
|
1063
|
+
ariaLabel = "Unsaved changes",
|
|
1064
|
+
className
|
|
1065
|
+
}) => {
|
|
1066
|
+
const reduceMotion = useReducedMotion3();
|
|
1067
|
+
const [mounted, setMounted] = useState3(false);
|
|
1068
|
+
useEffect(() => setMounted(true), []);
|
|
1069
|
+
if (!mounted || typeof document === "undefined") return null;
|
|
1070
|
+
return createPortal(
|
|
1071
|
+
/* @__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(
|
|
1072
|
+
motion3.div,
|
|
1073
|
+
{
|
|
1074
|
+
role: "region",
|
|
1075
|
+
"aria-label": ariaLabel,
|
|
1076
|
+
initial: reduceMotion ? { opacity: 0 } : { opacity: 0, y: 28, scale: 0.94 },
|
|
1077
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
1078
|
+
exit: reduceMotion ? { opacity: 0 } : { opacity: 0, y: 18, scale: 0.96 },
|
|
1079
|
+
transition: { type: "spring", stiffness: 420, damping: 32 },
|
|
1080
|
+
className: cn(
|
|
1081
|
+
"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",
|
|
1082
|
+
className
|
|
1083
|
+
),
|
|
1084
|
+
children: [
|
|
1085
|
+
/* @__PURE__ */ jsx21("span", { className: "text-sm text-muted-foreground", children: message }),
|
|
1086
|
+
/* @__PURE__ */ jsxs17("span", { className: "flex items-center gap-1.5", children: [
|
|
1087
|
+
/* @__PURE__ */ jsx21(Button, { variant: "ghost", size: "sm", onClick: onDiscard, disabled: isSaving, children: discardLabel }),
|
|
1088
|
+
/* @__PURE__ */ jsx21(Button, { size: "sm", onClick: onSave, disabled: saveDisabled || isSaving, children: isSaving ? "Saving\u2026" : saveLabel })
|
|
1089
|
+
] })
|
|
1090
|
+
]
|
|
1091
|
+
}
|
|
1092
|
+
) }) : null }),
|
|
1093
|
+
document.body
|
|
1094
|
+
);
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
// src/app/settings/DangerZone.tsx
|
|
1098
|
+
import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1099
|
+
var DangerZoneAction = ({
|
|
1100
|
+
title,
|
|
1101
|
+
description,
|
|
1102
|
+
action,
|
|
1103
|
+
className
|
|
1104
|
+
}) => /* @__PURE__ */ jsxs18(
|
|
1105
|
+
"div",
|
|
1106
|
+
{
|
|
1107
|
+
className: cn(
|
|
1108
|
+
"flex flex-col gap-3 px-4 py-3.5 sm:flex-row sm:items-center sm:justify-between",
|
|
1109
|
+
className
|
|
1110
|
+
),
|
|
1111
|
+
children: [
|
|
1112
|
+
/* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
|
|
1113
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm font-medium text-foreground", children: title }),
|
|
1114
|
+
description ? /* @__PURE__ */ jsx22("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
|
|
1115
|
+
] }),
|
|
1116
|
+
/* @__PURE__ */ jsx22("div", { className: "shrink-0", children: action })
|
|
1117
|
+
]
|
|
1118
|
+
}
|
|
1119
|
+
);
|
|
1120
|
+
var DangerZone = ({
|
|
1121
|
+
title = "Danger zone",
|
|
1122
|
+
description,
|
|
1123
|
+
children,
|
|
1124
|
+
className
|
|
1125
|
+
}) => /* @__PURE__ */ jsxs18(
|
|
1126
|
+
"section",
|
|
1127
|
+
{
|
|
1128
|
+
className: cn(
|
|
1129
|
+
"overflow-hidden rounded-xl border border-destructive/30",
|
|
1130
|
+
className
|
|
1131
|
+
),
|
|
1132
|
+
children: [
|
|
1133
|
+
(title || description) && /* @__PURE__ */ jsxs18("header", { className: "border-b border-destructive/20 bg-destructive/5 px-4 py-3", children: [
|
|
1134
|
+
title ? /* @__PURE__ */ jsx22("h3", { className: "text-sm font-semibold text-destructive", children: title }) : null,
|
|
1135
|
+
description ? /* @__PURE__ */ jsx22("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
|
|
1136
|
+
] }),
|
|
1137
|
+
/* @__PURE__ */ jsx22("div", { className: "divide-y divide-border bg-card", children })
|
|
1138
|
+
]
|
|
1139
|
+
}
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
// src/app/integrations/IntegrationCard.tsx
|
|
1143
|
+
import { useId as useId2 } from "react";
|
|
1144
|
+
import { Fragment as Fragment2, jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1145
|
+
var INTEGRATION_CATALOG_CARD_HEIGHT_CLASS = "h-[12.25rem] min-h-[12.25rem] max-h-[12.25rem]";
|
|
1146
|
+
var statusLabel = {
|
|
1147
|
+
available: null,
|
|
1148
|
+
connected: "Connected",
|
|
1149
|
+
disabled: "Disabled",
|
|
1150
|
+
locked: "Locked"
|
|
1151
|
+
};
|
|
1152
|
+
var catalogCardShellClass = cn(
|
|
1153
|
+
"group relative box-border flex flex-col overflow-hidden rounded-2xl px-4 pb-4 pt-4 text-left font-normal",
|
|
1154
|
+
INTEGRATION_CATALOG_CARD_HEIGHT_CLASS,
|
|
1155
|
+
TIMBAL_V2_ELEVATED_SURFACE,
|
|
1156
|
+
"transition-opacity duration-200 ease-out"
|
|
1157
|
+
);
|
|
1158
|
+
var catalogCardInteractiveClass = cn(
|
|
1159
|
+
catalogCardShellClass,
|
|
1160
|
+
"cursor-pointer bg-transparent hover:bg-transparent active:bg-transparent",
|
|
1161
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
1162
|
+
);
|
|
1163
|
+
var catalogCardMutedClass = cn(
|
|
1164
|
+
"border-border/60 saturate-[0.72]",
|
|
1165
|
+
"from-muted/80 to-muted/50 dark:border-white/[0.06] dark:from-white/[0.04] dark:to-white/[0.02]"
|
|
1166
|
+
);
|
|
1167
|
+
var logoShellClass = cn(
|
|
1168
|
+
"relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-xl",
|
|
1169
|
+
TIMBAL_V2_LOGO_TILE
|
|
1170
|
+
);
|
|
1171
|
+
var IntegrationCard = ({
|
|
1172
|
+
name,
|
|
1173
|
+
description,
|
|
1174
|
+
logo,
|
|
1175
|
+
badge,
|
|
1176
|
+
status = "available",
|
|
1177
|
+
action,
|
|
1178
|
+
onClick,
|
|
1179
|
+
ariaLabel,
|
|
1180
|
+
className
|
|
1181
|
+
}) => {
|
|
1182
|
+
const titleId = useId2();
|
|
1183
|
+
const locked = status === "locked";
|
|
1184
|
+
const dimmed = status === "disabled" || locked;
|
|
1185
|
+
const body = /* @__PURE__ */ jsxs19("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
1186
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex shrink-0 items-start gap-3 pr-2", children: [
|
|
1187
|
+
logo ? /* @__PURE__ */ jsx23("span", { className: logoShellClass, "aria-hidden": Boolean(ariaLabel), children: logo }) : null,
|
|
1188
|
+
/* @__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: [
|
|
1189
|
+
/* @__PURE__ */ jsxs19("div", { className: "min-w-0", children: [
|
|
1190
|
+
/* @__PURE__ */ jsx23(
|
|
1191
|
+
"h4",
|
|
1192
|
+
{
|
|
1193
|
+
id: onClick && !action ? void 0 : titleId,
|
|
1194
|
+
className: "truncate text-sm font-normal leading-snug text-foreground",
|
|
1195
|
+
children: name
|
|
1196
|
+
}
|
|
1197
|
+
),
|
|
1198
|
+
statusLabel[status] ? /* @__PURE__ */ jsx23("p", { className: "mt-0.5 text-xs text-muted-foreground", children: statusLabel[status] }) : null
|
|
1199
|
+
] }),
|
|
1200
|
+
badge ? /* @__PURE__ */ jsx23("span", { className: "shrink-0", children: badge }) : null
|
|
1201
|
+
] }) })
|
|
1202
|
+
] }),
|
|
1203
|
+
description ? /* @__PURE__ */ jsx23(
|
|
1204
|
+
"p",
|
|
1205
|
+
{
|
|
1206
|
+
className: cn(
|
|
1207
|
+
"mt-3 line-clamp-3 shrink-0 text-sm leading-relaxed text-muted-foreground",
|
|
1208
|
+
dimmed && "text-muted-foreground/80"
|
|
1209
|
+
),
|
|
1210
|
+
children: description
|
|
1211
|
+
}
|
|
1212
|
+
) : null,
|
|
1213
|
+
action ? /* @__PURE__ */ jsxs19(Fragment2, { children: [
|
|
1214
|
+
/* @__PURE__ */ jsx23("div", { className: "min-h-0 flex-1", "aria-hidden": true }),
|
|
1215
|
+
/* @__PURE__ */ jsx23("div", { className: "relative mt-3 shrink-0", children: action })
|
|
1216
|
+
] }) : null
|
|
1217
|
+
] });
|
|
1218
|
+
const shellClass3 = cn(
|
|
1219
|
+
catalogCardShellClass,
|
|
1220
|
+
dimmed && catalogCardMutedClass,
|
|
1221
|
+
locked && "cursor-default opacity-75",
|
|
1222
|
+
className
|
|
1223
|
+
);
|
|
1224
|
+
if (onClick && !action) {
|
|
1225
|
+
return /* @__PURE__ */ jsx23(
|
|
1226
|
+
"button",
|
|
1227
|
+
{
|
|
1228
|
+
type: "button",
|
|
1229
|
+
onClick,
|
|
1230
|
+
disabled: locked,
|
|
1231
|
+
"aria-label": ariaLabel,
|
|
1232
|
+
className: cn(
|
|
1233
|
+
catalogCardInteractiveClass,
|
|
1234
|
+
dimmed && catalogCardMutedClass,
|
|
1235
|
+
locked && "cursor-default opacity-75",
|
|
1236
|
+
className
|
|
1237
|
+
),
|
|
1238
|
+
children: body
|
|
1239
|
+
}
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
return /* @__PURE__ */ jsx23("article", { className: shellClass3, "aria-labelledby": titleId, children: body });
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
// src/app/integrations/IntegrationsEmptyState.tsx
|
|
1246
|
+
import { useId as useId3 } from "react";
|
|
1247
|
+
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1248
|
+
var IntegrationsEmptyState = ({
|
|
1249
|
+
title = "No integrations yet",
|
|
1250
|
+
description = "Connect a provider to start syncing data and powering your workforce.",
|
|
1251
|
+
icon,
|
|
1252
|
+
action,
|
|
1253
|
+
className
|
|
1254
|
+
}) => {
|
|
1255
|
+
const titleId = useId3();
|
|
1256
|
+
return /* @__PURE__ */ jsxs20(
|
|
1257
|
+
"section",
|
|
1258
|
+
{
|
|
1259
|
+
className: cn(
|
|
1260
|
+
"flex flex-col items-center justify-center gap-3 rounded-2xl px-6 py-14 text-center",
|
|
1261
|
+
TIMBAL_V2_ELEVATED_SURFACE,
|
|
1262
|
+
className
|
|
1263
|
+
),
|
|
1264
|
+
"aria-labelledby": titleId,
|
|
1265
|
+
children: [
|
|
1266
|
+
icon ? /* @__PURE__ */ jsx24(
|
|
1267
|
+
"span",
|
|
1268
|
+
{
|
|
1269
|
+
className: cn(
|
|
1270
|
+
"flex size-14 items-center justify-center overflow-hidden rounded-2xl",
|
|
1271
|
+
TIMBAL_V2_LOGO_TILE,
|
|
1272
|
+
"text-muted-foreground"
|
|
1273
|
+
),
|
|
1274
|
+
"aria-hidden": true,
|
|
1275
|
+
children: icon
|
|
1276
|
+
}
|
|
1277
|
+
) : null,
|
|
1278
|
+
/* @__PURE__ */ jsx24("h3", { id: titleId, className: "text-base font-normal text-foreground", children: title }),
|
|
1279
|
+
description ? /* @__PURE__ */ jsx24("p", { className: "max-w-sm text-sm text-muted-foreground", children: description }) : null,
|
|
1280
|
+
action ? /* @__PURE__ */ jsx24("div", { className: "mt-1", children: action }) : null
|
|
1281
|
+
]
|
|
1282
|
+
}
|
|
1283
|
+
);
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
// src/app/integrations/PlanBadge.tsx
|
|
1287
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
1288
|
+
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";
|
|
1289
|
+
var PlanBadge = ({ children, className }) => /* @__PURE__ */ jsx25("span", { className: cn(planBadgeClass, className), children });
|
|
1290
|
+
|
|
1291
|
+
// src/app/integrations/ConnectionRow.tsx
|
|
1292
|
+
import { Fragment as Fragment3, jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1293
|
+
var logoShellClass2 = cn(
|
|
1294
|
+
"flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-lg",
|
|
1295
|
+
TIMBAL_V2_LOGO_TILE
|
|
1296
|
+
);
|
|
1297
|
+
var ConnectionRow = ({
|
|
1298
|
+
name,
|
|
1299
|
+
logo,
|
|
1300
|
+
meta,
|
|
1301
|
+
badge,
|
|
1302
|
+
action,
|
|
1303
|
+
onClick,
|
|
1304
|
+
ariaLabel,
|
|
1305
|
+
className
|
|
1306
|
+
}) => {
|
|
1307
|
+
const inner = /* @__PURE__ */ jsxs21(Fragment3, { children: [
|
|
1308
|
+
logo ? /* @__PURE__ */ jsx26("span", { className: logoShellClass2, children: logo }) : null,
|
|
1309
|
+
/* @__PURE__ */ jsxs21("div", { className: "min-w-0 flex-1", children: [
|
|
1310
|
+
/* @__PURE__ */ jsx26("p", { className: "truncate text-sm font-normal text-foreground", children: name }),
|
|
1311
|
+
meta ? /* @__PURE__ */ jsx26("p", { className: "truncate text-xs text-muted-foreground", children: meta }) : null
|
|
1312
|
+
] }),
|
|
1313
|
+
badge ? /* @__PURE__ */ jsx26("span", { className: "shrink-0", children: badge }) : null,
|
|
1314
|
+
action ? /* @__PURE__ */ jsx26("span", { className: "shrink-0", children: action }) : null
|
|
1315
|
+
] });
|
|
1316
|
+
const rowClass2 = cn(
|
|
1317
|
+
"flex w-full items-center gap-3 px-4 py-3 text-left",
|
|
1318
|
+
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",
|
|
1319
|
+
className
|
|
1320
|
+
);
|
|
1321
|
+
if (onClick) {
|
|
1322
|
+
return /* @__PURE__ */ jsx26(
|
|
1323
|
+
"button",
|
|
1324
|
+
{
|
|
1325
|
+
type: "button",
|
|
1326
|
+
role: "listitem",
|
|
1327
|
+
onClick,
|
|
1328
|
+
"aria-label": ariaLabel,
|
|
1329
|
+
className: rowClass2,
|
|
1330
|
+
children: inner
|
|
1331
|
+
}
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
return /* @__PURE__ */ jsx26("div", { role: "listitem", className: rowClass2, children: inner });
|
|
1335
|
+
};
|
|
1336
|
+
var connectionRowListClass = cn(
|
|
1337
|
+
"overflow-hidden rounded-2xl",
|
|
1338
|
+
TIMBAL_V2_ELEVATED_SURFACE
|
|
1339
|
+
);
|
|
1340
|
+
|
|
1341
|
+
// src/app/integrations/ConnectionRowList.tsx
|
|
1342
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
1343
|
+
var ConnectionRowList = ({
|
|
1344
|
+
children,
|
|
1345
|
+
"aria-label": ariaLabel = "Connected integrations",
|
|
1346
|
+
className
|
|
1347
|
+
}) => /* @__PURE__ */ jsx27(
|
|
1348
|
+
"div",
|
|
1349
|
+
{
|
|
1350
|
+
role: "list",
|
|
1351
|
+
"aria-label": ariaLabel,
|
|
1352
|
+
className: cn(connectionRowListClass, "divide-y divide-border", className),
|
|
1353
|
+
children
|
|
1354
|
+
}
|
|
1355
|
+
);
|
|
1356
|
+
|
|
1357
|
+
// src/app/navigation/SubNav.tsx
|
|
1358
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
1359
|
+
var SubNav = ({
|
|
1360
|
+
items,
|
|
1361
|
+
activeId,
|
|
1362
|
+
onChange,
|
|
1363
|
+
className,
|
|
1364
|
+
"aria-label": ariaLabel = "Section navigation",
|
|
1365
|
+
layoutId
|
|
1366
|
+
}) => {
|
|
1367
|
+
return /* @__PURE__ */ jsx28("nav", { className: cn("aui-app-sub-nav", className), "aria-label": ariaLabel, children: /* @__PURE__ */ jsx28(
|
|
1368
|
+
PillSegmentedTabs,
|
|
1369
|
+
{
|
|
1370
|
+
value: activeId,
|
|
1371
|
+
onChange,
|
|
1372
|
+
tabs: items.map((item) => ({ key: item.id, label: item.label })),
|
|
1373
|
+
trackVariant: "flush",
|
|
1374
|
+
layoutId: layoutId ?? "app-sub-nav-segmented",
|
|
1375
|
+
"aria-label": ariaLabel
|
|
1376
|
+
}
|
|
1377
|
+
) });
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
// src/app/navigation/Breadcrumbs.tsx
|
|
1381
|
+
import { jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1382
|
+
var Breadcrumbs = ({ items, className }) => {
|
|
1383
|
+
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) => {
|
|
1384
|
+
const isLast = index === items.length - 1;
|
|
1385
|
+
return /* @__PURE__ */ jsxs22("li", { className: "inline-flex items-center gap-1.5", children: [
|
|
1386
|
+
index > 0 ? /* @__PURE__ */ jsx29("span", { className: "text-muted-foreground/50", "aria-hidden": true, children: "/" }) : null,
|
|
1387
|
+
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(
|
|
1388
|
+
"button",
|
|
1389
|
+
{
|
|
1390
|
+
type: "button",
|
|
1391
|
+
className: appBreadcrumbLinkClass,
|
|
1392
|
+
onClick: item.onClick,
|
|
1393
|
+
children: item.label
|
|
1394
|
+
}
|
|
1395
|
+
)
|
|
1396
|
+
] }, index);
|
|
1397
|
+
}) }) });
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
// src/app/forms/Field.tsx
|
|
1401
|
+
import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1402
|
+
var Field = ({
|
|
1403
|
+
label,
|
|
1404
|
+
hint,
|
|
1405
|
+
error,
|
|
1406
|
+
children,
|
|
1407
|
+
className,
|
|
1408
|
+
htmlFor
|
|
1409
|
+
}) => {
|
|
1410
|
+
return /* @__PURE__ */ jsxs23("div", { className: cn("aui-app-field", appFieldClass, className), children: [
|
|
1411
|
+
/* @__PURE__ */ jsx30("label", { className: appFieldLabelClass, htmlFor, children: label }),
|
|
1412
|
+
children,
|
|
1413
|
+
hint && !error ? /* @__PURE__ */ jsx30("p", { className: appFieldHintClass, children: hint }) : null,
|
|
1414
|
+
error ? /* @__PURE__ */ jsx30("p", { className: "text-xs text-destructive", role: "alert", children: error }) : null
|
|
1415
|
+
] });
|
|
1416
|
+
};
|
|
1417
|
+
var FieldInput = ({
|
|
1418
|
+
label,
|
|
1419
|
+
hint,
|
|
1420
|
+
error,
|
|
1421
|
+
fieldClassName,
|
|
1422
|
+
className,
|
|
1423
|
+
id,
|
|
1424
|
+
...inputProps
|
|
1425
|
+
}) => {
|
|
1426
|
+
const inputId = id ?? inputProps.name;
|
|
1427
|
+
return /* @__PURE__ */ jsx30(
|
|
1428
|
+
Field,
|
|
1429
|
+
{
|
|
1430
|
+
label,
|
|
1431
|
+
hint,
|
|
1432
|
+
error,
|
|
1433
|
+
htmlFor: inputId,
|
|
1434
|
+
className: fieldClassName,
|
|
1435
|
+
children: /* @__PURE__ */ jsx30(
|
|
1436
|
+
"input",
|
|
1437
|
+
{
|
|
1438
|
+
id: inputId,
|
|
1439
|
+
className: cn(appInputClass, className),
|
|
1440
|
+
"aria-invalid": error ? true : void 0,
|
|
1441
|
+
...inputProps
|
|
1442
|
+
}
|
|
1443
|
+
)
|
|
1444
|
+
}
|
|
1445
|
+
);
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
// src/app/forms/FieldTextarea.tsx
|
|
1449
|
+
import { jsx as jsx31 } from "react/jsx-runtime";
|
|
1450
|
+
var textareaClass = cn(
|
|
1451
|
+
appInputClass,
|
|
1452
|
+
"min-h-[5.5rem] resize-y py-2.5 leading-relaxed"
|
|
1453
|
+
);
|
|
1454
|
+
var FieldTextarea = ({
|
|
1455
|
+
label,
|
|
1456
|
+
hint,
|
|
1457
|
+
error,
|
|
1458
|
+
fieldClassName,
|
|
1459
|
+
className,
|
|
1460
|
+
id,
|
|
1461
|
+
...props
|
|
1462
|
+
}) => {
|
|
1463
|
+
const textareaId = id ?? props.name;
|
|
1464
|
+
return /* @__PURE__ */ jsx31(
|
|
1465
|
+
Field,
|
|
1466
|
+
{
|
|
1467
|
+
label,
|
|
1468
|
+
hint,
|
|
1469
|
+
error,
|
|
1470
|
+
htmlFor: textareaId,
|
|
1471
|
+
className: fieldClassName,
|
|
1472
|
+
children: /* @__PURE__ */ jsx31(
|
|
1473
|
+
"textarea",
|
|
1474
|
+
{
|
|
1475
|
+
id: textareaId,
|
|
1476
|
+
className: cn(textareaClass, className),
|
|
1477
|
+
"aria-invalid": error ? true : void 0,
|
|
1478
|
+
...props
|
|
1479
|
+
}
|
|
1480
|
+
)
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
// src/app/forms/FieldSelect.tsx
|
|
1486
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
1487
|
+
import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1488
|
+
var selectWrapClass = "relative";
|
|
1489
|
+
var selectClass = cn(
|
|
1490
|
+
appInputClass,
|
|
1491
|
+
"appearance-none pr-9"
|
|
1492
|
+
);
|
|
1493
|
+
var FieldSelect = ({
|
|
1494
|
+
label,
|
|
1495
|
+
hint,
|
|
1496
|
+
error,
|
|
1497
|
+
fieldClassName,
|
|
1498
|
+
className,
|
|
1499
|
+
children,
|
|
1500
|
+
id,
|
|
1501
|
+
...props
|
|
1502
|
+
}) => {
|
|
1503
|
+
const selectId = id ?? props.name;
|
|
1504
|
+
return /* @__PURE__ */ jsx32(
|
|
1505
|
+
Field,
|
|
1506
|
+
{
|
|
1507
|
+
label,
|
|
1508
|
+
hint,
|
|
1509
|
+
error,
|
|
1510
|
+
htmlFor: selectId,
|
|
1511
|
+
className: fieldClassName,
|
|
1512
|
+
children: /* @__PURE__ */ jsxs24("div", { className: selectWrapClass, children: [
|
|
1513
|
+
/* @__PURE__ */ jsx32(
|
|
1514
|
+
"select",
|
|
1515
|
+
{
|
|
1516
|
+
id: selectId,
|
|
1517
|
+
className: cn(selectClass, className),
|
|
1518
|
+
"aria-invalid": error ? true : void 0,
|
|
1519
|
+
...props,
|
|
1520
|
+
children
|
|
1521
|
+
}
|
|
1522
|
+
),
|
|
1523
|
+
/* @__PURE__ */ jsx32(
|
|
1524
|
+
ChevronDownIcon,
|
|
1525
|
+
{
|
|
1526
|
+
className: "pointer-events-none absolute top-1/2 right-3 size-4 -translate-y-1/2 text-muted-foreground",
|
|
1527
|
+
"aria-hidden": true
|
|
1528
|
+
}
|
|
1529
|
+
)
|
|
1530
|
+
] })
|
|
1531
|
+
}
|
|
1532
|
+
);
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
// src/app/forms/FieldSwitch.tsx
|
|
1536
|
+
import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
1537
|
+
var trackClass = cn(
|
|
1538
|
+
"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-[background,box-shadow,border-color] duration-200",
|
|
1539
|
+
"peer-focus-visible:ring-2 peer-focus-visible:ring-foreground/10",
|
|
1540
|
+
"peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
1541
|
+
TIMBAL_V2_SWITCH_TRACK_OFF,
|
|
1542
|
+
"peer-checked:border-foreground/15 peer-checked:from-primary-fill-from peer-checked:to-primary-fill-to peer-checked:shadow-card",
|
|
1543
|
+
"peer-checked:[&>span]:translate-x-4"
|
|
1544
|
+
);
|
|
1545
|
+
var thumbClass = cn(
|
|
1546
|
+
"pointer-events-none inline-block size-4 shrink-0 translate-x-0.5 rounded-full transition-transform",
|
|
1547
|
+
TIMBAL_V2_SWITCH_THUMB
|
|
1548
|
+
);
|
|
1549
|
+
var FieldSwitch = ({
|
|
1550
|
+
label,
|
|
1551
|
+
description,
|
|
1552
|
+
className,
|
|
1553
|
+
id,
|
|
1554
|
+
...props
|
|
1555
|
+
}) => {
|
|
1556
|
+
const inputId = id ?? props.name ?? "switch";
|
|
1557
|
+
return /* @__PURE__ */ jsxs25(
|
|
1558
|
+
"label",
|
|
1559
|
+
{
|
|
1560
|
+
className: cn(
|
|
1561
|
+
"aui-app-field-switch flex cursor-pointer items-start gap-3",
|
|
1562
|
+
className
|
|
1563
|
+
),
|
|
1564
|
+
htmlFor: inputId,
|
|
1565
|
+
children: [
|
|
1566
|
+
/* @__PURE__ */ jsxs25("span", { className: "relative mt-0.5", children: [
|
|
1567
|
+
/* @__PURE__ */ jsx33(
|
|
1568
|
+
"input",
|
|
1569
|
+
{
|
|
1570
|
+
id: inputId,
|
|
1571
|
+
type: "checkbox",
|
|
1572
|
+
role: "switch",
|
|
1573
|
+
className: "peer sr-only",
|
|
1574
|
+
...props
|
|
1575
|
+
}
|
|
1576
|
+
),
|
|
1577
|
+
/* @__PURE__ */ jsx33("span", { className: trackClass, "aria-hidden": true, children: /* @__PURE__ */ jsx33("span", { className: thumbClass }) })
|
|
1578
|
+
] }),
|
|
1579
|
+
/* @__PURE__ */ jsxs25("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
|
|
1580
|
+
/* @__PURE__ */ jsx33("span", { className: "text-sm font-medium text-foreground", children: label }),
|
|
1581
|
+
description ? /* @__PURE__ */ jsx33("span", { className: "text-xs text-muted-foreground", children: description }) : null
|
|
1582
|
+
] })
|
|
1583
|
+
]
|
|
1584
|
+
}
|
|
1585
|
+
);
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
// src/app/forms/SearchInput.tsx
|
|
1589
|
+
import { SearchIcon } from "lucide-react";
|
|
1590
|
+
import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
1591
|
+
var SearchInput = ({
|
|
1592
|
+
className,
|
|
1593
|
+
placeholder = "Search\u2026",
|
|
1594
|
+
...props
|
|
1595
|
+
}) => {
|
|
1596
|
+
return /* @__PURE__ */ jsxs26(
|
|
1597
|
+
"label",
|
|
1598
|
+
{
|
|
1599
|
+
className: cn(
|
|
1600
|
+
"aui-app-search-input inline-flex min-w-[12rem] items-center gap-2",
|
|
1601
|
+
appSearchInputClass,
|
|
1602
|
+
className
|
|
1603
|
+
),
|
|
1604
|
+
children: [
|
|
1605
|
+
/* @__PURE__ */ jsx34(SearchIcon, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
1606
|
+
/* @__PURE__ */ jsx34(
|
|
1607
|
+
"input",
|
|
1608
|
+
{
|
|
1609
|
+
type: "search",
|
|
1610
|
+
placeholder,
|
|
1611
|
+
className: "min-w-0 flex-1 border-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground/70",
|
|
1612
|
+
...props
|
|
1613
|
+
}
|
|
1614
|
+
)
|
|
1615
|
+
]
|
|
1616
|
+
}
|
|
1617
|
+
);
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
// src/app/forms/FormSection.tsx
|
|
1621
|
+
import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
1622
|
+
var FormSection = ({ title, children, className }) => {
|
|
1623
|
+
return /* @__PURE__ */ jsxs27("fieldset", { className: cn("aui-app-form-section", appSectionClass, "border-0 p-0", className), children: [
|
|
1624
|
+
title ? /* @__PURE__ */ jsx35("legend", { className: cn(appSectionTitleClass, "mb-3 px-0"), children: title }) : null,
|
|
1625
|
+
/* @__PURE__ */ jsx35("div", { className: "flex flex-col gap-4", children })
|
|
1626
|
+
] });
|
|
1627
|
+
};
|
|
1628
|
+
|
|
1629
|
+
// src/app/data/FilterBar.tsx
|
|
1630
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
1631
|
+
var FilterBar = ({ children, className }) => {
|
|
1632
|
+
return /* @__PURE__ */ jsx36(
|
|
1633
|
+
"div",
|
|
1634
|
+
{
|
|
1635
|
+
className: cn("aui-app-filter-bar", appFilterBarClass, className),
|
|
1636
|
+
role: "toolbar",
|
|
1637
|
+
"aria-label": "Filters",
|
|
1638
|
+
children
|
|
1639
|
+
}
|
|
1640
|
+
);
|
|
1641
|
+
};
|
|
1642
|
+
|
|
1643
|
+
// src/app/data/DataTable.tsx
|
|
1644
|
+
import { useMemo, useState as useState4 } from "react";
|
|
1645
|
+
import { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon } from "lucide-react";
|
|
1646
|
+
import { jsx as jsx37, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
1647
|
+
var shellClass2 = "overflow-hidden rounded-xl border border-border bg-card shadow-card";
|
|
1648
|
+
var tableClass = "w-full border-collapse bg-transparent text-sm";
|
|
1649
|
+
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";
|
|
1650
|
+
var bodyCellClass = "border-b border-border/40 bg-transparent px-4 py-2.5 text-foreground";
|
|
1651
|
+
var rowClass = "bg-transparent transition-colors hover:bg-foreground/[0.03] data-[clickable=true]:cursor-pointer";
|
|
1652
|
+
var footCellClass = "border-t border-border/60 bg-transparent px-4 py-2.5 text-xs text-muted-foreground";
|
|
1653
|
+
var footInnerClass = "flex flex-wrap items-center gap-2";
|
|
1654
|
+
var emptyCellClass = "bg-transparent px-4 py-10 text-center text-sm text-muted-foreground";
|
|
1655
|
+
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";
|
|
1656
|
+
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";
|
|
1657
|
+
var alignClass = {
|
|
1658
|
+
left: "text-left",
|
|
1659
|
+
center: "text-center",
|
|
1660
|
+
right: "text-right"
|
|
1661
|
+
};
|
|
1662
|
+
function compareSortValues(a, b) {
|
|
1663
|
+
if (a == null && b == null) return 0;
|
|
1664
|
+
if (a == null) return 1;
|
|
1665
|
+
if (b == null) return -1;
|
|
1666
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
|
|
1667
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
1668
|
+
if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
|
|
1669
|
+
return String(a).localeCompare(String(b), void 0, { sensitivity: "base" });
|
|
1670
|
+
}
|
|
1671
|
+
function nextSort(current, columnId) {
|
|
1672
|
+
if (current?.columnId !== columnId) {
|
|
1673
|
+
return { columnId, direction: "asc" };
|
|
1674
|
+
}
|
|
1675
|
+
if (current.direction === "asc") {
|
|
1676
|
+
return { columnId, direction: "desc" };
|
|
1677
|
+
}
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
function SortIndicator({
|
|
1681
|
+
active,
|
|
1682
|
+
direction
|
|
1683
|
+
}) {
|
|
1684
|
+
const iconClass = "size-3.5 shrink-0 opacity-60 group-hover:opacity-100";
|
|
1685
|
+
if (!active) {
|
|
1686
|
+
return /* @__PURE__ */ jsx37(ArrowUpDownIcon, { className: iconClass, "aria-hidden": true });
|
|
1687
|
+
}
|
|
1688
|
+
if (direction === "desc") {
|
|
1689
|
+
return /* @__PURE__ */ jsx37(ArrowDownIcon, { className: iconClass, "aria-hidden": true });
|
|
1690
|
+
}
|
|
1691
|
+
return /* @__PURE__ */ jsx37(ArrowUpIcon, { className: iconClass, "aria-hidden": true });
|
|
1692
|
+
}
|
|
1693
|
+
function DataTable({
|
|
1694
|
+
columns,
|
|
1695
|
+
rows,
|
|
1696
|
+
getRowKey,
|
|
1697
|
+
emptyTitle = "No data",
|
|
1698
|
+
emptyDescription,
|
|
1699
|
+
emptyMode = "replace",
|
|
1700
|
+
className,
|
|
1701
|
+
sort: sortProp,
|
|
1702
|
+
defaultSort = null,
|
|
1703
|
+
onSortChange,
|
|
1704
|
+
showRowCount = false,
|
|
1705
|
+
rowCountLabel,
|
|
1706
|
+
footer,
|
|
1707
|
+
onRowClick,
|
|
1708
|
+
stickyHeader = false,
|
|
1709
|
+
dense = false,
|
|
1710
|
+
caption
|
|
1711
|
+
}) {
|
|
1712
|
+
const [uncontrolledSort, setUncontrolledSort] = useState4(
|
|
1713
|
+
defaultSort
|
|
1714
|
+
);
|
|
1715
|
+
const isSortControlled = sortProp !== void 0;
|
|
1716
|
+
const sort = isSortControlled ? sortProp : uncontrolledSort;
|
|
1717
|
+
const setSort = (next) => {
|
|
1718
|
+
if (!isSortControlled) {
|
|
1719
|
+
setUncontrolledSort(next);
|
|
1720
|
+
}
|
|
1721
|
+
onSortChange?.(next);
|
|
1722
|
+
};
|
|
1723
|
+
const sortedRows = useMemo(() => {
|
|
1724
|
+
if (!sort) return rows;
|
|
1725
|
+
const column = columns.find((col) => col.id === sort.columnId);
|
|
1726
|
+
if (!column?.sortable) return rows;
|
|
1727
|
+
const getValue = column.sortValue ?? ((row) => {
|
|
1728
|
+
const rendered = column.cell(row);
|
|
1729
|
+
if (rendered === null || rendered === void 0 || typeof rendered === "string" || typeof rendered === "number" || typeof rendered === "boolean") {
|
|
1730
|
+
return rendered;
|
|
1731
|
+
}
|
|
1732
|
+
return String(rendered);
|
|
1733
|
+
});
|
|
1734
|
+
return [...rows].sort((a, b) => {
|
|
1735
|
+
const cmp = compareSortValues(getValue(a), getValue(b));
|
|
1736
|
+
return sort.direction === "asc" ? cmp : -cmp;
|
|
1737
|
+
});
|
|
1738
|
+
}, [columns, rows, sort]);
|
|
1739
|
+
const cellPad = dense ? "px-3 py-2" : void 0;
|
|
1740
|
+
const headPad = dense ? "px-3 py-2" : void 0;
|
|
1741
|
+
if (rows.length === 0 && emptyMode === "replace") {
|
|
1742
|
+
return /* @__PURE__ */ jsx37(EmptyState, { title: emptyTitle, description: emptyDescription, className });
|
|
1743
|
+
}
|
|
1744
|
+
const rowCountText = rowCountLabel?.(sortedRows.length) ?? `${sortedRows.length} row${sortedRows.length === 1 ? "" : "s"}`;
|
|
1745
|
+
const hasFoot = Boolean((showRowCount || footer) && sortedRows.length > 0);
|
|
1746
|
+
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: [
|
|
1747
|
+
caption ? /* @__PURE__ */ jsx37("caption", { className: "sr-only", children: caption }) : null,
|
|
1748
|
+
/* @__PURE__ */ jsx37("thead", { className: cn(stickyHeader && stickyHeadClass), children: /* @__PURE__ */ jsx37("tr", { children: columns.map((col) => {
|
|
1749
|
+
const isSorted = sort?.columnId === col.id;
|
|
1750
|
+
const ariaSort = col.sortable ? isSorted ? sort.direction === "asc" ? "ascending" : "descending" : "none" : void 0;
|
|
1751
|
+
const headerContent = col.sortable ? /* @__PURE__ */ jsxs28(
|
|
1752
|
+
"button",
|
|
1753
|
+
{
|
|
1754
|
+
type: "button",
|
|
1755
|
+
className: sortButtonClass,
|
|
1756
|
+
onClick: () => setSort(nextSort(sort, col.id)),
|
|
1757
|
+
children: [
|
|
1758
|
+
/* @__PURE__ */ jsx37("span", { className: "truncate", children: col.header }),
|
|
1759
|
+
/* @__PURE__ */ jsx37(SortIndicator, { active: Boolean(isSorted), direction: sort?.direction })
|
|
1760
|
+
]
|
|
1761
|
+
}
|
|
1762
|
+
) : col.header;
|
|
1763
|
+
return /* @__PURE__ */ jsx37(
|
|
1764
|
+
"th",
|
|
1765
|
+
{
|
|
1766
|
+
scope: "col",
|
|
1767
|
+
"aria-sort": ariaSort,
|
|
1768
|
+
className: cn(
|
|
1769
|
+
headCellClass,
|
|
1770
|
+
headPad,
|
|
1771
|
+
col.align && alignClass[col.align],
|
|
1772
|
+
col.headerClassName
|
|
1773
|
+
),
|
|
1774
|
+
children: headerContent
|
|
1775
|
+
},
|
|
1776
|
+
col.id
|
|
1777
|
+
);
|
|
1778
|
+
}) }) }),
|
|
1779
|
+
/* @__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: [
|
|
1780
|
+
/* @__PURE__ */ jsx37("p", { className: "font-medium text-foreground", children: emptyTitle }),
|
|
1781
|
+
emptyDescription ? /* @__PURE__ */ jsx37("p", { className: "max-w-sm text-muted-foreground", children: emptyDescription }) : null
|
|
1782
|
+
] }) }) }) : sortedRows.map((row) => /* @__PURE__ */ jsx37(
|
|
1783
|
+
"tr",
|
|
1784
|
+
{
|
|
1785
|
+
className: rowClass,
|
|
1786
|
+
"data-clickable": onRowClick ? "true" : void 0,
|
|
1787
|
+
onClick: onRowClick ? () => onRowClick(row) : void 0,
|
|
1788
|
+
onKeyDown: onRowClick ? (event) => {
|
|
1789
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1790
|
+
event.preventDefault();
|
|
1791
|
+
onRowClick(row);
|
|
1792
|
+
}
|
|
1793
|
+
} : void 0,
|
|
1794
|
+
tabIndex: onRowClick ? 0 : void 0,
|
|
1795
|
+
role: onRowClick ? "button" : void 0,
|
|
1796
|
+
children: columns.map((col) => /* @__PURE__ */ jsx37(
|
|
1797
|
+
"td",
|
|
1798
|
+
{
|
|
1799
|
+
className: cn(
|
|
1800
|
+
bodyCellClass,
|
|
1801
|
+
cellPad,
|
|
1802
|
+
col.align && alignClass[col.align],
|
|
1803
|
+
col.className
|
|
1804
|
+
),
|
|
1805
|
+
children: col.cell(row)
|
|
1806
|
+
},
|
|
1807
|
+
col.id
|
|
1808
|
+
))
|
|
1809
|
+
},
|
|
1810
|
+
getRowKey(row)
|
|
1811
|
+
)) }),
|
|
1812
|
+
hasFoot ? /* @__PURE__ */ jsx37("tfoot", { children: /* @__PURE__ */ jsx37("tr", { children: /* @__PURE__ */ jsx37("td", { colSpan: columns.length, className: footCellClass, children: /* @__PURE__ */ jsxs28(
|
|
1813
|
+
"div",
|
|
1814
|
+
{
|
|
1815
|
+
className: cn(
|
|
1816
|
+
footInnerClass,
|
|
1817
|
+
showRowCount && footer ? "justify-between" : "justify-start"
|
|
1818
|
+
),
|
|
1819
|
+
children: [
|
|
1820
|
+
showRowCount ? /* @__PURE__ */ jsx37("span", { children: rowCountText }) : null,
|
|
1821
|
+
footer
|
|
1822
|
+
]
|
|
1823
|
+
}
|
|
1824
|
+
) }) }) }) : null
|
|
1825
|
+
] }) }) });
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// src/app/data/ChartPanel.tsx
|
|
1829
|
+
import { useId as useId4 } from "react";
|
|
1830
|
+
|
|
1831
|
+
// src/app/data/metrics-shared.tsx
|
|
1832
|
+
import { jsx as jsx38, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
1833
|
+
var metricCardShellClass = cn(
|
|
1834
|
+
studioIntegrationCardClass,
|
|
1835
|
+
"aui-app-metric-card shadow-none",
|
|
1836
|
+
"flex flex-col overflow-hidden"
|
|
1837
|
+
);
|
|
1838
|
+
var metricCardHeaderClass = "flex items-start justify-between gap-3 px-4 pb-1 pt-3";
|
|
1839
|
+
var metricTilesRowClass = "grid w-full min-w-0";
|
|
1840
|
+
var metricChartRegionClass = "relative min-h-0 w-full border-t border-border/40 pt-2";
|
|
1841
|
+
var metricChartPlotRegionClass = "relative min-h-0 w-full border-t border-border/40 px-0 pt-5 pb-3";
|
|
1842
|
+
var metricCellDividerClass = "border-r border-border/40";
|
|
1843
|
+
var MetricCardHeader = ({
|
|
1844
|
+
title,
|
|
1845
|
+
titleId,
|
|
1846
|
+
description,
|
|
1847
|
+
actions
|
|
1848
|
+
}) => {
|
|
1849
|
+
if (!title && !description && !actions) return null;
|
|
1850
|
+
return /* @__PURE__ */ jsxs29("header", { className: metricCardHeaderClass, children: [
|
|
1851
|
+
/* @__PURE__ */ jsxs29("div", { className: "min-w-0", children: [
|
|
1852
|
+
title ? /* @__PURE__ */ jsx38("h3", { id: titleId, className: "text-base font-normal text-foreground", children: title }) : null,
|
|
1853
|
+
description ? /* @__PURE__ */ jsx38("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description }) : null
|
|
1854
|
+
] }),
|
|
1855
|
+
actions ? /* @__PURE__ */ jsx38("div", { className: "shrink-0", children: actions }) : null
|
|
1856
|
+
] });
|
|
1857
|
+
};
|
|
1858
|
+
function metricTilesGridColsClass(n) {
|
|
1859
|
+
switch (n) {
|
|
1860
|
+
case 1:
|
|
1861
|
+
return "grid-cols-1";
|
|
1862
|
+
case 2:
|
|
1863
|
+
return "grid-cols-2";
|
|
1864
|
+
case 3:
|
|
1865
|
+
return "grid-cols-3";
|
|
1866
|
+
case 5:
|
|
1867
|
+
return "grid-cols-2 sm:grid-cols-5";
|
|
1868
|
+
case 6:
|
|
1869
|
+
return "grid-cols-2 sm:grid-cols-3 lg:grid-cols-6";
|
|
1870
|
+
default:
|
|
1871
|
+
return "grid-cols-2 md:grid-cols-4";
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// src/app/data/ChartPanel.tsx
|
|
1876
|
+
import { jsx as jsx39, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1877
|
+
var ChartPanel = ({
|
|
1878
|
+
title,
|
|
1879
|
+
description,
|
|
1880
|
+
artifact,
|
|
1881
|
+
children,
|
|
1882
|
+
actions,
|
|
1883
|
+
height = 300,
|
|
1884
|
+
className
|
|
1885
|
+
}) => {
|
|
1886
|
+
const titleId = useId4();
|
|
1887
|
+
const resolvedTitle = title ?? artifact?.title;
|
|
1888
|
+
const hasHeader = Boolean(resolvedTitle || description || actions);
|
|
1889
|
+
const body = children ?? (artifact ? /* @__PURE__ */ jsx39(ChartArtifactView, { artifact, embedded: true, height }) : null);
|
|
1890
|
+
return /* @__PURE__ */ jsxs30(
|
|
1891
|
+
"section",
|
|
1892
|
+
{
|
|
1893
|
+
className: cn(metricCardShellClass, "aui-app-chart-panel", className),
|
|
1894
|
+
"aria-labelledby": resolvedTitle ? titleId : void 0,
|
|
1895
|
+
children: [
|
|
1896
|
+
/* @__PURE__ */ jsx39(
|
|
1897
|
+
MetricCardHeader,
|
|
1898
|
+
{
|
|
1899
|
+
title: resolvedTitle,
|
|
1900
|
+
titleId,
|
|
1901
|
+
description,
|
|
1902
|
+
actions
|
|
1903
|
+
}
|
|
1904
|
+
),
|
|
1905
|
+
/* @__PURE__ */ jsx39(
|
|
1906
|
+
"div",
|
|
1907
|
+
{
|
|
1908
|
+
className: cn(
|
|
1909
|
+
"relative min-h-0 w-full",
|
|
1910
|
+
hasHeader ? metricChartPlotRegionClass : "pt-2 pb-3"
|
|
1911
|
+
),
|
|
1912
|
+
children: body ?? /* @__PURE__ */ jsx39(
|
|
1913
|
+
"div",
|
|
1914
|
+
{
|
|
1915
|
+
className: "flex items-center justify-center text-sm font-normal text-muted-foreground",
|
|
1916
|
+
style: { minHeight: height },
|
|
1917
|
+
role: "status",
|
|
1918
|
+
children: "No chart"
|
|
1919
|
+
}
|
|
1920
|
+
)
|
|
1921
|
+
}
|
|
1922
|
+
)
|
|
1923
|
+
]
|
|
1924
|
+
}
|
|
1925
|
+
);
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
// src/app/data/MetricTile.tsx
|
|
1929
|
+
import { Fragment as Fragment4, jsx as jsx40, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1930
|
+
var trendToneClass = {
|
|
1931
|
+
up: "border-border/80 bg-muted/40 text-muted-foreground",
|
|
1932
|
+
down: "border-border/80 bg-muted/40 text-muted-foreground",
|
|
1933
|
+
neutral: "border-border/80 bg-muted/30 text-muted-foreground"
|
|
1934
|
+
};
|
|
1935
|
+
var metricTileBaseClass = "relative flex min-w-0 flex-1 flex-col gap-1 px-4 py-3 text-left font-normal";
|
|
1936
|
+
var metricTileInteractiveClass = cn(
|
|
1937
|
+
metricTileBaseClass,
|
|
1938
|
+
"bg-transparent hover:bg-transparent active:bg-transparent",
|
|
1939
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-foreground/10"
|
|
1940
|
+
);
|
|
1941
|
+
var MetricTile = ({
|
|
1942
|
+
label,
|
|
1943
|
+
value,
|
|
1944
|
+
unit,
|
|
1945
|
+
trend,
|
|
1946
|
+
trendTone = "neutral",
|
|
1947
|
+
active = false,
|
|
1948
|
+
showDivider = false,
|
|
1949
|
+
onSelect,
|
|
1950
|
+
ariaLabel,
|
|
1951
|
+
className
|
|
1952
|
+
}) => {
|
|
1953
|
+
const content = /* @__PURE__ */ jsxs31(Fragment4, { children: [
|
|
1954
|
+
active ? /* @__PURE__ */ jsx40(
|
|
1955
|
+
"span",
|
|
1956
|
+
{
|
|
1957
|
+
"aria-hidden": true,
|
|
1958
|
+
className: "absolute inset-x-0 bottom-0 h-0.5 bg-foreground dark:bg-white"
|
|
1959
|
+
}
|
|
1960
|
+
) : null,
|
|
1961
|
+
/* @__PURE__ */ jsx40("span", { className: "text-xs font-normal text-muted-foreground", children: label }),
|
|
1962
|
+
/* @__PURE__ */ jsxs31("span", { className: "flex items-center gap-2", children: [
|
|
1963
|
+
/* @__PURE__ */ jsxs31("span", { className: "flex items-baseline gap-1", children: [
|
|
1964
|
+
/* @__PURE__ */ jsx40("span", { className: "text-2xl font-normal tracking-tight text-foreground tabular-nums", children: value }),
|
|
1965
|
+
unit ? /* @__PURE__ */ jsx40("span", { className: "text-xs font-normal text-muted-foreground", children: unit }) : null
|
|
1966
|
+
] }),
|
|
1967
|
+
trend ? /* @__PURE__ */ jsx40(
|
|
1968
|
+
"span",
|
|
1969
|
+
{
|
|
1970
|
+
className: cn(
|
|
1971
|
+
"rounded-full border px-1.5 py-0.5 text-xs font-normal",
|
|
1972
|
+
trendToneClass[trendTone]
|
|
1973
|
+
),
|
|
1974
|
+
children: trend
|
|
1975
|
+
}
|
|
1976
|
+
) : null
|
|
1977
|
+
] })
|
|
1978
|
+
] });
|
|
1979
|
+
const divider = showDivider ? metricCellDividerClass : void 0;
|
|
1980
|
+
if (onSelect) {
|
|
1981
|
+
return /* @__PURE__ */ jsx40(
|
|
1982
|
+
"button",
|
|
1983
|
+
{
|
|
1984
|
+
type: "button",
|
|
1985
|
+
onClick: onSelect,
|
|
1986
|
+
"aria-pressed": active,
|
|
1987
|
+
"aria-label": ariaLabel,
|
|
1988
|
+
className: cn(metricTileInteractiveClass, divider, className),
|
|
1989
|
+
children: content
|
|
1990
|
+
}
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
return /* @__PURE__ */ jsx40("div", { className: cn(metricTileBaseClass, divider, className), children: content });
|
|
1994
|
+
};
|
|
1995
|
+
|
|
1996
|
+
// src/app/data/MetricRow.tsx
|
|
1997
|
+
import { useId as useId5, useState as useState5 } from "react";
|
|
1998
|
+
import { jsx as jsx41, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
1999
|
+
var MetricRow = ({
|
|
2000
|
+
title,
|
|
2001
|
+
description,
|
|
2002
|
+
actions,
|
|
2003
|
+
metrics,
|
|
2004
|
+
activeMetricId,
|
|
2005
|
+
defaultActiveMetricId,
|
|
2006
|
+
onMetricChange,
|
|
2007
|
+
metricsAriaLabel = "Metrics",
|
|
2008
|
+
className
|
|
2009
|
+
}) => {
|
|
2010
|
+
const titleId = useId5();
|
|
2011
|
+
const selectable = onMetricChange != null || activeMetricId != null;
|
|
2012
|
+
const [internalId, setInternalId] = useState5(
|
|
2013
|
+
defaultActiveMetricId ?? metrics[0]?.id
|
|
2014
|
+
);
|
|
2015
|
+
const activeId = activeMetricId ?? internalId;
|
|
2016
|
+
const select = (id) => {
|
|
2017
|
+
if (activeMetricId == null) setInternalId(id);
|
|
2018
|
+
onMetricChange?.(id);
|
|
2019
|
+
};
|
|
2020
|
+
return /* @__PURE__ */ jsxs32(
|
|
2021
|
+
"section",
|
|
2022
|
+
{
|
|
2023
|
+
className: cn(metricCardShellClass, className),
|
|
2024
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
2025
|
+
children: [
|
|
2026
|
+
/* @__PURE__ */ jsx41(
|
|
2027
|
+
MetricCardHeader,
|
|
2028
|
+
{
|
|
2029
|
+
title,
|
|
2030
|
+
titleId,
|
|
2031
|
+
description,
|
|
2032
|
+
actions
|
|
2033
|
+
}
|
|
2034
|
+
),
|
|
2035
|
+
/* @__PURE__ */ jsx41(
|
|
2036
|
+
"div",
|
|
2037
|
+
{
|
|
2038
|
+
role: selectable ? "group" : void 0,
|
|
2039
|
+
"aria-label": selectable ? metricsAriaLabel : void 0,
|
|
2040
|
+
className: cn(
|
|
2041
|
+
metricTilesRowClass,
|
|
2042
|
+
metricTilesGridColsClass(metrics.length),
|
|
2043
|
+
(title || description || actions) && "mt-3"
|
|
2044
|
+
),
|
|
2045
|
+
children: metrics.map((m, index) => /* @__PURE__ */ jsx41(
|
|
2046
|
+
MetricTile,
|
|
2047
|
+
{
|
|
2048
|
+
label: m.label,
|
|
2049
|
+
value: m.value,
|
|
2050
|
+
unit: m.unit,
|
|
2051
|
+
trend: m.trend,
|
|
2052
|
+
trendTone: m.trendTone,
|
|
2053
|
+
active: selectable && m.id === activeId,
|
|
2054
|
+
showDivider: index < metrics.length - 1,
|
|
2055
|
+
onSelect: selectable ? () => select(m.id) : void 0
|
|
2056
|
+
},
|
|
2057
|
+
m.id
|
|
2058
|
+
))
|
|
2059
|
+
}
|
|
2060
|
+
)
|
|
2061
|
+
]
|
|
2062
|
+
}
|
|
2063
|
+
);
|
|
2064
|
+
};
|
|
2065
|
+
|
|
2066
|
+
// src/app/data/MetricChartCard.tsx
|
|
2067
|
+
import { useId as useId6, useState as useState6 } from "react";
|
|
2068
|
+
import { jsx as jsx42, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2069
|
+
var MetricChartCard = ({
|
|
2070
|
+
title,
|
|
2071
|
+
description,
|
|
2072
|
+
actions,
|
|
2073
|
+
metrics,
|
|
2074
|
+
activeMetricId,
|
|
2075
|
+
defaultActiveMetricId,
|
|
2076
|
+
onMetricChange,
|
|
2077
|
+
xKey = "date",
|
|
2078
|
+
variant = "area",
|
|
2079
|
+
height = 300,
|
|
2080
|
+
formatX,
|
|
2081
|
+
formatValue,
|
|
2082
|
+
emptyLabel = "No data yet",
|
|
2083
|
+
metricsAriaLabel = "Metrics",
|
|
2084
|
+
className
|
|
2085
|
+
}) => {
|
|
2086
|
+
const titleId = useId6();
|
|
2087
|
+
const [internalId, setInternalId] = useState6(
|
|
2088
|
+
defaultActiveMetricId ?? metrics[0]?.id
|
|
2089
|
+
);
|
|
2090
|
+
const activeId = activeMetricId ?? internalId;
|
|
2091
|
+
const active = metrics.find((m) => m.id === activeId) ?? metrics[0];
|
|
2092
|
+
const select = (id) => {
|
|
2093
|
+
if (activeMetricId == null) setInternalId(id);
|
|
2094
|
+
onMetricChange?.(id);
|
|
2095
|
+
};
|
|
2096
|
+
const hasHeader = Boolean(title || description || actions);
|
|
2097
|
+
const chartAriaLabel = typeof active?.label === "string" ? `${active.label} over time` : "Metric chart";
|
|
2098
|
+
return /* @__PURE__ */ jsxs33(
|
|
2099
|
+
"section",
|
|
2100
|
+
{
|
|
2101
|
+
className: cn(metricCardShellClass, className),
|
|
2102
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
2103
|
+
children: [
|
|
2104
|
+
/* @__PURE__ */ jsx42(
|
|
2105
|
+
MetricCardHeader,
|
|
2106
|
+
{
|
|
2107
|
+
title,
|
|
2108
|
+
titleId,
|
|
2109
|
+
description,
|
|
2110
|
+
actions
|
|
2111
|
+
}
|
|
2112
|
+
),
|
|
2113
|
+
/* @__PURE__ */ jsx42(
|
|
2114
|
+
"div",
|
|
2115
|
+
{
|
|
2116
|
+
role: "group",
|
|
2117
|
+
"aria-label": metricsAriaLabel,
|
|
2118
|
+
className: cn(
|
|
2119
|
+
metricTilesRowClass,
|
|
2120
|
+
metricTilesGridColsClass(metrics.length),
|
|
2121
|
+
hasHeader && "mt-3"
|
|
2122
|
+
),
|
|
2123
|
+
children: metrics.map((m, index) => /* @__PURE__ */ jsx42(
|
|
2124
|
+
MetricTile,
|
|
2125
|
+
{
|
|
2126
|
+
label: m.label,
|
|
2127
|
+
value: m.value,
|
|
2128
|
+
unit: m.unit,
|
|
2129
|
+
trend: m.trend,
|
|
2130
|
+
trendTone: m.trendTone,
|
|
2131
|
+
active: m.id === active?.id,
|
|
2132
|
+
showDivider: index < metrics.length - 1,
|
|
2133
|
+
onSelect: () => select(m.id)
|
|
2134
|
+
},
|
|
2135
|
+
m.id
|
|
2136
|
+
))
|
|
2137
|
+
}
|
|
2138
|
+
),
|
|
2139
|
+
/* @__PURE__ */ jsx42("div", { className: metricChartRegionClass, "aria-live": "polite", "aria-atomic": "true", children: active?.data && active.data.length > 0 ? /* @__PURE__ */ jsx42(
|
|
2140
|
+
LineAreaChart,
|
|
2141
|
+
{
|
|
2142
|
+
data: active.data,
|
|
2143
|
+
xKey,
|
|
2144
|
+
series: [
|
|
2145
|
+
{
|
|
2146
|
+
dataKey: active.dataKey ?? "value",
|
|
2147
|
+
label: typeof active.label === "string" ? active.label : active.id,
|
|
2148
|
+
color: active.color
|
|
2149
|
+
}
|
|
2150
|
+
],
|
|
2151
|
+
variant,
|
|
2152
|
+
layout: "flush",
|
|
2153
|
+
height,
|
|
2154
|
+
formatX,
|
|
2155
|
+
formatValue,
|
|
2156
|
+
ariaLabel: chartAriaLabel
|
|
2157
|
+
},
|
|
2158
|
+
active.id
|
|
2159
|
+
) : /* @__PURE__ */ jsx42(
|
|
2160
|
+
"div",
|
|
2161
|
+
{
|
|
2162
|
+
className: "flex w-full items-center justify-center text-sm font-normal text-muted-foreground",
|
|
2163
|
+
style: { height },
|
|
2164
|
+
role: "status",
|
|
2165
|
+
children: emptyLabel
|
|
2166
|
+
}
|
|
2167
|
+
) })
|
|
2168
|
+
]
|
|
2169
|
+
}
|
|
2170
|
+
);
|
|
2171
|
+
};
|
|
2172
|
+
|
|
2173
|
+
// src/charts/sparkline.tsx
|
|
2174
|
+
import { useId as useId7 } from "react";
|
|
2175
|
+
import { Fragment as Fragment5, jsx as jsx43, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
2176
|
+
var Sparkline = ({
|
|
2177
|
+
data,
|
|
2178
|
+
dataKey = "value",
|
|
2179
|
+
color = "var(--primary, #6366f1)",
|
|
2180
|
+
area = true,
|
|
2181
|
+
width = 96,
|
|
2182
|
+
height = 28,
|
|
2183
|
+
strokeWidth = 1.5,
|
|
2184
|
+
className,
|
|
2185
|
+
ariaLabel = "Trend"
|
|
2186
|
+
}) => {
|
|
2187
|
+
const uid = useId7();
|
|
2188
|
+
const values = data.map((d) => typeof d === "number" ? d : toNum(d[dataKey]));
|
|
2189
|
+
if (values.length === 0) {
|
|
2190
|
+
return /* @__PURE__ */ jsx43("span", { className: cn("inline-block", className), style: { width, height } });
|
|
2191
|
+
}
|
|
2192
|
+
const pad = strokeWidth + 1;
|
|
2193
|
+
const min = Math.min(...values);
|
|
2194
|
+
const max = Math.max(...values);
|
|
2195
|
+
const range = max - min || 1;
|
|
2196
|
+
const innerW = width - pad * 2;
|
|
2197
|
+
const innerH = height - pad * 2;
|
|
2198
|
+
const points = values.map((v, i) => ({
|
|
2199
|
+
x: pad + (values.length > 1 ? i / (values.length - 1) * innerW : innerW / 2),
|
|
2200
|
+
y: pad + innerH - (v - min) / range * innerH
|
|
2201
|
+
}));
|
|
2202
|
+
return /* @__PURE__ */ jsxs34(
|
|
2203
|
+
"svg",
|
|
2204
|
+
{
|
|
2205
|
+
width,
|
|
2206
|
+
height,
|
|
2207
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
2208
|
+
className: cn("block", className),
|
|
2209
|
+
role: "img",
|
|
2210
|
+
"aria-label": ariaLabel,
|
|
2211
|
+
preserveAspectRatio: "none",
|
|
2212
|
+
children: [
|
|
2213
|
+
area && /* @__PURE__ */ jsxs34(Fragment5, { children: [
|
|
2214
|
+
/* @__PURE__ */ jsx43("defs", { children: /* @__PURE__ */ jsxs34("linearGradient", { id: `${uid}-spark`, x1: "0", x2: "0", y1: "0", y2: "1", children: [
|
|
2215
|
+
/* @__PURE__ */ jsx43("stop", { offset: "0%", style: { stopColor: color, stopOpacity: 0.25 } }),
|
|
2216
|
+
/* @__PURE__ */ jsx43("stop", { offset: "100%", style: { stopColor: color, stopOpacity: 0 } })
|
|
2217
|
+
] }) }),
|
|
2218
|
+
/* @__PURE__ */ jsx43("path", { d: monotoneAreaPath(points, height - pad), fill: `url(#${uid}-spark)` })
|
|
2219
|
+
] }),
|
|
2220
|
+
/* @__PURE__ */ jsx43(
|
|
2221
|
+
"path",
|
|
2222
|
+
{
|
|
2223
|
+
d: monotoneLinePath(points),
|
|
2224
|
+
fill: "none",
|
|
2225
|
+
stroke: color,
|
|
2226
|
+
strokeWidth,
|
|
2227
|
+
strokeLinecap: "round",
|
|
2228
|
+
strokeLinejoin: "round"
|
|
2229
|
+
}
|
|
2230
|
+
)
|
|
2231
|
+
]
|
|
2232
|
+
}
|
|
2233
|
+
);
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2236
|
+
export {
|
|
2237
|
+
APP_KIT_AGENT_INSTRUCTIONS,
|
|
2238
|
+
appPageColumnClass,
|
|
2239
|
+
appShellTopbarInsetClass,
|
|
2240
|
+
appShellInsetTopClass,
|
|
2241
|
+
appSurfaceCardClass,
|
|
2242
|
+
appStatTileClass,
|
|
2243
|
+
appFilterBarClass,
|
|
2244
|
+
appSearchInputClass,
|
|
2245
|
+
useAppShellChat,
|
|
2246
|
+
AppShell,
|
|
2247
|
+
AppShellTopbar,
|
|
2248
|
+
AppShellChatTrigger,
|
|
2249
|
+
PageHeader,
|
|
2250
|
+
Page,
|
|
2251
|
+
Section,
|
|
2252
|
+
AppCopilotProvider,
|
|
2253
|
+
useAppCopilotContext,
|
|
2254
|
+
AppChatPanel,
|
|
2255
|
+
SurfaceCard,
|
|
2256
|
+
StatTile,
|
|
2257
|
+
EmptyState,
|
|
2258
|
+
StatusBadge,
|
|
2259
|
+
AppConfirmDialog,
|
|
2260
|
+
InfoCard,
|
|
2261
|
+
StatusDot,
|
|
2262
|
+
DescriptionList,
|
|
2263
|
+
ExpandableSection,
|
|
2264
|
+
ResourceCard,
|
|
2265
|
+
SettingsSectionHeader,
|
|
2266
|
+
SettingsSection,
|
|
2267
|
+
FieldRow,
|
|
2268
|
+
FloatingUnsavedChangesBar,
|
|
2269
|
+
DangerZoneAction,
|
|
2270
|
+
DangerZone,
|
|
2271
|
+
INTEGRATION_CATALOG_CARD_HEIGHT_CLASS,
|
|
2272
|
+
IntegrationCard,
|
|
2273
|
+
IntegrationsEmptyState,
|
|
2274
|
+
PlanBadge,
|
|
2275
|
+
ConnectionRow,
|
|
2276
|
+
connectionRowListClass,
|
|
2277
|
+
ConnectionRowList,
|
|
2278
|
+
SubNav,
|
|
2279
|
+
Breadcrumbs,
|
|
2280
|
+
Field,
|
|
2281
|
+
FieldInput,
|
|
2282
|
+
FieldTextarea,
|
|
2283
|
+
FieldSelect,
|
|
2284
|
+
FieldSwitch,
|
|
2285
|
+
SearchInput,
|
|
2286
|
+
FormSection,
|
|
2287
|
+
FilterBar,
|
|
2288
|
+
DataTable,
|
|
2289
|
+
ChartPanel,
|
|
2290
|
+
MetricTile,
|
|
2291
|
+
MetricRow,
|
|
2292
|
+
MetricChartCard,
|
|
2293
|
+
Sparkline
|
|
2294
|
+
};
|