Skip to content

Commit df0bce3

Browse files
authored
Code blocks have an optional text-wrap toggle (#2009)
* Code blocks have an optional text-wrap button * Wrap “words”, not “all” * wrapping is default false * Change the wording in the tooltip
1 parent 0fe85ea commit df0bce3

File tree

4 files changed

+141
-31
lines changed

4 files changed

+141
-31
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export function TextInlineIcon({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M3 3V21"
6+
stroke="currentColor"
7+
strokeWidth="2"
8+
strokeLinecap="round"
9+
strokeLinejoin="round"
10+
/>
11+
<path
12+
d="M13 21L13 16"
13+
stroke="currentColor"
14+
strokeWidth="2"
15+
strokeLinecap="round"
16+
strokeLinejoin="round"
17+
/>
18+
<path
19+
d="M13 8L13 3"
20+
stroke="currentColor"
21+
strokeWidth="2"
22+
strokeLinecap="round"
23+
strokeLinejoin="round"
24+
/>
25+
<path
26+
d="M7 12L20 12"
27+
stroke="currentColor"
28+
strokeWidth="2"
29+
strokeLinecap="round"
30+
strokeLinejoin="round"
31+
/>
32+
<path
33+
d="M17.5 15.5L21 12L17.5 8.5"
34+
stroke="currentColor"
35+
strokeWidth="2"
36+
strokeLinecap="round"
37+
strokeLinejoin="round"
38+
/>
39+
</svg>
40+
);
41+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export function TextWrapIcon({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M3 3V21"
6+
stroke="currentColor"
7+
strokeWidth="2"
8+
strokeLinecap="round"
9+
strokeLinejoin="round"
10+
/>
11+
<path
12+
d="M21 21V3"
13+
stroke="currentColor"
14+
strokeWidth="2"
15+
strokeLinecap="round"
16+
strokeLinejoin="round"
17+
/>
18+
<path
19+
d="M7 7H14C15.6569 7 17 8.34315 17 10V13C17 14.6569 15.6569 16 14 16H9"
20+
stroke="currentColor"
21+
strokeWidth="2"
22+
strokeLinecap="round"
23+
strokeLinejoin="round"
24+
/>
25+
<path
26+
d="M11 13L8 16L11 19"
27+
stroke="currentColor"
28+
strokeWidth="2"
29+
strokeLinecap="round"
30+
strokeLinejoin="round"
31+
/>
32+
</svg>
33+
);
34+
}

apps/webapp/app/components/code/CodeBlock.tsx

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { Clipboard, ClipboardCheck } from "lucide-react";
33
import type { Language, PrismTheme } from "prism-react-renderer";
44
import { Highlight, Prism } from "prism-react-renderer";
55
import { forwardRef, ReactNode, useCallback, useEffect, useState } from "react";
6+
import { TextWrapIcon } from "~/assets/icons/TextWrapIcon";
67
import { cn } from "~/utils/cn";
78
import { Button } from "../primitives/Buttons";
89
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../primitives/Dialog";
910
import { Paragraph } from "../primitives/Paragraph";
1011
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
12+
import { TextInlineIcon } from "~/assets/icons/TextInlineIcon";
1113

1214
//This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx
1315
//it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting
@@ -31,6 +33,9 @@ type CodeBlockProps = {
3133
/** Show copy to clipboard button */
3234
showCopyButton?: boolean;
3335

36+
/** Show text wrapping button */
37+
showTextWrapping?: boolean;
38+
3439
/** Display line numbers */
3540
showLineNumbers?: boolean;
3641

@@ -183,6 +188,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
183188
(
184189
{
185190
showCopyButton = true,
191+
showTextWrapping = false,
186192
showLineNumbers = true,
187193
showOpenInModal = true,
188194
highlightedRanges,
@@ -202,6 +208,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
202208
const [copied, setCopied] = useState(false);
203209
const [modalCopied, setModalCopied] = useState(false);
204210
const [isModalOpen, setIsModalOpen] = useState(false);
211+
const [isWrapped, setIsWrapped] = useState(false);
205212

206213
const onCopied = useCallback(
207214
(event: React.MouseEvent<HTMLButtonElement>) => {
@@ -263,6 +270,25 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
263270
showChrome ? "right-1.5 top-1.5" : "top-2.5"
264271
)}
265272
>
273+
{showTextWrapping && (
274+
<TooltipProvider>
275+
<Tooltip disableHoverableContent>
276+
<TooltipTrigger
277+
onClick={() => setIsWrapped(!isWrapped)}
278+
className="transition-colors focus-custom hover:cursor-pointer hover:text-text-bright"
279+
>
280+
{isWrapped ? (
281+
<TextInlineIcon className="size-4" />
282+
) : (
283+
<TextWrapIcon className="size-4" />
284+
)}
285+
</TooltipTrigger>
286+
<TooltipContent side="left" className="text-xs">
287+
{isWrapped ? "Unwrap" : "Wrap"}
288+
</TooltipContent>
289+
</Tooltip>
290+
</TooltipProvider>
291+
)}
266292
{showCopyButton && (
267293
<TooltipProvider>
268294
<Tooltip open={copied || mouseOver} disableHoverableContent>
@@ -311,16 +337,27 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
311337
maxLineWidth={maxLineWidth}
312338
className="px-2 py-3"
313339
preClassName="text-xs"
340+
isWrapped={isWrapped}
314341
/>
315342
) : (
316343
<div
317344
dir="ltr"
318-
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
345+
className={cn(
346+
"px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
347+
!isWrapped && "overflow-x-auto",
348+
isWrapped && "overflow-y-auto"
349+
)}
319350
style={{
320351
maxHeight,
321352
}}
322353
>
323-
<pre className="relative mr-2 p-2 font-mono text-xs leading-relaxed" dir="ltr">
354+
<pre
355+
className={cn(
356+
"relative mr-2 p-2 font-mono text-xs leading-relaxed",
357+
isWrapped && "[&_span]:whitespace-pre-wrap [&_span]:break-words"
358+
)}
359+
dir="ltr"
360+
>
324361
{code}
325362
</pre>
326363
</div>
@@ -355,6 +392,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
355392
maxLineWidth={maxLineWidth}
356393
className="min-h-full"
357394
preClassName="text-sm"
395+
isWrapped={isWrapped}
358396
/>
359397
) : (
360398
<div
@@ -410,6 +448,7 @@ type HighlightCodeProps = {
410448
maxLineWidth?: number;
411449
className?: string;
412450
preClassName?: string;
451+
isWrapped: boolean;
413452
};
414453

415454
function HighlightCode({
@@ -421,11 +460,11 @@ function HighlightCode({
421460
maxLineWidth,
422461
className,
423462
preClassName,
463+
isWrapped,
424464
}: HighlightCodeProps) {
425465
const [isLoaded, setIsLoaded] = useState(false);
426466

427467
useEffect(() => {
428-
// This ensures the language definitions are loaded
429468
Promise.all([
430469
//@ts-ignore
431470
import("prismjs/components/prism-json"),
@@ -434,16 +473,23 @@ function HighlightCode({
434473
]).then(() => setIsLoaded(true));
435474
}, []);
436475

476+
const containerClasses = cn(
477+
"px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
478+
!isWrapped && "overflow-x-auto",
479+
isWrapped && "overflow-y-auto",
480+
className
481+
);
482+
483+
const preClasses = cn(
484+
"relative mr-2 font-mono leading-relaxed",
485+
preClassName,
486+
isWrapped && "[&_span]:whitespace-pre-wrap [&_span]:break-words"
487+
);
488+
437489
if (!isLoaded) {
438490
return (
439-
<div
440-
dir="ltr"
441-
className={cn(
442-
"overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
443-
className
444-
)}
445-
>
446-
<pre className={cn("relative mr-2 font-mono leading-relaxed", preClassName)}>{code}</pre>
491+
<div dir="ltr" className={containerClasses}>
492+
<pre className={preClasses}>{code}</pre>
447493
</div>
448494
);
449495
}
@@ -457,22 +503,8 @@ function HighlightCode({
457503
getLineProps,
458504
getTokenProps,
459505
}) => (
460-
<div
461-
dir="ltr"
462-
className={cn(
463-
"overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
464-
className
465-
)}
466-
>
467-
<pre
468-
className={cn(
469-
"relative mr-2 font-mono leading-relaxed",
470-
inheritedClassName,
471-
preClassName
472-
)}
473-
style={inheritedStyle}
474-
dir="ltr"
475-
>
506+
<div dir="ltr" className={containerClasses}>
507+
<pre className={cn(preClasses, inheritedClassName)} style={inheritedStyle} dir="ltr">
476508
{tokens
477509
.map((line, index) => {
478510
if (index === tokens.length - 1 && line.length === 1 && line[0].content === "\n") {
@@ -495,7 +527,8 @@ function HighlightCode({
495527
{...lineProps}
496528
className={cn(
497529
"flex w-full justify-start transition-opacity duration-500",
498-
lineProps.className
530+
lineProps.className,
531+
isWrapped && "flex-wrap"
499532
)}
500533
style={{
501534
opacity: shouldDim ? dimAmount : undefined,
@@ -504,9 +537,10 @@ function HighlightCode({
504537
>
505538
{showLineNumbers && (
506539
<div
507-
className={
508-
"mr-2 flex-none select-none text-right text-charcoal-500 transition-opacity duration-500"
509-
}
540+
className={cn(
541+
"mr-2 flex-none select-none text-right text-charcoal-500 transition-opacity duration-500",
542+
isWrapped && "sticky left-0"
543+
)}
510544
style={{
511545
width: `calc(8 * ${(maxLineWidth as number) / 16}rem)`,
512546
}}

apps/webapp/app/components/runs/v3/PacketDisplay.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function PacketDisplay({
4444
code={data}
4545
maxLines={20}
4646
showLineNumbers={false}
47+
showTextWrapping
4748
/>
4849
);
4950
}

0 commit comments

Comments
 (0)