@usetheo/ui 0.12.0-next.0 → 0.13.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 +111 -0
- package/README.md +20 -19
- package/dist/chunk-6ZQKEY54.js +149 -0
- package/dist/chunk-6ZQKEY54.js.map +1 -0
- package/dist/chunk-AVPHVQZS.js +73 -0
- package/dist/chunk-AVPHVQZS.js.map +1 -0
- package/dist/{chunk-ZNILW4G5.js → chunk-DW247T3Q.js} +2 -2
- package/dist/{chunk-ZNILW4G5.js.map → chunk-DW247T3Q.js.map} +1 -1
- package/dist/chunk-GXBFGWQN.js +81 -0
- package/dist/chunk-GXBFGWQN.js.map +1 -0
- package/dist/chunk-I32I36LW.js +113 -0
- package/dist/chunk-I32I36LW.js.map +1 -0
- package/dist/chunk-JPTPIZ5V.js +120 -0
- package/dist/chunk-JPTPIZ5V.js.map +1 -0
- package/dist/{chunk-TO3UAT6O.js → chunk-K7PYLTMP.js} +3 -3
- package/dist/{chunk-TO3UAT6O.js.map → chunk-K7PYLTMP.js.map} +1 -1
- package/dist/{chunk-R2PAGRDP.js → chunk-MYEHGDC2.js} +2 -2
- package/dist/{chunk-R2PAGRDP.js.map → chunk-MYEHGDC2.js.map} +1 -1
- package/dist/{chunk-IPEYGWA7.js → chunk-PTHRL242.js} +4 -4
- package/dist/{chunk-IPEYGWA7.js.map → chunk-PTHRL242.js.map} +1 -1
- package/dist/chunk-RC5XME4T.js +33 -0
- package/dist/chunk-RC5XME4T.js.map +1 -0
- package/dist/chunk-UK27KR35.js +73 -0
- package/dist/chunk-UK27KR35.js.map +1 -0
- package/dist/chunk-UOMQPIB4.js +48 -0
- package/dist/chunk-UOMQPIB4.js.map +1 -0
- package/dist/{chunk-TNBJ36XJ.js → chunk-XZKEGEPT.js} +4 -4
- package/dist/{chunk-TNBJ36XJ.js.map → chunk-XZKEGEPT.js.map} +1 -1
- package/dist/components.css +1 -1
- package/dist/composites/agent-editor/index.js +2 -2
- package/dist/composites/data-table/index.js +1 -1
- package/dist/composites/rule-editor/index.js +3 -3
- package/dist/composites/skill-editor/index.js +3 -3
- package/dist/composites/stability-bundle-viewer/index.js +4 -0
- package/dist/composites/stability-bundle-viewer/index.js.map +1 -0
- package/dist/index.d.ts +162 -1
- package/dist/index.js +44 -36
- package/dist/index.js.map +1 -1
- package/dist/preset-v3-legacy.js.map +1 -1
- package/dist/primitives/branch-indicator/index.js +4 -0
- package/dist/primitives/branch-indicator/index.js.map +1 -0
- package/dist/primitives/channel-card/index.js +4 -0
- package/dist/primitives/channel-card/index.js.map +1 -0
- package/dist/primitives/export-chat-dialog/index.js +4 -0
- package/dist/primitives/export-chat-dialog/index.js.map +1 -0
- package/dist/primitives/gateway-status-indicator/index.js +4 -0
- package/dist/primitives/gateway-status-indicator/index.js.map +1 -0
- package/dist/primitives/pin-input/index.js +1 -1
- package/dist/primitives/run-status-pill/index.js +4 -0
- package/dist/primitives/run-status-pill/index.js.map +1 -0
- package/dist/primitives/thinking-level-selector/index.js +4 -0
- package/dist/primitives/thinking-level-selector/index.js.map +1 -0
- package/dist/primitives/update-banner/index.js +4 -0
- package/dist/primitives/update-banner/index.js.map +1 -0
- package/package.json +126 -92
- package/registry/r/data-table.json +1 -1
- package/registry/r/pin-input.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,117 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.13.0] - 2026-05-29
|
|
11
|
+
|
|
12
|
+
**Minor (additive only — zero breaking changes).**
|
|
13
|
+
|
|
14
|
+
Seven new components shipped as Phase 1 of the `theokit-ui-parity` plan
|
|
15
|
+
(`.claude/knowledge-base/plans/theokit-ui-parity-plan.md` v1.1). All seven
|
|
16
|
+
mirror OpenClaw Control UI patterns the framework lacked, designed to be
|
|
17
|
+
composable into any theokit app via `@usetheo/ui` barrel.
|
|
18
|
+
|
|
19
|
+
### Added — Phase 2 component
|
|
20
|
+
|
|
21
|
+
- **`<ChannelCard>`** (primitive, `agent/`) — inbound gateway connection
|
|
22
|
+
surface (Telegram, Discord, Slack, WhatsApp, Webhook, MCP). 4 statuses
|
|
23
|
+
(`disconnected | connecting | connected | error`) with the toggle button
|
|
24
|
+
reflecting the current state: `connected` shows "Disconnect", others show
|
|
25
|
+
"Connect", `connecting` keeps the button disabled (transient state guard).
|
|
26
|
+
Closed `ChannelPlatform` enum prevents silent typos at the backend
|
|
27
|
+
boundary. 7 Vitest tests including vitest-axe. Ladle story with 5
|
|
28
|
+
variants (Connected/Disconnected/Connecting/ErrorState/MCP). Consumed by
|
|
29
|
+
the dogfood-app `/channels` page end-to-end against the
|
|
30
|
+
`server/routes/channels.ts` registry.
|
|
31
|
+
|
|
32
|
+
### Added — Phase 1 components (44/44 Vitest GREEN)
|
|
33
|
+
|
|
34
|
+
- **`<ThinkingLevelSelector>`** (primitive, `agent/`) — multi-state combobox
|
|
35
|
+
for LLM reasoning budget. Mirrors OpenClaw thinking selector. Values:
|
|
36
|
+
`"inherited" | "off" | "minimal" | "low" | "medium" | "high" | "xhigh"`.
|
|
37
|
+
- **`<RunStatusPill>`** (primitive, `agent/`) — compact status indicator
|
|
38
|
+
for Run/Task lifecycle. 6 states mirror SDK `Task` enum (D362):
|
|
39
|
+
`queued | in_progress | finished | error | cancelled | interrupted`.
|
|
40
|
+
- **`<BranchIndicator>`** (primitive, `agent/`) — small "×N" pill that
|
|
41
|
+
shows when a run was retried/branched. Returns `null` for `< 2` or
|
|
42
|
+
non-integer (EC-10 guard).
|
|
43
|
+
- **`<GatewayStatusIndicator>`** (primitive, `infra/`) — live
|
|
44
|
+
connection-status dot. 4 statuses (online/offline/degraded/reconnecting)
|
|
45
|
+
× 2 variants (compact/labeled) + optional latency text.
|
|
46
|
+
- **`<UpdateBanner>`** (primitive, `infra/`) — top-of-app alert for newer
|
|
47
|
+
version. Dismiss persistence is consumer's responsibility (EC-16).
|
|
48
|
+
- **`<ExportChatDialog>`** (primitive, `agent/`) — modal exporting chat in
|
|
49
|
+
`markdown | json | jsonl | sharegpt`. Async-aware (disables buttons
|
|
50
|
+
during pending export).
|
|
51
|
+
- **`<StabilityBundleViewer>`** (composite, `infra/`) — crash bundle JSON
|
|
52
|
+
inspector. Sections collapse independently. EC-9 absorbed: handles
|
|
53
|
+
missing optional sections gracefully.
|
|
54
|
+
|
|
55
|
+
### Added — Phase 0 tooling
|
|
56
|
+
|
|
57
|
+
- **`scripts/inventory-components.mjs`** — walks
|
|
58
|
+
`src/components/{primitives,composites}/<name>/` producing
|
|
59
|
+
`component-inventory.json`. CI drift-gate seed.
|
|
60
|
+
- **`scripts/generate-missing-stories.mjs`** — gerador + CI check mode.
|
|
61
|
+
EC-5/D12 absorbed: ships `kebabToPascal(name)` helper with 9 Vitest
|
|
62
|
+
cases + match-confirmation via `export\s+...` regex.
|
|
63
|
+
- **`wrangler.toml` + `.github/workflows/deploy-ladle.yml`** —
|
|
64
|
+
Cloudflare Pages deploy config for the Ladle component catalog.
|
|
65
|
+
Workflow contains `pnpm ladle:build` step BEFORE deploy (EC-14).
|
|
66
|
+
- New package scripts: `inventory`, `stories:check`, `stories:generate`,
|
|
67
|
+
`stories:test`.
|
|
68
|
+
|
|
69
|
+
### Changed — Build pipeline hardening
|
|
70
|
+
|
|
71
|
+
- **`scripts/regen-subpath-exports.ts`** now delegates to `sync-exports.ts`
|
|
72
|
+
for the final write. Previously the post-build step sorted ALL exports
|
|
73
|
+
alphabetically while `sync-exports.buildExports` produced the canonical
|
|
74
|
+
`BASE → sorted components → ISOLATED` order — meaning `pnpm build &&
|
|
75
|
+
pnpm quality:structure` regressed every time. The validator and the
|
|
76
|
+
build now share a single source of truth.
|
|
77
|
+
- **`scripts/sync-exports.ts`** now post-formats `package.json` via
|
|
78
|
+
`biome format --write` after writing. `JSON.stringify(_, _, 2)` always
|
|
79
|
+
expands short arrays like `sideEffects` and `onlyBuiltDependencies`,
|
|
80
|
+
while `pnpm format:check` (Biome) expects them inline; the two were
|
|
81
|
+
fighting after every sync.
|
|
82
|
+
- **`tailwind.config.ts`** dropped the `satisfies Config` clause. The
|
|
83
|
+
preset (`src/styles/tailwind-preset.ts`) resolves the v4 `Config` type
|
|
84
|
+
via the `src/styles/node_modules/tailwindcss` symlink to v4.3.0, while
|
|
85
|
+
the root config would resolve v3.4.19 — the v3/v4 type seam mismatched
|
|
86
|
+
on `darkMode` (`DarkModeStrategy` vs `Partial<DarkModeConfig>`).
|
|
87
|
+
Runtime correctness is enforced by `pnpm dogfood:v4-zero-config`.
|
|
88
|
+
- **`biome.json`** override extended to cover `scripts/**/*.mjs` so the
|
|
89
|
+
`noConsole` rule does not block tool scripts (it was already exempted
|
|
90
|
+
for `.ts`).
|
|
91
|
+
- **`pin-input.tsx`** — removed stale `// biome-ignore lint/suspicious/noArrayIndexKey`
|
|
92
|
+
comment now flagged as unused suppression.
|
|
93
|
+
- **`export-chat-dialog.tsx` + `run-status-pill.tsx` + `thinking-level-selector.tsx`** —
|
|
94
|
+
added precise `biome-ignore` comments documenting the `a11y/useSemanticElements`
|
|
95
|
+
and `suspicious/noConsole` exceptions, with the reason inline.
|
|
96
|
+
|
|
97
|
+
### Documentation
|
|
98
|
+
|
|
99
|
+
- 7 new component pages under `theo-opendocs/content/theoui/{agent,infra}/`.
|
|
100
|
+
- New `ChannelCard` page under `theo-opendocs/content/theoui/agent/channel-card.mdx`
|
|
101
|
+
with live preview (`ComponentPreview` + `PropsTable`) deployed to
|
|
102
|
+
`https://channel-card.theo-opendocs.pages.dev/`.
|
|
103
|
+
|
|
104
|
+
## [0.12.0] - 2026-05-28
|
|
105
|
+
|
|
106
|
+
**Stable release — promoted from `0.12.0-next.0` after dogfood validation + cross-repo contract gates landed.**
|
|
107
|
+
|
|
108
|
+
First `@usetheo/ui` release on the npm `latest` tag without a `-next.*` pre-release suffix. Content is the union of everything shipped in `0.12.0-next.0` (2026-05-25) plus the additions below from `[Unreleased]` consolidation. **Zero breaking changes vs `0.12.0-next.0`** — consumers on `^0.12.0-next.0` upgrade transparently.
|
|
109
|
+
|
|
110
|
+
### Added (dogfood-fixes-and-coverage-expansion T4.1, 2026-05-28)
|
|
111
|
+
|
|
112
|
+
- **`scripts/validate-exports.mjs`** (NEW) — FAANG-grade pre-publish gate com 6 runtime checks: (1) exports['.'] declared, (2) type:module consistency D13, (3) import condition file exists + dynamic import runtime, (4) require condition runtime check (skip se ESM-only), (5) ESM-only intentional notice, (6) TODOS subpath exports validados (não só `.`). Bloqueia `npm publish` se shape OR comportamento regridir.
|
|
113
|
+
- **`prepublishOnly`** hook estendido: `pnpm build && pnpm test:contract && node scripts/validate-exports.mjs`. EC-8 fix do edge case review FAANG-grade.
|
|
114
|
+
- **`validate:exports`** script standalone — pode rodar manual `pnpm validate:exports`.
|
|
115
|
+
|
|
116
|
+
### Added (cross-repo-integration-coesao, 2026-05-28)
|
|
117
|
+
|
|
118
|
+
- **Contract test mirror** — `tests/contract/theokit-consumer.test.ts` (6 BDD `it()`) validates `dist/vite-plugin.js` shape (default export factory + Plugin/Plugin[] return + `name: string`) and presence of `dist/preset.css`, `dist/styles.css`, `dist/fonts.css`. Roda via novo `pnpm test:contract`. Bloqueia publish via `prepublishOnly: pnpm build && pnpm test:contract`. Cumpre [ADR 0001](docs/adr/0001-vite-plugin-subpath-export-contract.md) (cross-repo contract). Plano coordenador no monorepo theokit-tools: [cross-repo-integration-coesao-plan.md](../.claude/knowledge-base/plans/cross-repo-integration-coesao-plan.md) T1.3.
|
|
119
|
+
- **`vitest.config.ts`** include estendido para cobrir `tests/**/*.{test,spec}.ts` (era só `src/`).
|
|
120
|
+
|
|
10
121
|
## [0.12.0-next.0] - 2026-05-25
|
|
11
122
|
|
|
12
123
|
Minor (additive, zero breaking change) — ships two LLM-facing artifacts
|
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ A React component library built for AI agent surfaces and cloud dashboards. **10
|
|
|
13
13
|
<!-- BEGIN:counts -->
|
|
14
14
|
[](./LICENSE)
|
|
15
15
|
[](https://react.dev)
|
|
16
|
-
[](#quality-gates)
|
|
17
|
+
[](#component-catalog)
|
|
18
18
|
[](https://ui.shadcn.com/docs/registry)
|
|
19
19
|
<!-- END:counts -->
|
|
20
20
|
|
|
@@ -143,33 +143,34 @@ The skill lives at [`skills/theo-ui/`](./skills/theo-ui/) and is installable via
|
|
|
143
143
|
## Component catalog
|
|
144
144
|
|
|
145
145
|
<!-- BEGIN:component-catalog-intro -->
|
|
146
|
-
**
|
|
146
|
+
**147 components**, organized by mechanical rule: a *primitive* imports no other `@usetheo/ui` component; a *composite* does.
|
|
147
147
|
<!-- END:component-catalog-intro -->
|
|
148
148
|
|
|
149
149
|
<details>
|
|
150
150
|
<summary>
|
|
151
151
|
<!-- BEGIN:primitives-count -->
|
|
152
|
-
**Primitives** (
|
|
152
|
+
**Primitives** (99) — building blocks
|
|
153
153
|
<!-- END:primitives-count -->
|
|
154
154
|
</summary>
|
|
155
155
|
|
|
156
156
|
<!-- BEGIN:primitives -->
|
|
157
157
|
`ActionBar` · `AgentErrorCard` · `AgentEvent` · `AgentHandoff` · `AgentProfile` · `AgentStartingState`
|
|
158
158
|
`AgentStreaming` · `Alert` · `ArtifactPreview` · `AttachmentChip` · `AuditLogEntry` · `AutoCompactNotice`
|
|
159
|
-
`Avatar` · `Badge` · `
|
|
160
|
-
`
|
|
161
|
-
`
|
|
162
|
-
`
|
|
163
|
-
`
|
|
164
|
-
`
|
|
165
|
-
`
|
|
166
|
-
`
|
|
167
|
-
`
|
|
168
|
-
`
|
|
169
|
-
`
|
|
170
|
-
`
|
|
159
|
+
`Avatar` · `Badge` · `BranchIndicator` · `BrowserControls` · `BuildLogStream` · `Button`
|
|
160
|
+
`CapabilityIndicator` · `Card` · `ChannelCard` · `ChatThread` · `Checkbox` · `ContextCard`
|
|
161
|
+
`ContextWindowBar` · `CopyButton` · `CostMeter` · `CreatedFilesCard` · `CronJobCard` · `DangerZone`
|
|
162
|
+
`Dialog` · `DiffViewer` · `DropdownMenu` · `EmptyState` · `ExportChatDialog` · `FolderContextCard`
|
|
163
|
+
`FolderSelector` · `FormField` · `GatewayStatusIndicator` · `HookConfig` · `HookEventLog` · `Input`
|
|
164
|
+
`IntentSelector` · `Label` · `LaneBoard` · `LoginSplit` · `MCPServerCard` · `MemoryEditor`
|
|
165
|
+
`MentionMenu` · `MetricsPanel` · `ModelCard` · `ModelSelector` · `Pagination` · `PermissionMatrix`
|
|
166
|
+
`PinInput` · `PlanBadge` · `Progress` · `ProgressChecklist` · `ProjectSwitcher` · `QuickActionChips`
|
|
167
|
+
`RadioGroup` · `RecentFoldersList` · `RuleCard` · `RunStats` · `RunStatusPill` · `RunningTasksPanel`
|
|
168
|
+
`ScrollArea` · `Select` · `SessionListItem` · `SessionTimeline` · `Sheet` · `Sidebar`
|
|
169
|
+
`Skeleton` · `SkillCard` · `SocialAuthRow` · `StatTile` · `StatusDot` · `StepsRail`
|
|
170
|
+
`SubAgentDispatch` · `Switch` · `SystemPromptEditor` · `Table` · `Tabs` · `TaskNode`
|
|
171
|
+
`TaskPlan` · `TerminalPanel` · `Textarea` · `ThinkingLevelSelector` · `Timestamp` · `Toast`
|
|
171
172
|
`Toaster` · `TokenUsageChart` · `ToolCall` · `ToolCallCard` · `ToolResult` · `ToolsList`
|
|
172
|
-
`Tooltip` · `TopNav`
|
|
173
|
+
`Tooltip` · `TopNav` · `UpdateBanner`
|
|
173
174
|
<!-- END:primitives -->
|
|
174
175
|
|
|
175
176
|
</details>
|
|
@@ -177,12 +178,12 @@ The skill lives at [`skills/theo-ui/`](./skills/theo-ui/) and is installable via
|
|
|
177
178
|
<details>
|
|
178
179
|
<summary>
|
|
179
180
|
<!-- BEGIN:composites-count -->
|
|
180
|
-
**Composites** (
|
|
181
|
+
**Composites** (48) — assembled flows
|
|
181
182
|
<!-- END:composites-count -->
|
|
182
183
|
</summary>
|
|
183
184
|
|
|
184
185
|
<!-- BEGIN:composites -->
|
|
185
|
-
`AccountMenu` · `AgentComposer` · `AgentEditor` · `AgentStream` · `AgentTimeline` · `ApprovalCard` · `ChatComposer` · `ChatMessage` · `ChatMessageAction` · `ChatMessageActions` · `ChatMessageBranch` · `ChatMessageBranchContent` · `ChatMessageBranchNext` · `ChatMessageBranchPage` · `ChatMessageBranchPrevious` · `ChatMessageBranchSelector` · `ChatMessageContent` · `ChatMessageResponse` · `ChatMessageRoot` · `ChatMessageToolbar` · `CodeBlock` · `CommandPalette` · `ConfirmDialog` · `CronJobsList` · `DataPart` · `DataTable` · `DeploymentRow` · `DomainConfig` · `EnvVarEditor` · `FilePart` · `MCPServerList` · `PageShell` · `PermissionModal` · `PreviewEnvCard` · `PreviewPanel` · `ProjectCard` · `ReasoningPart` · `RollbackUI` · `RuleEditor` · `SkillEditor` · `SkillsList` · `SourceDocumentPart` · `SourceUrlPart` · `TaskHeader` · `TextPart` · `ToolCallPart` · `UsageMeter`
|
|
186
|
+
`AccountMenu` · `AgentComposer` · `AgentEditor` · `AgentStream` · `AgentTimeline` · `ApprovalCard` · `ChatComposer` · `ChatMessage` · `ChatMessageAction` · `ChatMessageActions` · `ChatMessageBranch` · `ChatMessageBranchContent` · `ChatMessageBranchNext` · `ChatMessageBranchPage` · `ChatMessageBranchPrevious` · `ChatMessageBranchSelector` · `ChatMessageContent` · `ChatMessageResponse` · `ChatMessageRoot` · `ChatMessageToolbar` · `CodeBlock` · `CommandPalette` · `ConfirmDialog` · `CronJobsList` · `DataPart` · `DataTable` · `DeploymentRow` · `DomainConfig` · `EnvVarEditor` · `FilePart` · `MCPServerList` · `PageShell` · `PermissionModal` · `PreviewEnvCard` · `PreviewPanel` · `ProjectCard` · `ReasoningPart` · `RollbackUI` · `RuleEditor` · `SkillEditor` · `SkillsList` · `SourceDocumentPart` · `SourceUrlPart` · `StabilityBundleViewer` · `TaskHeader` · `TextPart` · `ToolCallPart` · `UsageMeter`
|
|
186
187
|
<!-- END:composites -->
|
|
187
188
|
|
|
188
189
|
</details>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { cn } from './chunk-ZSRJCIWF.js';
|
|
2
|
+
import { Info, AlertTriangle, AlertOctagon } from 'lucide-react';
|
|
3
|
+
import { forwardRef, useState } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var SEVERITY_META = {
|
|
7
|
+
fatal: {
|
|
8
|
+
icon: AlertOctagon,
|
|
9
|
+
className: "text-destructive",
|
|
10
|
+
label: "Fatal"
|
|
11
|
+
},
|
|
12
|
+
error: {
|
|
13
|
+
icon: AlertTriangle,
|
|
14
|
+
className: "text-amber-600 dark:text-amber-400",
|
|
15
|
+
label: "Error"
|
|
16
|
+
},
|
|
17
|
+
warn: {
|
|
18
|
+
icon: Info,
|
|
19
|
+
className: "text-blue-600 dark:text-blue-400",
|
|
20
|
+
label: "Warning"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function CollapsibleSection({
|
|
24
|
+
title,
|
|
25
|
+
testId,
|
|
26
|
+
defaultOpen = true,
|
|
27
|
+
children
|
|
28
|
+
}) {
|
|
29
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
30
|
+
return /* @__PURE__ */ jsxs("section", { className: "rounded border border-border", "data-testid": testId, children: [
|
|
31
|
+
/* @__PURE__ */ jsxs(
|
|
32
|
+
"button",
|
|
33
|
+
{
|
|
34
|
+
type: "button",
|
|
35
|
+
onClick: () => setOpen((v) => !v),
|
|
36
|
+
className: "flex w-full items-center justify-between px-3 py-2 text-left font-medium text-sm hover:bg-muted/30",
|
|
37
|
+
"aria-expanded": open,
|
|
38
|
+
"data-testid": `${testId}-toggle`,
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ jsx("span", { children: title }),
|
|
41
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: open ? "\u25BE" : "\u25B8" })
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
open && /* @__PURE__ */ jsx("div", { className: "border-border border-t px-3 py-2", children })
|
|
46
|
+
] });
|
|
47
|
+
}
|
|
48
|
+
var StabilityBundleViewer = forwardRef(
|
|
49
|
+
({ bundle, onCopy, className, "data-testid": dataTestId }, ref) => {
|
|
50
|
+
const meta = SEVERITY_META[bundle.severity] ?? SEVERITY_META.error;
|
|
51
|
+
const Icon = meta.icon;
|
|
52
|
+
const envEntries = bundle.env !== void 0 ? Object.entries(bundle.env) : [];
|
|
53
|
+
const configEntries = bundle.config !== void 0 ? Object.entries(bundle.config) : [];
|
|
54
|
+
const metadataEntries = bundle.metadata !== void 0 ? Object.entries(bundle.metadata) : [];
|
|
55
|
+
return /* @__PURE__ */ jsxs(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
ref,
|
|
59
|
+
className: cn("flex flex-col gap-3", className),
|
|
60
|
+
"data-testid": dataTestId ?? "stability-bundle-viewer",
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsxs("header", { className: "flex items-start gap-3 rounded border border-border bg-card p-3", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(Icon, { className: cn("size-5", meta.className), "aria-hidden": true }),
|
|
64
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
65
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
className: cn("font-semibold text-sm", meta.className),
|
|
70
|
+
"data-testid": "stability-severity",
|
|
71
|
+
children: meta.label
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
"time",
|
|
76
|
+
{
|
|
77
|
+
className: "text-muted-foreground text-xs",
|
|
78
|
+
dateTime: bundle.timestamp,
|
|
79
|
+
"data-testid": "stability-timestamp",
|
|
80
|
+
children: bundle.timestamp
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", "data-testid": "stability-summary", children: bundle.summary })
|
|
85
|
+
] }),
|
|
86
|
+
onCopy !== void 0 && /* @__PURE__ */ jsx(
|
|
87
|
+
"button",
|
|
88
|
+
{
|
|
89
|
+
type: "button",
|
|
90
|
+
onClick: onCopy,
|
|
91
|
+
className: "rounded border border-border px-2 py-1 text-xs hover:bg-muted/30",
|
|
92
|
+
"data-testid": "stability-copy",
|
|
93
|
+
children: "Copy"
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
] }),
|
|
97
|
+
bundle.error !== void 0 && /* @__PURE__ */ jsxs(CollapsibleSection, { title: "Error", testId: "stability-section-error", children: [
|
|
98
|
+
/* @__PURE__ */ jsxs("p", { className: "font-mono text-sm", children: [
|
|
99
|
+
/* @__PURE__ */ jsx("strong", { children: bundle.error.name }),
|
|
100
|
+
": ",
|
|
101
|
+
bundle.error.message
|
|
102
|
+
] }),
|
|
103
|
+
bundle.error.stack !== void 0 && bundle.error.stack.length > 0 && /* @__PURE__ */ jsx(
|
|
104
|
+
"pre",
|
|
105
|
+
{
|
|
106
|
+
className: "mt-2 overflow-x-auto rounded bg-muted/30 p-2 text-xs",
|
|
107
|
+
"data-testid": "stability-stack",
|
|
108
|
+
children: bundle.error.stack
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
] }),
|
|
112
|
+
envEntries.length > 0 && /* @__PURE__ */ jsx(
|
|
113
|
+
CollapsibleSection,
|
|
114
|
+
{
|
|
115
|
+
title: `Environment (${envEntries.length})`,
|
|
116
|
+
testId: "stability-section-env",
|
|
117
|
+
defaultOpen: false,
|
|
118
|
+
children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
119
|
+
/* @__PURE__ */ jsx("thead", { className: "text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
120
|
+
/* @__PURE__ */ jsx("th", { className: "text-left", children: "Key" }),
|
|
121
|
+
/* @__PURE__ */ jsx("th", { className: "text-left", children: "Value" })
|
|
122
|
+
] }) }),
|
|
123
|
+
/* @__PURE__ */ jsx("tbody", { children: envEntries.map(([k, v]) => /* @__PURE__ */ jsxs("tr", { className: "border-border/40 border-t", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 pr-2 font-mono", children: k }),
|
|
125
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 font-mono", children: v })
|
|
126
|
+
] }, k)) })
|
|
127
|
+
] })
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
configEntries.length > 0 && /* @__PURE__ */ jsx(CollapsibleSection, { title: "Config", testId: "stability-section-config", defaultOpen: false, children: /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded bg-muted/30 p-2 text-xs", children: JSON.stringify(bundle.config, null, 2) }) }),
|
|
131
|
+
metadataEntries.length > 0 && /* @__PURE__ */ jsx(
|
|
132
|
+
CollapsibleSection,
|
|
133
|
+
{
|
|
134
|
+
title: "Metadata",
|
|
135
|
+
testId: "stability-section-metadata",
|
|
136
|
+
defaultOpen: false,
|
|
137
|
+
children: /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded bg-muted/30 p-2 text-xs", children: JSON.stringify(bundle.metadata, null, 2) })
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
StabilityBundleViewer.displayName = "StabilityBundleViewer";
|
|
146
|
+
|
|
147
|
+
export { StabilityBundleViewer };
|
|
148
|
+
//# sourceMappingURL=chunk-6ZQKEY54.js.map
|
|
149
|
+
//# sourceMappingURL=chunk-6ZQKEY54.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/composites/stability-bundle-viewer/stability-bundle-viewer.tsx"],"names":[],"mappings":";;;;;AAgCA,IAAM,aAAA,GAGF;AAAA,EACF,KAAA,EAAO;AAAA,IACL,IAAA,EAAM,YAAA;AAAA,IACN,SAAA,EAAW,kBAAA;AAAA,IACX,KAAA,EAAO;AAAA,GACT;AAAA,EACA,KAAA,EAAO;AAAA,IACL,IAAA,EAAM,aAAA;AAAA,IACN,SAAA,EAAW,oCAAA;AAAA,IACX,KAAA,EAAO;AAAA,GACT;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,IAAA;AAAA,IACN,SAAA,EAAW,kCAAA;AAAA,IACX,KAAA,EAAO;AAAA;AAEX,CAAA;AASA,SAAS,kBAAA,CAAmB;AAAA,EAC1B,KAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA,GAAc,IAAA;AAAA,EACd;AACF,CAAA,EAA4B;AAC1B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,WAAW,CAAA;AAC5C,EAAA,uBACE,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,8BAAA,EAA+B,eAAa,MAAA,EAC7D,QAAA,EAAA;AAAA,oBAAA,IAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,QAAA;AAAA,QACL,SAAS,MAAM,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,QAChC,SAAA,EAAU,oGAAA;AAAA,QACV,eAAA,EAAe,IAAA;AAAA,QACf,aAAA,EAAa,GAAG,MAAM,CAAA,OAAA,CAAA;AAAA,QAEtB,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,UAAM,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,8BACZ,MAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAE,QAAA,EAAA,IAAA,GAAO,WAAM,QAAA,EAAI;AAAA;AAAA;AAAA,KACtC;AAAA,IACC,IAAA,oBAAQ,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oCAAoC,QAAA,EAAS;AAAA,GAAA,EACvE,CAAA;AAEJ;AAEO,IAAM,qBAAA,GAAwB,UAAA;AAAA,EACnC,CAAC,EAAE,MAAA,EAAQ,MAAA,EAAQ,WAAW,aAAA,EAAe,UAAA,IAAc,GAAA,KAAQ;AACjE,IAAA,MAAM,IAAA,GAAO,aAAA,CAAc,MAAA,CAAO,QAAQ,KAAK,aAAA,CAAc,KAAA;AAC7D,IAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAClB,IAAA,MAAM,UAAA,GAAa,OAAO,GAAA,KAAQ,MAAA,GAAY,OAAO,OAAA,CAAQ,MAAA,CAAO,GAAG,CAAA,GAAI,EAAC;AAC5E,IAAA,MAAM,aAAA,GAAgB,OAAO,MAAA,KAAW,MAAA,GAAY,OAAO,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA,GAAI,EAAC;AACrF,IAAA,MAAM,eAAA,GAAkB,OAAO,QAAA,KAAa,MAAA,GAAY,OAAO,OAAA,CAAQ,MAAA,CAAO,QAAQ,CAAA,GAAI,EAAC;AAE3F,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA,CAAG,qBAAA,EAAuB,SAAS,CAAA;AAAA,QAC9C,eAAa,UAAA,IAAc,yBAAA;AAAA,QAE3B,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,QAAA,EAAA,EAAO,WAAU,iEAAA,EAChB,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,IAAA,EAAA,EAAK,WAAW,EAAA,CAAG,QAAA,EAAU,KAAK,SAAS,CAAA,EAAG,eAAW,IAAA,EAAC,CAAA;AAAA,4BAC3D,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,QAAA,EACb,QAAA,EAAA;AAAA,8BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,2BAAA,EACb,QAAA,EAAA;AAAA,gCAAA,GAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACC,SAAA,EAAW,EAAA,CAAG,uBAAA,EAAyB,IAAA,CAAK,SAAS,CAAA;AAAA,oBACrD,aAAA,EAAY,oBAAA;AAAA,oBAEX,QAAA,EAAA,IAAA,CAAK;AAAA;AAAA,iBACR;AAAA,gCACA,GAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACC,SAAA,EAAU,+BAAA;AAAA,oBACV,UAAU,MAAA,CAAO,SAAA;AAAA,oBACjB,aAAA,EAAY,qBAAA;AAAA,oBAEX,QAAA,EAAA,MAAA,CAAO;AAAA;AAAA;AACV,eAAA,EACF,CAAA;AAAA,kCACC,GAAA,EAAA,EAAE,SAAA,EAAU,gBAAe,aAAA,EAAY,mBAAA,EACrC,iBAAO,OAAA,EACV;AAAA,aAAA,EACF,CAAA;AAAA,YACC,WAAW,MAAA,oBACV,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,OAAA,EAAS,MAAA;AAAA,gBACT,SAAA,EAAU,kEAAA;AAAA,gBACV,aAAA,EAAY,gBAAA;AAAA,gBACb,QAAA,EAAA;AAAA;AAAA;AAED,WAAA,EAEJ,CAAA;AAAA,UAEC,MAAA,CAAO,UAAU,MAAA,oBAChB,IAAA,CAAC,sBAAmB,KAAA,EAAM,OAAA,EAAQ,QAAO,yBAAA,EACvC,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,GAAA,EAAA,EAAE,WAAU,mBAAA,EACX,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,QAAA,EAAA,EAAQ,QAAA,EAAA,MAAA,CAAO,KAAA,CAAM,IAAA,EAAK,CAAA;AAAA,cAAS,IAAA;AAAA,cAAG,OAAO,KAAA,CAAM;AAAA,aAAA,EACtD,CAAA;AAAA,YACC,MAAA,CAAO,MAAM,KAAA,KAAU,MAAA,IAAa,OAAO,KAAA,CAAM,KAAA,CAAM,SAAS,CAAA,oBAC/D,GAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBACC,SAAA,EAAU,sDAAA;AAAA,gBACV,aAAA,EAAY,iBAAA;AAAA,gBAEX,iBAAO,KAAA,CAAM;AAAA;AAAA;AAChB,WAAA,EAEJ,CAAA;AAAA,UAGD,UAAA,CAAW,SAAS,CAAA,oBACnB,GAAA;AAAA,YAAC,kBAAA;AAAA,YAAA;AAAA,cACC,KAAA,EAAO,CAAA,aAAA,EAAgB,UAAA,CAAW,MAAM,CAAA,CAAA,CAAA;AAAA,cACxC,MAAA,EAAO,uBAAA;AAAA,cACP,WAAA,EAAa,KAAA;AAAA,cAEb,QAAA,kBAAA,IAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,gBAAA,EACf,QAAA,EAAA;AAAA,gCAAA,GAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,uBAAA,EACf,QAAA,kBAAA,IAAA,CAAC,IAAA,EAAA,EACC,QAAA,EAAA;AAAA,kCAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,WAAA,EAAY,QAAA,EAAA,KAAA,EAAG,CAAA;AAAA,kCAC7B,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,WAAA,EAAY,QAAA,EAAA,OAAA,EAAK;AAAA,iBAAA,EACjC,CAAA,EACF,CAAA;AAAA,gCACA,GAAA,CAAC,OAAA,EAAA,EACE,QAAA,EAAA,UAAA,CAAW,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,qBACpB,IAAA,CAAC,IAAA,EAAA,EAAW,SAAA,EAAU,2BAAA,EACpB,QAAA,EAAA;AAAA,kCAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,qBAAA,EAAuB,QAAA,EAAA,CAAA,EAAE,CAAA;AAAA,kCACvC,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,gBAAA,EAAkB,QAAA,EAAA,CAAA,EAAE;AAAA,iBAAA,EAAA,EAF3B,CAGT,CACD,CAAA,EACH;AAAA,eAAA,EACF;AAAA;AAAA,WACF;AAAA,UAGD,aAAA,CAAc,SAAS,CAAA,oBACtB,GAAA,CAAC,sBAAmB,KAAA,EAAM,QAAA,EAAS,MAAA,EAAO,0BAAA,EAA2B,WAAA,EAAa,KAAA,EAChF,8BAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mDACZ,QAAA,EAAA,IAAA,CAAK,SAAA,CAAU,OAAO,MAAA,EAAQ,IAAA,EAAM,CAAC,CAAA,EACxC,CAAA,EACF,CAAA;AAAA,UAGD,eAAA,CAAgB,SAAS,CAAA,oBACxB,GAAA;AAAA,YAAC,kBAAA;AAAA,YAAA;AAAA,cACC,KAAA,EAAM,UAAA;AAAA,cACN,MAAA,EAAO,4BAAA;AAAA,cACP,WAAA,EAAa,KAAA;AAAA,cAEb,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iDAAA,EACZ,QAAA,EAAA,IAAA,CAAK,UAAU,MAAA,CAAO,QAAA,EAAU,IAAA,EAAM,CAAC,CAAA,EAC1C;AAAA;AAAA;AACF;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AACA,qBAAA,CAAsB,WAAA,GAAc,uBAAA","file":"chunk-6ZQKEY54.js","sourcesContent":["import { AlertOctagon, AlertTriangle, Info, type LucideIcon } from \"lucide-react\";\nimport { forwardRef, useState } from \"react\";\n\nimport { cn } from \"../../../lib/cn.js\";\n\n/**\n * StabilityBundleViewer — inspector for a stability-bundle JSON produced by a\n * server crash. Sections (error, env, config, metadata) collapse independently.\n *\n * EC-9: handles bundles missing optional sections gracefully. Renderer DOES\n * NOT redact env values — bundle writer is expected to redact at emit-time.\n */\n\nexport type StabilitySeverity = \"fatal\" | \"error\" | \"warn\";\n\nexport interface StabilityBundle {\n timestamp: string;\n severity: StabilitySeverity;\n summary: string;\n error?: { name: string; message: string; stack?: string };\n env?: Record<string, string>;\n config?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface StabilityBundleViewerProps {\n bundle: StabilityBundle;\n onCopy?: () => void;\n className?: string;\n \"data-testid\"?: string;\n}\n\nconst SEVERITY_META: Record<\n StabilitySeverity,\n { icon: LucideIcon; className: string; label: string }\n> = {\n fatal: {\n icon: AlertOctagon,\n className: \"text-destructive\",\n label: \"Fatal\",\n },\n error: {\n icon: AlertTriangle,\n className: \"text-amber-600 dark:text-amber-400\",\n label: \"Error\",\n },\n warn: {\n icon: Info,\n className: \"text-blue-600 dark:text-blue-400\",\n label: \"Warning\",\n },\n};\n\ninterface CollapsibleSectionProps {\n title: string;\n testId: string;\n defaultOpen?: boolean;\n children: React.ReactNode;\n}\n\nfunction CollapsibleSection({\n title,\n testId,\n defaultOpen = true,\n children,\n}: CollapsibleSectionProps) {\n const [open, setOpen] = useState(defaultOpen);\n return (\n <section className=\"rounded border border-border\" data-testid={testId}>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"flex w-full items-center justify-between px-3 py-2 text-left font-medium text-sm hover:bg-muted/30\"\n aria-expanded={open}\n data-testid={`${testId}-toggle`}\n >\n <span>{title}</span>\n <span aria-hidden>{open ? \"▾\" : \"▸\"}</span>\n </button>\n {open && <div className=\"border-border border-t px-3 py-2\">{children}</div>}\n </section>\n );\n}\n\nexport const StabilityBundleViewer = forwardRef<HTMLDivElement, StabilityBundleViewerProps>(\n ({ bundle, onCopy, className, \"data-testid\": dataTestId }, ref) => {\n const meta = SEVERITY_META[bundle.severity] ?? SEVERITY_META.error;\n const Icon = meta.icon;\n const envEntries = bundle.env !== undefined ? Object.entries(bundle.env) : [];\n const configEntries = bundle.config !== undefined ? Object.entries(bundle.config) : [];\n const metadataEntries = bundle.metadata !== undefined ? Object.entries(bundle.metadata) : [];\n\n return (\n <div\n ref={ref}\n className={cn(\"flex flex-col gap-3\", className)}\n data-testid={dataTestId ?? \"stability-bundle-viewer\"}\n >\n <header className=\"flex items-start gap-3 rounded border border-border bg-card p-3\">\n <Icon className={cn(\"size-5\", meta.className)} aria-hidden />\n <div className=\"flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span\n className={cn(\"font-semibold text-sm\", meta.className)}\n data-testid=\"stability-severity\"\n >\n {meta.label}\n </span>\n <time\n className=\"text-muted-foreground text-xs\"\n dateTime={bundle.timestamp}\n data-testid=\"stability-timestamp\"\n >\n {bundle.timestamp}\n </time>\n </div>\n <p className=\"mt-1 text-sm\" data-testid=\"stability-summary\">\n {bundle.summary}\n </p>\n </div>\n {onCopy !== undefined && (\n <button\n type=\"button\"\n onClick={onCopy}\n className=\"rounded border border-border px-2 py-1 text-xs hover:bg-muted/30\"\n data-testid=\"stability-copy\"\n >\n Copy\n </button>\n )}\n </header>\n\n {bundle.error !== undefined && (\n <CollapsibleSection title=\"Error\" testId=\"stability-section-error\">\n <p className=\"font-mono text-sm\">\n <strong>{bundle.error.name}</strong>: {bundle.error.message}\n </p>\n {bundle.error.stack !== undefined && bundle.error.stack.length > 0 && (\n <pre\n className=\"mt-2 overflow-x-auto rounded bg-muted/30 p-2 text-xs\"\n data-testid=\"stability-stack\"\n >\n {bundle.error.stack}\n </pre>\n )}\n </CollapsibleSection>\n )}\n\n {envEntries.length > 0 && (\n <CollapsibleSection\n title={`Environment (${envEntries.length})`}\n testId=\"stability-section-env\"\n defaultOpen={false}\n >\n <table className=\"w-full text-xs\">\n <thead className=\"text-muted-foreground\">\n <tr>\n <th className=\"text-left\">Key</th>\n <th className=\"text-left\">Value</th>\n </tr>\n </thead>\n <tbody>\n {envEntries.map(([k, v]) => (\n <tr key={k} className=\"border-border/40 border-t\">\n <td className=\"py-1 pr-2 font-mono\">{k}</td>\n <td className=\"py-1 font-mono\">{v}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </CollapsibleSection>\n )}\n\n {configEntries.length > 0 && (\n <CollapsibleSection title=\"Config\" testId=\"stability-section-config\" defaultOpen={false}>\n <pre className=\"overflow-x-auto rounded bg-muted/30 p-2 text-xs\">\n {JSON.stringify(bundle.config, null, 2)}\n </pre>\n </CollapsibleSection>\n )}\n\n {metadataEntries.length > 0 && (\n <CollapsibleSection\n title=\"Metadata\"\n testId=\"stability-section-metadata\"\n defaultOpen={false}\n >\n <pre className=\"overflow-x-auto rounded bg-muted/30 p-2 text-xs\">\n {JSON.stringify(bundle.metadata, null, 2)}\n </pre>\n </CollapsibleSection>\n )}\n </div>\n );\n },\n);\nStabilityBundleViewer.displayName = \"StabilityBundleViewer\";\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { cn } from './chunk-ZSRJCIWF.js';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var STATUS_META = {
|
|
6
|
+
online: {
|
|
7
|
+
dot: "bg-emerald-500",
|
|
8
|
+
label: "Online",
|
|
9
|
+
aria: "Gateway online"
|
|
10
|
+
},
|
|
11
|
+
offline: {
|
|
12
|
+
dot: "bg-red-500",
|
|
13
|
+
label: "Offline",
|
|
14
|
+
aria: "Gateway offline"
|
|
15
|
+
},
|
|
16
|
+
degraded: {
|
|
17
|
+
dot: "bg-amber-500",
|
|
18
|
+
label: "Degraded",
|
|
19
|
+
aria: "Gateway degraded"
|
|
20
|
+
},
|
|
21
|
+
reconnecting: {
|
|
22
|
+
dot: "bg-blue-500",
|
|
23
|
+
label: "Reconnecting\u2026",
|
|
24
|
+
aria: "Gateway reconnecting"
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function formatLatency(ms) {
|
|
28
|
+
if (ms === void 0) return null;
|
|
29
|
+
if (!Number.isFinite(ms) || ms < 0) return null;
|
|
30
|
+
if (ms < 1) return "<1ms";
|
|
31
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
32
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
33
|
+
}
|
|
34
|
+
var GatewayStatusIndicator = forwardRef(
|
|
35
|
+
({ status, latencyMs, variant = "labeled", className, "data-testid": dataTestId, ...rest }, ref) => {
|
|
36
|
+
const meta = STATUS_META[status];
|
|
37
|
+
const latency = formatLatency(latencyMs);
|
|
38
|
+
return /* @__PURE__ */ jsxs(
|
|
39
|
+
"span",
|
|
40
|
+
{
|
|
41
|
+
ref,
|
|
42
|
+
role: "img",
|
|
43
|
+
"aria-label": meta.aria,
|
|
44
|
+
className: cn("inline-flex items-center gap-2 font-medium text-xs", className),
|
|
45
|
+
"data-testid": dataTestId ?? "gateway-status-indicator",
|
|
46
|
+
"data-status": status,
|
|
47
|
+
...rest,
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsx(
|
|
50
|
+
"span",
|
|
51
|
+
{
|
|
52
|
+
className: cn(
|
|
53
|
+
"inline-block size-2 rounded-full",
|
|
54
|
+
meta.dot,
|
|
55
|
+
status === "reconnecting" && "animate-pulse"
|
|
56
|
+
),
|
|
57
|
+
"aria-hidden": true
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
variant === "labeled" && /* @__PURE__ */ jsxs("span", { className: "text-foreground", children: [
|
|
61
|
+
meta.label,
|
|
62
|
+
latency !== null && /* @__PURE__ */ jsx("span", { className: "ml-1 text-muted-foreground", "data-testid": "gateway-latency", children: latency })
|
|
63
|
+
] })
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
GatewayStatusIndicator.displayName = "GatewayStatusIndicator";
|
|
70
|
+
|
|
71
|
+
export { GatewayStatusIndicator };
|
|
72
|
+
//# sourceMappingURL=chunk-AVPHVQZS.js.map
|
|
73
|
+
//# sourceMappingURL=chunk-AVPHVQZS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/primitives/gateway-status-indicator/gateway-status-indicator.tsx"],"names":[],"mappings":";;;;AAyBA,IAAM,WAAA,GAAmF;AAAA,EACvF,MAAA,EAAQ;AAAA,IACN,GAAA,EAAK,gBAAA;AAAA,IACL,KAAA,EAAO,QAAA;AAAA,IACP,IAAA,EAAM;AAAA,GACR;AAAA,EACA,OAAA,EAAS;AAAA,IACP,GAAA,EAAK,YAAA;AAAA,IACL,KAAA,EAAO,SAAA;AAAA,IACP,IAAA,EAAM;AAAA,GACR;AAAA,EACA,QAAA,EAAU;AAAA,IACR,GAAA,EAAK,cAAA;AAAA,IACL,KAAA,EAAO,UAAA;AAAA,IACP,IAAA,EAAM;AAAA,GACR;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,GAAA,EAAK,aAAA;AAAA,IACL,KAAA,EAAO,oBAAA;AAAA,IACP,IAAA,EAAM;AAAA;AAEV,CAAA;AAEA,SAAS,cAAc,EAAA,EAAuC;AAC5D,EAAA,IAAI,EAAA,KAAO,QAAW,OAAO,IAAA;AAC7B,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,EAAE,CAAA,IAAK,EAAA,GAAK,GAAG,OAAO,IAAA;AAC3C,EAAA,IAAI,EAAA,GAAK,GAAG,OAAO,MAAA;AACnB,EAAA,IAAI,KAAK,GAAA,EAAM,OAAO,GAAG,IAAA,CAAK,KAAA,CAAM,EAAE,CAAC,CAAA,EAAA,CAAA;AACvC,EAAA,OAAO,CAAA,EAAA,CAAI,EAAA,GAAK,GAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAClC;AAEO,IAAM,sBAAA,GAAyB,UAAA;AAAA,EACpC,CACE,EAAE,MAAA,EAAQ,SAAA,EAAW,OAAA,GAAU,SAAA,EAAW,SAAA,EAAW,aAAA,EAAe,UAAA,EAAY,GAAG,IAAA,EAAK,EACxF,GAAA,KACG;AACH,IAAA,MAAM,IAAA,GAAO,YAAY,MAAM,CAAA;AAC/B,IAAA,MAAM,OAAA,GAAU,cAAc,SAAS,CAAA;AACvC,IAAA,uBACE,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,IAAA,EAAK,KAAA;AAAA,QACL,cAAY,IAAA,CAAK,IAAA;AAAA,QACjB,SAAA,EAAW,EAAA,CAAG,oDAAA,EAAsD,SAAS,CAAA;AAAA,QAC7E,eAAa,UAAA,IAAc,0BAAA;AAAA,QAC3B,aAAA,EAAa,MAAA;AAAA,QACZ,GAAG,IAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAW,EAAA;AAAA,gBACT,kCAAA;AAAA,gBACA,IAAA,CAAK,GAAA;AAAA,gBACL,WAAW,cAAA,IAAkB;AAAA,eAC/B;AAAA,cACA,aAAA,EAAW;AAAA;AAAA,WACb;AAAA,UACC,OAAA,KAAY,SAAA,oBACX,IAAA,CAAC,MAAA,EAAA,EAAK,WAAU,iBAAA,EACb,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,KAAA;AAAA,YACL,OAAA,KAAY,wBACX,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,4BAAA,EAA6B,aAAA,EAAY,mBACtD,QAAA,EAAA,OAAA,EACH;AAAA,WAAA,EAEJ;AAAA;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AACA,sBAAA,CAAuB,WAAA,GAAc,wBAAA","file":"chunk-AVPHVQZS.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from \"react\";\n\nimport { cn } from \"../../../lib/cn.js\";\n\n/**\n * GatewayStatusIndicator — live connection-status dot for a gateway/server.\n *\n * Variants:\n * - compact : colored dot only (sidebar footer use)\n * - labeled : dot + label + optional latency text\n *\n * `reconnecting` state animates with `animate-pulse` and respects\n * `prefers-reduced-motion` automatically (Tailwind defaults).\n */\n\nexport type GatewayStatus = \"online\" | \"offline\" | \"degraded\" | \"reconnecting\";\n\nexport interface GatewayStatusIndicatorProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, \"title\"> {\n status: GatewayStatus;\n latencyMs?: number;\n variant?: \"compact\" | \"labeled\";\n \"data-testid\"?: string;\n}\n\nconst STATUS_META: Record<GatewayStatus, { dot: string; label: string; aria: string }> = {\n online: {\n dot: \"bg-emerald-500\",\n label: \"Online\",\n aria: \"Gateway online\",\n },\n offline: {\n dot: \"bg-red-500\",\n label: \"Offline\",\n aria: \"Gateway offline\",\n },\n degraded: {\n dot: \"bg-amber-500\",\n label: \"Degraded\",\n aria: \"Gateway degraded\",\n },\n reconnecting: {\n dot: \"bg-blue-500\",\n label: \"Reconnecting…\",\n aria: \"Gateway reconnecting\",\n },\n};\n\nfunction formatLatency(ms: number | undefined): string | null {\n if (ms === undefined) return null;\n if (!Number.isFinite(ms) || ms < 0) return null;\n if (ms < 1) return \"<1ms\";\n if (ms < 1000) return `${Math.round(ms)}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nexport const GatewayStatusIndicator = forwardRef<HTMLSpanElement, GatewayStatusIndicatorProps>(\n (\n { status, latencyMs, variant = \"labeled\", className, \"data-testid\": dataTestId, ...rest },\n ref,\n ) => {\n const meta = STATUS_META[status];\n const latency = formatLatency(latencyMs);\n return (\n <span\n ref={ref}\n role=\"img\"\n aria-label={meta.aria}\n className={cn(\"inline-flex items-center gap-2 font-medium text-xs\", className)}\n data-testid={dataTestId ?? \"gateway-status-indicator\"}\n data-status={status}\n {...rest}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n meta.dot,\n status === \"reconnecting\" && \"animate-pulse\",\n )}\n aria-hidden\n />\n {variant === \"labeled\" && (\n <span className=\"text-foreground\">\n {meta.label}\n {latency !== null && (\n <span className=\"ml-1 text-muted-foreground\" data-testid=\"gateway-latency\">\n {latency}\n </span>\n )}\n </span>\n )}\n </span>\n );\n },\n);\nGatewayStatusIndicator.displayName = \"GatewayStatusIndicator\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/composites/data-table/data-table.tsx"],"names":["_"],"mappings":"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","file":"chunk-ZNILW4G5.js","sourcesContent":["import { ChevronDown, ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { Fragment, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../../../lib/cn.js\";\nimport { DropdownMenu } from \"../../primitives/dropdown-menu/index.js\";\nimport { EmptyState } from \"../../primitives/empty-state/index.js\";\nimport { Pagination } from \"../../primitives/pagination/index.js\";\nimport { Skeleton } from \"../../primitives/skeleton/index.js\";\nimport { Table } from \"../../primitives/table/index.js\";\n\n/**\n * DataTable — generic, sortable, expandable composite over `<Table>`.\n *\n * Adds operator-grade entity-list patterns on top of the plain Table\n * primitive: sortable headers, sticky header, expandable rows\n * (multi-row by default), row action menus (Dropdown), client-side\n * pagination, loading skeleton rows, empty state. Both sort and\n * pagination support controlled OR uncontrolled mode (consumer\n * passes onSortChange / onPageChange to take over state).\n *\n * @example\n * <DataTable\n * columns={[\n * { key: \"name\", label: \"Name\", sortable: true },\n * { key: \"status\", label: \"Status\" },\n * ]}\n * data={domains}\n * rowKey={(d) => d.id}\n * expandable={(d) => d.status === \"pending\" ? <DnsRecords domain={d} /> : null}\n * rowActions={(d) => (\n * <>\n * <DropdownMenu.Item onSelect={() => editDomain(d)}>Edit</DropdownMenu.Item>\n * <DropdownMenu.Item onSelect={() => deleteDomain(d)}>Delete</DropdownMenu.Item>\n * </>\n * )}\n * />\n */\nexport interface DataTableColumn<T> {\n key: string;\n label: ReactNode;\n align?: \"left\" | \"center\" | \"right\";\n sortable?: boolean;\n width?: string;\n render?: (row: T) => ReactNode;\n className?: string;\n}\n\nexport interface DataTableSort {\n key: string;\n direction: \"asc\" | \"desc\";\n}\n\nexport interface DataTableProps<T> {\n data: T[];\n columns: DataTableColumn<T>[];\n rowKey: (row: T) => string;\n stickyHeader?: boolean;\n expandable?: (row: T) => ReactNode | null;\n expandMode?: \"single\" | \"multiple\";\n rowActions?: (row: T) => ReactNode;\n pagination?: {\n pageSize: number;\n controlledPage?: number;\n onPageChange?: (page: number) => void;\n } | null;\n defaultSort?: DataTableSort;\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n loading?: boolean;\n emptyState?: ReactNode;\n className?: string;\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a === b) return 0;\n if (a === null || a === undefined) return -1;\n if (b === null || b === undefined) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b));\n}\n\nfunction DataTable<T>(props: DataTableProps<T>): ReactNode {\n const {\n data,\n columns,\n rowKey,\n stickyHeader = true,\n expandable,\n expandMode = \"multiple\",\n rowActions,\n pagination,\n defaultSort,\n sort: controlledSort,\n onSortChange,\n loading = false,\n emptyState,\n className,\n } = props;\n\n const isControlledSort = onSortChange !== undefined;\n const [uncontrolledSort, setUncontrolledSort] = useState<DataTableSort | null>(\n defaultSort ?? null,\n );\n const sort = isControlledSort ? (controlledSort ?? null) : uncontrolledSort;\n\n const isControlledPage = pagination?.controlledPage !== undefined;\n const [uncontrolledPage, setUncontrolledPage] = useState(0);\n const currentPage = isControlledPage ? (pagination?.controlledPage ?? 0) : uncontrolledPage;\n\n // EC-9: clamp pageSize to >= 1 to avoid divide-by-zero / infinite render\n const effectivePageSize = Math.max(1, pagination?.pageSize ?? 10);\n\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n\n function handleSort(columnKey: string) {\n // Cycle: none → asc → desc → none\n let nextSort: DataTableSort | null;\n if (sort?.key !== columnKey) {\n nextSort = { key: columnKey, direction: \"asc\" };\n } else if (sort.direction === \"asc\") {\n nextSort = { key: columnKey, direction: \"desc\" };\n } else {\n nextSort = null;\n }\n if (isControlledSort) {\n onSortChange?.(nextSort);\n } else {\n setUncontrolledSort(nextSort);\n // EC-8: sort change resets pagination to page 0\n if (!isControlledPage) setUncontrolledPage(0);\n }\n }\n\n function handlePageChange(page: number) {\n // Pagination uses 1-indexed; internal state 0-indexed\n const zeroIdx = page - 1;\n if (isControlledPage) {\n pagination?.onPageChange?.(zeroIdx);\n } else {\n setUncontrolledPage(zeroIdx);\n }\n }\n\n function toggleExpand(key: string) {\n if (expandMode === \"single\") {\n setExpanded((prev) => (prev.has(key) ? new Set() : new Set([key])));\n } else {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.add(key);\n }\n return next;\n });\n }\n }\n\n // Apply client-side sort in uncontrolled mode\n const sortedData = useMemo(() => {\n if (isControlledSort || sort === null) return data;\n const col = columns.find((c) => c.key === sort.key);\n if (!col) return data;\n const sorted = [...data].sort((a, b) => {\n const aVal = col.render\n ? null\n : (a as Record<string, unknown>)[sort.key as keyof T as string];\n const bVal = col.render\n ? null\n : (b as Record<string, unknown>)[sort.key as keyof T as string];\n const cmp = compareValues(aVal, bVal);\n return sort.direction === \"asc\" ? cmp : -cmp;\n });\n return sorted;\n }, [data, sort, isControlledSort, columns]);\n\n // Apply client-side pagination in uncontrolled mode\n const visibleData = useMemo(() => {\n if (!pagination) return sortedData;\n if (isControlledPage) return sortedData; // consumer pre-sliced\n const start = currentPage * effectivePageSize;\n return sortedData.slice(start, start + effectivePageSize);\n }, [sortedData, pagination, isControlledPage, currentPage, effectivePageSize]);\n\n // EC-1 fix: compute colSpan accounting for chevron + actions columns\n const extraCols = (expandable ? 1 : 0) + (rowActions ? 1 : 0);\n const expandedColSpan = columns.length + extraCols;\n const totalCols = columns.length + extraCols;\n\n // Loading state (EC-7: loading > empty)\n if (loading) {\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => (\n <Table.HeaderCell key={col.key} align={col.align}>\n {col.label}\n </Table.HeaderCell>\n ))}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {Array.from({ length: 5 }, (_, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows are positional placeholders\n <Table.Row key={`skeleton-${i}`}>\n {Array.from({ length: totalCols }, (_, j) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton cells are positional placeholders\n <Table.Cell key={`s-${i}-${j}`}>\n <Skeleton className=\"h-4 w-full\" />\n </Table.Cell>\n ))}\n </Table.Row>\n ))}\n </Table.Body>\n </Table>\n </div>\n );\n }\n\n // Empty state (after loading check)\n if (sortedData.length === 0) {\n return (\n <div className={cn(\"w-full\", className)}>\n {emptyState ?? <EmptyState title=\"No data\" description=\"There's nothing here yet.\" />}\n </div>\n );\n }\n\n const totalPages = pagination ? Math.ceil(sortedData.length / effectivePageSize) : 1;\n\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 z-10 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => {\n const isSortable = col.sortable === true;\n const isActive = sort?.key === col.key;\n return (\n <Table.HeaderCell\n key={col.key}\n align={col.align}\n onSort={isSortable ? () => handleSort(col.key) : undefined}\n sortDirection={isSortable ? (isActive ? sort?.direction : \"none\") : undefined}\n style={col.width ? { width: col.width } : undefined}\n >\n {col.label}\n </Table.HeaderCell>\n );\n })}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {visibleData.map((row) => {\n const key = rowKey(row);\n const expandedContent = expandable ? expandable(row) : null;\n const isExpandable = expandedContent !== null && expandedContent !== undefined;\n const isExpanded = expanded.has(key);\n return (\n <Fragment key={key}>\n <Table.Row>\n {expandable ? (\n <Table.Cell>\n {isExpandable ? (\n <button\n type=\"button\"\n onClick={() => toggleExpand(key)}\n aria-expanded={isExpanded}\n aria-controls={`expanded-${key}`}\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n className=\"inline-flex items-center justify-center rounded-md p-0.5 hover:bg-muted\"\n >\n {isExpanded ? (\n <ChevronDown aria-hidden=\"true\" className=\"size-4\" />\n ) : (\n <ChevronRight aria-hidden=\"true\" className=\"size-4\" />\n )}\n </button>\n ) : null}\n </Table.Cell>\n ) : null}\n {columns.map((col) => (\n <Table.Cell key={col.key} align={col.align} className={col.className}>\n {col.render\n ? col.render(row)\n : String((row as Record<string, unknown>)[col.key] ?? \"\")}\n </Table.Cell>\n ))}\n {rowActions ? (\n <Table.Cell align=\"right\">\n <DropdownMenu>\n <DropdownMenu.Trigger\n aria-label=\"Row actions\"\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-md\",\n \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n >\n <MoreHorizontal aria-hidden=\"true\" className=\"size-4\" />\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"end\">{rowActions(row)}</DropdownMenu.Content>\n </DropdownMenu>\n </Table.Cell>\n ) : null}\n </Table.Row>\n {isExpanded && isExpandable ? (\n <tr id={`expanded-${key}`}>\n <td colSpan={expandedColSpan} className=\"bg-muted/30 p-4\">\n {expandedContent}\n </td>\n </tr>\n ) : null}\n </Fragment>\n );\n })}\n </Table.Body>\n </Table>\n {pagination && totalPages > 1 ? (\n <div className=\"mt-4 flex items-center justify-end\">\n <Pagination\n currentPage={currentPage + 1}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n />\n </div>\n ) : null}\n </div>\n );\n}\n\nexport { DataTable };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/composites/data-table/data-table.tsx"],"names":["_"],"mappings":"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","file":"chunk-DW247T3Q.js","sourcesContent":["import { ChevronDown, ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { Fragment, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../../../lib/cn.js\";\nimport { DropdownMenu } from \"../../primitives/dropdown-menu/index.js\";\nimport { EmptyState } from \"../../primitives/empty-state/index.js\";\nimport { Pagination } from \"../../primitives/pagination/index.js\";\nimport { Skeleton } from \"../../primitives/skeleton/index.js\";\nimport { Table } from \"../../primitives/table/index.js\";\n\n/**\n * DataTable — generic, sortable, expandable composite over `<Table>`.\n *\n * Adds operator-grade entity-list patterns on top of the plain Table\n * primitive: sortable headers, sticky header, expandable rows\n * (multi-row by default), row action menus (Dropdown), client-side\n * pagination, loading skeleton rows, empty state. Both sort and\n * pagination support controlled OR uncontrolled mode (consumer\n * passes onSortChange / onPageChange to take over state).\n *\n * @example\n * <DataTable\n * columns={[\n * { key: \"name\", label: \"Name\", sortable: true },\n * { key: \"status\", label: \"Status\" },\n * ]}\n * data={domains}\n * rowKey={(d) => d.id}\n * expandable={(d) => d.status === \"pending\" ? <DnsRecords domain={d} /> : null}\n * rowActions={(d) => (\n * <>\n * <DropdownMenu.Item onSelect={() => editDomain(d)}>Edit</DropdownMenu.Item>\n * <DropdownMenu.Item onSelect={() => deleteDomain(d)}>Delete</DropdownMenu.Item>\n * </>\n * )}\n * />\n */\nexport interface DataTableColumn<T> {\n key: string;\n label: ReactNode;\n align?: \"left\" | \"center\" | \"right\";\n sortable?: boolean;\n width?: string;\n render?: (row: T) => ReactNode;\n className?: string;\n}\n\nexport interface DataTableSort {\n key: string;\n direction: \"asc\" | \"desc\";\n}\n\nexport interface DataTableProps<T> {\n data: T[];\n columns: DataTableColumn<T>[];\n rowKey: (row: T) => string;\n stickyHeader?: boolean;\n expandable?: (row: T) => ReactNode | null;\n expandMode?: \"single\" | \"multiple\";\n rowActions?: (row: T) => ReactNode;\n pagination?: {\n pageSize: number;\n controlledPage?: number;\n onPageChange?: (page: number) => void;\n } | null;\n defaultSort?: DataTableSort;\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n loading?: boolean;\n emptyState?: ReactNode;\n className?: string;\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a === b) return 0;\n if (a === null || a === undefined) return -1;\n if (b === null || b === undefined) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b));\n}\n\nfunction DataTable<T>(props: DataTableProps<T>): ReactNode {\n const {\n data,\n columns,\n rowKey,\n stickyHeader = true,\n expandable,\n expandMode = \"multiple\",\n rowActions,\n pagination,\n defaultSort,\n sort: controlledSort,\n onSortChange,\n loading = false,\n emptyState,\n className,\n } = props;\n\n const isControlledSort = onSortChange !== undefined;\n const [uncontrolledSort, setUncontrolledSort] = useState<DataTableSort | null>(\n defaultSort ?? null,\n );\n const sort = isControlledSort ? (controlledSort ?? null) : uncontrolledSort;\n\n const isControlledPage = pagination?.controlledPage !== undefined;\n const [uncontrolledPage, setUncontrolledPage] = useState(0);\n const currentPage = isControlledPage ? (pagination?.controlledPage ?? 0) : uncontrolledPage;\n\n // EC-9: clamp pageSize to >= 1 to avoid divide-by-zero / infinite render\n const effectivePageSize = Math.max(1, pagination?.pageSize ?? 10);\n\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n\n function handleSort(columnKey: string) {\n // Cycle: none → asc → desc → none\n let nextSort: DataTableSort | null;\n if (sort?.key !== columnKey) {\n nextSort = { key: columnKey, direction: \"asc\" };\n } else if (sort.direction === \"asc\") {\n nextSort = { key: columnKey, direction: \"desc\" };\n } else {\n nextSort = null;\n }\n if (isControlledSort) {\n onSortChange?.(nextSort);\n } else {\n setUncontrolledSort(nextSort);\n // EC-8: sort change resets pagination to page 0\n if (!isControlledPage) setUncontrolledPage(0);\n }\n }\n\n function handlePageChange(page: number) {\n // Pagination uses 1-indexed; internal state 0-indexed\n const zeroIdx = page - 1;\n if (isControlledPage) {\n pagination?.onPageChange?.(zeroIdx);\n } else {\n setUncontrolledPage(zeroIdx);\n }\n }\n\n function toggleExpand(key: string) {\n if (expandMode === \"single\") {\n setExpanded((prev) => (prev.has(key) ? new Set() : new Set([key])));\n } else {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.add(key);\n }\n return next;\n });\n }\n }\n\n // Apply client-side sort in uncontrolled mode\n const sortedData = useMemo(() => {\n if (isControlledSort || sort === null) return data;\n const col = columns.find((c) => c.key === sort.key);\n if (!col) return data;\n const sorted = [...data].sort((a, b) => {\n const aVal = col.render\n ? null\n : (a as Record<string, unknown>)[sort.key as keyof T as string];\n const bVal = col.render\n ? null\n : (b as Record<string, unknown>)[sort.key as keyof T as string];\n const cmp = compareValues(aVal, bVal);\n return sort.direction === \"asc\" ? cmp : -cmp;\n });\n return sorted;\n }, [data, sort, isControlledSort, columns]);\n\n // Apply client-side pagination in uncontrolled mode\n const visibleData = useMemo(() => {\n if (!pagination) return sortedData;\n if (isControlledPage) return sortedData; // consumer pre-sliced\n const start = currentPage * effectivePageSize;\n return sortedData.slice(start, start + effectivePageSize);\n }, [sortedData, pagination, isControlledPage, currentPage, effectivePageSize]);\n\n // EC-1 fix: compute colSpan accounting for chevron + actions columns\n const extraCols = (expandable ? 1 : 0) + (rowActions ? 1 : 0);\n const expandedColSpan = columns.length + extraCols;\n const totalCols = columns.length + extraCols;\n\n // Loading state (EC-7: loading > empty)\n if (loading) {\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => (\n <Table.HeaderCell key={col.key} align={col.align}>\n {col.label}\n </Table.HeaderCell>\n ))}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {Array.from({ length: 5 }, (_, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows are positional placeholders\n <Table.Row key={`skeleton-${i}`}>\n {Array.from({ length: totalCols }, (_, j) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton cells are positional placeholders\n <Table.Cell key={`s-${i}-${j}`}>\n <Skeleton className=\"h-4 w-full\" />\n </Table.Cell>\n ))}\n </Table.Row>\n ))}\n </Table.Body>\n </Table>\n </div>\n );\n }\n\n // Empty state (after loading check)\n if (sortedData.length === 0) {\n return (\n <div className={cn(\"w-full\", className)}>\n {emptyState ?? <EmptyState title=\"No data\" description=\"There's nothing here yet.\" />}\n </div>\n );\n }\n\n const totalPages = pagination ? Math.ceil(sortedData.length / effectivePageSize) : 1;\n\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 z-10 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => {\n const isSortable = col.sortable === true;\n const isActive = sort?.key === col.key;\n return (\n <Table.HeaderCell\n key={col.key}\n align={col.align}\n onSort={isSortable ? () => handleSort(col.key) : undefined}\n sortDirection={isSortable ? (isActive ? sort?.direction : \"none\") : undefined}\n style={col.width ? { width: col.width } : undefined}\n >\n {col.label}\n </Table.HeaderCell>\n );\n })}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {visibleData.map((row) => {\n const key = rowKey(row);\n const expandedContent = expandable ? expandable(row) : null;\n const isExpandable = expandedContent !== null && expandedContent !== undefined;\n const isExpanded = expanded.has(key);\n return (\n <Fragment key={key}>\n <Table.Row>\n {expandable ? (\n <Table.Cell>\n {isExpandable ? (\n <button\n type=\"button\"\n onClick={() => toggleExpand(key)}\n aria-expanded={isExpanded}\n aria-controls={`expanded-${key}`}\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n className=\"inline-flex items-center justify-center rounded-md p-0.5 hover:bg-muted\"\n >\n {isExpanded ? (\n <ChevronDown aria-hidden=\"true\" className=\"size-4\" />\n ) : (\n <ChevronRight aria-hidden=\"true\" className=\"size-4\" />\n )}\n </button>\n ) : null}\n </Table.Cell>\n ) : null}\n {columns.map((col) => (\n <Table.Cell key={col.key} align={col.align} className={col.className}>\n {col.render\n ? col.render(row)\n : String((row as Record<string, unknown>)[col.key] ?? \"\")}\n </Table.Cell>\n ))}\n {rowActions ? (\n <Table.Cell align=\"right\">\n <DropdownMenu>\n <DropdownMenu.Trigger\n aria-label=\"Row actions\"\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-md\",\n \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n >\n <MoreHorizontal aria-hidden=\"true\" className=\"size-4\" />\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"end\">{rowActions(row)}</DropdownMenu.Content>\n </DropdownMenu>\n </Table.Cell>\n ) : null}\n </Table.Row>\n {isExpanded && isExpandable ? (\n <tr id={`expanded-${key}`}>\n <td colSpan={expandedColSpan} className=\"bg-muted/30 p-4\">\n {expandedContent}\n </td>\n </tr>\n ) : null}\n </Fragment>\n );\n })}\n </Table.Body>\n </Table>\n {pagination && totalPages > 1 ? (\n <div className=\"mt-4 flex items-center justify-end\">\n <Pagination\n currentPage={currentPage + 1}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n />\n </div>\n ) : null}\n </div>\n );\n}\n\nexport { DataTable };\n"]}
|