import React from 'react' ; import { isElement } from 'react-is' ; import classnames from 'classnames' ; import { MDCChipSetFoundation } from '@material/chips/chip-set/foundation' ; import ChipCheckmark from './ChipCheckmark' ; import { ChipProps } from './Chip' ; type ChipType = React . ReactElement < ChipProps > ; export interface ChipSetProps { className ? : string ; selectedChipIds ? : string [ ] ; handleSelect ? : ( selectedChipIds : string [ ] ) => void ; updateChips ? : ( chips : Partial < ChipProps > [ ] ) => void ; choice ? : boolean ; filter ? : boolean ; input ? : boolean ; children : ChipType | ChipType [ ] | React . ReactNode ; } interface ChipSetState { foundation : MDCChipSetFoundation | null ; selectedChipIds : string [ ] ; hasInitialized : boolean ; } export default class ChipSet extends React . Component < ChipSetProps , ChipSetState > { checkmarkWidth = 0 ; constructor ( props ) { super ( props ) ; this . state = { selectedChipIds : props . selectedChipIds ! , foundation : null , hasInitialized : false , } ; } static defaultProps : Partial < ChipSetProps > = { className : '' , selectedChipIds : [ ] , handleSelect : ( ) => { } , updateChips : ( ) => { } , choice : false , filter : false , input : false , } ; componentDidMount ( ) { const foundation = new MDCChipSetFoundation ( this . adapter ) ; this . setState ( { foundation } ) ; foundation . init ( ) ; } componentDidUpdate ( prevProps , prevState ) { const { selectedChipIds } = this . props ; if ( this . state . foundation !== prevState . foundation ) { this . initChipSelection ( ) ; } if ( selectedChipIds !== prevProps . selectedChipIds ) { this . setState ( { selectedChipIds : selectedChipIds ! } ) ; } } componentWillUnmount ( ) { this . state . foundation && this . state . foundation . destroy ( ) ; } get classes ( ) { const { className , filter , choice , input } = this . props ; return classnames ( 'mdc-chip-set' , className , { 'mdc-chip-set--filter' : filter , 'mdc-chip-set--choice' : choice , 'mdc-chip-set--input' : input , } ) ; } get adapter ( ) { return { hasClass : ( className ) => this . classes . split ( ' ' ) . includes ( className ) , setSelected : ( ) => { const selectedChipIds = this . state . foundation ! . getSelectedChipIds ( ) . slice ( ) ; this . setState ( { selectedChipIds } , ( ) => { this . props . handleSelect ! ( selectedChipIds ) ; } ) ; } , removeChip : this . removeChip , } ; } initChipSelection ( ) { React . Children . forEach ( this . props . children , ( child ) => { if ( ! child || ! isElement ( child ) ) { return ; } const { id } = child . props as ChipProps ; const selected = this . state . selectedChipIds . indexOf ( id ! ) > - 1 ; if ( selected ) { this . state . foundation ! . select ( id ! ) ; } } ) ; this . setState ( { hasInitialized : true } ) ; } handleInteraction = ( chipId ) => { this . state . foundation ! . handleChipInteraction ( chipId ) ; } ; handleSelect = ( chipId , selected ) => { this . state . foundation ! . handleChipSelection ( chipId , selected ) ; } ; handleRemove = ( chipId ) => { this . state . foundation ! . handleChipRemoval ( chipId ) ; } ; removeChip = ( chipId ) => { const { updateChips , children } = this . props ; if ( ! children ) return ; const chips = React . Children . toArray ( children ) . filter ( isElement ) . slice ( ) as React . ReactElement < ChipProps > [ ] ; for ( let i = 0 ; i < chips . length ; i ++ ) { const chip = chips [ i ] ; if ( chip . props . id === chipId ) { chips . splice ( i , 1 ) ; break ; } } const chipsArray = chips . length ? chips . map ( ( chip ) => chip . props ) : [ ] ; updateChips ! ( chipsArray ) ; } ; setCheckmarkWidth = ( checkmark ) => { if ( ! ! this . checkmarkWidth || ! checkmark ) { return ; } this . checkmarkWidth = checkmark . width ; } ; computeBoundingRect = ( chipElement ) => { const { height , width : chipWidth } = chipElement . getBoundingClientRect ( ) ; const width = chipWidth + this . checkmarkWidth ; return { height , width } ; } ; renderChip = ( chip ? ) => { if ( ! chip || ! isElement ( chip ) ) return ; const { choice , filter , input } = this . props ; if ( ( choice || filter || input ) && ! chip . props . id ) { throw new Error ( 'Chip variant missing required property: id.' ) ; } const { selectedChipIds } = this . state ; const selected = selectedChipIds . indexOf ( chip . props . id ) > - 1 ; const { handleInteraction , handleSelect , handleRemove , ... chipProps } = chip . props ; const props = Object . assign ( { } , ... chipProps , { selected , handleSelect : ( id , selected ) : void => { handleSelect ! ( id , selected ) ; this . handleSelect ( id , selected ) ; } , handleInteraction : ( id ) : void => { handleInteraction ! ( id ) ; this . handleInteraction ( id ) ; } , handleRemove : ( id ) : void => { handleRemove ! ( id ) ; this . handleRemove ( id ) ; } , chipCheckmark : filter ? ( < ChipCheckmark ref = { this . setCheckmarkWidth } / > ) : null , computeBoundingRect : filter ? this . computeBoundingRect : null , } ) ; return React . cloneElement ( chip , props ) ; } ; render ( ) { if ( ! this . state . hasInitialized ) return null ; return ( < div className = { this . classes } > { React . Children . map ( this . props . children as ChipType | ChipType [ ] , this . renderChip ) } < /div>); } }