import React , { useRef , useEffect , useState , useMemo } from 'react' ; import PropTypes from 'prop-types' ; import ClipboardJS from 'clipboard' ; import facepaint from 'facepaint' ; import debounce from 'lodash/debounce' ; import { css , cx } from '@leafygreen-ui/emotion' ; import { useIsomorphicLayoutEffect } from '@leafygreen-ui/hooks' ; import { spacing } from '@leafygreen-ui/tokens' ; import { useUsingKeyboardContext } from '@leafygreen-ui/leafygreen-provider' ; import { variantColors } from './globalStyles' ; import { Language , CodeProps , Mode } from './types' ; import Syntax from './Syntax' ; import Panel from './Panel' ; import WindowChrome from './WindowChrome' ; import { isComponentType } from '@leafygreen-ui/lib' ; export function hasMultipleLines ( string ) { return string . trim ( ) . includes ( '\n' ) ; } const mq = facepaint ( [ `@media only screen and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 2)` , `@media only screen and (min-device-width: 813px) and (-webkit-min-device-pixel-ratio: 2)` , ] ) ; const singleLineComponentHeight = 36 ; const lineHeight = 24 ; const codeWrapperStyle = css `
  overflow-x: auto;
  // Many applications have global styles that are adding a border and border radius to this element.
  border-radius: 0;
  border: 0;
  // We apply left / right padding in Syntax to support line highlighting
  padding-top: ${ spacing [ 2 ] }px;
  padding-bottom: ${ spacing [ 2 ] }px;
  margin: 0;
  position: relative;
  flex-grow: 1;

  ${ mq ( { whiteSpace : [ 'pre' , 'pre-wrap' , 'pre' ] , } ) }
` ; const codeWrapperStyleWithWindowChrome = css `
  border-left: 0;
` ; const singleLineWrapperStyle = css `
  display: flex;
  align-items: center;
  padding-top: ${ ( singleLineComponentHeight - lineHeight ) / 2 }px;
  padding-bottom: ${ ( singleLineComponentHeight - lineHeight ) / 2 }px;
` ; const wrapperFocusStyle = css `
  &:focus {
    outline: none;
  }
` ; const languagePickerStyle = css `
  display: flex;
  flex-direction: column-reverse;
  width: 700px;
` ; function getWrapperVariantStyle ( mode ) { const colors = variantColors [ mode ] ; const borderStyle = mode === 'dark' ? `border: 0` : `border-color: ${ colors [ 1 ] }` ; return css `
    ${ borderStyle };
    background-color: ${ colors [ 0 ] };
    color: ${ colors [ 3 ] };
  ` ; } const ScrollState = { None : 'none' , Left : 'left' , Right : 'right' , Both : 'both' , } as const ; type ScrollState = typeof ScrollState [ keyof typeof ScrollState ] ; function getScrollShadowStyle ( scrollState , mode ) { const colors = variantColors [ mode ] ; const shadowColor = mode === Mode . Light ? 'rgba(93,108,116,0.3)' : 'rgba(0,0,0,0.35)' ; if ( scrollState === ScrollState . Both ) { return css `
      box-shadow: inset 6px 0 6px -6px ${ shadowColor },
        inset -6px 0 6px -6px ${ shadowColor }, inset 0 6px 6px -6px ${ colors [ 0 ] },
        inset 0 -6px 6px -6px ${ colors [ 0 ] };
    ` ; } if ( scrollState === ScrollState . Left ) { return css `
      box-shadow: inset 6px 0 6px -6px ${ shadowColor };
    ` ; } if ( scrollState === ScrollState . Right ) { return css `
      box-shadow: inset -6px 0 6px -6px ${ shadowColor };
    ` ; } return '' ; } type DetailedElementProps < T > = React . DetailedHTMLProps < React . HTMLAttributes < T > , T > ; param props . children param props . className param props . language param props . darkMode param props . showLineNumbers param props . copyable param props . onCopy function Code ( { children = '' , className , language : languageProp , darkMode = false , showLineNumbers = false , showWindowChrome = false , chromeTitle = '' , copyable = true , onCopy , highlightLines = [ ] , languageOptions , onChange , customActionButtons = [ ] , showCustomActionButtons = false , ... rest } ) { const scrollableElementRef = useRef < HTMLPreElement > ( null ) ; const { usingKeyboard : showFocus } = useUsingKeyboardContext ( ) ; const [ scrollState , setScrollState ] = useState < ScrollState > ( ScrollState . None ) ; const [ showCopyBar , setShowCopyBar ] = useState ( false ) ; const mode = darkMode ? Mode . Dark : Mode . Light ; const isMultiline = useMemo ( ( ) => hasMultipleLines ( children ) , [ children ] ) ; const filteredCustomActionIconButtons = customActionButtons . filter ( ( item ) => isComponentType ( item , 'IconButton' ) === true ) ; const showCustomActionsInPanel = showCustomActionButtons && ! ! filteredCustomActionIconButtons . length ; const currentLanguage = languageOptions ?. find ( option => option . displayName === languageProp ) ; const highlightLanguage = currentLanguage ? currentLanguage . language : languageProp ; useEffect ( ( ) => { setShowCopyBar ( copyable && ClipboardJS . isSupported ( ) ) ; } , [ copyable , showWindowChrome ] ) ; useIsomorphicLayoutEffect ( ( ) => { const scrollableElement = scrollableElementRef . current ; if ( scrollableElement != null && scrollableElement . scrollWidth > scrollableElement . clientWidth ) { setScrollState ( ScrollState . Right ) ; } } , [ ] ) ; const wrapperClassName = cx ( css `
      border: ${ currentLanguage ? '1px solid;' : '2px solid;' };
    ` , codeWrapperStyle , getWrapperVariantStyle ( mode ) , { [ codeWrapperStyleWithWindowChrome ] : showWindowChrome , } , className , getScrollShadowStyle ( scrollState , mode ) , { [ singleLineWrapperStyle ] : ! isMultiline } , { [ wrapperFocusStyle ] : ! showFocus } ) ; const renderedSyntaxComponent = ( < Syntax showLineNumbers = { showLineNumbers } darkMode = { darkMode } language = { highlightLanguage as Language } highlightLines = { highlightLines } > { children } < /Syntax>); function handleScroll ( e ) { const { scrollWidth , clientWidth : elementWidth } = e . target as HTMLElement ; const isScrollable = scrollWidth > elementWidth ; if ( isScrollable ) { const scrollPosition = ( e . target as HTMLElement ) . scrollLeft ; const maxPosition = scrollWidth - elementWidth ; if ( scrollPosition > 0 && scrollPosition < maxPosition ) { setScrollState ( ScrollState . Both ) ; } else if ( scrollPosition > 0 ) { setScrollState ( ScrollState . Left ) ; } else if ( scrollPosition < maxPosition ) { setScrollState ( ScrollState . Right ) ; } } } const debounceScroll = debounce ( handleScroll , 50 , { leading : true } ) ; const onScroll = e => { e . persist ( ) ; debounceScroll ( e ) ; } ; const borderStyle = darkMode ? `border: 0` : `border: 1px solid ${ variantColors [ mode ] [ 1 ] }` ; const wrapperStyle = css `
    ${ borderStyle };
    border-radius: 4px;
    overflow: hidden;
  ` ; return ( < div className = { wrapperStyle } > { showWindowChrome && ( < WindowChrome chromeTitle = { chromeTitle } darkMode = { darkMode } / > ) } < div className = { cx ( css `
            display: flex;
          ` , { [ languagePickerStyle ] : ! ! currentLanguage , } ) } > < pre { ... ( rest as DetailedElementProps < HTMLPreElement > ) } className = { wrapperClassName } onScroll = { onScroll } ref = { scrollableElementRef } tabIndex = { scrollState !== ScrollState . None ? 0 : - 1 } > { renderedSyntaxComponent } < /pre> { } { } { ! showWindowChrome && ( copyable || ! ! currentLanguage || showCustomActionsInPanel ) && ( < Panel language = { currentLanguage } languageOptions = { languageOptions } onChange = { onChange } contents = { children } onCopy = { onCopy } showCopyButton = { showCopyBar } darkMode = { darkMode } isMultiline = { isMultiline } customActionButtons = { filteredCustomActionIconButtons } showCustomActionButtons = { showCustomActionsInPanel } / > ) } < /div> < /div>); } Code . displayName = 'Code' ; Code . propTypes = { children : PropTypes . string . isRequired , language : PropTypes . oneOfType ( [ PropTypes . oneOf ( Object . values ( Language ) ) , PropTypes . string , ] ) , darkMode : PropTypes . bool , className : PropTypes . string , showLineNumbers : PropTypes . bool , showWindowChrome : PropTypes . bool , chromeTitle : PropTypes . string , highlightLines : PropTypes . arrayOf ( PropTypes . oneOfType ( [ PropTypes . arrayOf ( PropTypes . number ) , PropTypes . number , ] ) ) , } ; export default Code ;