@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,44 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type TabDescriptor<Id extends string> = {
|
|
3
|
+
readonly id: Id;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
/** Shown beside the label — a count of what is inside. */
|
|
6
|
+
readonly count?: number;
|
|
7
|
+
};
|
|
8
|
+
export type TabsProps<Id extends string> = {
|
|
9
|
+
readonly tabs: readonly TabDescriptor<Id>[];
|
|
10
|
+
readonly active: Id;
|
|
11
|
+
readonly onChange: (id: Id) => void;
|
|
12
|
+
/** Names the set for assistive tech: "Insights sections". */
|
|
13
|
+
readonly label: string;
|
|
14
|
+
/** Renders the active panel. Called once — only the open panel is mounted. */
|
|
15
|
+
readonly children: (active: Id) => ReactNode;
|
|
16
|
+
/** `underline` for page-level navigation, `enclosed` for tabs inside a card. */
|
|
17
|
+
readonly variant?: "underline" | "enclosed";
|
|
18
|
+
readonly className?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Tabs that behave the way the tab pattern promises.
|
|
22
|
+
*
|
|
23
|
+
* The parts people leave out are the parts that matter. A roving `tabIndex`
|
|
24
|
+
* puts *one* stop in the tab list, so Tab moves past the whole set into the
|
|
25
|
+
* panel instead of stepping through every tab — and that only works if arrow
|
|
26
|
+
* keys move between them, which is why `onKeyDown` here is not optional garnish.
|
|
27
|
+
* Home and End jump to the ends. `aria-controls` and `aria-labelledby` tie each
|
|
28
|
+
* tab to its panel in both directions, which is what makes "go to the panel this
|
|
29
|
+
* tab controls" a thing a screen reader can offer.
|
|
30
|
+
*
|
|
31
|
+
* Selection is controlled and only the active panel is mounted. Tab state is
|
|
32
|
+
* almost always also URL state — someone will want to link to a tab — and
|
|
33
|
+
* owning it internally means fighting the component to do that. Mounting one
|
|
34
|
+
* panel keeps hidden panels from fetching, animating, or holding focus.
|
|
35
|
+
*
|
|
36
|
+
* Focus follows the arrow keys, and only the arrow keys. The APG requires it:
|
|
37
|
+
* with a roving `tabIndex` the tab you arrowed away from becomes
|
|
38
|
+
* `tabIndex={-1}` and `aria-selected={false}`, so leaving focus there strands
|
|
39
|
+
* it on a tab that is no longer either the selection or a tab stop, and the
|
|
40
|
+
* next Tab press escapes from the wrong place. Selection by click does *not*
|
|
41
|
+
* move focus — the clicked tab already has it — and focus is never pushed into
|
|
42
|
+
* the panel, which is the "improvement" that stops people arrowing on.
|
|
43
|
+
*/
|
|
44
|
+
export declare function Tabs<Id extends string>({ tabs, active, onChange, label, children, variant, className, }: TabsProps<Id>): import("react").JSX.Element;
|
package/dist/ui/tabs.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useRef } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tabs that behave the way the tab pattern promises.
|
|
6
|
+
*
|
|
7
|
+
* The parts people leave out are the parts that matter. A roving `tabIndex`
|
|
8
|
+
* puts *one* stop in the tab list, so Tab moves past the whole set into the
|
|
9
|
+
* panel instead of stepping through every tab — and that only works if arrow
|
|
10
|
+
* keys move between them, which is why `onKeyDown` here is not optional garnish.
|
|
11
|
+
* Home and End jump to the ends. `aria-controls` and `aria-labelledby` tie each
|
|
12
|
+
* tab to its panel in both directions, which is what makes "go to the panel this
|
|
13
|
+
* tab controls" a thing a screen reader can offer.
|
|
14
|
+
*
|
|
15
|
+
* Selection is controlled and only the active panel is mounted. Tab state is
|
|
16
|
+
* almost always also URL state — someone will want to link to a tab — and
|
|
17
|
+
* owning it internally means fighting the component to do that. Mounting one
|
|
18
|
+
* panel keeps hidden panels from fetching, animating, or holding focus.
|
|
19
|
+
*
|
|
20
|
+
* Focus follows the arrow keys, and only the arrow keys. The APG requires it:
|
|
21
|
+
* with a roving `tabIndex` the tab you arrowed away from becomes
|
|
22
|
+
* `tabIndex={-1}` and `aria-selected={false}`, so leaving focus there strands
|
|
23
|
+
* it on a tab that is no longer either the selection or a tab stop, and the
|
|
24
|
+
* next Tab press escapes from the wrong place. Selection by click does *not*
|
|
25
|
+
* move focus — the clicked tab already has it — and focus is never pushed into
|
|
26
|
+
* the panel, which is the "improvement" that stops people arrowing on.
|
|
27
|
+
*/ export function Tabs({ tabs, active, onChange, label, children, variant = "underline", className }) {
|
|
28
|
+
const base = useId();
|
|
29
|
+
const tabId = (id)=>`${base}-tab-${id}`;
|
|
30
|
+
const panelId = (id)=>`${base}-panel-${id}`;
|
|
31
|
+
// Every tab button is rendered whatever the selection, so the node to focus
|
|
32
|
+
// already exists at the moment the key is handled — no effect, no timer.
|
|
33
|
+
const buttons = useRef(new Map());
|
|
34
|
+
/** Arrow/Home/End selection: change the tab *and* take focus with it. */ function focusTab(id) {
|
|
35
|
+
onChange(id);
|
|
36
|
+
buttons.current.get(id)?.focus();
|
|
37
|
+
}
|
|
38
|
+
function move(delta) {
|
|
39
|
+
const index = tabs.findIndex((tab)=>tab.id === active);
|
|
40
|
+
const next = tabs[(index + delta + tabs.length) % tabs.length];
|
|
41
|
+
if (next !== undefined) focusTab(next.id);
|
|
42
|
+
}
|
|
43
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
44
|
+
className: cn("flex flex-col gap-4", className),
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ _jsx("div", {
|
|
47
|
+
role: "tablist",
|
|
48
|
+
"aria-label": label,
|
|
49
|
+
onKeyDown: (event)=>{
|
|
50
|
+
if (event.key === "ArrowRight") {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
move(1);
|
|
53
|
+
} else if (event.key === "ArrowLeft") {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
move(-1);
|
|
56
|
+
} else if (event.key === "Home") {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
if (tabs[0] !== undefined) focusTab(tabs[0].id);
|
|
59
|
+
} else if (event.key === "End") {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
const last = tabs[tabs.length - 1];
|
|
62
|
+
if (last !== undefined) focusTab(last.id);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
className: cn("flex flex-wrap items-center", variant === "underline" ? "gap-1 border-b border-border" : "gap-1 rounded-md border border-border p-1"),
|
|
66
|
+
children: tabs.map((tab)=>{
|
|
67
|
+
const selected = tab.id === active;
|
|
68
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
69
|
+
ref: (node)=>{
|
|
70
|
+
if (node === null) buttons.current.delete(tab.id);
|
|
71
|
+
else buttons.current.set(tab.id, node);
|
|
72
|
+
},
|
|
73
|
+
type: "button",
|
|
74
|
+
role: "tab",
|
|
75
|
+
id: tabId(tab.id),
|
|
76
|
+
"aria-selected": selected,
|
|
77
|
+
"aria-controls": panelId(tab.id),
|
|
78
|
+
tabIndex: selected ? 0 : -1,
|
|
79
|
+
onClick: ()=>onChange(tab.id),
|
|
80
|
+
className: cn("inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium transition-colors", variant === "underline" ? // inactive, so selecting a tab does not shift the row.
|
|
81
|
+
selected ? "-mb-px border-b-2 border-primary-emphasis text-foreground" : "-mb-px border-b-2 border-transparent text-muted-foreground hover:text-foreground" : selected ? "rounded-sm bg-muted text-foreground" : "rounded-sm text-muted-foreground hover:text-foreground"),
|
|
82
|
+
children: [
|
|
83
|
+
tab.label,
|
|
84
|
+
tab.count === undefined ? null : /*#__PURE__*/ _jsx("span", {
|
|
85
|
+
className: "rounded-sm bg-muted px-1.5 font-mono text-xs text-muted-foreground",
|
|
86
|
+
children: tab.count
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
}, tab.id);
|
|
90
|
+
})
|
|
91
|
+
}),
|
|
92
|
+
/*#__PURE__*/ _jsx("div", {
|
|
93
|
+
role: "tabpanel",
|
|
94
|
+
id: panelId(active),
|
|
95
|
+
"aria-labelledby": tabId(active),
|
|
96
|
+
tabIndex: 0,
|
|
97
|
+
children: children(active)
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
export type TextareaProps = React.ComponentProps<"textarea"> & {
|
|
3
|
+
/** Grows the textarea to fit its content instead of scrolling internally. */
|
|
4
|
+
readonly autoResize?: boolean;
|
|
5
|
+
/** Pixel height autoResize grows to before it caps and scrolls. */
|
|
6
|
+
readonly maxHeight?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function Textarea({ className, autoResize, maxHeight, onChange, ...props }: TextareaProps): React.JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
// Matches ChatInput's MAX_ROWS_PX — the same cap, so a textarea grown by
|
|
5
|
+
// autoResize behaves identically wherever it's used.
|
|
6
|
+
const DEFAULT_MAX_HEIGHT_PX = 200;
|
|
7
|
+
export function Textarea({ className, autoResize, maxHeight = DEFAULT_MAX_HEIGHT_PX, onChange, ...props }) {
|
|
8
|
+
const ref = useRef(null);
|
|
9
|
+
const resize = (element)=>{
|
|
10
|
+
element.style.height = "auto";
|
|
11
|
+
element.style.height = `${Math.min(element.scrollHeight, maxHeight)}px`;
|
|
12
|
+
};
|
|
13
|
+
useEffect(()=>{
|
|
14
|
+
if (autoResize && ref.current) resize(ref.current);
|
|
15
|
+
}, [
|
|
16
|
+
autoResize,
|
|
17
|
+
maxHeight
|
|
18
|
+
]);
|
|
19
|
+
return /*#__PURE__*/ _jsx("textarea", {
|
|
20
|
+
ref: ref,
|
|
21
|
+
"data-slot": "textarea",
|
|
22
|
+
style: autoResize ? {
|
|
23
|
+
maxHeight
|
|
24
|
+
} : undefined,
|
|
25
|
+
className: cn("flex min-h-16 w-full min-w-0 rounded-md border border-input bg-card px-3 py-2 text-sm shadow-xs transition-colors", autoResize && "resize-none overflow-y-auto", "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground", "aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
26
|
+
onChange: (event)=>{
|
|
27
|
+
if (autoResize) resize(event.currentTarget);
|
|
28
|
+
onChange?.(event);
|
|
29
|
+
},
|
|
30
|
+
...props
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type ResolvedThemeMode, type ThemeMode, type ThemePreset } from "../lib/theme.js";
|
|
3
|
+
export type ThemeContextValue = {
|
|
4
|
+
readonly mode: ThemeMode;
|
|
5
|
+
readonly setMode: (mode: ThemeMode) => void;
|
|
6
|
+
readonly cycleMode: () => void;
|
|
7
|
+
readonly resolvedMode: ResolvedThemeMode;
|
|
8
|
+
readonly preset: ThemePreset;
|
|
9
|
+
readonly setPreset: (preset: ThemePreset) => void;
|
|
10
|
+
};
|
|
11
|
+
export type ThemeProviderProps = {
|
|
12
|
+
readonly children: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* localStorage key. Hosts that persist per user should pass a key that
|
|
15
|
+
* includes the user id (e.g. `corbits-theme:user_1`).
|
|
16
|
+
*/
|
|
17
|
+
readonly storageKey?: string;
|
|
18
|
+
readonly defaultMode?: ThemeMode;
|
|
19
|
+
readonly defaultPreset?: ThemePreset;
|
|
20
|
+
/**
|
|
21
|
+
* Element that receives `.dark`/`.light`, `data-theme`, and `color-scheme`.
|
|
22
|
+
* Defaults to `document.documentElement`. A scoped root still inherits the
|
|
23
|
+
* OS-following media query, which only inspects `:root` — explicit light on
|
|
24
|
+
* a dark-OS host requires `.light` on `documentElement` regardless.
|
|
25
|
+
*/
|
|
26
|
+
readonly root?: Element | null;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Owns light/dark/system mode and named preset overlays for the Corbits theme.
|
|
30
|
+
*
|
|
31
|
+
* The host mounts this once near the root and keys `storageKey` by user when
|
|
32
|
+
* signed in. Mode is applied as `.dark` on the root (see theme.css); presets
|
|
33
|
+
* as `data-theme`. Components never import storage — they call `useTheme`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function ThemeProvider({ children, storageKey, defaultMode, defaultPreset, root, }: ThemeProviderProps): import("react").JSX.Element;
|
|
36
|
+
export declare function useTheme(): ThemeContextValue;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { DEFAULT_THEME_STORAGE_KEY, applyThemeToRoot, nextThemeMode, parseThemePreference, resolveThemeMode, serializeThemePreference } from "../lib/theme.js";
|
|
4
|
+
const ThemeContext = /*#__PURE__*/ createContext(null);
|
|
5
|
+
function readSystemPrefersDark() {
|
|
6
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
10
|
+
}
|
|
11
|
+
function readStoredPreference(storageKey, fallback) {
|
|
12
|
+
if (typeof window === "undefined") return fallback;
|
|
13
|
+
try {
|
|
14
|
+
return parseThemePreference(window.localStorage.getItem(storageKey), fallback);
|
|
15
|
+
} catch {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function writeStoredPreference(storageKey, preference) {
|
|
20
|
+
if (typeof window === "undefined") return;
|
|
21
|
+
try {
|
|
22
|
+
window.localStorage.setItem(storageKey, serializeThemePreference(preference));
|
|
23
|
+
} catch {
|
|
24
|
+
// Quota / private mode — preference still applies for this session.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Owns light/dark/system mode and named preset overlays for the Corbits theme.
|
|
29
|
+
*
|
|
30
|
+
* The host mounts this once near the root and keys `storageKey` by user when
|
|
31
|
+
* signed in. Mode is applied as `.dark` on the root (see theme.css); presets
|
|
32
|
+
* as `data-theme`. Components never import storage — they call `useTheme`.
|
|
33
|
+
*/ export function ThemeProvider({ children, storageKey = DEFAULT_THEME_STORAGE_KEY, defaultMode = "system", defaultPreset = "default", root = null }) {
|
|
34
|
+
const fallback = useMemo(()=>({
|
|
35
|
+
mode: defaultMode,
|
|
36
|
+
preset: defaultPreset
|
|
37
|
+
}), [
|
|
38
|
+
defaultMode,
|
|
39
|
+
defaultPreset
|
|
40
|
+
]);
|
|
41
|
+
const [preference, setPreference] = useState(()=>readStoredPreference(storageKey, fallback));
|
|
42
|
+
const [systemPrefersDark, setSystemPrefersDark] = useState(readSystemPrefersDark);
|
|
43
|
+
// Re-read when the host changes the storage key (e.g. user signs in).
|
|
44
|
+
useEffect(()=>{
|
|
45
|
+
setPreference(readStoredPreference(storageKey, fallback));
|
|
46
|
+
}, [
|
|
47
|
+
storageKey,
|
|
48
|
+
fallback
|
|
49
|
+
]);
|
|
50
|
+
useEffect(()=>{
|
|
51
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
55
|
+
const onChange = ()=>setSystemPrefersDark(media.matches);
|
|
56
|
+
onChange();
|
|
57
|
+
media.addEventListener("change", onChange);
|
|
58
|
+
return ()=>media.removeEventListener("change", onChange);
|
|
59
|
+
}, []);
|
|
60
|
+
const resolvedMode = resolveThemeMode(preference.mode, systemPrefersDark);
|
|
61
|
+
useEffect(()=>{
|
|
62
|
+
const target = root ?? (typeof document !== "undefined" ? document.documentElement : null);
|
|
63
|
+
if (target === null) return;
|
|
64
|
+
applyThemeToRoot(target, resolvedMode, preference.preset);
|
|
65
|
+
}, [
|
|
66
|
+
root,
|
|
67
|
+
resolvedMode,
|
|
68
|
+
preference.preset
|
|
69
|
+
]);
|
|
70
|
+
const setMode = useCallback((mode)=>{
|
|
71
|
+
setPreference((current)=>{
|
|
72
|
+
const next = {
|
|
73
|
+
mode,
|
|
74
|
+
preset: current.preset
|
|
75
|
+
};
|
|
76
|
+
writeStoredPreference(storageKey, next);
|
|
77
|
+
return next;
|
|
78
|
+
});
|
|
79
|
+
}, [
|
|
80
|
+
storageKey
|
|
81
|
+
]);
|
|
82
|
+
const setPreset = useCallback((preset)=>{
|
|
83
|
+
setPreference((current)=>{
|
|
84
|
+
const next = {
|
|
85
|
+
mode: current.mode,
|
|
86
|
+
preset
|
|
87
|
+
};
|
|
88
|
+
writeStoredPreference(storageKey, next);
|
|
89
|
+
return next;
|
|
90
|
+
});
|
|
91
|
+
}, [
|
|
92
|
+
storageKey
|
|
93
|
+
]);
|
|
94
|
+
const cycleMode = useCallback(()=>{
|
|
95
|
+
setMode(nextThemeMode(preference.mode));
|
|
96
|
+
}, [
|
|
97
|
+
preference.mode,
|
|
98
|
+
setMode
|
|
99
|
+
]);
|
|
100
|
+
const value = useMemo(()=>({
|
|
101
|
+
mode: preference.mode,
|
|
102
|
+
setMode,
|
|
103
|
+
cycleMode,
|
|
104
|
+
resolvedMode,
|
|
105
|
+
preset: preference.preset,
|
|
106
|
+
setPreset
|
|
107
|
+
}), [
|
|
108
|
+
preference.mode,
|
|
109
|
+
preference.preset,
|
|
110
|
+
setMode,
|
|
111
|
+
cycleMode,
|
|
112
|
+
resolvedMode,
|
|
113
|
+
setPreset
|
|
114
|
+
]);
|
|
115
|
+
return /*#__PURE__*/ _jsx(ThemeContext.Provider, {
|
|
116
|
+
value: value,
|
|
117
|
+
children: children
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
export function useTheme() {
|
|
121
|
+
const value = useContext(ThemeContext);
|
|
122
|
+
if (value === null) {
|
|
123
|
+
throw new Error("No ThemeProvider in scope — wrap the tree in <ThemeProvider>.");
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ButtonProps } from "./button.js";
|
|
2
|
+
export type ThemeToggleProps = Omit<ButtonProps, "onClick" | "children" | "aria-label" | "title"> & {
|
|
3
|
+
/** Override the accessible name; defaults to the current mode label. */
|
|
4
|
+
readonly label?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Cycles system → light → dark. Intended for the host identity dock (or any
|
|
8
|
+
* chrome that needs a one-control theme switch). Uses `useTheme`, so it must
|
|
9
|
+
* sit under `ThemeProvider`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ThemeToggle({ label, variant, size, ...props }: ThemeToggleProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Monitor, Moon, Sun } from "lucide-react";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
import { useTheme } from "./theme-provider.js";
|
|
5
|
+
function iconFor(mode) {
|
|
6
|
+
if (mode === "light") return /*#__PURE__*/ _jsx(Sun, {});
|
|
7
|
+
if (mode === "dark") return /*#__PURE__*/ _jsx(Moon, {});
|
|
8
|
+
return /*#__PURE__*/ _jsx(Monitor, {});
|
|
9
|
+
}
|
|
10
|
+
function labelFor(mode) {
|
|
11
|
+
if (mode === "light") return "Light theme";
|
|
12
|
+
if (mode === "dark") return "Dark theme";
|
|
13
|
+
return "System theme";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Cycles system → light → dark. Intended for the host identity dock (or any
|
|
17
|
+
* chrome that needs a one-control theme switch). Uses `useTheme`, so it must
|
|
18
|
+
* sit under `ThemeProvider`.
|
|
19
|
+
*/ export function ThemeToggle({ label, variant = "ghost", size = "sm", ...props }) {
|
|
20
|
+
const { mode, cycleMode } = useTheme();
|
|
21
|
+
const name = label ?? labelFor(mode);
|
|
22
|
+
return /*#__PURE__*/ _jsx(Button, {
|
|
23
|
+
...props,
|
|
24
|
+
type: "button",
|
|
25
|
+
variant: variant,
|
|
26
|
+
size: size,
|
|
27
|
+
onClick: cycleMode,
|
|
28
|
+
title: name,
|
|
29
|
+
"aria-label": name,
|
|
30
|
+
children: iconFor(mode)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ThinkingMarkProps } from "./thinking-mark.js";
|
|
2
|
+
export interface ThinkingIndicatorProps extends ThinkingMarkProps {
|
|
3
|
+
/** Visible status text, also announced by assistive technology. */
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
/** A thinking status with Silk, Strata, or Echo motion. Defaults to Silk. */
|
|
7
|
+
export declare function ThinkingIndicator({ label, className, style, ...markProps }: ThinkingIndicatorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { ThinkingMark } from "./thinking-mark.js";
|
|
4
|
+
/** A thinking status with Silk, Strata, or Echo motion. Defaults to Silk. */ export function ThinkingIndicator({ label = "Thinking...", className, style, ...markProps }) {
|
|
5
|
+
return /*#__PURE__*/ _jsxs("span", {
|
|
6
|
+
role: "status",
|
|
7
|
+
className: cn("inline-flex items-center gap-1.5 text-sm text-muted-foreground", className),
|
|
8
|
+
style: style,
|
|
9
|
+
children: [
|
|
10
|
+
/*#__PURE__*/ _jsx(ThinkingMark, {
|
|
11
|
+
...markProps,
|
|
12
|
+
className: "-translate-y-[8%]"
|
|
13
|
+
}),
|
|
14
|
+
/*#__PURE__*/ _jsx("span", {
|
|
15
|
+
children: label
|
|
16
|
+
})
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ThinkingLabelProps = {
|
|
2
|
+
/** What the agent is doing, in its own words — rotated in order. */
|
|
3
|
+
readonly verbs: readonly string[];
|
|
4
|
+
/** Rotation cadence. A heartbeat, not a ticker — keep it at ~1s or slower. */
|
|
5
|
+
readonly intervalMs?: number;
|
|
6
|
+
/** What the status role announces, once. Defaults to "Working". */
|
|
7
|
+
readonly statusLabel?: string;
|
|
8
|
+
readonly className?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The shimmering "working" word, rotating through caller-supplied verbs on
|
|
12
|
+
* an interval. Every verb renders stacked in one grid cell, so the element's
|
|
13
|
+
* width is the widest verb and a swap crossfades instead of reflowing
|
|
14
|
+
* siblings. Visual rotation is decorative: a single `role="status"`
|
|
15
|
+
* announces "Working" once, so a screen reader is not re-interrupted every
|
|
16
|
+
* few seconds. Reduced motion is live via `usePrefersReducedMotion` and
|
|
17
|
+
* stops the rotation rather than just stilling the shimmer.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ThinkingLabel({ verbs, intervalMs, statusLabel, className, }: ThinkingLabelProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { ShimmerText } from "./shimmer-text.js";
|
|
6
|
+
const DEFAULT_INTERVAL_MS = 3_000;
|
|
7
|
+
/**
|
|
8
|
+
* The shimmering "working" word, rotating through caller-supplied verbs on
|
|
9
|
+
* an interval. Every verb renders stacked in one grid cell, so the element's
|
|
10
|
+
* width is the widest verb and a swap crossfades instead of reflowing
|
|
11
|
+
* siblings. Visual rotation is decorative: a single `role="status"`
|
|
12
|
+
* announces "Working" once, so a screen reader is not re-interrupted every
|
|
13
|
+
* few seconds. Reduced motion is live via `usePrefersReducedMotion` and
|
|
14
|
+
* stops the rotation rather than just stilling the shimmer.
|
|
15
|
+
*/ export function ThinkingLabel({ verbs, intervalMs = DEFAULT_INTERVAL_MS, statusLabel = "Working", className }) {
|
|
16
|
+
const [at, setAt] = useState(0);
|
|
17
|
+
const reduceMotion = usePrefersReducedMotion();
|
|
18
|
+
useEffect(()=>{
|
|
19
|
+
if (verbs.length < 2 || !reduceMotion) return;
|
|
20
|
+
setAt(0);
|
|
21
|
+
}, [
|
|
22
|
+
verbs.length,
|
|
23
|
+
reduceMotion
|
|
24
|
+
]);
|
|
25
|
+
useEffect(()=>{
|
|
26
|
+
if (verbs.length < 2 || reduceMotion) return;
|
|
27
|
+
const timer = setInterval(()=>setAt((current)=>(current + 1) % verbs.length), intervalMs);
|
|
28
|
+
return ()=>clearInterval(timer);
|
|
29
|
+
}, [
|
|
30
|
+
verbs.length,
|
|
31
|
+
intervalMs,
|
|
32
|
+
reduceMotion
|
|
33
|
+
]);
|
|
34
|
+
if (verbs.length === 0) return null;
|
|
35
|
+
const active = at % verbs.length;
|
|
36
|
+
return /*#__PURE__*/ _jsxs("span", {
|
|
37
|
+
className: cn("text-sm", className),
|
|
38
|
+
children: [
|
|
39
|
+
/*#__PURE__*/ _jsx("span", {
|
|
40
|
+
role: "status",
|
|
41
|
+
className: "sr-only",
|
|
42
|
+
children: statusLabel
|
|
43
|
+
}),
|
|
44
|
+
/*#__PURE__*/ _jsx("span", {
|
|
45
|
+
"aria-hidden": true,
|
|
46
|
+
className: "inline-grid",
|
|
47
|
+
children: verbs.map((verb, index)=>/*#__PURE__*/ _jsx(ShimmerText, {
|
|
48
|
+
className: cn("col-start-1 row-start-1 transition-opacity duration-200", index === active ? "opacity-100" : "opacity-0"),
|
|
49
|
+
children: verb
|
|
50
|
+
}, index))
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
export type ThinkingVariant = "silk" | "strata" | "echo";
|
|
3
|
+
export interface ThinkingMarkProps {
|
|
4
|
+
variant?: ThinkingVariant;
|
|
5
|
+
paused?: boolean;
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: CSSProperties;
|
|
8
|
+
}
|
|
9
|
+
/** Decorative animated Corbits mark. Pair with visible text or ThinkingIndicator. */
|
|
10
|
+
export declare function ThinkingMark({ variant, paused, className, style }: ThinkingMarkProps): import("react").JSX.Element;
|