@tangle-network/ui 11.16.10 → 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,11 @@
|
|
|
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
|
+
|
|
3
9
|
## 11.16.10
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -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
|
|
@@ -146,6 +146,49 @@ function CommandText({ command }) {
|
|
|
146
146
|
}, i) : token) });
|
|
147
147
|
}
|
|
148
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
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
149
192
|
* One shell command as a terminal block: a `$ command` prompt line with its
|
|
150
193
|
* exit status, and stdout and stderr in separate regions below it.
|
|
151
194
|
*
|
|
@@ -208,26 +251,20 @@ const CommandPreview = memo(({ part, defaultExpanded = false, className }) => {
|
|
|
208
251
|
id: bodyId,
|
|
209
252
|
className: "border-t border-white/10",
|
|
210
253
|
children: [
|
|
211
|
-
output.stdout ? /* @__PURE__ */ jsx(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
className: cn("max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] px-3 py-2 leading-relaxed", focusRing),
|
|
216
|
-
children: output.stdout
|
|
254
|
+
output.stdout ? /* @__PURE__ */ jsx(OutputRegion, {
|
|
255
|
+
label: "stdout",
|
|
256
|
+
text: output.stdout,
|
|
257
|
+
className: ""
|
|
217
258
|
}) : null,
|
|
218
|
-
output.stderr ? /* @__PURE__ */ jsx(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"
|
|
222
|
-
className: cn("max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]", focusRing),
|
|
223
|
-
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)]"
|
|
224
263
|
}) : null,
|
|
225
|
-
errorText ? /* @__PURE__ */ jsx(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"
|
|
229
|
-
className: cn("max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]", focusRing),
|
|
230
|
-
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)]"
|
|
231
268
|
}) : null
|
|
232
269
|
]
|
|
233
270
|
}) : null]
|
package/package.json
CHANGED
|
@@ -80,3 +80,21 @@ describe("CommandPreview", () => {
|
|
|
80
80
|
expect(screen.getByRole("region", { name: "stdout" })).toHaveTextContent("M a");
|
|
81
81
|
});
|
|
82
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";
|
|
@@ -205,6 +205,65 @@ function CommandText({ command }: { command: string }) {
|
|
|
205
205
|
);
|
|
206
206
|
}
|
|
207
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
|
+
|
|
208
267
|
/**
|
|
209
268
|
* One shell command as a terminal block: a `$ command` prompt line with its
|
|
210
269
|
* exit status, and stdout and stderr in separate regions below it.
|
|
@@ -305,43 +364,25 @@ export const CommandPreview = memo(
|
|
|
305
364
|
{expanded && hasBody ? (
|
|
306
365
|
<div id={bodyId} className="border-t border-white/10">
|
|
307
366
|
{output.stdout ? (
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
"max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] px-3 py-2 leading-relaxed",
|
|
314
|
-
focusRing,
|
|
315
|
-
)}
|
|
316
|
-
>
|
|
317
|
-
{output.stdout}
|
|
318
|
-
</pre>
|
|
367
|
+
<OutputRegion
|
|
368
|
+
label="stdout"
|
|
369
|
+
text={output.stdout}
|
|
370
|
+
className=""
|
|
371
|
+
/>
|
|
319
372
|
) : null}
|
|
320
373
|
{output.stderr ? (
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
"max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]",
|
|
327
|
-
focusRing,
|
|
328
|
-
)}
|
|
329
|
-
>
|
|
330
|
-
{output.stderr}
|
|
331
|
-
</pre>
|
|
374
|
+
<OutputRegion
|
|
375
|
+
label="stderr"
|
|
376
|
+
text={output.stderr}
|
|
377
|
+
className="border-t border-white/10 text-[var(--surface-danger-text)]"
|
|
378
|
+
/>
|
|
332
379
|
) : null}
|
|
333
380
|
{errorText ? (
|
|
334
|
-
<
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"max-h-80 overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] border-t border-white/10 px-3 py-2 leading-relaxed text-[var(--surface-danger-text)]",
|
|
340
|
-
focusRing,
|
|
341
|
-
)}
|
|
342
|
-
>
|
|
343
|
-
{errorText}
|
|
344
|
-
</pre>
|
|
381
|
+
<OutputRegion
|
|
382
|
+
label="error"
|
|
383
|
+
text={errorText}
|
|
384
|
+
className="border-t border-white/10 text-[var(--surface-danger-text)]"
|
|
385
|
+
/>
|
|
345
386
|
) : null}
|
|
346
387
|
</div>
|
|
347
388
|
) : null}
|