import React , { forwardRef , createRef , useState , Ref , ReactElement , ReactNode , } from 'react' ; import PropTypes from 'prop-types' ; import findLast from 'lodash/findLast' ; import { uiColors } from '@leafygreen-ui/palette' ; import Tooltip from '@leafygreen-ui/tooltip' ; import { css , cx } from '@leafygreen-ui/emotion' ; import { useMutationObserver } from '@leafygreen-ui/hooks' ; import Stage from './Stage' ; import Counter from './Counter' ; import { getPipelineCounterTooltip , isStageElement , isElementOverflowed , } from './utils' ; import { getRootStyle , Size , layout , colors , pipelineAttr , pipelineStages , stageChevronAttr , } from './styles' ; interface StateForStyles { hasHiddenStages : boolean ; size : Size ; } interface PipelineProps { /**
     * Content that will appear inside of the Pipeline component.
     */ children : ReactNode ; /**
     * Optional className prop to apply to Pipeline content container.
     */ className ? : string ; /**
     * Alter the rendered size of the component
     */ size : Size ; } const getBaseStyle = ( { size } ) : string => cx ( getRootStyle ( { size } ) , css `
      counter-reset: hiddenCount;
      flex-direction: row;
    ` ) ; const getPipelineStyle = ( { size } ) : string => { const { minWidth } = layout [ size ] ; return cx ( getRootStyle ( { size } ) , css `
      flex-grow: 1;
      flex-shrink: 1;
      flex-basis: 100%;
      flex-wrap: wrap;
      overflow: hidden;
      list-style: none;
      padding: 0;
      margin: 0;
      min-width: ${ minWidth }px;
    ` ) ; } ; export const lastVisibleClassName = 'leafygreen-ui-pipeline-stage--last-visible' ; const getLastVisibleStageChevronStyles = ( { hasHiddenStages , size , } ) : string => { const { height , chevron } = layout [ size ] ; const { primary , secondary } = colors ; const outerSize = height / 2 ; const boxShadow = hasHiddenStages ? { innerColor : uiColors . white , outerColor : secondary . backgroundColor } : { innerColor : 'transparent' , outerColor : 'transparent' } ; return css `
    .${ lastVisibleClassName } > ${ stageChevronAttr . selector }::before {
      background-color: ${ primary . backgroundColor };
      box-shadow: 0 0 0 ${ chevron . size }px ${ boxShadow . innerColor },
        0 0 0 ${ outerSize }px ${ boxShadow . outerColor };
    }
  ` ; } ; const getStatefulStyles = ( state ) => ( { base : getBaseStyle ( state ) , pipeline : getPipelineStyle ( state ) , lastVisibleStageChevron : getLastVisibleStageChevronStyles ( state ) , } ) ; param props . children param props . className param props . size param props . variant const Pipeline = forwardRef ( ( { children , className = '' , size = Size . XSmall , ... rest } , ref ) : ReactElement => { const [ pipelineNode , setPipelineNode ] = useState < HTMLElement | null > ( null ) ; const [ hasHiddenStages , setHasHiddenStages ] = useState ( false ) ; /**
     * Determines whether the Pipeline element is overflowed.
     * If the Pipeline is overflowed, this means that we have stages which aren't visible and
     * so we should display the counter.
     */ const handleCounterDisplay = ( ) => { const result = isElementOverflowed ( pipelineNode ! ) ; setHasHiddenStages ( result ) ; } ; /**
     * Decorates the last visible stage with a className necessary for applying
     * the correct visual appearance. The mutation of the DOM is required otherwise the
     * Stage components will re-render, triggering an infinite loop on the
     * mutation observer.
     */ const setLastVisibleStage = ( ) => { const allStages = Array . from ( pipelineNode ! . children as HTMLCollectionOf < HTMLElement > ) ; allStages . forEach ( element => { element . classList . remove ( lastVisibleClassName ) ; } ) ; const lastVisibleStage = findLast ( allStages , element => element . dataset . stageVisible !== 'false' ) ; if ( lastVisibleStage ) { lastVisibleStage . classList . add ( lastVisibleClassName ) ; } } ; param records const observeChanges = ( records ) => { const types = records . map ( r => r . type ) ; const attrs = records . map ( r => r . attributeName ) ; if ( attrs . includes ( 'data-stage-visible' ) || types . includes ( 'childList' ) ) { handleCounterDisplay ( ) ; setLastVisibleStage ( ) ; } } ; useMutationObserver ( pipelineNode , { childList : true , subtree : true , attributes : true , } , observeChanges ) ; const childrenAsPipelineStages = React . Children . map ( children , child => { const props = { size , intersectionNode : pipelineNode , ref : createRef < HTMLLIElement > ( ) , } ; return isStageElement ( child ) ? React . cloneElement ( child , props ) : React . createElement ( Stage , { ... props , children : child } ) ; } ) ; const tooltipText = getPipelineCounterTooltip ( childrenAsPipelineStages ) ; const { base : baseStyle , pipeline : pipelineStyle , lastVisibleStageChevron : lastVisibleStageChevronStyle , } = getStatefulStyles ( { hasHiddenStages , size } ) ; return ( < div { ... rest } { ... pipelineAttr . prop } data - testid = "pipeline" ref = { ref } className = { cx ( baseStyle , className ) } > < ol { ... pipelineStages . prop } ref = { setPipelineNode } data - testid = "pipeline-stages" className = { cx ( pipelineStyle , lastVisibleStageChevronStyle ) } > { childrenAsPipelineStages } < /ol> { hasHiddenStages && React . Children . count ( childrenAsPipelineStages ) > 0 && ( < Tooltip align = "top" justify = "middle" trigger = { < Counter size = { size } / > } triggerEvent = "hover" darkMode = { true } > { tooltipText } < /Tooltip>)} < /div>); } ) ; Pipeline . displayName = 'Pipeline' ; Pipeline . propTypes = { children : PropTypes . node , className : PropTypes . string , size : PropTypes . oneOf ( Object . values ( Size ) ) . isRequired , } ; export default Pipeline ;