@@ -3,11 +3,13 @@ import { Clipboard, ClipboardCheck } from "lucide-react";
33import type { Language , PrismTheme } from "prism-react-renderer" ;
44import { Highlight , Prism } from "prism-react-renderer" ;
55import { forwardRef , ReactNode , useCallback , useEffect , useState } from "react" ;
6+ import { TextWrapIcon } from "~/assets/icons/TextWrapIcon" ;
67import { cn } from "~/utils/cn" ;
78import { Button } from "../primitives/Buttons" ;
89import { Dialog , DialogContent , DialogHeader , DialogTitle } from "../primitives/Dialog" ;
910import { Paragraph } from "../primitives/Paragraph" ;
1011import { 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
415454function 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 } }
0 commit comments