@tangle-network/ui 11.9.0 → 11.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +14 -0
- package/dist/active-sessions-store-Gt1R0rFG.js +262 -0
- package/dist/{active-sessions-store-CeOmXgv5.d.ts → active-sessions-store-nl70ZnMC.d.ts} +40 -41
- package/dist/artifact-pane-BL_DFWNl.d.ts +32 -0
- package/dist/artifact-pane-DMXT9a8U.js +70 -0
- package/dist/auth-DixfCMCk.js +154 -0
- package/dist/auth.d.ts +2 -74
- package/dist/auth.js +2 -15
- package/dist/branding-26d5ARIr.d.ts +14 -0
- package/dist/button-uhNnwdsD.js +78 -0
- package/dist/button-yoIv77e2.d.ts +16 -0
- package/dist/chat-CPLL0Fd9.js +667 -0
- package/dist/chat.d.ts +2 -201
- package/dist/chat.js +2 -30
- package/dist/code-block-B9miUPht.d.ts +23 -0
- package/dist/code-block-CngSn37j.js +165 -0
- package/dist/diff-preview-cySuqowW.js +533 -0
- package/dist/document-editor-pane-BcoABlhn.d.ts +131 -0
- package/dist/document-editor-pane-Cz6oEF1o.js +1287 -0
- package/dist/dropdown-menu-BpcLa5vF.js +101 -0
- package/dist/editor.d.ts +76 -78
- package/dist/editor.js +2 -33
- package/dist/file-format-BgXbr-Ib.js +160 -0
- package/dist/files-CYlOo0mE.js +743 -0
- package/dist/files.d.ts +2 -243
- package/dist/files.js +3 -40
- package/dist/focus-Cw02pNSr.js +56 -0
- package/dist/format-DfcLWqMa.js +54 -0
- package/dist/hooks.d.ts +30 -37
- package/dist/hooks.js +111 -45
- package/dist/index-2UntyjBb.d.ts +52 -0
- package/dist/index-COgqsFAQ.d.ts +42 -0
- package/dist/index-CPDET_s1.d.ts +549 -0
- package/dist/index-CUhfHT_V.d.ts +204 -0
- package/dist/index-Cg6gYAqh.d.ts +73 -0
- package/dist/index-Cq-EJzqW.d.ts +16 -0
- package/dist/index-DhYmSaLF.d.ts +245 -0
- package/dist/index-DjKxO357.d.ts +128 -0
- package/dist/index-b56ew9aF.d.ts +107 -0
- package/dist/index.d.ts +26 -43
- package/dist/index.js +35 -460
- package/dist/markdown-BprL96Gl.js +45 -0
- package/dist/markdown.d.ts +3 -18
- package/dist/markdown.js +3 -14
- package/dist/message-vhUI0c5G.d.ts +16 -0
- package/dist/nav.d.ts +6 -8
- package/dist/nav.js +29 -11
- package/dist/openui-artifact-renderer-D3E5bIAI.d.ts +114 -0
- package/dist/openui-artifact-renderer-DT4xbvWt.js +266 -0
- package/dist/openui.d.ts +2 -115
- package/dist/openui.js +2 -12
- package/dist/parts-5mhy9yJ3.d.ts +37 -0
- package/dist/primitives-DE-blUz-.js +1239 -0
- package/dist/primitives.d.ts +5 -523
- package/dist/primitives.js +9 -211
- package/dist/redaction-CGGB2ENG.js +74 -0
- package/dist/redaction.d.ts +2 -42
- package/dist/redaction.js +2 -7
- package/dist/run-OyWmIDqu.d.ts +41 -0
- package/dist/run-ZsIKPA6J.js +17 -0
- package/dist/run-group-D1InSdQu.js +410 -0
- package/dist/run.d.ts +3 -126
- package/dist/run.js +5 -39
- package/dist/sdk-hooks.d.ts +2 -285
- package/dist/sdk-hooks.js +3 -35
- package/dist/stores.d.ts +13 -14
- package/dist/stores.js +34 -76
- package/dist/table-DHyqDPbU.js +146 -0
- package/dist/tabs-ujuqAdtb.js +40 -0
- package/dist/text-shimmer-BUUo6I-g.js +64 -0
- package/dist/tool-call-feed-BeB1_4pX.d.ts +47 -0
- package/dist/tool-call-feed-C1W1N54c.js +109 -0
- package/dist/tool-call-step-Bi3_YJRV.js +434 -0
- package/dist/tool-display-BAuAVa43.js +158 -0
- package/dist/{tool-display-z4JcDmMQ.d.ts → tool-display-DgmL5rIU.d.ts} +16 -16
- package/dist/tool-previews.d.ts +2 -48
- package/dist/tool-previews.js +2 -22
- package/dist/types.d.ts +6 -19
- package/dist/types.js +0 -1
- package/dist/use-auto-scroll-C5Pjeqon.js +153 -0
- package/dist/use-sse-stream-BjVBDbxK.js +884 -0
- package/dist/use-sse-stream-Dm6R5YWz.d.ts +282 -0
- package/dist/utils-Bl_CZ0Qp.js +37 -0
- package/dist/{chunk-RQHJBTEU.js → utils-C_uf36nf.js} +4 -6
- package/dist/utils.d.ts +2 -51
- package/dist/utils.js +6 -32
- package/package.json +39 -41
- package/src/auth/auth.tsx +5 -1
- package/src/chat/agent-timeline.tsx +6 -1
- package/src/chat/chat-container.tsx +2 -1
- package/src/editor/document-editor-pane.test.tsx +3 -1
- package/src/editor/document-editor-pane.tsx +5 -1
- package/src/editor/markdown-document-editor.test.tsx +9 -4
- package/src/files/file-artifact-pane.tsx +3 -2
- package/src/files/file-preview.tsx +3 -1
- package/src/files/file-tabs.tsx +9 -2
- package/src/lib/focus.ts +63 -0
- package/src/markdown/code-block.tsx +5 -1
- package/src/primitives/badge.tsx +2 -1
- package/src/primitives/button.tsx +2 -1
- package/src/primitives/card.tsx +1 -2
- package/src/primitives/dialog.tsx +7 -1
- package/src/primitives/input.tsx +15 -14
- package/src/primitives/segmented-control.tsx +5 -3
- package/src/primitives/select.tsx +3 -1
- package/src/primitives/switch.tsx +3 -1
- package/src/primitives/tabs.tsx +6 -4
- package/src/primitives/terminal-display.tsx +6 -8
- package/src/primitives/toast.tsx +5 -1
- package/src/tool-previews/command-preview.tsx +5 -1
- package/src/tool-previews/web-search-preview.tsx +2 -1
- package/src/utils/index.ts +7 -0
- package/dist/artifact-pane-DpuWcAiF.d.ts +0 -33
- package/dist/button-CMQuQEW_.d.ts +0 -17
- package/dist/chunk-34A66VBG.js +0 -214
- package/dist/chunk-3OI2QKFD.js +0 -0
- package/dist/chunk-3TI5DEHV.js +0 -299
- package/dist/chunk-4CLN43XT.js +0 -45
- package/dist/chunk-565EATZZ.js +0 -156
- package/dist/chunk-5Z5ZYMOJ.js +0 -0
- package/dist/chunk-6BGQA4BQ.js +0 -0
- package/dist/chunk-7YOCS6IF.js +0 -728
- package/dist/chunk-EOAX2ENE.js +0 -60
- package/dist/chunk-GYPQXTJU.js +0 -230
- package/dist/chunk-HFL6R6IF.js +0 -37
- package/dist/chunk-HGML45QH.js +0 -104
- package/dist/chunk-KNYSKNZX.js +0 -550
- package/dist/chunk-L57WRIND.js +0 -59
- package/dist/chunk-LDLOJKPC.js +0 -1585
- package/dist/chunk-LTEKI7YJ.js +0 -59
- package/dist/chunk-MKTSMWVD.js +0 -109
- package/dist/chunk-ND3ZFXQK.js +0 -179
- package/dist/chunk-NKDZ7GZE.js +0 -192
- package/dist/chunk-NWECTSOH.js +0 -589
- package/dist/chunk-OEX7NZE3.js +0 -321
- package/dist/chunk-Q56BYXQF.js +0 -61
- package/dist/chunk-REJESC5U.js +0 -117
- package/dist/chunk-RQGKSCEZ.js +0 -0
- package/dist/chunk-TN4FUEWY.js +0 -437
- package/dist/chunk-VPWY4I5O.js +0 -1048
- package/dist/chunk-WUQDUBJG.js +0 -168
- package/dist/chunk-XGKULLYE.js +0 -40
- package/dist/chunk-XU3HB5QL.js +0 -127
- package/dist/chunk-XYAIX4SU.js +0 -133
- package/dist/chunk-YJ6RE5WV.js +0 -875
- package/dist/chunk-YTO5V6QS.js +0 -19
- package/dist/chunk-ZKCEAIIZ.js +0 -1395
- package/dist/code-block-0kSpWMnf.d.ts +0 -25
- package/dist/document-editor-pane-EX25CEYW.js +0 -12
- package/dist/document-editor-pane-IAEQn_Dl.d.ts +0 -128
- package/dist/message-BHWbxBtT.d.ts +0 -15
- package/dist/parts-dj7AcUg0.d.ts +0 -36
- package/dist/run-PfLmDAox.d.ts +0 -41
- package/dist/tool-call-feed-D9iofJgW.d.ts +0 -46
|
@@ -29,6 +29,11 @@ async function freshEditor(load: () => Promise<EditorPeers.DocumentEditorPeers>)
|
|
|
29
29
|
return (await import("./markdown-document-editor")).MarkdownDocumentEditor;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// The first render imports tiptap for real. A cold import on a busy runner
|
|
33
|
+
// takes several seconds, well past waitFor's 1s and a test's 5s defaults.
|
|
34
|
+
const PEERS_LOADED = { timeout: 15_000 };
|
|
35
|
+
const LOADS_PEERS = 30_000;
|
|
36
|
+
|
|
32
37
|
afterEach(() => {
|
|
33
38
|
vi.doUnmock("./editor-peers");
|
|
34
39
|
vi.resetModules();
|
|
@@ -42,7 +47,7 @@ describe("MarkdownDocumentEditor", () => {
|
|
|
42
47
|
|
|
43
48
|
await waitFor(() => {
|
|
44
49
|
expect(container.querySelector(".ProseMirror")).not.toBeNull();
|
|
45
|
-
});
|
|
50
|
+
}, PEERS_LOADED);
|
|
46
51
|
|
|
47
52
|
const surface = container.querySelector(".ProseMirror");
|
|
48
53
|
expect(surface?.querySelector("h1")?.textContent).toBe("Title");
|
|
@@ -50,7 +55,7 @@ describe("MarkdownDocumentEditor", () => {
|
|
|
50
55
|
// StarterKit arrives through `peers.starterKit.default`. A broken hand-off
|
|
51
56
|
// leaves the document without the heading node that StarterKit configures.
|
|
52
57
|
expect(screen.getByTitle(/^Bold/)).toBeInTheDocument();
|
|
53
|
-
});
|
|
58
|
+
}, LOADS_PEERS);
|
|
54
59
|
|
|
55
60
|
it("holds a placeholder while the peers are still loading", async () => {
|
|
56
61
|
let releasePeers: (() => void) | null = null;
|
|
@@ -85,7 +90,7 @@ describe("MarkdownDocumentEditor", () => {
|
|
|
85
90
|
|
|
86
91
|
await waitFor(() => {
|
|
87
92
|
expect(container.querySelector(".ProseMirror")).not.toBeNull();
|
|
88
|
-
});
|
|
93
|
+
}, PEERS_LOADED);
|
|
89
94
|
|
|
90
95
|
// The editor reports once when it parses the initial content. What must
|
|
91
96
|
// not grow is the count across a value-prop write: pushing that back
|
|
@@ -98,7 +103,7 @@ describe("MarkdownDocumentEditor", () => {
|
|
|
98
103
|
expect(container.querySelector(".ProseMirror h1")?.textContent).toBe("Changed");
|
|
99
104
|
});
|
|
100
105
|
expect(onChange).toHaveBeenCalledTimes(callsAfterMount);
|
|
101
|
-
});
|
|
106
|
+
}, LOADS_PEERS);
|
|
102
107
|
|
|
103
108
|
it("fails with the install list when the peers cannot be loaded", async () => {
|
|
104
109
|
const missing = "install @tiptap/react and @tiptap/starter-kit";
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type DocumentEditorMode,
|
|
6
6
|
type DocumentEditorPaneCollaborationConfig,
|
|
7
7
|
} from "../editor/document-editor-pane";
|
|
8
|
+
import { focusRing } from "../lib/focus";
|
|
8
9
|
import { ArtifactPane, type ArtifactPaneProps } from "../primitives/artifact-pane";
|
|
9
10
|
import { detectFileFormat } from "./file-format";
|
|
10
11
|
import { FilePreview, type FilePreviewProps } from "./file-preview";
|
|
@@ -99,7 +100,7 @@ export function FileArtifactPane({
|
|
|
99
100
|
type="button"
|
|
100
101
|
aria-label={`Download ${filename}`}
|
|
101
102
|
onClick={onDownload}
|
|
102
|
-
className=
|
|
103
|
+
className={`rounded-[var(--radius-sm)] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`}
|
|
103
104
|
>
|
|
104
105
|
<Download className="h-4 w-4" />
|
|
105
106
|
</button>
|
|
@@ -109,7 +110,7 @@ export function FileArtifactPane({
|
|
|
109
110
|
type="button"
|
|
110
111
|
aria-label={`Close ${filename}`}
|
|
111
112
|
onClick={onClose}
|
|
112
|
-
className=
|
|
113
|
+
className={`rounded-[var(--radius-sm)] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`}
|
|
113
114
|
>
|
|
114
115
|
<X className="h-4 w-4" />
|
|
115
116
|
</button>
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
X,
|
|
23
23
|
type LucideIcon,
|
|
24
24
|
} from "lucide-react";
|
|
25
|
+
import { focusRingInset } from "../lib/focus";
|
|
25
26
|
import { cn } from "../lib/utils";
|
|
26
27
|
import { Button } from "../primitives/button";
|
|
27
28
|
import { Markdown } from "../markdown/markdown";
|
|
@@ -219,7 +220,8 @@ function ImagePreview({ src, filename }: { src: string; filename: string }) {
|
|
|
219
220
|
aria-pressed={naturalSize}
|
|
220
221
|
aria-label={naturalSize ? "Fit image to pane" : "Show image at natural size"}
|
|
221
222
|
className={cn(
|
|
222
|
-
"flex items-center justify-center
|
|
223
|
+
"flex items-center justify-center",
|
|
224
|
+
focusRingInset,
|
|
223
225
|
naturalSize ? "m-auto cursor-zoom-out" : "h-full w-full cursor-zoom-in p-4",
|
|
224
226
|
)}
|
|
225
227
|
>
|
package/src/files/file-tabs.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { X, FileText, FileCode, FileSpreadsheet } from "lucide-react";
|
|
6
|
+
import { focusRing, focusRingInset } from "../lib/focus";
|
|
6
7
|
import { cn } from "../lib/utils";
|
|
7
8
|
|
|
8
9
|
export interface FileTabData {
|
|
@@ -49,7 +50,10 @@ export function FileTabs({ tabs, activeId, onSelect, onClose, className }: FileT
|
|
|
49
50
|
<button
|
|
50
51
|
type="button"
|
|
51
52
|
onClick={() => onSelect(tab.id)}
|
|
52
|
-
className=
|
|
53
|
+
className={cn(
|
|
54
|
+
"flex min-w-0 items-center gap-1.5 px-3 py-1.5 text-xs transition-colors hover:text-foreground",
|
|
55
|
+
focusRingInset,
|
|
56
|
+
)}
|
|
53
57
|
>
|
|
54
58
|
<Icon className="h-3 w-3 shrink-0" />
|
|
55
59
|
<span className="max-w-[120px] truncate">{tab.name}</span>
|
|
@@ -59,7 +63,10 @@ export function FileTabs({ tabs, activeId, onSelect, onClose, className }: FileT
|
|
|
59
63
|
type="button"
|
|
60
64
|
aria-label={`Close ${tab.name}`}
|
|
61
65
|
onClick={() => onClose(tab.id)}
|
|
62
|
-
className=
|
|
66
|
+
className={cn(
|
|
67
|
+
"mr-1 rounded p-0.5 opacity-0 transition-opacity hover:bg-accent focus-visible:opacity-100 group-hover:opacity-100",
|
|
68
|
+
focusRing,
|
|
69
|
+
)}
|
|
63
70
|
>
|
|
64
71
|
<X className="h-2.5 w-2.5" />
|
|
65
72
|
</button>
|
package/src/lib/focus.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The focus treatment every control shares, in two shapes over one palette
|
|
3
|
+
* (`--focus-border`, `--focus-halo` and `--border-strong` from
|
|
4
|
+
* `@tangle-network/brand`'s tokens.css).
|
|
5
|
+
*
|
|
6
|
+
* A FIELD — input, textarea, select trigger, composer — shows focus on its own
|
|
7
|
+
* border: the hairline shifts to `--focus-border` and a 3px `--focus-halo` ring
|
|
8
|
+
* spreads outside it. It keys off `:focus`, because a caret in a field needs the
|
|
9
|
+
* cue whether the field was clicked or tabbed into.
|
|
10
|
+
*
|
|
11
|
+
* A CONTROL — button, tab, switch, link, card — has no border to shift, so it
|
|
12
|
+
* draws a 1px `--focus-border` outline with the same 3px halo beyond it. It keys
|
|
13
|
+
* off `:focus-visible`, so a mouse click leaves no ring.
|
|
14
|
+
*
|
|
15
|
+
* The halo is a Tailwind ring, which renders as box-shadow: it follows
|
|
16
|
+
* border-radius and never moves layout, and a consumer removes it with
|
|
17
|
+
* `focus:ring-0` / `focus-visible:ring-0`, which tailwind-merge resolves against
|
|
18
|
+
* these classes. The control's line is an outline so that forced-colors mode,
|
|
19
|
+
* which drops box-shadows, still paints it. A field has its border for that, so
|
|
20
|
+
* it hides the outline with `outline-hidden`, which stays transparent-but-present
|
|
21
|
+
* in forced-colors mode.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resting hairline, neutral hover, and `:focus` border plus halo for a text
|
|
26
|
+
* field. The hover is a plain `hover:` class so a consumer's `hover:border-*`
|
|
27
|
+
* replaces it through tailwind-merge; the focus border outranks it because
|
|
28
|
+
* Tailwind emits `focus:` after `hover:`.
|
|
29
|
+
*/
|
|
30
|
+
export const focusField =
|
|
31
|
+
"border-border transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--border-strong)] focus:outline-hidden focus:border-[var(--focus-border)] focus:ring-3 focus:ring-[var(--focus-halo)]";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The field treatment for a container that wraps a borderless field, such as a
|
|
35
|
+
* composer around a textarea and its buttons. It lights while a FIELD inside
|
|
36
|
+
* has focus: a text input, textarea, native select or contenteditable editor. A
|
|
37
|
+
* focused button, checkbox or button-type input inside the container shows only
|
|
38
|
+
* its own ring, so a click on the send button does not read as field focus and
|
|
39
|
+
* a tab to it does not stack two indicators. `:focus-within` cannot tell the
|
|
40
|
+
* two apart, so the rule keys off `:has()`; a consumer's `focus-within:*` class
|
|
41
|
+
* therefore does not replace it. To style that container's focus differently,
|
|
42
|
+
* compose your own classes instead of this helper. The field selector is
|
|
43
|
+
* written out in full because Tailwind reads class names statically.
|
|
44
|
+
*/
|
|
45
|
+
export const focusFieldWithin =
|
|
46
|
+
"border-border transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--border-strong)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:border-[var(--focus-border)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-3 has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-[var(--focus-halo)]";
|
|
47
|
+
|
|
48
|
+
/** Error state for a field: a danger hairline and the same focus shape in danger. Compose after `focusField`. */
|
|
49
|
+
export const focusFieldInvalid =
|
|
50
|
+
"border-[var(--surface-danger-border)] hover:border-[var(--surface-danger-border)] focus:border-[var(--focus-border-danger)] focus:ring-[var(--focus-halo-danger)]";
|
|
51
|
+
|
|
52
|
+
const focusRingBase =
|
|
53
|
+
"focus-visible:outline-1 focus-visible:outline-[var(--focus-border)] focus-visible:ring-4 focus-visible:ring-[var(--focus-halo)]";
|
|
54
|
+
|
|
55
|
+
/** Keyboard-only focus for a control: a 1px focus line with the halo beyond it. */
|
|
56
|
+
export const focusRing = `${focusRingBase} focus-visible:outline-offset-0`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `focusRing` drawn inside the box, for a control flush against a scroll
|
|
60
|
+
* container or its neighbours (a tab strip, a list row), where an outer ring
|
|
61
|
+
* would be clipped or would overlap them.
|
|
62
|
+
*/
|
|
63
|
+
export const focusRingInset = `${focusRingBase} focus-visible:-outline-offset-1 focus-visible:ring-inset`;
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "react";
|
|
10
10
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
|
11
11
|
import { Check, Copy } from "lucide-react";
|
|
12
|
+
import { focusRing } from "../lib/focus";
|
|
12
13
|
import { cn } from "../lib/utils";
|
|
13
14
|
|
|
14
15
|
// Theme-aware syntax highlighting — reads CSS custom properties at render time.
|
|
@@ -191,7 +192,10 @@ export const CopyButton = memo(({ text }: { text: string }) => {
|
|
|
191
192
|
return (
|
|
192
193
|
<button
|
|
193
194
|
onClick={handleCopy}
|
|
194
|
-
className=
|
|
195
|
+
className={cn(
|
|
196
|
+
"flex items-center justify-center w-6 h-6 rounded-md bg-muted border border-border hover:border-[var(--border-strong)] transition-colors",
|
|
197
|
+
focusRing,
|
|
198
|
+
)}
|
|
195
199
|
title="Copy to clipboard"
|
|
196
200
|
>
|
|
197
201
|
{copied ? (
|
package/src/primitives/badge.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
2
|
import type * as React from "react";
|
|
3
|
+
import { focusRing } from "../lib/focus";
|
|
3
4
|
import { cn } from "../lib/utils";
|
|
4
5
|
|
|
5
6
|
const badgeVariants = cva(
|
|
6
|
-
|
|
7
|
+
`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 font-medium text-xs transition-colors ${focusRing}`,
|
|
7
8
|
{
|
|
8
9
|
variants: {
|
|
9
10
|
variant: {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Slot } from "@radix-ui/react-slot";
|
|
2
2
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { focusRing } from "../lib/focus";
|
|
4
5
|
import { cn } from "../lib/utils";
|
|
5
6
|
|
|
6
7
|
const buttonVariants = cva(
|
|
7
|
-
|
|
8
|
+
`inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium text-sm transition-all ${focusRing} disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`,
|
|
8
9
|
{
|
|
9
10
|
variants: {
|
|
10
11
|
variant: {
|
package/src/primitives/card.tsx
CHANGED
|
@@ -22,8 +22,7 @@ const Card = React.forwardRef<
|
|
|
22
22
|
"rounded-[var(--radius-lg)] border text-card-foreground transition-[border-color,box-shadow]",
|
|
23
23
|
"duration-[var(--transition-default)]",
|
|
24
24
|
variants[variant],
|
|
25
|
-
hover &&
|
|
26
|
-
"cursor-pointer hover:border-primary/30",
|
|
25
|
+
hover && "cursor-pointer hover:border-[var(--border-strong)]",
|
|
27
26
|
className,
|
|
28
27
|
)}
|
|
29
28
|
{...props}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
2
|
import { X } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { focusRing } from "../lib/focus";
|
|
4
5
|
import { cn } from "../lib/utils";
|
|
5
6
|
|
|
6
7
|
const Dialog = DialogPrimitive.Root;
|
|
@@ -58,7 +59,12 @@ const DialogContent = React.forwardRef<
|
|
|
58
59
|
{...props}
|
|
59
60
|
>
|
|
60
61
|
{children}
|
|
61
|
-
<DialogPrimitive.Close
|
|
62
|
+
<DialogPrimitive.Close
|
|
63
|
+
className={cn(
|
|
64
|
+
"absolute top-4 right-4 rounded-sm opacity-70 transition-[opacity,box-shadow] hover:opacity-100 focus-visible:opacity-100 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
|
|
65
|
+
focusRing,
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
62
68
|
<X className="h-4 w-4" />
|
|
63
69
|
<span className="sr-only">Close</span>
|
|
64
70
|
</DialogPrimitive.Close>
|
package/src/primitives/input.tsx
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { focusField, focusFieldInvalid } from "../lib/focus";
|
|
2
3
|
import { cn } from "../lib/utils";
|
|
3
4
|
|
|
4
5
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
6
|
|
|
6
7
|
const inputVariants = cva(
|
|
7
|
-
|
|
8
|
+
cn(
|
|
9
|
+
"flex w-full rounded-lg border bg-card px-4 py-2 text-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 file:border-0 file:bg-transparent file:font-medium file:text-sm",
|
|
10
|
+
focusField,
|
|
11
|
+
),
|
|
8
12
|
{
|
|
9
13
|
variants: {
|
|
14
|
+
// `default` and `sandbox` render the same field; both names stay valid.
|
|
10
15
|
variant: {
|
|
11
|
-
default: "
|
|
12
|
-
sandbox: "
|
|
13
|
-
error:
|
|
16
|
+
default: "",
|
|
17
|
+
sandbox: "",
|
|
18
|
+
error: focusFieldInvalid,
|
|
14
19
|
},
|
|
15
20
|
size: {
|
|
16
21
|
default: "h-11",
|
|
@@ -75,6 +80,7 @@ Input.displayName = "Input";
|
|
|
75
80
|
|
|
76
81
|
export interface TextareaProps
|
|
77
82
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
83
|
+
/** Both values render the same field. */
|
|
78
84
|
variant?: "default" | "sandbox";
|
|
79
85
|
label?: string;
|
|
80
86
|
error?: string;
|
|
@@ -83,26 +89,21 @@ export interface TextareaProps
|
|
|
83
89
|
|
|
84
90
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
85
91
|
(
|
|
86
|
-
|
|
92
|
+
// `variant` is taken out so it never reaches the DOM element.
|
|
93
|
+
{ className, variant: _variant, label, error, hint, id, ...props },
|
|
87
94
|
ref,
|
|
88
95
|
) => {
|
|
89
96
|
const textareaId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
|
|
90
97
|
|
|
91
|
-
const variants = {
|
|
92
|
-
default: "border-input focus:ring-ring",
|
|
93
|
-
sandbox:
|
|
94
|
-
"border-border focus:border-[var(--border-accent-hover)] focus:ring-[var(--border-accent)]",
|
|
95
|
-
};
|
|
96
|
-
|
|
97
98
|
const textarea = (
|
|
98
99
|
<textarea
|
|
99
100
|
id={textareaId}
|
|
100
101
|
className={cn(
|
|
101
|
-
"flex min-h-[120px] w-full resize-y rounded-lg border bg-card px-4 py-3 text-sm
|
|
102
|
+
"flex min-h-[120px] w-full resize-y rounded-lg border bg-card px-4 py-3 text-sm",
|
|
102
103
|
"placeholder:text-muted-foreground",
|
|
103
|
-
"focus:outline-none focus:ring-2 focus:ring-offset-0",
|
|
104
104
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
105
|
-
|
|
105
|
+
focusField,
|
|
106
|
+
error && focusFieldInvalid,
|
|
106
107
|
className,
|
|
107
108
|
)}
|
|
108
109
|
ref={ref}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
declare const process: { env: { NODE_ENV?: string } } | undefined
|
|
4
4
|
|
|
5
5
|
import * as React from "react"
|
|
6
|
+
import { focusRing, focusRingInset } from "../lib/focus"
|
|
6
7
|
import { cn } from "../lib/utils"
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -120,9 +121,10 @@ export function SegmentedControl<T extends string = string>({
|
|
|
120
121
|
if (!active) onValueChange(option.value)
|
|
121
122
|
}}
|
|
122
123
|
className={cn(
|
|
123
|
-
"relative inline-flex items-center gap-2 whitespace-nowrap text-sm transition-
|
|
124
|
-
variant === "row" && "rounded-md px-3 py-1.5",
|
|
125
|
-
|
|
124
|
+
"relative inline-flex items-center gap-2 whitespace-nowrap text-sm transition-[color,background-color,border-color,box-shadow]",
|
|
125
|
+
variant === "row" && ["rounded-md px-3 py-1.5", focusRing],
|
|
126
|
+
// The strip scrolls horizontally, which would clip an outer ring.
|
|
127
|
+
variant === "tabs" && ["rounded-none border-b-2 -mb-px px-4 py-2", focusRingInset],
|
|
126
128
|
// Active styling is the ONLY styled state — unselected
|
|
127
129
|
// segments stay transparent on purpose so they don't
|
|
128
130
|
// compete visually with the selection.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
4
4
|
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { focusField } from "../lib/focus";
|
|
6
7
|
import { cn } from "../lib/utils";
|
|
7
8
|
|
|
8
9
|
const Select = SelectPrimitive.Root;
|
|
@@ -18,7 +19,8 @@ const SelectTrigger = React.forwardRef<
|
|
|
18
19
|
<SelectPrimitive.Trigger
|
|
19
20
|
ref={ref}
|
|
20
21
|
className={cn(
|
|
21
|
-
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border
|
|
22
|
+
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-card px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
|
23
|
+
focusField,
|
|
22
24
|
className,
|
|
23
25
|
)}
|
|
24
26
|
{...props}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
|
4
4
|
import * as React from "react";
|
|
5
|
+
import { focusRing } from "../lib/focus";
|
|
5
6
|
import { cn } from "../lib/utils";
|
|
6
7
|
|
|
7
8
|
const Switch = React.forwardRef<
|
|
@@ -10,7 +11,8 @@ const Switch = React.forwardRef<
|
|
|
10
11
|
>(({ className, ...props }, ref) => (
|
|
11
12
|
<SwitchPrimitives.Root
|
|
12
13
|
className={cn(
|
|
13
|
-
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-
|
|
14
|
+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-[background-color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
|
15
|
+
focusRing,
|
|
14
16
|
className,
|
|
15
17
|
)}
|
|
16
18
|
{...props}
|
package/src/primitives/tabs.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { focusRing } from "../lib/focus";
|
|
3
4
|
import { cn } from "../lib/utils";
|
|
4
5
|
|
|
5
6
|
const Tabs = TabsPrimitive.Root;
|
|
@@ -35,11 +36,11 @@ const TabsTrigger = React.forwardRef<
|
|
|
35
36
|
>(({ className, variant = "default", ...props }, ref) => {
|
|
36
37
|
const variants = {
|
|
37
38
|
default:
|
|
38
|
-
|
|
39
|
+
`inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium transition-all ${focusRing} disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm`,
|
|
39
40
|
pills:
|
|
40
|
-
|
|
41
|
+
`inline-flex items-center justify-center whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium transition-all ${focusRing} disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:bg-muted`,
|
|
41
42
|
underline:
|
|
42
|
-
|
|
43
|
+
`inline-flex items-center justify-center whitespace-nowrap rounded-t-md pb-3 text-sm font-medium transition-all ${focusRing} disabled:pointer-events-none disabled:opacity-50 border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:text-foreground data-[state=inactive]:text-muted-foreground`,
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
return (
|
|
@@ -59,7 +60,8 @@ const TabsContent = React.forwardRef<
|
|
|
59
60
|
<TabsPrimitive.Content
|
|
60
61
|
ref={ref}
|
|
61
62
|
className={cn(
|
|
62
|
-
"mt-4
|
|
63
|
+
"mt-4",
|
|
64
|
+
focusRing,
|
|
63
65
|
"data-[state=active]:fade-in-0 data-[state=active]:slide-in-from-bottom-2 data-[state=active]:animate-in",
|
|
64
66
|
className,
|
|
65
67
|
)}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
|
+
import { focusFieldWithin } from "../lib/focus";
|
|
3
4
|
import { cn } from "../lib/utils";
|
|
4
5
|
|
|
5
6
|
export interface TerminalDisplayProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -142,11 +143,13 @@ TerminalCursor.displayName = "TerminalCursor";
|
|
|
142
143
|
export interface TerminalInputProps
|
|
143
144
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onSubmit"> {
|
|
144
145
|
onSubmit?: (value: string) => void;
|
|
146
|
+
/** Both values render the same field. */
|
|
145
147
|
variant?: "default" | "sandbox";
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
const TerminalInput = React.forwardRef<HTMLInputElement, TerminalInputProps>(
|
|
149
|
-
|
|
151
|
+
// `variant` is taken out so it never reaches the DOM element.
|
|
152
|
+
({ className, onSubmit, variant: _variant, ...props }, ref) => {
|
|
150
153
|
const [value, setValue] = React.useState("");
|
|
151
154
|
|
|
152
155
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
@@ -156,16 +159,11 @@ const TerminalInput = React.forwardRef<HTMLInputElement, TerminalInputProps>(
|
|
|
156
159
|
}
|
|
157
160
|
};
|
|
158
161
|
|
|
159
|
-
const variants = {
|
|
160
|
-
default: "border-border focus-within:border-border",
|
|
161
|
-
sandbox: "border-border focus-within:border-[var(--border-accent-hover)]",
|
|
162
|
-
};
|
|
163
|
-
|
|
164
162
|
return (
|
|
165
163
|
<div
|
|
166
164
|
className={cn(
|
|
167
|
-
"flex items-center rounded-lg border bg-background px-4 py-2.5 font-mono text-sm
|
|
168
|
-
|
|
165
|
+
"flex items-center rounded-lg border bg-background px-4 py-2.5 font-mono text-sm",
|
|
166
|
+
focusFieldWithin,
|
|
169
167
|
className,
|
|
170
168
|
)}
|
|
171
169
|
>
|
package/src/primitives/toast.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
X,
|
|
10
10
|
} from "lucide-react";
|
|
11
11
|
import * as React from "react";
|
|
12
|
+
import { focusRing } from "../lib/focus";
|
|
12
13
|
import { cn } from "../lib/utils";
|
|
13
14
|
|
|
14
15
|
const toastVariants = cva(
|
|
@@ -76,7 +77,10 @@ function ToastComponent({
|
|
|
76
77
|
<button
|
|
77
78
|
type="button"
|
|
78
79
|
onClick={() => onDismiss(id)}
|
|
79
|
-
className=
|
|
80
|
+
className={cn(
|
|
81
|
+
"shrink-0 rounded-md p-1 opacity-70 transition-[opacity,box-shadow] hover:opacity-100 focus-visible:opacity-100",
|
|
82
|
+
focusRing,
|
|
83
|
+
)}
|
|
80
84
|
aria-label="Dismiss notification"
|
|
81
85
|
>
|
|
82
86
|
<X className="h-4 w-4" />
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ChevronRight,
|
|
6
6
|
Loader2,
|
|
7
7
|
} from "lucide-react";
|
|
8
|
+
import { focusRing } from "../lib/focus";
|
|
8
9
|
import { cn } from "../lib/utils";
|
|
9
10
|
import type { ToolPart } from "../types/parts";
|
|
10
11
|
import { PreviewCard, PreviewError, PreviewLoading } from "./preview-primitives";
|
|
@@ -76,7 +77,10 @@ export const CommandPreview = memo(({ part }: CommandPreviewProps) => {
|
|
|
76
77
|
>
|
|
77
78
|
<button
|
|
78
79
|
onClick={() => setExpanded((value) => !value)}
|
|
79
|
-
className=
|
|
80
|
+
className={cn(
|
|
81
|
+
"flex w-full items-center gap-2 rounded-[var(--radius-md)] border border-border bg-muted px-3 py-2 text-left transition-colors hover:border-[var(--border-strong)] hover:bg-accent/45",
|
|
82
|
+
focusRing,
|
|
83
|
+
)}
|
|
80
84
|
>
|
|
81
85
|
<code className="min-w-0 flex-1 truncate text-xs font-mono text-foreground">
|
|
82
86
|
{command}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { memo, useMemo } from "react";
|
|
2
2
|
import { Globe } from "lucide-react";
|
|
3
|
+
import { focusRing } from "../lib/focus";
|
|
3
4
|
import type { ToolPart } from "../types/parts";
|
|
4
5
|
import { PreviewCard, PreviewEmpty, PreviewError, PreviewLoading } from "./preview-primitives";
|
|
5
6
|
|
|
@@ -94,7 +95,7 @@ export const WebSearchPreview = memo(({ part }: WebSearchPreviewProps) => {
|
|
|
94
95
|
href={result.url}
|
|
95
96
|
target="_blank"
|
|
96
97
|
rel="noreferrer"
|
|
97
|
-
className=
|
|
98
|
+
className={`block rounded-[var(--radius-md)] border border-border bg-muted/55 px-3 py-3 transition-colors hover:border-[var(--border-strong)] hover:bg-accent/50 ${focusRing}`}
|
|
98
99
|
>
|
|
99
100
|
<div className="text-sm font-medium text-foreground">
|
|
100
101
|
{result.title}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
interface ArtifactPaneProps {
|
|
5
|
-
eyebrow?: ReactNode;
|
|
6
|
-
title: ReactNode;
|
|
7
|
-
subtitle?: ReactNode;
|
|
8
|
-
meta?: ReactNode;
|
|
9
|
-
headerActions?: ReactNode;
|
|
10
|
-
tabs?: ReactNode;
|
|
11
|
-
toolbar?: ReactNode;
|
|
12
|
-
footer?: ReactNode;
|
|
13
|
-
emptyState?: ReactNode;
|
|
14
|
-
children?: ReactNode;
|
|
15
|
-
className?: string;
|
|
16
|
-
contentClassName?: string;
|
|
17
|
-
/** Extra classes for the header band (e.g. to align its surface with the host chrome). */
|
|
18
|
-
headerClassName?: string;
|
|
19
|
-
/**
|
|
20
|
-
* Suppress the eyebrow/title/subtitle/meta block. Use when the host already
|
|
21
|
-
* shows the artifact's identity elsewhere (e.g. a surrounding path row), so the
|
|
22
|
-
* pane keeps only its tabs, toolbar, and header actions. The whole header row
|
|
23
|
-
* collapses when nothing else would render in it.
|
|
24
|
-
*/
|
|
25
|
-
hideTitleBlock?: boolean;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* ArtifactPane — shared frame for files, previews, documents, inspectors, and
|
|
29
|
-
* other artifact-like surfaces inside sandbox applications.
|
|
30
|
-
*/
|
|
31
|
-
declare function ArtifactPane({ eyebrow, title, subtitle, meta, headerActions, tabs, toolbar, footer, emptyState, children, className, contentClassName, headerClassName, hideTitleBlock, }: ArtifactPaneProps): react_jsx_runtime.JSX.Element;
|
|
32
|
-
|
|
33
|
-
export { ArtifactPane as A, type ArtifactPaneProps as a };
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
|
|
4
|
-
declare const buttonVariants: (props?: ({
|
|
5
|
-
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "sandbox" | null | undefined;
|
|
6
|
-
size?: "default" | "sm" | "lg" | "xl" | "icon" | null | undefined;
|
|
7
|
-
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
8
|
-
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
9
|
-
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "sandbox" | null;
|
|
10
|
-
size?: "default" | "sm" | "lg" | "xl" | "icon" | null;
|
|
11
|
-
asChild?: boolean;
|
|
12
|
-
loading?: boolean;
|
|
13
|
-
children?: React.ReactNode;
|
|
14
|
-
}
|
|
15
|
-
declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
16
|
-
|
|
17
|
-
export { Button as B, type ButtonProps as a, buttonVariants as b };
|