@corbits/react-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +501 -0
- package/README.md +125 -0
- package/dist/blocks/login/auth-layout.d.ts +38 -0
- package/dist/blocks/login/auth-layout.js +52 -0
- package/dist/blocks/login/login-form.d.ts +60 -0
- package/dist/blocks/login/login-form.js +151 -0
- package/dist/hooks/use-command-palette-navigation.d.ts +25 -0
- package/dist/hooks/use-command-palette-navigation.js +52 -0
- package/dist/hooks/use-controllable-state.d.ts +28 -0
- package/dist/hooks/use-controllable-state.js +44 -0
- package/dist/hooks/use-delayed-autofocus.d.ts +9 -0
- package/dist/hooks/use-delayed-autofocus.js +20 -0
- package/dist/hooks/use-dismissable-popover.d.ts +37 -0
- package/dist/hooks/use-dismissable-popover.js +64 -0
- package/dist/hooks/use-list-selection.d.ts +66 -0
- package/dist/hooks/use-list-selection.js +147 -0
- package/dist/hooks/use-prefers-reduced-motion.d.ts +13 -0
- package/dist/hooks/use-prefers-reduced-motion.js +28 -0
- package/dist/hooks/use-scroll-current-into-view.d.ts +19 -0
- package/dist/hooks/use-scroll-current-into-view.js +33 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +131 -0
- package/dist/lib/artifact.d.ts +72 -0
- package/dist/lib/artifact.js +67 -0
- package/dist/lib/chart-geometry.d.ts +62 -0
- package/dist/lib/chart-geometry.js +86 -0
- package/dist/lib/chart-palette.d.ts +76 -0
- package/dist/lib/chart-palette.js +92 -0
- package/dist/lib/chat-message.d.ts +115 -0
- package/dist/lib/chat-message.js +41 -0
- package/dist/lib/chat-parts.d.ts +64 -0
- package/dist/lib/chat-parts.js +16 -0
- package/dist/lib/csv.d.ts +60 -0
- package/dist/lib/csv.js +108 -0
- package/dist/lib/metrics.d.ts +70 -0
- package/dist/lib/metrics.js +101 -0
- package/dist/lib/now-item.d.ts +65 -0
- package/dist/lib/now-item.js +59 -0
- package/dist/lib/relative-time.d.ts +9 -0
- package/dist/lib/relative-time.js +47 -0
- package/dist/lib/theme.d.ts +30 -0
- package/dist/lib/theme.js +77 -0
- package/dist/lib/url.d.ts +26 -0
- package/dist/lib/url.js +37 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/lib/workflow-display-flow.d.ts +83 -0
- package/dist/lib/workflow-display-flow.js +104 -0
- package/dist/lib/workflow-registry.d.ts +90 -0
- package/dist/lib/workflow-registry.js +56 -0
- package/dist/lib/workflow-run-error.d.ts +31 -0
- package/dist/lib/workflow-run-error.js +105 -0
- package/dist/lib/workflow-run-progress.d.ts +97 -0
- package/dist/lib/workflow-run-progress.js +135 -0
- package/dist/lib/workflow-run.d.ts +43 -0
- package/dist/lib/workflow-run.js +56 -0
- package/dist/styles.css +3080 -0
- package/dist/test/render-hook.d.ts +14 -0
- package/dist/test/render-hook.js +38 -0
- package/dist/theme.css +628 -0
- package/dist/ui/agent-turn.d.ts +26 -0
- package/dist/ui/agent-turn.js +60 -0
- package/dist/ui/animated-number.d.ts +25 -0
- package/dist/ui/animated-number.js +63 -0
- package/dist/ui/approval-card.d.ts +46 -0
- package/dist/ui/approval-card.js +116 -0
- package/dist/ui/artifact-body.d.ts +26 -0
- package/dist/ui/artifact-body.js +170 -0
- package/dist/ui/artifact-notice.d.ts +21 -0
- package/dist/ui/artifact-notice.js +36 -0
- package/dist/ui/avatar.d.ts +37 -0
- package/dist/ui/avatar.js +73 -0
- package/dist/ui/badge.d.ts +10 -0
- package/dist/ui/badge.js +35 -0
- package/dist/ui/bar-chart.d.ts +38 -0
- package/dist/ui/bar-chart.js +118 -0
- package/dist/ui/block-card.d.ts +29 -0
- package/dist/ui/block-card.js +66 -0
- package/dist/ui/boot-screen.d.ts +23 -0
- package/dist/ui/boot-screen.js +49 -0
- package/dist/ui/bulk-action-bar.d.ts +37 -0
- package/dist/ui/bulk-action-bar.js +64 -0
- package/dist/ui/button.d.ts +12 -0
- package/dist/ui/button.js +73 -0
- package/dist/ui/card.d.ts +6 -0
- package/dist/ui/card.js +37 -0
- package/dist/ui/category-bars.d.ts +20 -0
- package/dist/ui/category-bars.js +69 -0
- package/dist/ui/chart-frame.d.ts +54 -0
- package/dist/ui/chart-frame.js +108 -0
- package/dist/ui/chat-composer.d.ts +24 -0
- package/dist/ui/chat-composer.js +32 -0
- package/dist/ui/chat-dock-timing.d.ts +15 -0
- package/dist/ui/chat-dock-timing.js +11 -0
- package/dist/ui/chat-input.d.ts +38 -0
- package/dist/ui/chat-input.js +138 -0
- package/dist/ui/chat-panel.d.ts +20 -0
- package/dist/ui/chat-panel.js +56 -0
- package/dist/ui/chat-thread.d.ts +28 -0
- package/dist/ui/chat-thread.js +61 -0
- package/dist/ui/checkbox.d.ts +25 -0
- package/dist/ui/checkbox.js +63 -0
- package/dist/ui/command-palette.d.ts +100 -0
- package/dist/ui/command-palette.js +341 -0
- package/dist/ui/compare-body.d.ts +52 -0
- package/dist/ui/compare-body.js +140 -0
- package/dist/ui/confirm-button.d.ts +29 -0
- package/dist/ui/confirm-button.js +41 -0
- package/dist/ui/corbits-mark.d.ts +29 -0
- package/dist/ui/corbits-mark.js +33 -0
- package/dist/ui/csv-table.d.ts +22 -0
- package/dist/ui/csv-table.js +85 -0
- package/dist/ui/dialog.d.ts +37 -0
- package/dist/ui/dialog.js +105 -0
- package/dist/ui/dictation-status-line.d.ts +20 -0
- package/dist/ui/dictation-status-line.js +38 -0
- package/dist/ui/dither-canvas.d.ts +31 -0
- package/dist/ui/dither-canvas.js +169 -0
- package/dist/ui/embed-body.d.ts +30 -0
- package/dist/ui/embed-body.js +92 -0
- package/dist/ui/empty-state.d.ts +26 -0
- package/dist/ui/empty-state.js +42 -0
- package/dist/ui/file-input.d.ts +25 -0
- package/dist/ui/file-input.js +66 -0
- package/dist/ui/filter-bar.d.ts +42 -0
- package/dist/ui/filter-bar.js +95 -0
- package/dist/ui/filter-chip.d.ts +14 -0
- package/dist/ui/filter-chip.js +23 -0
- package/dist/ui/gate-block.d.ts +16 -0
- package/dist/ui/gate-block.js +37 -0
- package/dist/ui/horizontal-stepper.d.ts +13 -0
- package/dist/ui/horizontal-stepper.js +65 -0
- package/dist/ui/input.d.ts +2 -0
- package/dist/ui/input.js +14 -0
- package/dist/ui/inspector-shell.d.ts +47 -0
- package/dist/ui/inspector-shell.js +98 -0
- package/dist/ui/intake-form.d.ts +39 -0
- package/dist/ui/intake-form.js +119 -0
- package/dist/ui/kind-card-grid.d.ts +23 -0
- package/dist/ui/kind-card-grid.js +43 -0
- package/dist/ui/library-page-header.d.ts +20 -0
- package/dist/ui/library-page-header.js +43 -0
- package/dist/ui/list-detail.d.ts +28 -0
- package/dist/ui/list-detail.js +55 -0
- package/dist/ui/live-run-banner.d.ts +13 -0
- package/dist/ui/live-run-banner.js +23 -0
- package/dist/ui/live-status-line.d.ts +22 -0
- package/dist/ui/live-status-line.js +37 -0
- package/dist/ui/menu.d.ts +18 -0
- package/dist/ui/menu.js +45 -0
- package/dist/ui/message-bubble.d.ts +24 -0
- package/dist/ui/message-bubble.js +64 -0
- package/dist/ui/mic-button.d.ts +35 -0
- package/dist/ui/mic-button.js +61 -0
- package/dist/ui/mic-permission-dialog.d.ts +20 -0
- package/dist/ui/mic-permission-dialog.js +50 -0
- package/dist/ui/notifications-bell.d.ts +28 -0
- package/dist/ui/notifications-bell.js +69 -0
- package/dist/ui/now-cards.d.ts +37 -0
- package/dist/ui/now-cards.js +116 -0
- package/dist/ui/page-panel.d.ts +37 -0
- package/dist/ui/page-panel.js +23 -0
- package/dist/ui/page-shell.d.ts +21 -0
- package/dist/ui/page-shell.js +26 -0
- package/dist/ui/parts-renderer.d.ts +22 -0
- package/dist/ui/parts-renderer.js +234 -0
- package/dist/ui/profile-card.d.ts +39 -0
- package/dist/ui/profile-card.js +124 -0
- package/dist/ui/progress-checklist.d.ts +25 -0
- package/dist/ui/progress-checklist.js +70 -0
- package/dist/ui/provider-mark.d.ts +32 -0
- package/dist/ui/provider-mark.js +31 -0
- package/dist/ui/quick-reply-chips.d.ts +14 -0
- package/dist/ui/quick-reply-chips.js +24 -0
- package/dist/ui/quote-card.d.ts +39 -0
- package/dist/ui/quote-card.js +66 -0
- package/dist/ui/reasoning-block.d.ts +35 -0
- package/dist/ui/reasoning-block.js +60 -0
- package/dist/ui/research-body.d.ts +70 -0
- package/dist/ui/research-body.js +270 -0
- package/dist/ui/rich-empty-state.d.ts +35 -0
- package/dist/ui/rich-empty-state.js +65 -0
- package/dist/ui/run-now-button.d.ts +23 -0
- package/dist/ui/run-now-button.js +42 -0
- package/dist/ui/section.d.ts +20 -0
- package/dist/ui/section.js +48 -0
- package/dist/ui/select.d.ts +2 -0
- package/dist/ui/select.js +22 -0
- package/dist/ui/selection-checkbox.d.ts +42 -0
- package/dist/ui/selection-checkbox.js +54 -0
- package/dist/ui/settings-panel.d.ts +35 -0
- package/dist/ui/settings-panel.js +68 -0
- package/dist/ui/shimmer-text.d.ts +19 -0
- package/dist/ui/shimmer-text.js +20 -0
- package/dist/ui/sidebar-item-row.d.ts +40 -0
- package/dist/ui/sidebar-item-row.js +64 -0
- package/dist/ui/sidebar-panel.d.ts +25 -0
- package/dist/ui/sidebar-panel.js +56 -0
- package/dist/ui/sidebar.d.ts +46 -0
- package/dist/ui/sidebar.js +111 -0
- package/dist/ui/skeleton.d.ts +13 -0
- package/dist/ui/skeleton.js +20 -0
- package/dist/ui/sortable-table.d.ts +43 -0
- package/dist/ui/sortable-table.js +103 -0
- package/dist/ui/sparkline.d.ts +27 -0
- package/dist/ui/sparkline.js +68 -0
- package/dist/ui/stat-grid.d.ts +55 -0
- package/dist/ui/stat-grid.js +87 -0
- package/dist/ui/status-dot.d.ts +13 -0
- package/dist/ui/status-dot.js +51 -0
- package/dist/ui/step-list.d.ts +21 -0
- package/dist/ui/step-list.js +77 -0
- package/dist/ui/switch.d.ts +23 -0
- package/dist/ui/switch.js +29 -0
- package/dist/ui/table.d.ts +8 -0
- package/dist/ui/table.js +48 -0
- package/dist/ui/tabs.d.ts +44 -0
- package/dist/ui/tabs.js +101 -0
- package/dist/ui/textarea.d.ts +8 -0
- package/dist/ui/textarea.js +32 -0
- package/dist/ui/theme-provider.d.ts +36 -0
- package/dist/ui/theme-provider.js +126 -0
- package/dist/ui/theme-toggle.d.ts +11 -0
- package/dist/ui/theme-toggle.js +32 -0
- package/dist/ui/thinking-indicator.d.ts +7 -0
- package/dist/ui/thinking-indicator.js +19 -0
- package/dist/ui/thinking-label.d.ts +19 -0
- package/dist/ui/thinking-label.js +54 -0
- package/dist/ui/thinking-mark.d.ts +10 -0
- package/dist/ui/thinking-mark.js +153 -0
- package/dist/ui/time-series-chart.d.ts +55 -0
- package/dist/ui/time-series-chart.js +249 -0
- package/dist/ui/toast.d.ts +16 -0
- package/dist/ui/toast.js +41 -0
- package/dist/ui/token-mosaic.d.ts +22 -0
- package/dist/ui/token-mosaic.js +65 -0
- package/dist/ui/tool-block.d.ts +54 -0
- package/dist/ui/tool-block.js +170 -0
- package/dist/ui/tool-narrative.d.ts +22 -0
- package/dist/ui/tool-narrative.js +77 -0
- package/dist/ui/tool-picker.d.ts +44 -0
- package/dist/ui/tool-picker.js +200 -0
- package/dist/ui/tooltip.d.ts +21 -0
- package/dist/ui/tooltip.js +48 -0
- package/dist/ui/top-bar.d.ts +53 -0
- package/dist/ui/top-bar.js +98 -0
- package/dist/ui/trace-waterfall.d.ts +38 -0
- package/dist/ui/trace-waterfall.js +121 -0
- package/dist/ui/typing-indicator.d.ts +9 -0
- package/dist/ui/typing-indicator.js +25 -0
- package/dist/ui/view-toggle.d.ts +17 -0
- package/dist/ui/view-toggle.js +46 -0
- package/dist/ui/voice-waveform.d.ts +16 -0
- package/dist/ui/voice-waveform.js +23 -0
- package/package.json +583 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { GATE_KIND_LABEL } from "../lib/workflow-registry.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* Presentational chrome for a pending gate: a run parked on a human
|
|
6
|
+
* decision. The interactive payload is entirely the caller's — this
|
|
7
|
+
* component only frames it, so the resume-payload contract stays wherever
|
|
8
|
+
* the host's signal-sending logic already lives.
|
|
9
|
+
*/ export function GateBlock({ gate, children, footer, className }) {
|
|
10
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
11
|
+
"data-gate-kind": gate.kind,
|
|
12
|
+
"aria-label": GATE_KIND_LABEL[gate.kind],
|
|
13
|
+
className: cn("rounded-lg border border-primary-emphasis/30 bg-primary/5 px-3.5 py-3", className),
|
|
14
|
+
children: [
|
|
15
|
+
/*#__PURE__*/ _jsx("p", {
|
|
16
|
+
className: "text-[10.5px] font-bold uppercase tracking-[0.05em] text-primary-emphasis",
|
|
17
|
+
children: GATE_KIND_LABEL[gate.kind]
|
|
18
|
+
}),
|
|
19
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
20
|
+
className: "mt-1 text-sm font-semibold",
|
|
21
|
+
children: gate.title
|
|
22
|
+
}),
|
|
23
|
+
gate.prompt === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
24
|
+
className: "mt-1 text-xs leading-snug text-muted-foreground",
|
|
25
|
+
children: gate.prompt
|
|
26
|
+
}),
|
|
27
|
+
children === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
28
|
+
className: "mt-3",
|
|
29
|
+
children: children
|
|
30
|
+
}),
|
|
31
|
+
footer === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
32
|
+
className: "mt-3 flex flex-wrap items-center gap-2",
|
|
33
|
+
children: footer
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WorkflowStep } from "../lib/workflow-run-progress.js";
|
|
2
|
+
export type HorizontalStepperProps = {
|
|
3
|
+
readonly steps: readonly WorkflowStep[];
|
|
4
|
+
readonly className?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* A phase rail: numbered pills connected by a rail that fills in once the
|
|
8
|
+
* segment behind it actually completes — the fill never runs ahead of real
|
|
9
|
+
* work. Scrolls the current step into view on mount and on every step
|
|
10
|
+
* change, so a run with more steps than fit on screen never leaves the
|
|
11
|
+
* active one off to the side unannounced.
|
|
12
|
+
*/
|
|
13
|
+
export declare function HorizontalStepper({ steps, className }: HorizontalStepperProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useScrollCurrentIntoView } from "../hooks/use-scroll-current-into-view.js";
|
|
3
|
+
import { workflowStepGlyph, workflowStepLabelClass } from "../lib/workflow-run-progress.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
// Above this many steps, an even split can't give every label readable room,
|
|
6
|
+
// so labels compress to numbers-only pills except for the current step — the
|
|
7
|
+
// one label a reader actually needs without scrolling. Every label still
|
|
8
|
+
// renders (`sr-only`) so a screen reader still hears the full sequence.
|
|
9
|
+
const LABEL_VISIBLE_STEP_THRESHOLD = 5;
|
|
10
|
+
function pillClass(status) {
|
|
11
|
+
if (status === "completed") return "bg-success text-success-foreground";
|
|
12
|
+
if (status === "failed") return "bg-destructive text-destructive-foreground";
|
|
13
|
+
if (status === "current") return "bg-primary text-primary-foreground";
|
|
14
|
+
return "bg-muted text-muted-foreground";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A phase rail: numbered pills connected by a rail that fills in once the
|
|
18
|
+
* segment behind it actually completes — the fill never runs ahead of real
|
|
19
|
+
* work. Scrolls the current step into view on mount and on every step
|
|
20
|
+
* change, so a run with more steps than fit on screen never leaves the
|
|
21
|
+
* active one off to the side unannounced.
|
|
22
|
+
*/ export function HorizontalStepper({ steps, className }) {
|
|
23
|
+
const currentStep = steps.find((step)=>step.status === "current");
|
|
24
|
+
const currentRef = useScrollCurrentIntoView(currentStep?.number ?? -1);
|
|
25
|
+
const compress = steps.length > LABEL_VISIBLE_STEP_THRESHOLD;
|
|
26
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
27
|
+
className: cn("border-b border-border bg-card px-6 py-5", className),
|
|
28
|
+
children: /*#__PURE__*/ _jsx("ol", {
|
|
29
|
+
"aria-label": "Workflow progress",
|
|
30
|
+
className: "flex items-center gap-2 overflow-x-auto [mask-image:linear-gradient(to_right,transparent,black_12px,black_calc(100%-12px),transparent)] md:gap-3",
|
|
31
|
+
children: steps.map((step, index)=>/*#__PURE__*/ _jsxs("li", {
|
|
32
|
+
ref: step.status === "current" ? currentRef : undefined,
|
|
33
|
+
"aria-current": step.status === "current" ? "step" : undefined,
|
|
34
|
+
className: cn("flex min-w-0 items-center gap-2 last:flex-initial md:gap-3", compress ? step.status === "current" ? "flex-[8_1_0%]" : "flex-1" : "flex-1"),
|
|
35
|
+
children: [
|
|
36
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
37
|
+
className: "relative flex size-8 shrink-0 items-center justify-center",
|
|
38
|
+
children: [
|
|
39
|
+
step.status === "current" ? /*#__PURE__*/ _jsx("span", {
|
|
40
|
+
"aria-hidden": true,
|
|
41
|
+
className: "absolute inset-0 rounded-full bg-primary/30 motion-safe:[animation:corbits-status-pulse_1.8s_ease-out_infinite]"
|
|
42
|
+
}) : null,
|
|
43
|
+
/*#__PURE__*/ _jsx("span", {
|
|
44
|
+
className: cn("relative flex size-8 items-center justify-center rounded-full text-sm font-medium transition-colors", pillClass(step.status)),
|
|
45
|
+
children: workflowStepGlyph(step.status, step.number)
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
}),
|
|
49
|
+
/*#__PURE__*/ _jsx("span", {
|
|
50
|
+
title: step.label,
|
|
51
|
+
className: cn("min-w-0 flex-1 truncate text-sm font-medium", compress && step.status !== "current" ? "sr-only" : workflowStepLabelClass(step.status)),
|
|
52
|
+
children: step.label
|
|
53
|
+
}),
|
|
54
|
+
index === steps.length - 1 ? null : /*#__PURE__*/ _jsx("span", {
|
|
55
|
+
className: cn("h-0.5 shrink-0 overflow-hidden rounded-full bg-border", compress ? "min-w-2 flex-1" : "w-4 md:w-6"),
|
|
56
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
57
|
+
"data-filled": step.status === "completed",
|
|
58
|
+
className: "block h-full w-full origin-left rounded-full bg-success transition-transform duration-300 ease-out data-[filled=false]:scale-x-0 data-[filled=true]:scale-x-100"
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
}, step.number))
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
}
|
package/dist/ui/input.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
// `border-input` is the control-boundary token: it clears the 3:1 UI-component
|
|
4
|
+
// threshold against both the page and the card, so the field actually has an
|
|
5
|
+
// edge. `border-border` is the soft decorative one and must not be used here.
|
|
6
|
+
// The focus ring comes from the theme's base layer, not from this file.
|
|
7
|
+
export function Input({ className, type, ...props }) {
|
|
8
|
+
return /*#__PURE__*/ _jsx("input", {
|
|
9
|
+
type: type,
|
|
10
|
+
"data-slot": "input",
|
|
11
|
+
className: cn("flex h-9 w-full min-w-0 rounded-md border border-input bg-card px-3 py-1 text-sm shadow-xs transition-colors", "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground", "aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
12
|
+
...props
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type InspectorEmptyProps = {
|
|
3
|
+
readonly title: string;
|
|
4
|
+
readonly description?: string;
|
|
5
|
+
readonly className?: string;
|
|
6
|
+
};
|
|
7
|
+
/** The "nothing selected" message inside an inspector rail. Deliberately not
|
|
8
|
+
* `EmptyState` — that component centres and pads for a full-width surface;
|
|
9
|
+
* an inspector's empty message sits left-aligned in a narrow side panel. */
|
|
10
|
+
export declare function InspectorEmpty({ title, description, className }: InspectorEmptyProps): import("react").JSX.Element;
|
|
11
|
+
export type InspectorHeaderProps = {
|
|
12
|
+
readonly eyebrow?: ReactNode;
|
|
13
|
+
readonly title: ReactNode;
|
|
14
|
+
readonly description?: ReactNode;
|
|
15
|
+
readonly actions?: ReactNode;
|
|
16
|
+
readonly className?: string;
|
|
17
|
+
};
|
|
18
|
+
/** The chip row, title and action buttons at the top of an inspector. */
|
|
19
|
+
export declare function InspectorHeader({ eyebrow, title, description, actions, className }: InspectorHeaderProps): import("react").JSX.Element;
|
|
20
|
+
export declare function InspectorPanelTitle({ children, className }: {
|
|
21
|
+
readonly children: ReactNode;
|
|
22
|
+
readonly className?: string;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
24
|
+
export type InspectorKvRow = {
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly value: ReactNode;
|
|
27
|
+
readonly mono?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare function InspectorKv({ rows, className }: {
|
|
30
|
+
readonly rows: readonly InspectorKvRow[];
|
|
31
|
+
readonly className?: string;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
export type InspectorShellProps = {
|
|
34
|
+
readonly header?: ReactNode;
|
|
35
|
+
readonly children?: ReactNode;
|
|
36
|
+
/** Shown instead of `children` when nothing is selected. */
|
|
37
|
+
readonly empty?: ReactNode;
|
|
38
|
+
readonly className?: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The side-rail frame every inspector shares: a header region, a scrollable
|
|
42
|
+
* body, and an empty state that replaces both when there is no selection.
|
|
43
|
+
* `InspectorHeader`/`InspectorKv`/`InspectorPanelTitle` are exported
|
|
44
|
+
* separately so a caller can compose its own layout inside this shell
|
|
45
|
+
* instead of going through a single big inspector component.
|
|
46
|
+
*/
|
|
47
|
+
export declare function InspectorShell({ header, children, empty, className }: InspectorShellProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
/** The "nothing selected" message inside an inspector rail. Deliberately not
|
|
4
|
+
* `EmptyState` — that component centres and pads for a full-width surface;
|
|
5
|
+
* an inspector's empty message sits left-aligned in a narrow side panel. */ export function InspectorEmpty({ title, description, className }) {
|
|
6
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
7
|
+
className: cn("max-w-64", className),
|
|
8
|
+
children: [
|
|
9
|
+
/*#__PURE__*/ _jsx("p", {
|
|
10
|
+
className: "text-sm font-semibold",
|
|
11
|
+
children: title
|
|
12
|
+
}),
|
|
13
|
+
description === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
14
|
+
className: "mt-1.5 text-xs leading-snug text-muted-foreground",
|
|
15
|
+
children: description
|
|
16
|
+
})
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** The chip row, title and action buttons at the top of an inspector. */ export function InspectorHeader({ eyebrow, title, description, actions, className }) {
|
|
21
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
22
|
+
className: cn("flex flex-col gap-2", className),
|
|
23
|
+
children: [
|
|
24
|
+
eyebrow === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
25
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
26
|
+
children: eyebrow
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
29
|
+
className: "text-base font-bold tracking-tight",
|
|
30
|
+
children: title
|
|
31
|
+
}),
|
|
32
|
+
description === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
33
|
+
className: "text-xs leading-snug text-muted-foreground",
|
|
34
|
+
children: description
|
|
35
|
+
}),
|
|
36
|
+
actions === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
37
|
+
className: "mt-1 flex flex-wrap items-center gap-1.5",
|
|
38
|
+
children: actions
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function InspectorPanelTitle({ children, className }) {
|
|
44
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
45
|
+
className: cn("mb-2 mt-4 text-[10.5px] font-bold uppercase tracking-[0.05em] text-muted-foreground first:mt-0", className),
|
|
46
|
+
children: children
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function InspectorKv({ rows, className }) {
|
|
50
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
51
|
+
className: cn("flex flex-col gap-1.5", className),
|
|
52
|
+
children: rows.map((row)=>/*#__PURE__*/ _jsxs("div", {
|
|
53
|
+
className: "flex items-baseline justify-between gap-3 text-xs",
|
|
54
|
+
children: [
|
|
55
|
+
/*#__PURE__*/ _jsx("span", {
|
|
56
|
+
className: "shrink-0 text-muted-foreground",
|
|
57
|
+
children: row.label
|
|
58
|
+
}),
|
|
59
|
+
/*#__PURE__*/ _jsx("span", {
|
|
60
|
+
className: cn("min-w-0 truncate text-right", row.mono === true && "font-mono"),
|
|
61
|
+
children: row.value
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
}, row.label))
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The side-rail frame every inspector shares: a header region, a scrollable
|
|
69
|
+
* body, and an empty state that replaces both when there is no selection.
|
|
70
|
+
* `InspectorHeader`/`InspectorKv`/`InspectorPanelTitle` are exported
|
|
71
|
+
* separately so a caller can compose its own layout inside this shell
|
|
72
|
+
* instead of going through a single big inspector component.
|
|
73
|
+
*/ export function InspectorShell({ header, children, empty, className }) {
|
|
74
|
+
if (empty !== undefined && header === undefined) {
|
|
75
|
+
return /*#__PURE__*/ _jsx("aside", {
|
|
76
|
+
"aria-label": "Inspector",
|
|
77
|
+
className: cn("flex min-h-0 flex-col overflow-auto border-l border-border bg-card", className),
|
|
78
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
79
|
+
className: "flex flex-1 flex-col items-start justify-center px-5 py-10",
|
|
80
|
+
children: empty
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return /*#__PURE__*/ _jsxs("aside", {
|
|
85
|
+
"aria-label": "Inspector",
|
|
86
|
+
className: cn("flex min-h-0 flex-col overflow-hidden border-l border-border bg-card", className),
|
|
87
|
+
children: [
|
|
88
|
+
header === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
89
|
+
className: "shrink-0 border-b border-border px-4 pb-3 pt-4",
|
|
90
|
+
children: header
|
|
91
|
+
}),
|
|
92
|
+
/*#__PURE__*/ _jsx("div", {
|
|
93
|
+
className: "min-h-0 flex-1 overflow-auto px-4 py-3",
|
|
94
|
+
children: children
|
|
95
|
+
})
|
|
96
|
+
]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One input a workflow asks for before it runs.
|
|
3
|
+
*
|
|
4
|
+
* A small closed set of types on purpose. Every additional widget is a
|
|
5
|
+
* rendering branch a consumer has to read past to change the one they use, and
|
|
6
|
+
* a workflow that needs a date picker or a file drop is better served by the
|
|
7
|
+
* host composing its own field than by this file growing a case for it.
|
|
8
|
+
*/
|
|
9
|
+
export type IntakeField = {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly type: "text" | "textarea" | "number" | "boolean" | "select";
|
|
13
|
+
readonly required?: boolean;
|
|
14
|
+
/** Guidance under the control. Wired up with `aria-describedby`. */
|
|
15
|
+
readonly help?: string;
|
|
16
|
+
readonly placeholder?: string;
|
|
17
|
+
/** For `select` only. Ignored otherwise. */
|
|
18
|
+
readonly options?: readonly {
|
|
19
|
+
readonly value: string;
|
|
20
|
+
readonly label: string;
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
export type IntakeFormProps = {
|
|
24
|
+
readonly fields: readonly IntakeField[];
|
|
25
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
26
|
+
readonly onChange: (next: Record<string, unknown>) => void;
|
|
27
|
+
/** Disambiguates ids when two forms are on one page. */
|
|
28
|
+
readonly idPrefix?: string;
|
|
29
|
+
readonly disabled?: boolean;
|
|
30
|
+
readonly className?: string;
|
|
31
|
+
};
|
|
32
|
+
/** True when every required field has a value — for enabling a submit control. */
|
|
33
|
+
export declare function intakeFieldsComplete(fields: readonly IntakeField[], values: Readonly<Record<string, unknown>>): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The inputs a run needs, rendered from a field list. Fully controlled: the
|
|
36
|
+
* host owns the values, so the same form backs a one-off run and a saved
|
|
37
|
+
* schedule without this file knowing which.
|
|
38
|
+
*/
|
|
39
|
+
export declare function IntakeForm({ fields, values, onChange, idPrefix, disabled, className }: IntakeFormProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { Input } from "./input.js";
|
|
5
|
+
/** True when every required field has a value — for enabling a submit control. */ export function intakeFieldsComplete(fields, values) {
|
|
6
|
+
return fields.every((field)=>{
|
|
7
|
+
if (field.required !== true) return true;
|
|
8
|
+
const value = values[field.name];
|
|
9
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
10
|
+
return value !== undefined && value !== null;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The inputs a run needs, rendered from a field list. Fully controlled: the
|
|
15
|
+
* host owns the values, so the same form backs a one-off run and a saved
|
|
16
|
+
* schedule without this file knowing which.
|
|
17
|
+
*/ export function IntakeForm({ fields, values, onChange, idPrefix, disabled = false, className }) {
|
|
18
|
+
const generatedPrefix = useId();
|
|
19
|
+
const prefix = idPrefix ?? generatedPrefix;
|
|
20
|
+
if (fields.length === 0) return null;
|
|
21
|
+
const set = (name, value)=>onChange({
|
|
22
|
+
...values,
|
|
23
|
+
[name]: value
|
|
24
|
+
});
|
|
25
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
26
|
+
className: cn("flex flex-col gap-4", className),
|
|
27
|
+
children: fields.map((field)=>{
|
|
28
|
+
const id = `${prefix}-${field.name}`;
|
|
29
|
+
const helpId = field.help === undefined ? undefined : `${id}-help`;
|
|
30
|
+
const value = values[field.name];
|
|
31
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
32
|
+
className: "flex flex-col gap-1.5",
|
|
33
|
+
children: [
|
|
34
|
+
field.type === "boolean" ? /*#__PURE__*/ _jsxs("label", {
|
|
35
|
+
htmlFor: id,
|
|
36
|
+
className: "flex items-center gap-2 text-sm",
|
|
37
|
+
children: [
|
|
38
|
+
/*#__PURE__*/ _jsx("input", {
|
|
39
|
+
id: id,
|
|
40
|
+
type: "checkbox",
|
|
41
|
+
checked: value === true,
|
|
42
|
+
required: field.required,
|
|
43
|
+
disabled: disabled,
|
|
44
|
+
"aria-describedby": helpId,
|
|
45
|
+
onChange: (event)=>set(field.name, event.target.checked),
|
|
46
|
+
className: "size-4 rounded-sm border border-input accent-[var(--primary)]"
|
|
47
|
+
}),
|
|
48
|
+
field.label,
|
|
49
|
+
field.required === true ? /*#__PURE__*/ _jsx(RequiredMark, {}) : null
|
|
50
|
+
]
|
|
51
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
52
|
+
children: [
|
|
53
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
54
|
+
htmlFor: id,
|
|
55
|
+
className: "text-sm font-medium",
|
|
56
|
+
children: [
|
|
57
|
+
field.label,
|
|
58
|
+
field.required === true ? /*#__PURE__*/ _jsx(RequiredMark, {}) : null
|
|
59
|
+
]
|
|
60
|
+
}),
|
|
61
|
+
field.type === "textarea" ? /*#__PURE__*/ _jsx("textarea", {
|
|
62
|
+
id: id,
|
|
63
|
+
value: typeof value === "string" ? value : "",
|
|
64
|
+
placeholder: field.placeholder,
|
|
65
|
+
required: field.required,
|
|
66
|
+
disabled: disabled,
|
|
67
|
+
"aria-describedby": helpId,
|
|
68
|
+
rows: 4,
|
|
69
|
+
onChange: (event)=>set(field.name, event.target.value),
|
|
70
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-50"
|
|
71
|
+
}) : field.type === "select" ? /*#__PURE__*/ _jsxs("select", {
|
|
72
|
+
id: id,
|
|
73
|
+
value: typeof value === "string" ? value : "",
|
|
74
|
+
required: field.required,
|
|
75
|
+
disabled: disabled,
|
|
76
|
+
"aria-describedby": helpId,
|
|
77
|
+
onChange: (event)=>set(field.name, event.target.value),
|
|
78
|
+
className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm disabled:opacity-50",
|
|
79
|
+
children: [
|
|
80
|
+
/*#__PURE__*/ _jsx("option", {
|
|
81
|
+
value: "",
|
|
82
|
+
children: "Choose…"
|
|
83
|
+
}),
|
|
84
|
+
(field.options ?? []).map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
85
|
+
value: option.value,
|
|
86
|
+
children: option.label
|
|
87
|
+
}, option.value))
|
|
88
|
+
]
|
|
89
|
+
}) : /*#__PURE__*/ _jsx(Input, {
|
|
90
|
+
id: id,
|
|
91
|
+
type: field.type === "number" ? "number" : "text",
|
|
92
|
+
value: typeof value === "string" || typeof value === "number" ? String(value) : "",
|
|
93
|
+
placeholder: field.placeholder,
|
|
94
|
+
required: field.required,
|
|
95
|
+
disabled: disabled,
|
|
96
|
+
"aria-describedby": helpId,
|
|
97
|
+
onChange: (event)=>set(field.name, field.type === "number" ? event.target.valueAsNumber : event.target.value)
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
}),
|
|
101
|
+
field.help === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
102
|
+
id: helpId,
|
|
103
|
+
className: "text-xs text-muted-foreground",
|
|
104
|
+
children: field.help
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
}, field.name);
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// The asterisk is decorative: `required` on the control is what actually tells
|
|
112
|
+
// assistive tech, and a screen reader announcing "asterisk" helps nobody.
|
|
113
|
+
function RequiredMark() {
|
|
114
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
115
|
+
"aria-hidden": true,
|
|
116
|
+
className: "ml-0.5 text-primary-emphasis",
|
|
117
|
+
children: "*"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type KindCardOption = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly description?: string;
|
|
6
|
+
readonly icon?: ReactNode;
|
|
7
|
+
readonly disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type KindCardGridProps = {
|
|
10
|
+
readonly options: readonly KindCardOption[];
|
|
11
|
+
readonly value?: string;
|
|
12
|
+
readonly onChange?: (id: string) => void;
|
|
13
|
+
/** Accessible name for the radiogroup. */
|
|
14
|
+
readonly label: string;
|
|
15
|
+
readonly columns?: 2 | 3 | 4;
|
|
16
|
+
readonly className?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Selectable card grid for channel-kind, grant-effect, provider, and
|
|
20
|
+
* workbench-type pickers. Each card is a real button with `aria-pressed`;
|
|
21
|
+
* the group is labelled for assistive tech.
|
|
22
|
+
*/
|
|
23
|
+
export declare function KindCardGrid({ options, value, onChange, label, columns, className, }: KindCardGridProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
const COLUMNS = {
|
|
4
|
+
2: "grid-cols-2",
|
|
5
|
+
3: "grid-cols-3",
|
|
6
|
+
4: "grid-cols-4"
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Selectable card grid for channel-kind, grant-effect, provider, and
|
|
10
|
+
* workbench-type pickers. Each card is a real button with `aria-pressed`;
|
|
11
|
+
* the group is labelled for assistive tech.
|
|
12
|
+
*/ export function KindCardGrid({ options, value, onChange, label, columns = 2, className }) {
|
|
13
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
14
|
+
role: "group",
|
|
15
|
+
"aria-label": label,
|
|
16
|
+
className: cn("grid gap-2", COLUMNS[columns], className),
|
|
17
|
+
children: options.map((option)=>{
|
|
18
|
+
const selected = value === option.id;
|
|
19
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
20
|
+
type: "button",
|
|
21
|
+
"aria-pressed": selected,
|
|
22
|
+
disabled: option.disabled === true,
|
|
23
|
+
onClick: ()=>onChange?.(option.id),
|
|
24
|
+
className: cn("flex flex-col gap-1 border px-3.5 py-3 text-left transition-colors ease-out", "hover:bg-muted active:brightness-95 disabled:pointer-events-none disabled:opacity-50", selected ? "border-primary-emphasis bg-primary/10" : "border-border bg-card"),
|
|
25
|
+
children: [
|
|
26
|
+
option.icon === undefined ? null : /*#__PURE__*/ _jsx("span", {
|
|
27
|
+
className: "mb-0.5 text-muted-foreground",
|
|
28
|
+
"aria-hidden": true,
|
|
29
|
+
children: option.icon
|
|
30
|
+
}),
|
|
31
|
+
/*#__PURE__*/ _jsx("span", {
|
|
32
|
+
className: "text-sm font-semibold",
|
|
33
|
+
children: option.title
|
|
34
|
+
}),
|
|
35
|
+
option.description === undefined ? null : /*#__PURE__*/ _jsx("span", {
|
|
36
|
+
className: "text-xs leading-snug text-muted-foreground",
|
|
37
|
+
children: option.description
|
|
38
|
+
})
|
|
39
|
+
]
|
|
40
|
+
}, option.id);
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type LibraryPageHeaderProps = {
|
|
3
|
+
readonly title: string;
|
|
4
|
+
/** `lg` (21px, default) for catalog pages; `sm` (17px) for denser lists. */
|
|
5
|
+
readonly titleSize?: "sm" | "lg";
|
|
6
|
+
/** When provided, renders an "{count} items" chip beside the title. */
|
|
7
|
+
readonly count?: number;
|
|
8
|
+
/** One line under the title — what this library holds. */
|
|
9
|
+
readonly subtitle?: string;
|
|
10
|
+
/** Trailing controls: search, view toggle, action buttons. */
|
|
11
|
+
readonly children?: ReactNode;
|
|
12
|
+
readonly className?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The header row every library page shares: title, item count, then trailing
|
|
16
|
+
* controls after a flexible spacer. The spacer is a real element rather than
|
|
17
|
+
* `justify-between` so a `LibrarySearchInput` can expand leftward over it
|
|
18
|
+
* without shifting its neighbours.
|
|
19
|
+
*/
|
|
20
|
+
export declare function LibraryPageHeader({ title, titleSize, count, subtitle, children, className, }: LibraryPageHeaderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
const TITLE_CLASS = {
|
|
4
|
+
lg: "text-[21px] font-semibold tracking-[-0.02em]",
|
|
5
|
+
sm: "text-[17px] font-semibold tracking-[-0.01em]"
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The header row every library page shares: title, item count, then trailing
|
|
9
|
+
* controls after a flexible spacer. The spacer is a real element rather than
|
|
10
|
+
* `justify-between` so a `LibrarySearchInput` can expand leftward over it
|
|
11
|
+
* without shifting its neighbours.
|
|
12
|
+
*/ export function LibraryPageHeader({ title, titleSize = "lg", count, subtitle, children, className }) {
|
|
13
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
14
|
+
"data-slot": "library-page-header",
|
|
15
|
+
className: cn("flex min-w-0 flex-wrap items-center gap-[14px] px-4 pt-5 pb-[14px] sm:px-7", className),
|
|
16
|
+
children: [
|
|
17
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
18
|
+
className: "flex min-w-0 shrink-0 flex-col gap-0.5",
|
|
19
|
+
children: [
|
|
20
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
21
|
+
className: TITLE_CLASS[titleSize],
|
|
22
|
+
children: title
|
|
23
|
+
}),
|
|
24
|
+
subtitle === undefined || subtitle === "" ? null : /*#__PURE__*/ _jsx("p", {
|
|
25
|
+
className: "truncate text-[12px] text-muted-foreground",
|
|
26
|
+
children: subtitle
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
}),
|
|
30
|
+
count === undefined ? null : /*#__PURE__*/ _jsxs("span", {
|
|
31
|
+
className: "shrink-0 rounded-md bg-muted px-[9px] py-[3px] font-mono text-[12px] text-muted-foreground",
|
|
32
|
+
children: [
|
|
33
|
+
count,
|
|
34
|
+
" items"
|
|
35
|
+
]
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsx("div", {
|
|
38
|
+
className: "min-w-2 flex-1"
|
|
39
|
+
}),
|
|
40
|
+
children
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type ListDetailProps = {
|
|
3
|
+
/** The index: a catalog grid, a table, a list of skills or tools. */
|
|
4
|
+
readonly list: ReactNode;
|
|
5
|
+
/** The open item. `null` shows the placeholder instead. */
|
|
6
|
+
readonly detail: ReactNode | null;
|
|
7
|
+
/** Clears the selection. Required — see the note about the back control. */
|
|
8
|
+
readonly onCloseDetail: () => void;
|
|
9
|
+
/** Shown on wide screens when nothing is selected. */
|
|
10
|
+
readonly placeholder?: ReactNode;
|
|
11
|
+
readonly detailLabel?: string;
|
|
12
|
+
readonly className?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Index on the left, the open item on the right.
|
|
16
|
+
*
|
|
17
|
+
* Two panes above `lg`, one below it. On a narrow screen the detail *replaces*
|
|
18
|
+
* the list rather than sitting under it: a phone-width column showing both
|
|
19
|
+
* means the user opens something and sees no change without scrolling.
|
|
20
|
+
*
|
|
21
|
+
* That swap is why `onCloseDetail` is required rather than optional. Once the
|
|
22
|
+
* list is hidden, the back control is the only way out — a caller who omitted
|
|
23
|
+
* it would ship a dead end on mobile, so the type does not let them.
|
|
24
|
+
*
|
|
25
|
+
* Both panes scroll independently, so reading a long detail does not scroll the
|
|
26
|
+
* list out from under the selection.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ListDetail({ list, detail, onCloseDetail, placeholder, detailLabel, className, }: ListDetailProps): import("react").JSX.Element;
|