@tangle-network/ui 11.16.9 → 11.16.11
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
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 11.16.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cbfa303: A terminal output whose height cap hides lines ends in a "Show all stdout (N lines)" control that shows the rest in place, so a region that stops flush with its frame no longer hides that more output exists.
|
|
8
|
+
|
|
9
|
+
## 11.16.10
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 06b665d: A closed terminal command is two lines of plain text that end in an ellipsis when cut, with the whole command in its title, so a reader can see that text is missing. Terminal output wraps on every width again: scrolling it sideways on a phone clipped columns with no sign that more was there.
|
|
14
|
+
|
|
3
15
|
## 11.16.9
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -19,10 +19,6 @@ export interface CommandPreviewProps {
|
|
|
19
19
|
* dark terminal. It scopes the dark token set onto itself with
|
|
20
20
|
* `data-theme="dark"`, so stderr, the badges and the muted prompt keep the
|
|
21
21
|
* contrast they were designed with on a dark ground, in a light console too.
|
|
22
|
-
*
|
|
23
|
-
* On a phone the output keeps its own lines and scrolls sideways inside the
|
|
24
|
-
* block: wrapped output breaks the columns of a listing and splits hashes
|
|
25
|
-
* and URLs mid-token. The command still wraps between its tokens.
|
|
26
22
|
*/
|
|
27
23
|
export declare const CommandPreview: import("react").MemoExoticComponent<({ part, defaultExpanded, className }: CommandPreviewProps) => import("react").JSX.Element>;
|
|
28
24
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { focusRing } from "../lib/focus.js";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
-
import { memo, useId, useState } from "react";
|
|
3
|
+
import { memo, useId, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { ChevronDown, ChevronRight, Loader2 } from "lucide-react";
|
|
6
6
|
//#region src/tool-previews/command-preview.tsx
|
|
@@ -138,20 +138,55 @@ function ExitBadge({ output, status }) {
|
|
|
138
138
|
children: label
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
|
-
|
|
142
|
-
* A command split at its whitespace, each token in an inline box. Browsers
|
|
143
|
-
* break after a hyphen, which split `--short` into `--` and `short` on a
|
|
144
|
-
* phone. A box moves to the next line whole, and breaks inside only when it
|
|
145
|
-
* is wider than the line (a full SHA). The text is unchanged, so a copy still
|
|
146
|
-
* pastes the exact command.
|
|
147
|
-
*/
|
|
148
|
-
function CommandText({ command, all }) {
|
|
141
|
+
function CommandText({ command }) {
|
|
149
142
|
const tokens = command.split(/(\s+)/);
|
|
150
|
-
|
|
151
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [tokens.slice(0, limit).map((token, i) => token.length > 0 && !/\s/.test(token) ? /* @__PURE__ */ jsx("span", {
|
|
143
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: tokens.map((token, i) => token.length > 0 && !/\s/.test(token) ? /* @__PURE__ */ jsx("span", {
|
|
152
144
|
className: "inline-block max-w-full [overflow-wrap:anywhere]",
|
|
153
145
|
children: token
|
|
154
|
-
}, i) : token)
|
|
146
|
+
}, i) : token) });
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* One output stream, capped at a readable height. When the cap hides lines,
|
|
150
|
+
* a control says so and shows the rest in place: a region that ends flush
|
|
151
|
+
* with its frame gave no sign that more output was there.
|
|
152
|
+
*/
|
|
153
|
+
function OutputRegion({ label, text, className }) {
|
|
154
|
+
const ref = useRef(null);
|
|
155
|
+
const [clipped, setClipped] = useState(false);
|
|
156
|
+
const [full, setFull] = useState(false);
|
|
157
|
+
useLayoutEffect(() => {
|
|
158
|
+
const el = ref.current;
|
|
159
|
+
if (!el || full) return;
|
|
160
|
+
const check = () => setClipped(el.scrollHeight > el.clientHeight + 1);
|
|
161
|
+
check();
|
|
162
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
163
|
+
const observer = new ResizeObserver(check);
|
|
164
|
+
observer.observe(el);
|
|
165
|
+
return () => observer.disconnect();
|
|
166
|
+
}, [full]);
|
|
167
|
+
const lines = text.replace(/\n$/, "").split("\n").length;
|
|
168
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
169
|
+
className,
|
|
170
|
+
children: [/* @__PURE__ */ jsx("pre", {
|
|
171
|
+
ref,
|
|
172
|
+
tabIndex: 0,
|
|
173
|
+
role: "region",
|
|
174
|
+
"aria-label": label,
|
|
175
|
+
className: cn("overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] px-3 py-2 leading-relaxed", full ? "" : "max-h-80", focusRing),
|
|
176
|
+
children: text
|
|
177
|
+
}), clipped && !full ? /* @__PURE__ */ jsxs("button", {
|
|
178
|
+
type: "button",
|
|
179
|
+
onClick: () => setFull(true),
|
|
180
|
+
className: cn("w-full border-t border-white/10 px-3 py-1.5 text-left font-sans text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground", focusRing),
|
|
181
|
+
children: [
|
|
182
|
+
"Show all ",
|
|
183
|
+
label,
|
|
184
|
+
" (",
|
|
185
|
+
lines,
|
|
186
|
+
" lines)"
|
|
187
|
+
]
|
|
188
|
+
}) : null]
|
|
189
|
+
});
|
|
155
190
|
}
|
|
156
191
|
/**
|
|
157
192
|
* One shell command as a terminal block: a `$ command` prompt line with its
|
|
@@ -162,10 +197,6 @@ function CommandText({ command, all }) {
|
|
|
162
197
|
* dark terminal. It scopes the dark token set onto itself with
|
|
163
198
|
* `data-theme="dark"`, so stderr, the badges and the muted prompt keep the
|
|
164
199
|
* contrast they were designed with on a dark ground, in a light console too.
|
|
165
|
-
*
|
|
166
|
-
* On a phone the output keeps its own lines and scrolls sideways inside the
|
|
167
|
-
* block: wrapped output breaks the columns of a listing and splits hashes
|
|
168
|
-
* and URLs mid-token. The command still wraps between its tokens.
|
|
169
200
|
*/
|
|
170
201
|
const CommandPreview = memo(({ part, defaultExpanded = false, className }) => {
|
|
171
202
|
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
@@ -183,11 +214,9 @@ const CommandPreview = memo(({ part, defaultExpanded = false, className }) => {
|
|
|
183
214
|
children: "$"
|
|
184
215
|
}),
|
|
185
216
|
/* @__PURE__ */ jsx("code", {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
all: expanded
|
|
190
|
-
})
|
|
217
|
+
title: expanded ? void 0 : command,
|
|
218
|
+
className: cn("min-w-0 flex-1 whitespace-pre-wrap [overflow-wrap:anywhere]", expanded ? "" : "line-clamp-2"),
|
|
219
|
+
children: expanded ? /* @__PURE__ */ jsx(CommandText, { command }) : command
|
|
191
220
|
}),
|
|
192
221
|
/* @__PURE__ */ jsx("span", {
|
|
193
222
|
className: "flex shrink-0 max-sm:order-last max-sm:basis-full max-sm:pl-4",
|
|
@@ -222,26 +251,20 @@ const CommandPreview = memo(({ part, defaultExpanded = false, className }) => {
|
|
|
222
251
|
id: bodyId,
|
|
223
252
|
className: "border-t border-white/10",
|
|
224
253
|
children: [
|
|
225
|
-
output.stdout ? /* @__PURE__ */ jsx(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
className: cn("max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] max-sm:whitespace-pre max-sm:[overflow-wrap:normal] px-3 py-2 leading-relaxed", focusRing),
|
|
230
|
-
children: output.stdout
|
|
254
|
+
output.stdout ? /* @__PURE__ */ jsx(OutputRegion, {
|
|
255
|
+
label: "stdout",
|
|
256
|
+
text: output.stdout,
|
|
257
|
+
className: ""
|
|
231
258
|
}) : null,
|
|
232
|
-
output.stderr ? /* @__PURE__ */ jsx(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"
|
|
236
|
-
className: cn("max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] max-sm:whitespace-pre max-sm:[overflow-wrap:normal] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]", focusRing),
|
|
237
|
-
children: output.stderr
|
|
259
|
+
output.stderr ? /* @__PURE__ */ jsx(OutputRegion, {
|
|
260
|
+
label: "stderr",
|
|
261
|
+
text: output.stderr,
|
|
262
|
+
className: "border-t border-white/10 text-[var(--surface-danger-text)]"
|
|
238
263
|
}) : null,
|
|
239
|
-
errorText ? /* @__PURE__ */ jsx(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
"
|
|
243
|
-
className: cn("max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] max-sm:whitespace-pre max-sm:[overflow-wrap:normal] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]", focusRing),
|
|
244
|
-
children: errorText
|
|
264
|
+
errorText ? /* @__PURE__ */ jsx(OutputRegion, {
|
|
265
|
+
label: "error",
|
|
266
|
+
text: errorText,
|
|
267
|
+
className: "border-t border-white/10 text-[var(--surface-danger-text)]"
|
|
245
268
|
}) : null
|
|
246
269
|
]
|
|
247
270
|
}) : null]
|
package/package.json
CHANGED
|
@@ -30,8 +30,9 @@ describe("CommandPreview", () => {
|
|
|
30
30
|
expect(screen.getByText("exit 2").className).toContain("surface-danger");
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it("boxes each token so a phone wraps between flags", () => {
|
|
33
|
+
it("boxes each token of an opened command so a phone wraps between flags", () => {
|
|
34
34
|
render(<CommandPreview part={part({ output: "M a" })} />);
|
|
35
|
+
fireEvent.click(screen.getByRole("button"));
|
|
35
36
|
const flag = screen.getByText("--short");
|
|
36
37
|
expect(flag.tagName).toBe("SPAN");
|
|
37
38
|
expect(flag.className).toContain("inline-block");
|
|
@@ -61,6 +62,14 @@ describe("CommandPreview", () => {
|
|
|
61
62
|
expect(screen.getByText("exit 0 · tool reported error").className).toContain("surface-warning");
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
it("shows a closed command as plain text that ends in an ellipsis, whole in its title", () => {
|
|
66
|
+
render(<CommandPreview part={part({ output: "M a" })} />);
|
|
67
|
+
const code = screen.getByTestId("command-preview").querySelector("code");
|
|
68
|
+
expect(code?.className).toContain("line-clamp-2");
|
|
69
|
+
expect(code).toHaveAttribute("title", "git status --short");
|
|
70
|
+
expect(code?.querySelector("span")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
64
73
|
it("is a quiet row until opened, then a dark terminal", () => {
|
|
65
74
|
render(<CommandPreview part={part({ output: "M a" })} />);
|
|
66
75
|
const block = screen.getByTestId("command-preview");
|
|
@@ -71,3 +80,21 @@ describe("CommandPreview", () => {
|
|
|
71
80
|
expect(screen.getByRole("region", { name: "stdout" })).toHaveTextContent("M a");
|
|
72
81
|
});
|
|
73
82
|
});
|
|
83
|
+
|
|
84
|
+
describe("CommandPreview output", () => {
|
|
85
|
+
it("offers the rest of an output its height cap hides", () => {
|
|
86
|
+
const scroll = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "scrollHeight");
|
|
87
|
+
const client = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "clientHeight");
|
|
88
|
+
Object.defineProperty(HTMLElement.prototype, "scrollHeight", { configurable: true, get: () => 900 });
|
|
89
|
+
Object.defineProperty(HTMLElement.prototype, "clientHeight", { configurable: true, get: () => 320 });
|
|
90
|
+
try {
|
|
91
|
+
render(<CommandPreview part={part({ output: "a\nb\nc" })} defaultExpanded />);
|
|
92
|
+
fireEvent.click(screen.getByRole("button", { name: "Show all stdout (3 lines)" }));
|
|
93
|
+
expect(screen.getByRole("region", { name: "stdout" }).className).not.toContain("max-h-80");
|
|
94
|
+
expect(screen.queryByRole("button", { name: /Show all stdout/ })).toBeNull();
|
|
95
|
+
} finally {
|
|
96
|
+
if (scroll) Object.defineProperty(HTMLElement.prototype, "scrollHeight", scroll);
|
|
97
|
+
if (client) Object.defineProperty(HTMLElement.prototype, "clientHeight", client);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memo, useId, useState } from "react";
|
|
1
|
+
import { memo, useId, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { ChevronDown, ChevronRight, Loader2 } from "lucide-react";
|
|
3
3
|
import { focusRing } from "../lib/focus";
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
@@ -187,23 +187,11 @@ function ExitBadge({
|
|
|
187
187
|
);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
* box; a long script renders the rest as plain text. */
|
|
192
|
-
const BOXED_TOKENS = 64;
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* A command split at its whitespace, each token in an inline box. Browsers
|
|
196
|
-
* break after a hyphen, which split `--short` into `--` and `short` on a
|
|
197
|
-
* phone. A box moves to the next line whole, and breaks inside only when it
|
|
198
|
-
* is wider than the line (a full SHA). The text is unchanged, so a copy still
|
|
199
|
-
* pastes the exact command.
|
|
200
|
-
*/
|
|
201
|
-
function CommandText({ command, all }: { command: string; all: boolean }) {
|
|
190
|
+
function CommandText({ command }: { command: string }) {
|
|
202
191
|
const tokens = command.split(/(\s+)/);
|
|
203
|
-
const limit = all ? tokens.length : BOXED_TOKENS * 2;
|
|
204
192
|
return (
|
|
205
193
|
<>
|
|
206
|
-
{tokens.
|
|
194
|
+
{tokens.map((token, i) =>
|
|
207
195
|
token.length > 0 && !/\s/.test(token) ? (
|
|
208
196
|
// Tokens repeat (two spaces, two "&&"), so position is the key.
|
|
209
197
|
<span key={i} className="inline-block max-w-full [overflow-wrap:anywhere]">
|
|
@@ -213,11 +201,69 @@ function CommandText({ command, all }: { command: string; all: boolean }) {
|
|
|
213
201
|
token
|
|
214
202
|
),
|
|
215
203
|
)}
|
|
216
|
-
{limit < tokens.length ? tokens.slice(limit).join("") : null}
|
|
217
204
|
</>
|
|
218
205
|
);
|
|
219
206
|
}
|
|
220
207
|
|
|
208
|
+
/**
|
|
209
|
+
* One output stream, capped at a readable height. When the cap hides lines,
|
|
210
|
+
* a control says so and shows the rest in place: a region that ends flush
|
|
211
|
+
* with its frame gave no sign that more output was there.
|
|
212
|
+
*/
|
|
213
|
+
function OutputRegion({
|
|
214
|
+
label,
|
|
215
|
+
text,
|
|
216
|
+
className,
|
|
217
|
+
}: {
|
|
218
|
+
label: string;
|
|
219
|
+
text: string;
|
|
220
|
+
className?: string;
|
|
221
|
+
}) {
|
|
222
|
+
const ref = useRef<HTMLPreElement>(null);
|
|
223
|
+
const [clipped, setClipped] = useState(false);
|
|
224
|
+
const [full, setFull] = useState(false);
|
|
225
|
+
useLayoutEffect(() => {
|
|
226
|
+
const el = ref.current;
|
|
227
|
+
if (!el || full) return;
|
|
228
|
+
const check = () => setClipped(el.scrollHeight > el.clientHeight + 1);
|
|
229
|
+
check();
|
|
230
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
231
|
+
const observer = new ResizeObserver(check);
|
|
232
|
+
observer.observe(el);
|
|
233
|
+
return () => observer.disconnect();
|
|
234
|
+
}, [full]);
|
|
235
|
+
const lines = text.replace(/\n$/, "").split("\n").length;
|
|
236
|
+
return (
|
|
237
|
+
<div className={className}>
|
|
238
|
+
<pre
|
|
239
|
+
ref={ref}
|
|
240
|
+
tabIndex={0}
|
|
241
|
+
role="region"
|
|
242
|
+
aria-label={label}
|
|
243
|
+
className={cn(
|
|
244
|
+
"overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] px-3 py-2 leading-relaxed",
|
|
245
|
+
full ? "" : "max-h-80",
|
|
246
|
+
focusRing,
|
|
247
|
+
)}
|
|
248
|
+
>
|
|
249
|
+
{text}
|
|
250
|
+
</pre>
|
|
251
|
+
{clipped && !full ? (
|
|
252
|
+
<button
|
|
253
|
+
type="button"
|
|
254
|
+
onClick={() => setFull(true)}
|
|
255
|
+
className={cn(
|
|
256
|
+
"w-full border-t border-white/10 px-3 py-1.5 text-left font-sans text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
257
|
+
focusRing,
|
|
258
|
+
)}
|
|
259
|
+
>
|
|
260
|
+
Show all {label} ({lines} lines)
|
|
261
|
+
</button>
|
|
262
|
+
) : null}
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
221
267
|
/**
|
|
222
268
|
* One shell command as a terminal block: a `$ command` prompt line with its
|
|
223
269
|
* exit status, and stdout and stderr in separate regions below it.
|
|
@@ -227,10 +273,6 @@ function CommandText({ command, all }: { command: string; all: boolean }) {
|
|
|
227
273
|
* dark terminal. It scopes the dark token set onto itself with
|
|
228
274
|
* `data-theme="dark"`, so stderr, the badges and the muted prompt keep the
|
|
229
275
|
* contrast they were designed with on a dark ground, in a light console too.
|
|
230
|
-
*
|
|
231
|
-
* On a phone the output keeps its own lines and scrolls sideways inside the
|
|
232
|
-
* block: wrapped output breaks the columns of a listing and splits hashes
|
|
233
|
-
* and URLs mid-token. The command still wraps between its tokens.
|
|
234
276
|
*/
|
|
235
277
|
export const CommandPreview = memo(
|
|
236
278
|
({ part, defaultExpanded = false, className }: CommandPreviewProps) => {
|
|
@@ -260,15 +302,16 @@ export const CommandPreview = memo(
|
|
|
260
302
|
$
|
|
261
303
|
</span>
|
|
262
304
|
<code
|
|
305
|
+
title={expanded ? undefined : command}
|
|
263
306
|
className={cn(
|
|
264
307
|
"min-w-0 flex-1 whitespace-pre-wrap [overflow-wrap:anywhere]",
|
|
265
|
-
// Closed, two lines of
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
expanded ? "" : "
|
|
308
|
+
// Closed, two lines of plain text that end in an ellipsis when
|
|
309
|
+
// cut, with the whole command in the title. Opened, each token
|
|
310
|
+
// is a box that wraps whole (line-clamp does not count boxes).
|
|
311
|
+
expanded ? "" : "line-clamp-2",
|
|
269
312
|
)}
|
|
270
313
|
>
|
|
271
|
-
<CommandText command={command}
|
|
314
|
+
{expanded ? <CommandText command={command} /> : command}
|
|
272
315
|
</code>
|
|
273
316
|
{/* On a phone the badge takes its own line under the command, so the
|
|
274
317
|
command keeps the row's width. */}
|
|
@@ -321,43 +364,25 @@ export const CommandPreview = memo(
|
|
|
321
364
|
{expanded && hasBody ? (
|
|
322
365
|
<div id={bodyId} className="border-t border-white/10">
|
|
323
366
|
{output.stdout ? (
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
"max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] max-sm:whitespace-pre max-sm:[overflow-wrap:normal] px-3 py-2 leading-relaxed",
|
|
330
|
-
focusRing,
|
|
331
|
-
)}
|
|
332
|
-
>
|
|
333
|
-
{output.stdout}
|
|
334
|
-
</pre>
|
|
367
|
+
<OutputRegion
|
|
368
|
+
label="stdout"
|
|
369
|
+
text={output.stdout}
|
|
370
|
+
className=""
|
|
371
|
+
/>
|
|
335
372
|
) : null}
|
|
336
373
|
{output.stderr ? (
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
"max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] max-sm:whitespace-pre max-sm:[overflow-wrap:normal] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]",
|
|
343
|
-
focusRing,
|
|
344
|
-
)}
|
|
345
|
-
>
|
|
346
|
-
{output.stderr}
|
|
347
|
-
</pre>
|
|
374
|
+
<OutputRegion
|
|
375
|
+
label="stderr"
|
|
376
|
+
text={output.stderr}
|
|
377
|
+
className="border-t border-white/10 text-[var(--surface-danger-text)]"
|
|
378
|
+
/>
|
|
348
379
|
) : null}
|
|
349
380
|
{errorText ? (
|
|
350
|
-
<
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
"max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] max-sm:whitespace-pre max-sm:[overflow-wrap:normal] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]",
|
|
356
|
-
focusRing,
|
|
357
|
-
)}
|
|
358
|
-
>
|
|
359
|
-
{errorText}
|
|
360
|
-
</pre>
|
|
381
|
+
<OutputRegion
|
|
382
|
+
label="error"
|
|
383
|
+
text={errorText}
|
|
384
|
+
className="border-t border-white/10 text-[var(--surface-danger-text)]"
|
|
385
|
+
/>
|
|
361
386
|
) : null}
|
|
362
387
|
</div>
|
|
363
388
|
) : null}
|