1- import { Div } from 'honorable'
21import {
32 type ComponentProps ,
3+ createContext ,
44 type PropsWithChildren ,
55 type ReactNode ,
66 type RefObject ,
7- createContext ,
87 useCallback ,
98 useContext ,
109 useEffect ,
@@ -16,10 +15,12 @@ import styled, { useTheme } from 'styled-components'
1615
1716import useResizeObserver from '../hooks/useResizeObserver'
1817
18+ import Button from './Button'
1919import Card , { type CardProps } from './Card'
20+ import Flex from './Flex'
2021import Highlight from './Highlight'
21- import { downloadMermaidSvg , Mermaid , MermaidRefHandle } from './Mermaid'
2222import { ListBoxItem } from './ListBoxItem'
23+ import { Mermaid , MermaidRefHandle } from './Mermaid'
2324import { Select } from './Select'
2425import SubTab from './SubTab'
2526import { TabList , type TabListStateProps } from './TabList'
@@ -34,10 +35,6 @@ import CheckIcon from './icons/CheckIcon'
3435import CopyIcon from './icons/CopyIcon'
3536import DropdownArrowIcon from './icons/DropdownArrowIcon'
3637import FileIcon from './icons/FileIcon'
37- import Button from './Button'
38- import { DownloadIcon } from '../icons'
39- import IconFrame from './IconFrame'
40- import Flex from './Flex'
4138
4239type CodeProps = Omit < CardProps , 'children' > & {
4340 children ?: string
@@ -139,15 +136,6 @@ const CopyButton = styled(CopyButtonBase)<{ $verticallyCenter: boolean }>(
139136 } )
140137)
141138
142- const MermaidButtonsSC = styled . div ( ( { theme } ) => ( {
143- position : 'absolute' ,
144- right : theme . spacing . medium ,
145- top : theme . spacing . medium ,
146- gap : theme . spacing . xsmall ,
147- display : 'flex' ,
148- alignItems : 'center' ,
149- } ) )
150-
151139type CodeTabData = {
152140 key : string
153141 label ?: string
@@ -321,10 +309,7 @@ function CodeContent({
321309 isStreaming ?: boolean
322310 setMermaidError ?: ( error : Nullable < Error > ) => void
323311} ) {
324- const { spacing, borderRadiuses } = useTheme ( )
325312 const mermaidRef = useRef < MermaidRefHandle > ( null )
326- const [ copied , setCopied ] = useState ( false )
327-
328313 const [ mermaidError , setMermaidErrorState ] = useState < Nullable < Error > > ( null )
329314 const setMermaidError = useCallback (
330315 ( error : Nullable < Error > ) => {
@@ -336,93 +321,62 @@ function CodeContent({
336321
337322 const codeString = children ?. trim ( ) || ''
338323 const multiLine = ! ! codeString . match ( / \r ? \n / ) || hasSetHeight
339- const handleCopy = useCallback (
340- ( ) =>
341- window . navigator . clipboard
342- . writeText ( codeString )
343- . then ( ( ) => setCopied ( true ) ) ,
344- [ codeString ]
345- )
346324
347- useEffect ( ( ) => {
348- if ( copied ) {
349- const timeout = setTimeout ( ( ) => setCopied ( false ) , 1000 )
350- return ( ) => clearTimeout ( timeout )
351- }
352- } , [ copied ] )
325+ const { copied, handleCopy } = useCopyText ( codeString )
353326
354327 if ( typeof children !== 'string' )
355328 throw new Error ( 'Code component expects a string as its children' )
356329
357- const isMermaidDownloadable =
358- language === 'mermaid' && ! isStreaming && ! mermaidError
330+ const validMermaid = language === 'mermaid' && ! isStreaming && ! mermaidError
359331
360332 return (
361- < div
362- css = { {
363- height : '100%' ,
364- overflow : 'auto' ,
365- alignItems : 'center' ,
366- } }
367- >
368- { isMermaidDownloadable ? (
369- < MermaidButtonsSC >
370- < IconFrame
371- clickable
372- onClick = { handleCopy }
373- icon = { copied ? < CheckIcon /> : < CopyIcon /> }
374- type = "floating"
375- tooltip = "Copy Mermaid code"
376- />
377- < IconFrame
378- clickable
379- onClick = { ( ) => {
380- const { svgStr } = mermaidRef . current
381- if ( ! svgStr ) return
382- downloadMermaidSvg ( svgStr )
383- } }
384- icon = { < DownloadIcon /> }
385- type = "floating"
386- tooltip = "Download as PNG"
387- />
388- </ MermaidButtonsSC >
389- ) : (
390- < CopyButton
391- copied = { copied }
392- handleCopy = { handleCopy }
393- $verticallyCenter = { ! multiLine }
394- />
395- ) }
396- < div
397- css = { {
398- ...( isMermaidDownloadable ? { backgroundColor : 'white' } : { } ) ,
399- padding : `${ multiLine ? spacing . medium : spacing . small } px ${
400- spacing . medium
401- } px`,
402- borderBottomLeftRadius : borderRadiuses . large ,
403- borderBottomRightRadius : borderRadiuses . large ,
404- } }
333+ < div css = { { position : 'relative' , overflow : 'hidden' , height : '100%' } } >
334+ < CodeContentSC
335+ $validMermaid = { validMermaid }
336+ $multiLine = { multiLine }
405337 >
406- { isMermaidDownloadable ? (
338+ { validMermaid ? (
407339 < Mermaid
408340 ref = { mermaidRef }
409341 setError = { setMermaidError }
410342 diagram = { codeString }
411343 />
412344 ) : (
413- < Highlight
414- key = { codeString }
415- language = { language }
416- { ...props }
417- >
418- { codeString }
419- </ Highlight >
345+ < >
346+ < CopyButton
347+ copied = { copied }
348+ handleCopy = { handleCopy }
349+ $verticallyCenter = { ! multiLine }
350+ />
351+ < Highlight
352+ key = { codeString }
353+ language = { language }
354+ { ...props }
355+ >
356+ { codeString }
357+ </ Highlight >
358+ </ >
420359 ) }
421- </ div >
360+ </ CodeContentSC >
422361 </ div >
423362 )
424363}
425364
365+ const CodeContentSC = styled . div < {
366+ $validMermaid : boolean
367+ $multiLine : boolean
368+ } > ( ( { theme, $validMermaid, $multiLine } ) => ( {
369+ height : '100%' ,
370+ overflow : 'auto' ,
371+ alignItems : 'center' ,
372+ padding : `${ $multiLine ? theme . spacing . medium : theme . spacing . small } px ${
373+ theme . spacing . medium
374+ } px`,
375+ borderBottomLeftRadius : theme . borderRadiuses . large ,
376+ borderBottomRightRadius : theme . borderRadiuses . large ,
377+ ...( $validMermaid ? { backgroundColor : 'white' , padding : 0 } : { } ) ,
378+ } ) )
379+
426380function CodeUnstyled ( {
427381 ref,
428382 children,
@@ -512,13 +466,7 @@ function CodeUnstyled({
512466 tabKey = { tab . key }
513467 mode = "multipanel"
514468 stateRef = { tabStateRef }
515- as = {
516- < Div
517- position = "relative"
518- height = "100%"
519- overflow = "hidden"
520- />
521- }
469+ css = { { height : '100%' , overflow : 'hidden' } }
522470 >
523471 < CodeContent
524472 language = { tab . language }
@@ -532,21 +480,15 @@ function CodeUnstyled({
532480 </ TabPanel >
533481 ) )
534482 ) : (
535- < Div
536- position = "relative"
537- height = "100%"
538- overflow = "hidden"
483+ < CodeContent
484+ language = { language }
485+ showLineNumbers = { showLineNumbers }
486+ hasSetHeight = { hasSetHeight }
487+ isStreaming = { isStreaming }
488+ setMermaidError = { setMermaidError }
539489 >
540- < CodeContent
541- language = { language }
542- showLineNumbers = { showLineNumbers }
543- hasSetHeight = { hasSetHeight }
544- isStreaming = { isStreaming }
545- setMermaidError = { setMermaidError }
546- >
547- { children }
548- </ CodeContent >
549- </ Div >
490+ { children }
491+ </ CodeContent >
550492 ) }
551493 </ Flex >
552494 </ Card >
@@ -558,17 +500,33 @@ function CodeUnstyled({
558500}
559501
560502const Code = styled ( CodeUnstyled ) ( ( _ ) => ( {
561- [ `${ CopyButton } , ${ MermaidButtonsSC } ` ] : {
503+ [ `${ CopyButton } ` ] : {
562504 opacity : 0 ,
563505 pointerEvents : 'none' ,
564506 transition : 'opacity 0.2s ease' ,
565507 } ,
566- [ `&:hover ${ CopyButton } , &:hover ${ MermaidButtonsSC } ` ] : {
508+ [ `&:hover ${ CopyButton } ` ] : {
567509 opacity : 1 ,
568510 pointerEvents : 'auto' ,
569511 transition : 'opacity 0.2s ease' ,
570512 } ,
571513} ) )
572514
515+ export function useCopyText ( text : string ) {
516+ const [ copied , setCopied ] = useState ( false )
517+ const handleCopy = useCallback (
518+ ( ) =>
519+ window . navigator . clipboard . writeText ( text ) . then ( ( ) => setCopied ( true ) ) ,
520+ [ text ]
521+ )
522+ useEffect ( ( ) => {
523+ if ( copied ) {
524+ const timeout = setTimeout ( ( ) => setCopied ( false ) , 1000 )
525+ return ( ) => clearTimeout ( timeout )
526+ }
527+ } , [ copied ] )
528+ return { copied, handleCopy }
529+ }
530+
573531export default Code
574532export type { CodeProps }
0 commit comments