@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,270 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { toSafeHref } from "../lib/url.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* Reads a brief (and its optional prose report) out of an artifact payload.
|
|
6
|
+
*
|
|
7
|
+
* Checks only `topic` and the two counts — the fields the header renders
|
|
8
|
+
* unconditionally and the ones whose absence would print "undefined sources".
|
|
9
|
+
* Everything below the header is already guarded by a presence check at its own
|
|
10
|
+
* render site, so validating it here would only reject briefs this component
|
|
11
|
+
* draws correctly.
|
|
12
|
+
*/ export function parseResearchArtifact(json) {
|
|
13
|
+
let raw;
|
|
14
|
+
try {
|
|
15
|
+
raw = JSON.parse(json);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
20
|
+
const candidate = raw;
|
|
21
|
+
if (typeof candidate.topic !== "string" || typeof candidate.sourceCount !== "number" || typeof candidate.itemCount !== "number") {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const body = typeof candidate.body === "string" ? candidate.body : undefined;
|
|
25
|
+
return body === undefined ? {
|
|
26
|
+
brief: candidate
|
|
27
|
+
} : {
|
|
28
|
+
brief: candidate,
|
|
29
|
+
body
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A citation list can name the same URL twice — one item that clustered under
|
|
34
|
+
* two sources. Both the numbering the reader sees and any export they take must
|
|
35
|
+
* agree on the count, so dedupe once, here, at the point of render.
|
|
36
|
+
*/ function dedupeByUrl(sources) {
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
return sources.filter((source)=>{
|
|
39
|
+
if (seen.has(source.url)) return false;
|
|
40
|
+
seen.add(source.url);
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A structured research brief: what was found, grouped, quoted and cited.
|
|
46
|
+
*
|
|
47
|
+
* The layout is an argument in order — headline stat line, the lead insight,
|
|
48
|
+
* then clusters, then evidence, then citations. A reader who stops after the
|
|
49
|
+
* first screen should still have the finding; a reader who wants to audit it
|
|
50
|
+
* can walk down to the sources without leaving the page.
|
|
51
|
+
*
|
|
52
|
+
* `body` is the prose report when the producer wrote one. Given both, the prose
|
|
53
|
+
* is the deliverable and the structured data becomes supporting evidence behind
|
|
54
|
+
* a `<details>` — collapsed rather than removed, because "show me your working"
|
|
55
|
+
* is the second question every reader of a generated report asks. `<details>` is
|
|
56
|
+
* a native disclosure: keyboard-operable and announced as expandable with no JS
|
|
57
|
+
* and no ARIA of our own.
|
|
58
|
+
*
|
|
59
|
+
* Prose is rendered as pre-wrapped text, not parsed as Markdown. The registry
|
|
60
|
+
* ships no Markdown dependency, and adding ~40kB of parser that a consumer then
|
|
61
|
+
* owns is not something a component should decide on their behalf. Wrap the
|
|
62
|
+
* `body` slot in your own renderer if you have one.
|
|
63
|
+
*/ export function ResearchBody({ brief, body, className }) {
|
|
64
|
+
const report = body?.trim() ?? "";
|
|
65
|
+
const citations = dedupeByUrl(brief.citations ?? []);
|
|
66
|
+
const data = /*#__PURE__*/ _jsxs("div", {
|
|
67
|
+
className: "flex flex-col gap-6",
|
|
68
|
+
children: [
|
|
69
|
+
brief.clusters === undefined || brief.clusters.length === 0 ? null : /*#__PURE__*/ _jsxs("section", {
|
|
70
|
+
className: "flex flex-col gap-3",
|
|
71
|
+
"aria-label": "Themes",
|
|
72
|
+
children: [
|
|
73
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
74
|
+
className: "text-sm font-semibold",
|
|
75
|
+
children: "Themes"
|
|
76
|
+
}),
|
|
77
|
+
brief.clusters.map((cluster)=>/*#__PURE__*/ _jsxs("article", {
|
|
78
|
+
className: "flex flex-col gap-2 rounded-lg border border-border p-4",
|
|
79
|
+
children: [
|
|
80
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
81
|
+
className: "flex items-start justify-between gap-3",
|
|
82
|
+
children: [
|
|
83
|
+
/*#__PURE__*/ _jsx("h4", {
|
|
84
|
+
className: "text-sm leading-snug font-semibold",
|
|
85
|
+
children: cluster.title
|
|
86
|
+
}),
|
|
87
|
+
cluster.sources === undefined || cluster.sources.length === 0 ? null : /*#__PURE__*/ _jsx("span", {
|
|
88
|
+
className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 text-xs text-muted-foreground",
|
|
89
|
+
children: cluster.sources.join(", ")
|
|
90
|
+
})
|
|
91
|
+
]
|
|
92
|
+
}),
|
|
93
|
+
cluster.summary === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
94
|
+
className: "text-xs leading-relaxed text-muted-foreground",
|
|
95
|
+
children: cluster.summary
|
|
96
|
+
}),
|
|
97
|
+
cluster.items === undefined || cluster.items.length === 0 ? null : /*#__PURE__*/ _jsx("ul", {
|
|
98
|
+
className: "flex flex-col gap-1.5",
|
|
99
|
+
children: cluster.items.map((item)=>{
|
|
100
|
+
const href = toSafeHref(item.url);
|
|
101
|
+
return /*#__PURE__*/ _jsx("li", {
|
|
102
|
+
className: "min-w-0",
|
|
103
|
+
children: href === undefined ? /*#__PURE__*/ _jsx("span", {
|
|
104
|
+
className: "text-xs leading-snug text-muted-foreground",
|
|
105
|
+
children: item.title ?? item.url
|
|
106
|
+
}) : /*#__PURE__*/ _jsx("a", {
|
|
107
|
+
href: href,
|
|
108
|
+
target: "_blank",
|
|
109
|
+
rel: "noopener noreferrer",
|
|
110
|
+
className: "text-xs leading-snug text-primary-emphasis underline-offset-2 hover:underline",
|
|
111
|
+
children: item.title ?? item.url
|
|
112
|
+
})
|
|
113
|
+
}, item.url);
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
]
|
|
117
|
+
}, cluster.id))
|
|
118
|
+
]
|
|
119
|
+
}),
|
|
120
|
+
brief.quotes === undefined || brief.quotes.length === 0 ? null : /*#__PURE__*/ _jsxs("section", {
|
|
121
|
+
className: "flex flex-col gap-3",
|
|
122
|
+
"aria-label": "Quotes",
|
|
123
|
+
children: [
|
|
124
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
125
|
+
className: "text-sm font-semibold",
|
|
126
|
+
children: "Quotes"
|
|
127
|
+
}),
|
|
128
|
+
brief.quotes.map((quote)=>{
|
|
129
|
+
const href = toSafeHref(quote.url);
|
|
130
|
+
return(// <figure>/<figcaption> rather than a div and a span: a quotation
|
|
131
|
+
// with an attribution is exactly what the element pair is for, and
|
|
132
|
+
// it is what makes the attribution reachable from the quote for a
|
|
133
|
+
// screen reader instead of being a loose line of text after it.
|
|
134
|
+
/*#__PURE__*/ _jsxs("figure", {
|
|
135
|
+
className: "border-l-2 border-border pl-4",
|
|
136
|
+
children: [
|
|
137
|
+
/*#__PURE__*/ _jsx("blockquote", {
|
|
138
|
+
className: "text-sm leading-relaxed italic",
|
|
139
|
+
children: quote.quote
|
|
140
|
+
}),
|
|
141
|
+
/*#__PURE__*/ _jsxs("figcaption", {
|
|
142
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
143
|
+
children: [
|
|
144
|
+
quote.author === undefined ? "" : `${quote.author} · `,
|
|
145
|
+
quote.source,
|
|
146
|
+
href === undefined ? null : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
147
|
+
children: [
|
|
148
|
+
" · ",
|
|
149
|
+
/*#__PURE__*/ _jsx("a", {
|
|
150
|
+
href: href,
|
|
151
|
+
target: "_blank",
|
|
152
|
+
rel: "noopener noreferrer",
|
|
153
|
+
className: "text-primary-emphasis underline-offset-2 hover:underline",
|
|
154
|
+
children: "source"
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
})
|
|
158
|
+
]
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
}, `${quote.source}-${quote.quote.slice(0, 32)}`));
|
|
162
|
+
})
|
|
163
|
+
]
|
|
164
|
+
}),
|
|
165
|
+
citations.length === 0 ? null : /*#__PURE__*/ _jsxs("section", {
|
|
166
|
+
className: "flex flex-col gap-3",
|
|
167
|
+
"aria-label": "Citations",
|
|
168
|
+
children: [
|
|
169
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
170
|
+
className: "text-sm font-semibold",
|
|
171
|
+
children: "Citations"
|
|
172
|
+
}),
|
|
173
|
+
/*#__PURE__*/ _jsx("ol", {
|
|
174
|
+
className: "flex flex-col gap-2",
|
|
175
|
+
children: citations.map((citation, index)=>{
|
|
176
|
+
const href = toSafeHref(citation.url);
|
|
177
|
+
return /*#__PURE__*/ _jsxs("li", {
|
|
178
|
+
className: "flex items-baseline gap-3 text-xs",
|
|
179
|
+
children: [
|
|
180
|
+
/*#__PURE__*/ _jsx("span", {
|
|
181
|
+
className: "w-6 shrink-0 text-right font-mono text-muted-foreground tabular-nums",
|
|
182
|
+
children: index + 1
|
|
183
|
+
}),
|
|
184
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
185
|
+
className: "min-w-0 leading-relaxed",
|
|
186
|
+
children: [
|
|
187
|
+
href === undefined ? /*#__PURE__*/ _jsx("span", {
|
|
188
|
+
className: "break-words",
|
|
189
|
+
children: citation.title ?? citation.url
|
|
190
|
+
}) : /*#__PURE__*/ _jsx("a", {
|
|
191
|
+
href: href,
|
|
192
|
+
target: "_blank",
|
|
193
|
+
rel: "noopener noreferrer",
|
|
194
|
+
className: "break-words text-primary-emphasis underline-offset-2 hover:underline",
|
|
195
|
+
children: citation.title ?? citation.url
|
|
196
|
+
}),
|
|
197
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
198
|
+
className: "text-muted-foreground",
|
|
199
|
+
children: [
|
|
200
|
+
" · ",
|
|
201
|
+
citation.source
|
|
202
|
+
]
|
|
203
|
+
})
|
|
204
|
+
]
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
}, citation.url);
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
]
|
|
211
|
+
})
|
|
212
|
+
]
|
|
213
|
+
});
|
|
214
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
215
|
+
className: cn("flex flex-col gap-6", className),
|
|
216
|
+
children: [
|
|
217
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
218
|
+
className: "flex flex-col gap-1.5",
|
|
219
|
+
children: [
|
|
220
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
221
|
+
className: "text-base leading-snug font-semibold",
|
|
222
|
+
children: brief.topic
|
|
223
|
+
}),
|
|
224
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
225
|
+
className: "font-mono text-xs text-muted-foreground tabular-nums",
|
|
226
|
+
children: [
|
|
227
|
+
brief.sourceCount.toLocaleString(),
|
|
228
|
+
" sources · ",
|
|
229
|
+
brief.itemCount.toLocaleString(),
|
|
230
|
+
" items",
|
|
231
|
+
brief.range === undefined ? "" : ` · ${brief.range}`
|
|
232
|
+
]
|
|
233
|
+
}),
|
|
234
|
+
brief.leadInsight === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
235
|
+
className: "max-w-prose text-sm leading-relaxed",
|
|
236
|
+
children: brief.leadInsight
|
|
237
|
+
})
|
|
238
|
+
]
|
|
239
|
+
}),
|
|
240
|
+
report === "" ? data : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
241
|
+
children: [
|
|
242
|
+
/*#__PURE__*/ _jsx("div", {
|
|
243
|
+
className: "max-w-prose text-sm leading-relaxed whitespace-pre-wrap",
|
|
244
|
+
children: report
|
|
245
|
+
}),
|
|
246
|
+
/*#__PURE__*/ _jsxs("details", {
|
|
247
|
+
className: "group border-t border-border pt-4",
|
|
248
|
+
children: [
|
|
249
|
+
/*#__PURE__*/ _jsxs("summary", {
|
|
250
|
+
className: "cursor-pointer text-sm font-semibold text-muted-foreground hover:text-foreground",
|
|
251
|
+
children: [
|
|
252
|
+
"Sources & data (",
|
|
253
|
+
brief.sourceCount.toLocaleString(),
|
|
254
|
+
" sources ·",
|
|
255
|
+
" ",
|
|
256
|
+
brief.itemCount.toLocaleString(),
|
|
257
|
+
" items)"
|
|
258
|
+
]
|
|
259
|
+
}),
|
|
260
|
+
/*#__PURE__*/ _jsx("div", {
|
|
261
|
+
className: "mt-5",
|
|
262
|
+
children: data
|
|
263
|
+
})
|
|
264
|
+
]
|
|
265
|
+
})
|
|
266
|
+
]
|
|
267
|
+
})
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type ButtonProps } from "./button.js";
|
|
3
|
+
export type RichEmptyStateAction = {
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly onClick?: () => void;
|
|
6
|
+
/** Renders the action as a link instead of a button. */
|
|
7
|
+
readonly href?: string;
|
|
8
|
+
/** At most one `primary` per empty state — it is the one suggested way out. */
|
|
9
|
+
readonly variant?: "primary" | "secondary";
|
|
10
|
+
};
|
|
11
|
+
export type RichEmptyStateProps = {
|
|
12
|
+
readonly icon?: ReactNode;
|
|
13
|
+
/** What is not here, as a statement: "No skills installed yet". */
|
|
14
|
+
readonly title: string;
|
|
15
|
+
/** Why, and what this surface will look like once it is populated. */
|
|
16
|
+
readonly description: ReactNode;
|
|
17
|
+
/** Suggested next steps, rendered as buttons or links. */
|
|
18
|
+
readonly actions?: readonly RichEmptyStateAction[];
|
|
19
|
+
/** Extra controls (e.g. a menu) below the primary actions. */
|
|
20
|
+
readonly footer?: ReactNode;
|
|
21
|
+
/** Action button size. Defaults to `md`; `sm` matches a denser host chrome (e.g. a top bar). */
|
|
22
|
+
readonly actionSize?: ButtonProps["size"];
|
|
23
|
+
readonly className?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The onboarding-grade empty state: icon, message, and suggested actions on a
|
|
27
|
+
* bordered surface of its own.
|
|
28
|
+
*
|
|
29
|
+
* It is not `EmptyState` — that is the quiet in-list "nothing matched" stack.
|
|
30
|
+
* This one is for a surface that is empty because the user has not started
|
|
31
|
+
* yet, where the empty state *is* the page and has to carry the way in. The
|
|
32
|
+
* icon stays decorative and hidden from assistive tech; the title carries the
|
|
33
|
+
* meaning.
|
|
34
|
+
*/
|
|
35
|
+
export declare function RichEmptyState({ icon, title, description, actions, footer, actionSize, className, }: RichEmptyStateProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
function ActionControl({ action, size }) {
|
|
5
|
+
const variant = action.variant === "primary" ? "primary" : "secondary";
|
|
6
|
+
if (action.href !== undefined) {
|
|
7
|
+
return /*#__PURE__*/ _jsx(Button, {
|
|
8
|
+
asChild: true,
|
|
9
|
+
variant: variant,
|
|
10
|
+
size: size,
|
|
11
|
+
children: /*#__PURE__*/ _jsx("a", {
|
|
12
|
+
href: action.href,
|
|
13
|
+
children: action.label
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return /*#__PURE__*/ _jsx(Button, {
|
|
18
|
+
type: "button",
|
|
19
|
+
variant: variant,
|
|
20
|
+
size: size,
|
|
21
|
+
onClick: action.onClick,
|
|
22
|
+
children: action.label
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The onboarding-grade empty state: icon, message, and suggested actions on a
|
|
27
|
+
* bordered surface of its own.
|
|
28
|
+
*
|
|
29
|
+
* It is not `EmptyState` — that is the quiet in-list "nothing matched" stack.
|
|
30
|
+
* This one is for a surface that is empty because the user has not started
|
|
31
|
+
* yet, where the empty state *is* the page and has to carry the way in. The
|
|
32
|
+
* icon stays decorative and hidden from assistive tech; the title carries the
|
|
33
|
+
* meaning.
|
|
34
|
+
*/ export function RichEmptyState({ icon, title, description, actions, footer, actionSize = "md", className }) {
|
|
35
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
36
|
+
"data-slot": "rich-empty-state",
|
|
37
|
+
className: cn("flex flex-col items-center justify-center rounded-[12px] border border-border bg-card px-6 py-10 text-center", className),
|
|
38
|
+
children: [
|
|
39
|
+
icon === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
40
|
+
"aria-hidden": true,
|
|
41
|
+
className: "mb-4 grid size-12 place-items-center rounded-[12px] bg-muted text-muted-foreground",
|
|
42
|
+
children: icon
|
|
43
|
+
}),
|
|
44
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
45
|
+
className: "text-[15px] font-bold tracking-[-0.01em]",
|
|
46
|
+
children: title
|
|
47
|
+
}),
|
|
48
|
+
/*#__PURE__*/ _jsx("p", {
|
|
49
|
+
className: "mt-2 max-w-md text-[13px] leading-relaxed text-muted-foreground",
|
|
50
|
+
children: description
|
|
51
|
+
}),
|
|
52
|
+
actions !== undefined && actions.length > 0 ? /*#__PURE__*/ _jsx("div", {
|
|
53
|
+
className: "mt-5 flex flex-wrap items-center justify-center gap-2",
|
|
54
|
+
children: actions.map((action)=>/*#__PURE__*/ _jsx(ActionControl, {
|
|
55
|
+
action: action,
|
|
56
|
+
size: actionSize
|
|
57
|
+
}, action.label))
|
|
58
|
+
}) : null,
|
|
59
|
+
footer === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
60
|
+
className: "mt-4",
|
|
61
|
+
children: footer
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ButtonProps } from "./button.js";
|
|
2
|
+
export type RunNowButtonProps = Omit<ButtonProps, "onClick" | "children"> & {
|
|
3
|
+
/** Resolve when the work is accepted, reject to show nothing changed. */
|
|
4
|
+
readonly onRun: () => void | Promise<void>;
|
|
5
|
+
readonly label?: string;
|
|
6
|
+
readonly pendingLabel?: string;
|
|
7
|
+
readonly doneLabel?: string;
|
|
8
|
+
/** How long the confirmation stays up, in ms. */
|
|
9
|
+
readonly confirmMs?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Fire something off now — send the brief, run the schedule ahead of time.
|
|
13
|
+
*
|
|
14
|
+
* The three states exist because "did that work?" is the only question the user
|
|
15
|
+
* has after pressing it, and a button that snaps back to its resting label
|
|
16
|
+
* answers it with nothing. The confirmation is announced through `aria-live`,
|
|
17
|
+
* not just shown, and the label carries it — a separate toast for a
|
|
18
|
+
* button-sized action is more machinery than the action deserves.
|
|
19
|
+
*
|
|
20
|
+
* A rejected promise returns the button to rest rather than claiming success.
|
|
21
|
+
* The caller surfaces the reason; this only refuses to lie about it.
|
|
22
|
+
*/
|
|
23
|
+
export declare function RunNowButton({ onRun, label, pendingLabel, doneLabel, confirmMs, disabled, ...props }: RunNowButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
/**
|
|
5
|
+
* Fire something off now — send the brief, run the schedule ahead of time.
|
|
6
|
+
*
|
|
7
|
+
* The three states exist because "did that work?" is the only question the user
|
|
8
|
+
* has after pressing it, and a button that snaps back to its resting label
|
|
9
|
+
* answers it with nothing. The confirmation is announced through `aria-live`,
|
|
10
|
+
* not just shown, and the label carries it — a separate toast for a
|
|
11
|
+
* button-sized action is more machinery than the action deserves.
|
|
12
|
+
*
|
|
13
|
+
* A rejected promise returns the button to rest rather than claiming success.
|
|
14
|
+
* The caller surfaces the reason; this only refuses to lie about it.
|
|
15
|
+
*/ export function RunNowButton({ onRun, label = "Run now", pendingLabel = "Starting…", doneLabel = "Started", confirmMs = 2000, disabled, ...props }) {
|
|
16
|
+
const [state, setState] = useState("idle");
|
|
17
|
+
const timerRef = useRef(undefined);
|
|
18
|
+
// Clear on unmount: a timer that fires into an unmounted component is a
|
|
19
|
+
// React warning today and a leak in every version.
|
|
20
|
+
useEffect(()=>()=>window.clearTimeout(timerRef.current), []);
|
|
21
|
+
const run = async ()=>{
|
|
22
|
+
setState("pending");
|
|
23
|
+
try {
|
|
24
|
+
await onRun();
|
|
25
|
+
setState("done");
|
|
26
|
+
timerRef.current = window.setTimeout(()=>setState("idle"), confirmMs);
|
|
27
|
+
} catch {
|
|
28
|
+
setState("idle");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return /*#__PURE__*/ _jsx(Button, {
|
|
32
|
+
...props,
|
|
33
|
+
disabled: disabled || state !== "idle",
|
|
34
|
+
onClick: ()=>{
|
|
35
|
+
void run();
|
|
36
|
+
},
|
|
37
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
38
|
+
"aria-live": "polite",
|
|
39
|
+
children: state === "pending" ? pendingLabel : state === "done" ? doneLabel : label
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type SectionProps = {
|
|
3
|
+
readonly title: ReactNode;
|
|
4
|
+
readonly description?: ReactNode;
|
|
5
|
+
/** Controls for this section: a filter, a view toggle, a primary action. */
|
|
6
|
+
readonly action?: ReactNode;
|
|
7
|
+
/** Item count shown beside the title. */
|
|
8
|
+
readonly count?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Heading level. Defaults to `h2`. Set `h1` for the page's own header — this
|
|
11
|
+
* is deliberately one component and not a separate `PageHeader`, because the
|
|
12
|
+
* only real difference between the two is the document outline, and a second
|
|
13
|
+
* near-identical file is how two headers drift apart.
|
|
14
|
+
*/
|
|
15
|
+
readonly as?: "h1" | "h2" | "h3";
|
|
16
|
+
readonly children: ReactNode;
|
|
17
|
+
readonly className?: string;
|
|
18
|
+
};
|
|
19
|
+
/** A titled block: heading, optional description and controls, then content. */
|
|
20
|
+
export declare function Section({ title, description, action, count, as, children, className }: SectionProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
const TITLE_CLASS = {
|
|
4
|
+
h1: "text-xl font-semibold tracking-tight",
|
|
5
|
+
h2: "text-sm font-semibold",
|
|
6
|
+
h3: "text-xs font-semibold"
|
|
7
|
+
};
|
|
8
|
+
/** A titled block: heading, optional description and controls, then content. */ export function Section({ title, description, action, count, as = "h2", children, className }) {
|
|
9
|
+
const Heading = as;
|
|
10
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
11
|
+
"data-slot": "section",
|
|
12
|
+
className: cn("flex flex-col gap-3", className),
|
|
13
|
+
children: [
|
|
14
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
15
|
+
className: "flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between sm:gap-4",
|
|
16
|
+
children: [
|
|
17
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
18
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
19
|
+
children: [
|
|
20
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
21
|
+
className: "flex items-center gap-2",
|
|
22
|
+
children: [
|
|
23
|
+
/*#__PURE__*/ _jsx(Heading, {
|
|
24
|
+
className: TITLE_CLASS[as],
|
|
25
|
+
children: title
|
|
26
|
+
}),
|
|
27
|
+
count === undefined ? null : /*#__PURE__*/ _jsx("span", {
|
|
28
|
+
className: "shrink-0 rounded-sm bg-muted px-1.5 py-0.5 font-mono text-xs text-muted-foreground",
|
|
29
|
+
children: count
|
|
30
|
+
})
|
|
31
|
+
]
|
|
32
|
+
}),
|
|
33
|
+
description === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
34
|
+
className: "text-sm leading-snug text-muted-foreground",
|
|
35
|
+
children: description
|
|
36
|
+
})
|
|
37
|
+
]
|
|
38
|
+
}),
|
|
39
|
+
action === undefined ? null : /*#__PURE__*/ _jsx("div", {
|
|
40
|
+
className: "flex shrink-0 items-center gap-2",
|
|
41
|
+
children: action
|
|
42
|
+
})
|
|
43
|
+
]
|
|
44
|
+
}),
|
|
45
|
+
children
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
// Same border/focus-ring/disabled treatment as `Input`; the chevron is a
|
|
5
|
+
// decorative overlay since a native `<select>` cannot be given its own icon.
|
|
6
|
+
export function Select({ className, children, ...props }) {
|
|
7
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
8
|
+
className: "group relative",
|
|
9
|
+
children: [
|
|
10
|
+
/*#__PURE__*/ _jsx("select", {
|
|
11
|
+
"data-slot": "select",
|
|
12
|
+
className: cn("flex h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-card px-3 py-1 pr-8 text-sm shadow-xs transition-colors", "selection:bg-primary selection:text-primary-foreground", "aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
13
|
+
...props,
|
|
14
|
+
children: children
|
|
15
|
+
}),
|
|
16
|
+
/*#__PURE__*/ _jsx(ChevronDown, {
|
|
17
|
+
"aria-hidden": true,
|
|
18
|
+
className: "pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground group-has-[:disabled]:opacity-50"
|
|
19
|
+
})
|
|
20
|
+
]
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type SelectionCheckboxState = boolean | "indeterminate";
|
|
2
|
+
export type SelectionCheckboxProps = {
|
|
3
|
+
/**
|
|
4
|
+
* `"indeterminate"` is for a header checkbox driven by `selectAll`: some
|
|
5
|
+
* but not all rows are selected. A per-row cell only ever passes a plain
|
|
6
|
+
* boolean.
|
|
7
|
+
*/
|
|
8
|
+
readonly checked: SelectionCheckboxState;
|
|
9
|
+
/**
|
|
10
|
+
* Fires on both a plain click and a cmd/ctrl-click — `useListSelection`'s
|
|
11
|
+
* `toggle` takes the same `shiftKey` modifier this passes through, so a
|
|
12
|
+
* row wires this straight to it without branching on the click type
|
|
13
|
+
* itself.
|
|
14
|
+
*/
|
|
15
|
+
readonly onToggle: (event: {
|
|
16
|
+
readonly shiftKey: boolean;
|
|
17
|
+
}) => void;
|
|
18
|
+
/** Row label for the default accessible name, e.g. `Select "Q3 rollup"`. */
|
|
19
|
+
readonly rowLabel: string;
|
|
20
|
+
/**
|
|
21
|
+
* Overrides the default `Select ${rowLabel}` accessible name outright —
|
|
22
|
+
* for a consumer localizing this published library's one hardcoded
|
|
23
|
+
* English string, or phrasing the row label differently than "Select ".
|
|
24
|
+
*/
|
|
25
|
+
readonly ariaLabel?: string;
|
|
26
|
+
readonly id?: string;
|
|
27
|
+
readonly className?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A row's selection control: hidden until the row is hovered or focused, and
|
|
31
|
+
* always visible on a touch device — there is no hover to reveal it on, so
|
|
32
|
+
* `(hover: none)` forces it on. The consumer's row needs a `group` class
|
|
33
|
+
* ancestor (e.g. `<TableRow className="group">`) for the hover reveal to
|
|
34
|
+
* find; nothing here declares that ancestor itself since the row, not this
|
|
35
|
+
* cell, owns that boundary.
|
|
36
|
+
*
|
|
37
|
+
* Built as a `role="checkbox"` button rather than `Checkbox`'s native input:
|
|
38
|
+
* a native input's own focus ring and box model fight the opacity-driven
|
|
39
|
+
* reveal below, where the control needs to participate in hover/focus state
|
|
40
|
+
* that belongs to an ancestor it does not render.
|
|
41
|
+
*/
|
|
42
|
+
export declare function SelectionCheckbox({ checked, onToggle, rowLabel, ariaLabel, id, className }: SelectionCheckboxProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check, Minus } from "lucide-react";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* A row's selection control: hidden until the row is hovered or focused, and
|
|
7
|
+
* always visible on a touch device — there is no hover to reveal it on, so
|
|
8
|
+
* `(hover: none)` forces it on. The consumer's row needs a `group` class
|
|
9
|
+
* ancestor (e.g. `<TableRow className="group">`) for the hover reveal to
|
|
10
|
+
* find; nothing here declares that ancestor itself since the row, not this
|
|
11
|
+
* cell, owns that boundary.
|
|
12
|
+
*
|
|
13
|
+
* Built as a `role="checkbox"` button rather than `Checkbox`'s native input:
|
|
14
|
+
* a native input's own focus ring and box model fight the opacity-driven
|
|
15
|
+
* reveal below, where the control needs to participate in hover/focus state
|
|
16
|
+
* that belongs to an ancestor it does not render.
|
|
17
|
+
*/ export function SelectionCheckbox({ checked, onToggle, rowLabel, ariaLabel, id, className }) {
|
|
18
|
+
const generatedId = useId();
|
|
19
|
+
const checkboxId = id ?? generatedId;
|
|
20
|
+
const isOn = checked !== false;
|
|
21
|
+
const isIndeterminate = checked === "indeterminate";
|
|
22
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
23
|
+
type: "button",
|
|
24
|
+
id: checkboxId,
|
|
25
|
+
role: "checkbox",
|
|
26
|
+
"aria-checked": isIndeterminate ? "mixed" : checked,
|
|
27
|
+
"aria-label": ariaLabel ?? `Select ${rowLabel}`,
|
|
28
|
+
onClick: (event)=>onToggle({
|
|
29
|
+
shiftKey: event.shiftKey
|
|
30
|
+
}),
|
|
31
|
+
onKeyDown: (event)=>{
|
|
32
|
+
if (event.key !== " " && event.key !== "Enter") return;
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
onToggle({
|
|
35
|
+
shiftKey: event.shiftKey
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
className: cn("relative flex size-4 shrink-0 items-center justify-center rounded-sm border border-input bg-card shadow-xs transition-colors", "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 [@media(hover:none)]:opacity-100", isOn && "border-primary bg-primary opacity-100", // The pop plays only on landing fully checked — an indeterminate
|
|
39
|
+
// header checkbox flips between indeterminate and checked/unchecked
|
|
40
|
+
// as rows come and go, and it should not re-pop on every one of
|
|
41
|
+
// those incidental transitions.
|
|
42
|
+
checked === true && "motion-safe:[animation:corbits-selection-pop_220ms_var(--ease-out)_both]", className),
|
|
43
|
+
children: [
|
|
44
|
+
/*#__PURE__*/ _jsx(Check, {
|
|
45
|
+
"aria-hidden": true,
|
|
46
|
+
className: cn("size-3 text-primary-foreground transition-transform", checked === true ? "scale-100" : "scale-0")
|
|
47
|
+
}),
|
|
48
|
+
/*#__PURE__*/ _jsx(Minus, {
|
|
49
|
+
"aria-hidden": true,
|
|
50
|
+
className: cn("absolute size-3 text-primary-foreground transition-transform", isIndeterminate ? "scale-100" : "scale-0")
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
}
|