@streamsai/ui 0.0.0-stage → 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 +21 -0
- package/README.md +57 -2
- package/dist/components/ui/agent-avatar.d.ts +50 -0
- package/dist/components/ui/agent-avatar.js +38 -0
- package/dist/components/ui/alert-dialog.d.ts +12 -0
- package/dist/components/ui/alert-dialog.js +37 -0
- package/dist/components/ui/alert.d.ts +10 -0
- package/dist/components/ui/alert.js +28 -0
- package/dist/components/ui/assets/loader.json.js +1 -0
- package/dist/components/ui/avatar-frame.d.ts +11 -0
- package/dist/components/ui/avatar-frame.js +20 -0
- package/dist/components/ui/avatar.d.ts +23 -0
- package/dist/components/ui/avatar.js +54 -0
- package/dist/components/ui/badge.d.ts +7 -0
- package/dist/components/ui/badge.js +36 -0
- package/dist/components/ui/bar-list.d.ts +40 -0
- package/dist/components/ui/bar-list.js +37 -0
- package/dist/components/ui/breadcrumb.d.ts +8 -0
- package/dist/components/ui/breadcrumb.js +21 -0
- package/dist/components/ui/button-group.d.ts +10 -0
- package/dist/components/ui/button-group.js +41 -0
- package/dist/components/ui/button.d.ts +9 -0
- package/dist/components/ui/button.js +39 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/card.js +25 -0
- package/dist/components/ui/checkbox.d.ts +3 -0
- package/dist/components/ui/checkbox.js +16 -0
- package/dist/components/ui/chip.d.ts +13 -0
- package/dist/components/ui/chip.js +22 -0
- package/dist/components/ui/clickable.d.ts +27 -0
- package/dist/components/ui/clickable.js +54 -0
- package/dist/components/ui/code-block.d.ts +68 -0
- package/dist/components/ui/code-block.js +112 -0
- package/dist/components/ui/code-view.d.ts +21 -0
- package/dist/components/ui/code-view.js +20 -0
- package/dist/components/ui/collapsible.d.ts +6 -0
- package/dist/components/ui/collapsible.js +18 -0
- package/dist/components/ui/combobox.d.ts +14 -0
- package/dist/components/ui/combobox.js +37 -0
- package/dist/components/ui/command.d.ts +15 -0
- package/dist/components/ui/command.js +34 -0
- package/dist/components/ui/context-menu.d.ts +21 -0
- package/dist/components/ui/context-menu.js +60 -0
- package/dist/components/ui/counter.d.ts +11 -0
- package/dist/components/ui/counter.js +20 -0
- package/dist/components/ui/dialog.d.ts +17 -0
- package/dist/components/ui/dialog.js +41 -0
- package/dist/components/ui/disabled-reason.d.ts +22 -0
- package/dist/components/ui/disabled-reason.js +16 -0
- package/dist/components/ui/dropdown-menu.d.ts +33 -0
- package/dist/components/ui/dropdown-menu.js +58 -0
- package/dist/components/ui/editable-text.d.ts +95 -0
- package/dist/components/ui/editable-text.js +191 -0
- package/dist/components/ui/heading.d.ts +24 -0
- package/dist/components/ui/heading.js +92 -0
- package/dist/components/ui/hover-card.d.ts +9 -0
- package/dist/components/ui/hover-card.js +16 -0
- package/dist/components/ui/icon-button.d.ts +23 -0
- package/dist/components/ui/icon-button.js +32 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/input.js +8 -0
- package/dist/components/ui/kbd.d.ts +12 -0
- package/dist/components/ui/kbd.js +12 -0
- package/dist/components/ui/label.d.ts +3 -0
- package/dist/components/ui/label.js +7 -0
- package/dist/components/ui/loader.d.ts +19 -0
- package/dist/components/ui/loader.js +157 -0
- package/dist/components/ui/message-toolbar.d.ts +20 -0
- package/dist/components/ui/message-toolbar.js +17 -0
- package/dist/components/ui/modal.d.ts +30 -0
- package/dist/components/ui/modal.js +41 -0
- package/dist/components/ui/nav-item.d.ts +25 -0
- package/dist/components/ui/nav-item.js +66 -0
- package/dist/components/ui/panel-section.d.ts +22 -0
- package/dist/components/ui/panel-section.js +26 -0
- package/dist/components/ui/popover.d.ts +14 -0
- package/dist/components/ui/popover.js +30 -0
- package/dist/components/ui/progress.d.ts +5 -0
- package/dist/components/ui/progress.js +16 -0
- package/dist/components/ui/provider-badge.d.ts +22 -0
- package/dist/components/ui/provider-badge.js +38 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/radio-group.js +21 -0
- package/dist/components/ui/reply-summary.d.ts +16 -0
- package/dist/components/ui/reply-summary.js +8 -0
- package/dist/components/ui/resizable.d.ts +8 -0
- package/dist/components/ui/resizable.js +15 -0
- package/dist/components/ui/rich-text-editor.d.ts +447 -0
- package/dist/components/ui/rich-text-editor.js +2419 -0
- package/dist/components/ui/running-indicator.d.ts +17 -0
- package/dist/components/ui/running-indicator.js +34 -0
- package/dist/components/ui/sandboxed-frame.d.ts +29 -0
- package/dist/components/ui/sandboxed-frame.js +77 -0
- package/dist/components/ui/scroll-area.d.ts +14 -0
- package/dist/components/ui/scroll-area.js +17 -0
- package/dist/components/ui/section-header.d.ts +12 -0
- package/dist/components/ui/section-header.js +12 -0
- package/dist/components/ui/select.d.ts +15 -0
- package/dist/components/ui/select.js +40 -0
- package/dist/components/ui/separator.d.ts +3 -0
- package/dist/components/ui/separator.js +13 -0
- package/dist/components/ui/sheet.d.ts +23 -0
- package/dist/components/ui/sheet.js +48 -0
- package/dist/components/ui/shimmer-text.d.ts +26 -0
- package/dist/components/ui/shimmer-text.js +32 -0
- package/dist/components/ui/sidebar.d.ts +63 -0
- package/dist/components/ui/sidebar.js +283 -0
- package/dist/components/ui/skeleton.d.ts +14 -0
- package/dist/components/ui/skeleton.js +12 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/sonner.js +14 -0
- package/dist/components/ui/spark-bars.d.ts +29 -0
- package/dist/components/ui/spark-bars.js +24 -0
- package/dist/components/ui/spinner.d.ts +16 -0
- package/dist/components/ui/spinner.js +26 -0
- package/dist/components/ui/stacked-bar.d.ts +37 -0
- package/dist/components/ui/stacked-bar.js +56 -0
- package/dist/components/ui/status-dot.d.ts +16 -0
- package/dist/components/ui/status-dot.js +32 -0
- package/dist/components/ui/switch.d.ts +5 -0
- package/dist/components/ui/switch.js +13 -0
- package/dist/components/ui/tabs.d.ts +14 -0
- package/dist/components/ui/tabs.js +55 -0
- package/dist/components/ui/text.d.ts +27 -0
- package/dist/components/ui/text.js +100 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/textarea.js +7 -0
- package/dist/components/ui/theme-toggle.d.ts +21 -0
- package/dist/components/ui/theme-toggle.js +17 -0
- package/dist/components/ui/toggle-group.d.ts +6 -0
- package/dist/components/ui/toggle-group.js +12 -0
- package/dist/components/ui/tooltip.d.ts +11 -0
- package/dist/components/ui/tooltip.js +21 -0
- package/dist/components/ui/trace.d.ts +24 -0
- package/dist/components/ui/trace.js +57 -0
- package/dist/components/ui/trend-line.d.ts +93 -0
- package/dist/components/ui/trend-line.js +215 -0
- package/dist/components/ui/truncate.d.ts +30 -0
- package/dist/components/ui/truncate.js +54 -0
- package/dist/components/ui/viewer-shell.d.ts +55 -0
- package/dist/components/ui/viewer-shell.js +15 -0
- package/dist/components/ui/virtual-list.d.ts +91 -0
- package/dist/components/ui/virtual-list.js +406 -0
- package/dist/components/ui/visually-hidden.d.ts +15 -0
- package/dist/components/ui/visually-hidden.js +19 -0
- package/dist/direction/index.d.ts +19 -0
- package/dist/direction/index.js +32 -0
- package/dist/hooks/use-code-highlighting.d.ts +22 -0
- package/dist/hooks/use-code-highlighting.js +47 -0
- package/dist/hooks/use-is-truncated.d.ts +7 -0
- package/dist/hooks/use-is-truncated.js +23 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.js +15 -0
- package/dist/icons/custom.d.ts +29 -0
- package/dist/icons/custom.js +48 -0
- package/dist/icons/index.d.ts +49 -0
- package/dist/icons/index.js +50 -0
- package/dist/icons/provider-logos.d.ts +17 -0
- package/dist/icons/provider-logos.js +28 -0
- package/dist/icons/providers.d.ts +9 -0
- package/dist/icons/providers.js +51 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.js +124 -0
- package/dist/lib/code-highlighter.d.ts +9 -0
- package/dist/lib/code-highlighter.js +65 -0
- package/dist/lib/code-language.d.ts +13 -0
- package/dist/lib/code-language.js +103 -0
- package/dist/lib/code-tokens.d.ts +9 -0
- package/dist/lib/code-tokens.js +27 -0
- package/dist/lib/copy-text.d.ts +6 -0
- package/dist/lib/copy-text.js +46 -0
- package/dist/lib/home-tile.d.ts +12 -0
- package/dist/lib/home-tile.js +45 -0
- package/dist/lib/reveal-mask.d.ts +9 -0
- package/dist/lib/reveal-mask.js +16 -0
- package/dist/lib/text-direction.d.ts +28 -0
- package/dist/lib/text-direction.js +119 -0
- package/dist/lib/utils.d.ts +11 -0
- package/dist/lib/utils.js +30 -0
- package/dist/motion/components.d.ts +184 -0
- package/dist/motion/components.js +211 -0
- package/dist/motion/config.d.ts +849 -0
- package/dist/motion/config.js +429 -0
- package/dist/motion/index.d.ts +8 -0
- package/dist/motion/index.js +8 -0
- package/dist/motion/modal-presence.d.ts +5 -0
- package/dist/motion/modal-presence.js +27 -0
- package/dist/motion/popup.d.ts +77 -0
- package/dist/motion/popup.js +103 -0
- package/dist/platform/index.d.ts +29 -0
- package/dist/platform/index.js +63 -0
- package/dist/styles/base.css +319 -0
- package/dist/styles/fonts/Figtree-Variable.woff2 +0 -0
- package/dist/styles/fonts/OFL.txt +93 -0
- package/dist/styles/fonts/Poppins-Medium.woff2 +0 -0
- package/dist/styles/fonts/Poppins-Regular.woff2 +0 -0
- package/dist/styles/fonts/Poppins-SemiBold.woff2 +0 -0
- package/dist/styles/fonts.css +28 -0
- package/dist/styles/globals.css +8 -0
- package/dist/styles/mcp-host.css +76 -0
- package/dist/styles/tokens.css +387 -0
- package/dist/styles/tokens.d.mts +57 -0
- package/dist/styles/tokens.mjs +59 -0
- package/dist/theme/index.d.ts +28 -0
- package/dist/theme/index.js +65 -0
- package/package.json +136 -3
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
import { cva } from 'class-variance-authority';
|
|
5
|
+
import { Swap } from '../../motion/index.js';
|
|
6
|
+
import { cn } from '../../lib/utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Typography applied to both the trigger button and the editing input so the
|
|
9
|
+
* layout does not shift when entering or leaving edit mode.
|
|
10
|
+
*/
|
|
11
|
+
const editableTextVariants = cva('', {
|
|
12
|
+
variants: {
|
|
13
|
+
/**
|
|
14
|
+
* Visual scale. Heading presentations mirror the heading scale
|
|
15
|
+
* (font-semibold, tracking-tight). Body presentations set only size and
|
|
16
|
+
* line-height. Decoupled from the `as` prop so an `h1` can render at the
|
|
17
|
+
* `h3` visual size without breaking document outline.
|
|
18
|
+
*/
|
|
19
|
+
presentation: {
|
|
20
|
+
h1: 'scroll-m-20 text-4xl font-semibold tracking-tight',
|
|
21
|
+
h2: 'scroll-m-20 text-3xl font-semibold tracking-tight',
|
|
22
|
+
h3: 'scroll-m-20 text-2xl font-semibold tracking-tight',
|
|
23
|
+
h4: 'scroll-m-20 text-xl font-semibold tracking-tight',
|
|
24
|
+
h5: 'scroll-m-20 text-lg font-semibold tracking-tight',
|
|
25
|
+
h6: 'scroll-m-20 text-base font-semibold tracking-tight',
|
|
26
|
+
/** PaneHeader bold titles — matches `Heading level={1} size="3"` (H3 Normal). */
|
|
27
|
+
pageTitle: 'scroll-m-20 font-heading text-h3 font-medium leading-tight tracking-tight',
|
|
28
|
+
base: 'text-base leading-normal',
|
|
29
|
+
sm: 'text-sm leading-normal',
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Text alignment. Applied to the trigger *and* the input from the same
|
|
33
|
+
* variant, so the text does not jump horizontally when entering edit mode —
|
|
34
|
+
* an input left at the browser default would always render left-aligned
|
|
35
|
+
* while a centred trigger sat above it.
|
|
36
|
+
*/
|
|
37
|
+
align: {
|
|
38
|
+
left: 'text-left',
|
|
39
|
+
center: 'text-center',
|
|
40
|
+
right: 'text-right',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
defaultVariants: { presentation: 'base', align: 'left' },
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* `EditableText` — inline-rename primitive.
|
|
47
|
+
*
|
|
48
|
+
* Replaces Vibe's `EditableText` (sidebar rename, 2 call sites) **and**
|
|
49
|
+
* `EditableHeading` (chat header title, 1 call site). One component covers
|
|
50
|
+
* both: use `as` for heading semantics and `presentation` for visual size.
|
|
51
|
+
*
|
|
52
|
+
* ### Accessibility contract
|
|
53
|
+
*
|
|
54
|
+
* **Read mode:** a native `<button>` — keyboard-focusable, announces as
|
|
55
|
+
* interactive, activatable via Enter or Space.
|
|
56
|
+
*
|
|
57
|
+
* **Edit mode:** a `<input type="text">` — on entry, focus moves to the
|
|
58
|
+
* input and the existing text is pre-selected so typing immediately replaces.
|
|
59
|
+
*
|
|
60
|
+
* **Commit:** Enter or blur. Blur is the right default here because it
|
|
61
|
+
* mirrors every OS file-rename pattern — clicking away is "done".
|
|
62
|
+
*
|
|
63
|
+
* **Cancel:** Escape. The _original_ value is restored (not just edit mode
|
|
64
|
+
* exited). Focus returns to the trigger button.
|
|
65
|
+
*
|
|
66
|
+
* **Empty:** blank input is rejected on commit; `onCommit` is not called and
|
|
67
|
+
* the prior value is silently restored.
|
|
68
|
+
*
|
|
69
|
+
* **Focus return:** after commit or cancel, focus returns to the trigger
|
|
70
|
+
* button — not to `document.body`.
|
|
71
|
+
*
|
|
72
|
+
* **Mode-change perception:** the native `<button>` announces as interactive.
|
|
73
|
+
* On activation focus shifts to the input, which is the screen-reader cue for
|
|
74
|
+
* the mode change. No `aria-live` region is used — it would fire twice (once
|
|
75
|
+
* on enter, once on exit) and is noisier than the natural focus-based cue.
|
|
76
|
+
* A visually-hidden `aria-describedby` hint tells SR users how to activate
|
|
77
|
+
* without lengthening the button's primary label.
|
|
78
|
+
*
|
|
79
|
+
* **Layout stability:** the trigger button and the input share identical
|
|
80
|
+
* padding and a 1 px border (transparent on the button, `border-input` on
|
|
81
|
+
* the input) so the surrounding layout does not shift on mode change.
|
|
82
|
+
*/
|
|
83
|
+
function EditableText({ value, onCommit, placeholder = 'Untitled', disabled = false, defaultEditing = false, onEditingChange, as: Tag, presentation, align, className, 'aria-label': ariaLabel, ...rest }) {
|
|
84
|
+
const [editing, setEditingState] = useState(defaultEditing && !disabled);
|
|
85
|
+
const [draft, setDraft] = useState(defaultEditing ? value : '');
|
|
86
|
+
// Mirrors `editing` synchronously. The input leaving edit mode stays mounted for its exit
|
|
87
|
+
// crossfade, so it still receives the blur when focus returns to the trigger; this lets that
|
|
88
|
+
// late blur be ignored instead of committing a second time.
|
|
89
|
+
const editingRef = useRef(editing);
|
|
90
|
+
function setEditing(next) {
|
|
91
|
+
editingRef.current = next;
|
|
92
|
+
setEditingState(next);
|
|
93
|
+
onEditingChange?.(next);
|
|
94
|
+
}
|
|
95
|
+
const triggerRef = useRef(null);
|
|
96
|
+
const inputRef = useRef(null);
|
|
97
|
+
// Set true in the Escape handler so the blur that fires when the input
|
|
98
|
+
// unmounts does not also commit on top of the cancel path.
|
|
99
|
+
const cancelledRef = useRef(false);
|
|
100
|
+
// Prevents the focus-return effect from stealing focus on initial mount.
|
|
101
|
+
const wasEditingRef = useRef(false);
|
|
102
|
+
const hintId = useId();
|
|
103
|
+
// Focus management: enter / leave edit mode.
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (editing) {
|
|
106
|
+
wasEditingRef.current = true;
|
|
107
|
+
inputRef.current?.focus();
|
|
108
|
+
inputRef.current?.select();
|
|
109
|
+
}
|
|
110
|
+
else if (wasEditingRef.current) {
|
|
111
|
+
// Return focus to trigger after commit or cancel.
|
|
112
|
+
triggerRef.current?.focus();
|
|
113
|
+
}
|
|
114
|
+
}, [editing]);
|
|
115
|
+
function startEditing() {
|
|
116
|
+
if (disabled)
|
|
117
|
+
return;
|
|
118
|
+
cancelledRef.current = false;
|
|
119
|
+
setDraft(value);
|
|
120
|
+
setEditing(true);
|
|
121
|
+
}
|
|
122
|
+
function commit() {
|
|
123
|
+
const trimmed = draft.trim();
|
|
124
|
+
if (trimmed.length > 0 && trimmed !== value) {
|
|
125
|
+
onCommit?.(trimmed);
|
|
126
|
+
}
|
|
127
|
+
// Empty or unchanged → restore silently. Either way exit edit mode.
|
|
128
|
+
setEditing(false);
|
|
129
|
+
}
|
|
130
|
+
function cancel() {
|
|
131
|
+
// Mark cancelled so the subsequent blur event does not double-commit.
|
|
132
|
+
cancelledRef.current = true;
|
|
133
|
+
// draft is discarded; the value prop retains the original.
|
|
134
|
+
setEditing(false);
|
|
135
|
+
}
|
|
136
|
+
function handleTriggerKeyDown(e) {
|
|
137
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
startEditing();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function handleInputKeyDown(e) {
|
|
143
|
+
if (e.key === 'Enter') {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
commit();
|
|
146
|
+
}
|
|
147
|
+
else if (e.key === 'Escape') {
|
|
148
|
+
cancel();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function handleBlur() {
|
|
152
|
+
if (cancelledRef.current || !editingRef.current) {
|
|
153
|
+
cancelledRef.current = false;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
commit();
|
|
157
|
+
}
|
|
158
|
+
const display = value;
|
|
159
|
+
const accessibleName = ariaLabel ?? (display || placeholder);
|
|
160
|
+
// Shared box model: identical padding + 1 px border keeps the layout
|
|
161
|
+
// stable across read/edit mode. The border is transparent on the trigger
|
|
162
|
+
// and `border-input` on the input.
|
|
163
|
+
const sharedBox = 'w-full rounded-sm border px-1 py-0.5 outline-none transition-colors bg-transparent';
|
|
164
|
+
const typographyClass = editableTextVariants({ presentation, align });
|
|
165
|
+
const trigger = (_jsx("button", { ref: triggerRef, type: "button", "data-slot": "editable-text", disabled: disabled, "aria-describedby": hintId, "aria-label": accessibleName, onClick: startEditing, onKeyDown: handleTriggerKeyDown, className: cn(sharedBox, 'cursor-text border-transparent text-foreground',
|
|
166
|
+
// Hover affordance. Without it nothing signals the text is editable until it
|
|
167
|
+
// is focused, which leaves mouse users guessing. Revealing the field boundary
|
|
168
|
+
// on hover is the established inline-edit pattern, and because the border is
|
|
169
|
+
// already present-but-transparent it costs no layout shift.
|
|
170
|
+
'enabled:hover:border-input enabled:hover:bg-muted/50',
|
|
171
|
+
// `enabled:` matters — CSS :hover still matches a disabled button, so without
|
|
172
|
+
// it a disabled field would light up on hover and look actionable.
|
|
173
|
+
'focus-visible:border-ring', 'disabled:cursor-not-allowed disabled:opacity-50', typographyClass), children: display ? (_jsx("span", { className: "block truncate", children: display })) : (_jsx("span", { className: "block truncate text-muted-foreground", children: placeholder })) }));
|
|
174
|
+
const input = (_jsx("input", { ref: inputRef, type: "text", "data-slot": "editable-text-input", value: draft, onChange: (e) => setDraft(e.target.value), onKeyDown: handleInputKeyDown, onBlur: handleBlur, "aria-label": accessibleName, placeholder: placeholder, className: cn(sharedBox, 'border-input text-foreground', 'focus-visible:border-ring', 'placeholder:text-muted-foreground', typographyClass) }));
|
|
175
|
+
// Visually-hidden hint referenced by the trigger button's aria-describedby.
|
|
176
|
+
// Tells SR users how to activate without polluting the button's primary name
|
|
177
|
+
// (which would be repeated on every announce).
|
|
178
|
+
const hint = (_jsx("span", { id: hintId, className: "sr-only", children: "Press Enter or Space to rename." }));
|
|
179
|
+
// A `span.block`, not a `div`: this element is nested inside `<Tag>` when `as` is set,
|
|
180
|
+
// and the content model of h1–h6 is *phrasing* content, which a div is not. `block`
|
|
181
|
+
// makes it lay out identically, so this costs nothing and keeps the markup valid.
|
|
182
|
+
const root = (_jsxs("span", { "data-slot": "editable-text-root", className: cn('relative block w-full', className), ...rest, children: [hint, _jsx(Swap, { inline: true, swapKey: editing ? 'input' : 'trigger', className: "block w-full", children: editing ? input : trigger })] }));
|
|
183
|
+
if (Tag !== undefined) {
|
|
184
|
+
// Semantic heading wrapper so screen-reader users can reach this field by heading
|
|
185
|
+
// level. The interactive button lives inside the heading rather than the heading
|
|
186
|
+
// carrying role="button", which keeps the document outline intact.
|
|
187
|
+
return _jsx(Tag, { children: root });
|
|
188
|
+
}
|
|
189
|
+
return root;
|
|
190
|
+
}
|
|
191
|
+
export { EditableText, editableTextVariants };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
2
|
+
/**
|
|
3
|
+
* Visual size scale for headings. Decoupled from the semantic level so an `h2`
|
|
4
|
+
* can render at the visual weight of an `h4` without breaking heading order.
|
|
5
|
+
*/
|
|
6
|
+
declare const headingVariants: (props?: ({
|
|
7
|
+
size?: "1" | "2" | "3" | "4" | "5" | "6" | null | undefined;
|
|
8
|
+
color?: "default" | "destructive" | "muted" | "primary" | null | undefined;
|
|
9
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
10
|
+
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
11
|
+
type HeadingProps = React.ComponentProps<'h1'> & VariantProps<typeof headingVariants> & {
|
|
12
|
+
/**
|
|
13
|
+
* Semantic HTML level (`h1`–`h6`). Governs document outline and screen-reader
|
|
14
|
+
* announcement. Defaults to `2`.
|
|
15
|
+
*/
|
|
16
|
+
level?: HeadingLevel;
|
|
17
|
+
/** Truncate overflowing text with an ellipsis; the full text shows in a tooltip while clipped. */
|
|
18
|
+
ellipsis?: boolean;
|
|
19
|
+
/** Only with `ellipsis`. `false` turns the tooltip off; a string overrides its content. */
|
|
20
|
+
tooltip?: boolean | string;
|
|
21
|
+
};
|
|
22
|
+
declare function Heading({ className, level, size, color, ellipsis, tooltip, ref: forwardedRef, ...props }: HeadingProps): import("react").JSX.Element;
|
|
23
|
+
export { Heading, headingVariants };
|
|
24
|
+
export type { HeadingLevel, HeadingProps };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef } from 'react';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
|
+
import { TruncationTooltip, useTruncationTooltip } from './truncate.js';
|
|
6
|
+
/**
|
|
7
|
+
* Visual size scale for headings. Decoupled from the semantic level so an `h2`
|
|
8
|
+
* can render at the visual weight of an `h4` without breaking heading order.
|
|
9
|
+
*/
|
|
10
|
+
const headingVariants = cva('font-heading font-semibold leading-tight tracking-tight text-foreground', {
|
|
11
|
+
variants: {
|
|
12
|
+
/**
|
|
13
|
+
* Visual size — what the heading *looks like*.
|
|
14
|
+
* Pick the level that fits the information hierarchy; pick the size that fits
|
|
15
|
+
* the visual hierarchy. They are independent.
|
|
16
|
+
*/
|
|
17
|
+
size: {
|
|
18
|
+
'1': 'text-4xl',
|
|
19
|
+
'2': 'text-3xl',
|
|
20
|
+
'3': 'text-h3 font-medium',
|
|
21
|
+
'4': 'text-xl',
|
|
22
|
+
'5': 'text-lg',
|
|
23
|
+
'6': 'text-base',
|
|
24
|
+
},
|
|
25
|
+
/**
|
|
26
|
+
* Color role — maps to the semantic token layer.
|
|
27
|
+
*/
|
|
28
|
+
color: {
|
|
29
|
+
default: 'text-foreground',
|
|
30
|
+
muted: 'text-muted-foreground',
|
|
31
|
+
primary: 'text-primary',
|
|
32
|
+
destructive: 'text-destructive',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
size: '2',
|
|
37
|
+
color: 'default',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Token-backed heading primitive.
|
|
42
|
+
*
|
|
43
|
+
* The semantic level (`level` prop, controlling which `<h*>` tag is rendered)
|
|
44
|
+
* is intentionally decoupled from the visual size (`size` prop). This lets you
|
|
45
|
+
* render an `h2` at the visual scale of an `h4` — a real and common layout need
|
|
46
|
+
* — without breaking heading-order accessibility rules.
|
|
47
|
+
*
|
|
48
|
+
* ```tsx
|
|
49
|
+
* // Visually large, semantically h1 (page title)
|
|
50
|
+
* <Heading level={1} size="1">Page title</Heading>
|
|
51
|
+
*
|
|
52
|
+
* // Sub-section inside an h2, looks like h3 but is h3
|
|
53
|
+
* <Heading level={3} size="3">Section title</Heading>
|
|
54
|
+
*
|
|
55
|
+
* // Sidebar header: visually small but correctly h2
|
|
56
|
+
* <Heading level={2} size="5">Recent activity</Heading>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Attach `node` to the caller's ref. Returns a detach function so React 19 callback-ref cleanups
|
|
61
|
+
* keep working: the caller's own cleanup when it returned one, else the legacy `ref(null)`.
|
|
62
|
+
*/
|
|
63
|
+
function attachRef(ref, node) {
|
|
64
|
+
if (typeof ref === 'function') {
|
|
65
|
+
const cleanup = ref(node);
|
|
66
|
+
return typeof cleanup === 'function' ? cleanup : () => ref(null);
|
|
67
|
+
}
|
|
68
|
+
if (ref) {
|
|
69
|
+
ref.current = node;
|
|
70
|
+
return () => {
|
|
71
|
+
ref.current = null;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
function Heading({ className, level = 2, size, color, ellipsis = false, tooltip = true, ref: forwardedRef, ...props }) {
|
|
77
|
+
const Tag = `h${level}`;
|
|
78
|
+
const ref = useRef(null);
|
|
79
|
+
const setRef = useCallback((node) => {
|
|
80
|
+
ref.current = node;
|
|
81
|
+
if (!node)
|
|
82
|
+
return undefined;
|
|
83
|
+
const detach = attachRef(forwardedRef, node);
|
|
84
|
+
return () => {
|
|
85
|
+
ref.current = null;
|
|
86
|
+
detach?.();
|
|
87
|
+
};
|
|
88
|
+
}, [forwardedRef]);
|
|
89
|
+
const truncation = useTruncationTooltip(ref, ellipsis, tooltip, props.children);
|
|
90
|
+
return (_jsx(TruncationTooltip, { text: truncation.text, truncated: truncation.truncated, children: _jsx(Tag, { ref: setRef, "data-slot": "heading", className: cn(headingVariants({ size, color, className }), ellipsis && 'truncate'), ...props }) }));
|
|
91
|
+
}
|
|
92
|
+
export { Heading, headingVariants };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PreviewCard as PreviewCardPrimitive } from '@base-ui/react/preview-card';
|
|
2
|
+
import { type PopupVariant } from '../../motion/popup.js';
|
|
3
|
+
declare function HoverCard({ actionsRef, onOpenChange, ...props }: PreviewCardPrimitive.Root.Props): import("react").JSX.Element;
|
|
4
|
+
declare function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props): import("react").JSX.Element;
|
|
5
|
+
declare function HoverCardContent({ className, align, alignOffset, side, sideOffset, motion, ...props }: PreviewCardPrimitive.Popup.Props & {
|
|
6
|
+
/** `fade` only fades, for a list that opens on hover inside a dense form. */
|
|
7
|
+
motion?: Extract<PopupVariant, 'scale' | 'fade'>;
|
|
8
|
+
} & Pick<PreviewCardPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'side' | 'sideOffset'>): import("react").JSX.Element;
|
|
9
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PreviewCard as PreviewCardPrimitive } from '@base-ui/react/preview-card';
|
|
3
|
+
import { cn } from '../../lib/utils.js';
|
|
4
|
+
import { PopupExitContext, usePopupExit, usePopupRender } from '../../motion/popup.js';
|
|
5
|
+
function HoverCard({ actionsRef, onOpenChange, ...props }) {
|
|
6
|
+
const exit = usePopupExit(actionsRef, onOpenChange);
|
|
7
|
+
return (_jsx(PopupExitContext, { value: exit.unmount, children: _jsx(PreviewCardPrimitive.Root, { "data-slot": "hover-card", ...props, actionsRef: exit.actionsRef, onOpenChange: exit.onOpenChange }) }));
|
|
8
|
+
}
|
|
9
|
+
function HoverCardTrigger({ ...props }) {
|
|
10
|
+
return _jsx(PreviewCardPrimitive.Trigger, { "data-slot": "hover-card-trigger", ...props });
|
|
11
|
+
}
|
|
12
|
+
function HoverCardContent({ className, align = 'center', alignOffset = 0, side = 'bottom', sideOffset = 4, motion = 'scale', ...props }) {
|
|
13
|
+
const renderPopup = usePopupRender(motion);
|
|
14
|
+
return (_jsx(PreviewCardPrimitive.Portal, { children: _jsx(PreviewCardPrimitive.Positioner, { align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, className: "isolate z-(--z-dropdown) outline-none", children: _jsx(PreviewCardPrimitive.Popup, { render: renderPopup, "data-slot": "hover-card-content", className: cn('z-(--z-dropdown) w-80 rounded-lg border bg-popover p-4 shadow-md outline-none', className), ...props }) }) }));
|
|
15
|
+
}
|
|
16
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import type { VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { Button } from './button.js';
|
|
4
|
+
/**
|
|
5
|
+
* Icon-only button. Wraps the generated `Button` primitive with `size="icon"`.
|
|
6
|
+
*
|
|
7
|
+
* `aria-label` is **required** at the type level — omitting it is a compile
|
|
8
|
+
* error, not an axe finding. Icon-only buttons with no accessible name are the
|
|
9
|
+
* most common a11y regression in icon-heavy UIs; this component makes it
|
|
10
|
+
* impossible to ship one accidentally.
|
|
11
|
+
*
|
|
12
|
+
* Sizes are restricted to the icon variants of `buttonVariants` so callers
|
|
13
|
+
* cannot accidentally render a text-button-sized icon target.
|
|
14
|
+
*/
|
|
15
|
+
declare const iconButtonVariants: (props?: ({
|
|
16
|
+
size?: "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
|
|
17
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
18
|
+
type IconButtonProps = Omit<ComponentProps<typeof Button>, 'aria-label' | 'size'> & VariantProps<typeof iconButtonVariants> & {
|
|
19
|
+
/** Accessible name — required. Icon-only buttons without a label fail axe. */
|
|
20
|
+
'aria-label': string;
|
|
21
|
+
};
|
|
22
|
+
declare function IconButton({ size, className, ...props }: IconButtonProps): import("react").JSX.Element;
|
|
23
|
+
export { IconButton, iconButtonVariants };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cva } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../../lib/utils.js';
|
|
4
|
+
import { Button } from './button.js';
|
|
5
|
+
/**
|
|
6
|
+
* Icon-only button. Wraps the generated `Button` primitive with `size="icon"`.
|
|
7
|
+
*
|
|
8
|
+
* `aria-label` is **required** at the type level — omitting it is a compile
|
|
9
|
+
* error, not an axe finding. Icon-only buttons with no accessible name are the
|
|
10
|
+
* most common a11y regression in icon-heavy UIs; this component makes it
|
|
11
|
+
* impossible to ship one accidentally.
|
|
12
|
+
*
|
|
13
|
+
* Sizes are restricted to the icon variants of `buttonVariants` so callers
|
|
14
|
+
* cannot accidentally render a text-button-sized icon target.
|
|
15
|
+
*/
|
|
16
|
+
const iconButtonVariants = cva('', {
|
|
17
|
+
variants: {
|
|
18
|
+
size: {
|
|
19
|
+
'icon-xs': '',
|
|
20
|
+
'icon-sm': '',
|
|
21
|
+
icon: '',
|
|
22
|
+
'icon-lg': '',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
size: 'icon',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
function IconButton({ size = 'icon', className, ...props }) {
|
|
30
|
+
return (_jsx(Button, { "data-slot": "icon-button", size: size ?? 'icon', className: cn(className), ...props }));
|
|
31
|
+
}
|
|
32
|
+
export { IconButton, iconButtonVariants };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Input as InputPrimitive } from '@base-ui/react/input';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
|
+
function Input({ className, type, ...props }) {
|
|
6
|
+
return (_jsx(InputPrimitive, { type: type, "data-slot": "input", className: cn('h-8 w-full min-w-0 rounded-lg border border-input bg-transparent text-foreground px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:cursor-pointer file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-placeholder focus-visible:border-ring disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground disabled:opacity-100 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40', className), ...props }));
|
|
7
|
+
}
|
|
8
|
+
export { Input };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
type KbdVariant = 'default' | 'ghost';
|
|
3
|
+
type KbdProps = React.ComponentProps<'kbd'> & {
|
|
4
|
+
/**
|
|
5
|
+
* `default` — bordered muted pill (dialogs, composer).
|
|
6
|
+
* `ghost` — calm glyphs only (left-nav shortcut hints).
|
|
7
|
+
*/
|
|
8
|
+
variant?: KbdVariant;
|
|
9
|
+
};
|
|
10
|
+
declare function Kbd({ className, children, variant, ...props }: KbdProps): React.JSX.Element;
|
|
11
|
+
declare function KbdGroup({ className, children, ...props }: React.ComponentProps<'span'>): React.JSX.Element;
|
|
12
|
+
export { Kbd, KbdGroup, type KbdProps, type KbdVariant };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { cn } from '../../lib/utils.js';
|
|
4
|
+
function Kbd({ className, children, variant = 'default', ...props }) {
|
|
5
|
+
return (_jsx("kbd", { "data-slot": "kbd", "data-variant": variant, className: cn('inline-flex items-center justify-center rounded font-mono text-body-3 text-muted-foreground', variant === 'ghost'
|
|
6
|
+
? 'border-transparent bg-transparent px-1 py-0 font-normal shadow-none'
|
|
7
|
+
: 'border border-border bg-muted px-1.5 py-0.5 font-medium shadow-sm', className), ...props, children: children }));
|
|
8
|
+
}
|
|
9
|
+
function KbdGroup({ className, children, ...props }) {
|
|
10
|
+
return (_jsx("span", { "data-slot": "kbd-group", className: cn('inline-flex items-center gap-0.5', className), ...props, children: children }));
|
|
11
|
+
}
|
|
12
|
+
export { Kbd, KbdGroup };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { cn } from '../../lib/utils.js';
|
|
4
|
+
function Label({ className, ...props }) {
|
|
5
|
+
return (_jsx("label", { "data-slot": "label", className: cn('flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50', className), ...props }));
|
|
6
|
+
}
|
|
7
|
+
export { Label };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
declare const SIZE_WIDTH: {
|
|
3
|
+
readonly small: 29;
|
|
4
|
+
readonly medium: 58;
|
|
5
|
+
readonly large: 112;
|
|
6
|
+
};
|
|
7
|
+
type LoaderSizeName = keyof typeof SIZE_WIDTH;
|
|
8
|
+
type LoaderSize = LoaderSizeName | {
|
|
9
|
+
width: number;
|
|
10
|
+
};
|
|
11
|
+
type LoaderProps = Omit<ComponentPropsWithoutRef<'span'>, 'children'> & {
|
|
12
|
+
/** Preset or `{ width }` (height = width / 2). Default `medium`. */
|
|
13
|
+
size?: LoaderSize;
|
|
14
|
+
/** Playback speed multiplier. Default 1. */
|
|
15
|
+
speed?: number;
|
|
16
|
+
};
|
|
17
|
+
declare function Loader({ className, size, speed, 'aria-label': ariaLabel, style, ...props }: LoaderProps): import("react").JSX.Element;
|
|
18
|
+
export { Loader };
|
|
19
|
+
export type { LoaderProps, LoaderSize, LoaderSizeName };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState, } from 'react';
|
|
3
|
+
import { useReducedMotion } from '../../motion/index.js';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
|
+
import loaderAnimation from './assets/loader.json.js';
|
|
6
|
+
/**
|
|
7
|
+
* Region / page loading indicator — the motion team's Lottie (dots → tilde → circle).
|
|
8
|
+
* Use `Loader` when a page or region is loading; keep `Spinner` for inline action states
|
|
9
|
+
* (pending buttons, "load more", dialogs).
|
|
10
|
+
*
|
|
11
|
+
* Size presets (web): `small` 29×14.5, `medium` 58×29 (default), `large` 112×56.
|
|
12
|
+
* iOS `LoaderView` keeps its own presets (32×16 / 64×32 / 92×46).
|
|
13
|
+
* Height is always half the width (the export's 2:1 canvas). Tint follows `currentColor`
|
|
14
|
+
* (`text-foreground` by default) so light and dark stay readable. With reduced motion, holds
|
|
15
|
+
* still on the tilde (frame 98), matching iOS.
|
|
16
|
+
*/
|
|
17
|
+
const STILL_FRAME = 98;
|
|
18
|
+
const ASPECT_RATIO = 2;
|
|
19
|
+
const SIZE_WIDTH = {
|
|
20
|
+
small: 29,
|
|
21
|
+
medium: 58,
|
|
22
|
+
large: 112,
|
|
23
|
+
};
|
|
24
|
+
function widthOf(size) {
|
|
25
|
+
return typeof size === 'object' ? size.width : SIZE_WIDTH[size];
|
|
26
|
+
}
|
|
27
|
+
/** Parse `rgb()` / `rgba()` / `#rrggbb` from getComputedStyle into 0–1 Lottie channels. */
|
|
28
|
+
function colorChannels(cssColor) {
|
|
29
|
+
const hex = /^#([0-9a-f]{6})$/i.exec(cssColor.trim());
|
|
30
|
+
if (hex?.[1]) {
|
|
31
|
+
const n = Number.parseInt(hex[1], 16);
|
|
32
|
+
return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255, 1];
|
|
33
|
+
}
|
|
34
|
+
const m = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(cssColor.trim());
|
|
35
|
+
if (!m)
|
|
36
|
+
return null;
|
|
37
|
+
const r = Number(m[1]);
|
|
38
|
+
const g = Number(m[2]);
|
|
39
|
+
const b = Number(m[3]);
|
|
40
|
+
const a = m[4] === undefined ? 1 : Number(m[4]);
|
|
41
|
+
if (![r, g, b, a].every((n) => Number.isFinite(n)))
|
|
42
|
+
return null;
|
|
43
|
+
return [r / 255, g / 255, b / 255, a];
|
|
44
|
+
}
|
|
45
|
+
/** Tint every solid fill/stroke and gradient stop so the mono export follows theme text. */
|
|
46
|
+
function tintAnimationData(data, tint) {
|
|
47
|
+
const clone = structuredClone(data);
|
|
48
|
+
const visit = (node) => {
|
|
49
|
+
if (!node || typeof node !== 'object')
|
|
50
|
+
return;
|
|
51
|
+
if (Array.isArray(node)) {
|
|
52
|
+
for (const child of node)
|
|
53
|
+
visit(child);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const obj = node;
|
|
57
|
+
const c = obj.c;
|
|
58
|
+
if (c && typeof c === 'object' && !Array.isArray(c)) {
|
|
59
|
+
const color = c;
|
|
60
|
+
if (Array.isArray(color.k) && typeof color.k[0] === 'number' && color.k.length >= 3) {
|
|
61
|
+
const alpha = typeof color.k[3] === 'number' ? color.k[3] : 1;
|
|
62
|
+
color.k = [tint[0], tint[1], tint[2], alpha];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const g = obj.g;
|
|
66
|
+
if (g && typeof g === 'object' && !Array.isArray(g)) {
|
|
67
|
+
const grad = g;
|
|
68
|
+
const stops = grad.k?.k;
|
|
69
|
+
if (Array.isArray(stops)) {
|
|
70
|
+
for (let i = 0; i + 3 < stops.length; i += 4) {
|
|
71
|
+
if (typeof stops[i + 1] === 'number' &&
|
|
72
|
+
typeof stops[i + 2] === 'number' &&
|
|
73
|
+
typeof stops[i + 3] === 'number') {
|
|
74
|
+
stops[i + 1] = tint[0];
|
|
75
|
+
stops[i + 2] = tint[1];
|
|
76
|
+
stops[i + 3] = tint[2];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const value of Object.values(obj))
|
|
82
|
+
visit(value);
|
|
83
|
+
};
|
|
84
|
+
visit(clone);
|
|
85
|
+
return clone;
|
|
86
|
+
}
|
|
87
|
+
function Loader({ className, size = 'medium', speed = 1, 'aria-label': ariaLabel = 'Loading', style, ...props }) {
|
|
88
|
+
const rootRef = useRef(null);
|
|
89
|
+
const hostRef = useRef(null);
|
|
90
|
+
const animationRef = useRef(null);
|
|
91
|
+
const reduceMotion = useReducedMotion();
|
|
92
|
+
const width = widthOf(size);
|
|
93
|
+
const height = width / ASPECT_RATIO;
|
|
94
|
+
// Live foreground color (theme / caller `text-*` / style.color). Re-tint when it changes —
|
|
95
|
+
// Lottie bakes RGB into the SVG, so a mount-time snapshot would stay wrong after dark mode.
|
|
96
|
+
const [cssColor, setCssColor] = useState(null);
|
|
97
|
+
const styleColor = typeof style?.color === 'string' ? style.color : undefined;
|
|
98
|
+
useLayoutEffect(() => {
|
|
99
|
+
const root = rootRef.current;
|
|
100
|
+
if (!root)
|
|
101
|
+
return;
|
|
102
|
+
const sync = () => {
|
|
103
|
+
const next = getComputedStyle(root).color;
|
|
104
|
+
setCssColor((prev) => (prev === next ? prev : next));
|
|
105
|
+
};
|
|
106
|
+
sync();
|
|
107
|
+
const observer = new MutationObserver(sync);
|
|
108
|
+
observer.observe(document.documentElement, {
|
|
109
|
+
attributes: true,
|
|
110
|
+
attributeFilter: ['class', 'style'],
|
|
111
|
+
});
|
|
112
|
+
return () => observer.disconnect();
|
|
113
|
+
}, [className, styleColor]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const host = hostRef.current;
|
|
116
|
+
if (!host || cssColor === null)
|
|
117
|
+
return;
|
|
118
|
+
// Dynamic import so jsdom (and any consumer that never mounts Loader) never
|
|
119
|
+
// evaluates lottie-web at module load — it touches canvas and throws without one.
|
|
120
|
+
let cancelled = false;
|
|
121
|
+
void import('lottie-web/build/player/lottie_light').then((mod) => {
|
|
122
|
+
if (cancelled || !hostRef.current)
|
|
123
|
+
return;
|
|
124
|
+
const lottie = mod.default;
|
|
125
|
+
const tint = colorChannels(cssColor) ?? [0.169, 0.169, 0.169, 1];
|
|
126
|
+
const data = tintAnimationData(loaderAnimation, tint);
|
|
127
|
+
const animation = lottie.loadAnimation({
|
|
128
|
+
container: hostRef.current,
|
|
129
|
+
renderer: 'svg',
|
|
130
|
+
loop: reduceMotion !== true,
|
|
131
|
+
autoplay: reduceMotion !== true,
|
|
132
|
+
animationData: data,
|
|
133
|
+
rendererSettings: {
|
|
134
|
+
preserveAspectRatio: 'xMidYMid meet',
|
|
135
|
+
progressiveLoad: true,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
animation.setSpeed(speed);
|
|
139
|
+
if (reduceMotion === true) {
|
|
140
|
+
animation.goToAndStop(STILL_FRAME, true);
|
|
141
|
+
}
|
|
142
|
+
animationRef.current = animation;
|
|
143
|
+
});
|
|
144
|
+
return () => {
|
|
145
|
+
cancelled = true;
|
|
146
|
+
animationRef.current?.destroy();
|
|
147
|
+
animationRef.current = null;
|
|
148
|
+
};
|
|
149
|
+
}, [reduceMotion, speed, width, cssColor]);
|
|
150
|
+
const mergedStyle = {
|
|
151
|
+
width,
|
|
152
|
+
height,
|
|
153
|
+
...style,
|
|
154
|
+
};
|
|
155
|
+
return (_jsx("span", { ref: rootRef, role: "status", "aria-label": ariaLabel, "data-slot": "loader", "data-size": typeof size === 'string' ? size : undefined, className: cn('inline-block text-foreground', className), style: mergedStyle, ...props, children: _jsx("span", { ref: hostRef, "aria-hidden": "true", className: "block size-full [&_svg]:block" }) }));
|
|
156
|
+
}
|
|
157
|
+
export { Loader };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
interface MessageToolbarProps extends ComponentProps<'div'> {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* The hover-action strip on a message row (Figma "right actions group"): white background,
|
|
7
|
+
* ui-background hairline, Vibe medium 8px radius, Shadow/XS, and 8px padding and gaps around
|
|
8
|
+
* 24px icon buttons, so the strip is 42px tall.
|
|
9
|
+
*/
|
|
10
|
+
declare function MessageToolbar({ children, className, ...props }: MessageToolbarProps): import("react").JSX.Element;
|
|
11
|
+
interface MessageToolbarButtonProps {
|
|
12
|
+
icon: ReactNode;
|
|
13
|
+
label: string;
|
|
14
|
+
/** Transient state that replaces the label in the tooltip and accessible name (e.g. "Copied!"). */
|
|
15
|
+
tooltip?: string;
|
|
16
|
+
onClick?: () => void;
|
|
17
|
+
active?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare function MessageToolbarButton({ icon, label, tooltip, onClick, active, }: MessageToolbarButtonProps): import("react").JSX.Element;
|
|
20
|
+
export { MessageToolbar, MessageToolbarButton, type MessageToolbarButtonProps };
|