import React from 'react' ; import classnames from 'classnames' ; import { withRipple , InjectedProps , RippledComponentProps , } from '@material/react-ripple' ; import { MDCChipFoundation } from '@material/chips/chip/foundation' ; import { MDCChipAdapter } from '@material/chips/chip/adapter' ; export interface ChipProps extends InjectedProps < HTMLDivElement > { id ? : string ; label ? : string ; className ? : string ; selected ? : boolean ; handleSelect ? : ( id : string , selected : boolean ) => void ; handleRemove ? : ( id : string ) => void ; handleInteraction ? : ( id : string ) => void ; handleTrailingIconInteraction ? : ( id : string ) => void ; onClick ? : React . MouseEventHandler < HTMLDivElement > ; onKeyDown ? : React . KeyboardEventHandler < HTMLDivElement > ; onTransitionEnd ? : React . TransitionEventHandler < HTMLDivElement > ; chipCheckmark ? : React . ReactElement < HTMLElement > ; leadingIcon ? : React . ReactElement < HTMLElement > ; shouldRemoveOnTrailingIconClick ? : boolean ; trailingIcon ? : React . ReactElement < HTMLElement > ; initRipple : ( surface : HTMLElement | null ) => void ; } type ChipState = { classList : Set < string > ; leadingIconClassList : Set < string > ; } ; export class Chip extends React . Component < ChipProps , ChipState > { chipElement : HTMLDivElement | null = null ; foundation ! : MDCChipFoundation ; static defaultProps : Partial < ChipProps > = { id : '' , label : '' , className : '' , selected : false , onClick : ( ) => { } , onKeyDown : ( ) => { } , onTransitionEnd : ( ) => { } , initRipple : ( ) => { } , handleSelect : ( ) => { } , handleRemove : ( ) => { } , handleInteraction : ( ) => { } , handleTrailingIconInteraction : ( ) => { } , shouldRemoveOnTrailingIconClick : true , } ; state : ChipState = { classList : new Set ( ) , leadingIconClassList : new Set ( ) , } ; componentDidMount ( ) { const { selected , shouldRemoveOnTrailingIconClick } = this . props ; this . foundation = new MDCChipFoundation ( this . adapter ) ; this . foundation . init ( ) ; this . foundation . setSelected ( selected ! ) ; if ( shouldRemoveOnTrailingIconClick !== this . foundation . getShouldRemoveOnTrailingIconClick ( ) ) { this . foundation . setShouldRemoveOnTrailingIconClick ( shouldRemoveOnTrailingIconClick ! ) ; } } componentDidUpdate ( prevProps ) { const { selected , shouldRemoveOnTrailingIconClick } = this . props ; if ( selected !== prevProps . selected ) { this . foundation . setSelected ( selected ! ) ; } if ( shouldRemoveOnTrailingIconClick !== prevProps . shouldRemoveOnTrailingIconClick ) { this . foundation . setShouldRemoveOnTrailingIconClick ( shouldRemoveOnTrailingIconClick ! ) ; } } componentWillUnmount ( ) { this . foundation . destroy ( ) ; } init = ( el ) => { this . chipElement = el ; this . props . initRipple && this . props . initRipple ( el ) ; } ; get classes ( ) { const { classList } = this . state ; const { className } = this . props ; return classnames ( 'mdc-chip' , Array . from ( classList ) , className ) ; } get adapter ( ) : MDCChipAdapter { return { addClass : ( className ) => { const classList = new Set ( this . state . classList ) ; classList . add ( className ) ; this . setState ( { classList } ) ; } , removeClass : ( className ) => { const classList = new Set ( this . state . classList ) ; classList . delete ( className ) ; this . setState ( { classList } ) ; } , hasClass : ( className ) => this . classes . split ( ' ' ) . includes ( className ) , hasLeadingIcon : ( ) => Boolean ( this . props . leadingIcon ) , eventTargetHasClass : ( target , className ) => target . classList . contains ( className ) , getComputedStyleValue : ( propertyName ) => { if ( ! this . chipElement ) return '' ; return window . getComputedStyle ( this . chipElement ) . getPropertyValue ( propertyName ) ; } , getRootBoundingClientRect : ( ) => { if ( ! this . chipElement ) { const defaultDOMRect = { bottom : 0 , height : 0 , left : 0 , right : 0 , top : 0 , width : 0 , x : 0 , y : 0 , } ; return defaultDOMRect ; } return this . chipElement . getBoundingClientRect ( ) ; } , getCheckmarkBoundingClientRect : ( ) => { const { chipCheckmark } = this . props ; if ( ! ( chipCheckmark && chipCheckmark . props && chipCheckmark . props . getBoundingClientRect ) ) { const defaultDOMRect = { bottom : 0 , height : 0 , left : 0 , right : 0 , top : 0 , width : 0 , x : 0 , y : 0 , } ; return defaultDOMRect ; } return chipCheckmark . props . getBoundingClientRect ( ) ; } , setStyleProperty : ( propertyName , value ) => { if ( ! this . chipElement ) return ; this . chipElement . style . setProperty ( propertyName , value ) ; } , notifyRemoval : ( ) => this . props . handleRemove ! ( this . props . id ! ) , notifyInteraction : ( ) => this . props . handleInteraction ! ( this . props . id ! ) , notifySelection : ( selected ) => this . props . handleSelect ! ( this . props . id ! , selected ) , notifyTrailingIconInteraction : ( ) => this . props . handleTrailingIconInteraction ! ( this . props . id ! ) , addClassToLeadingIcon : ( className ) => { const leadingIconClassList = new Set ( this . state . leadingIconClassList ) ; leadingIconClassList . add ( className ) ; this . setState ( { leadingIconClassList } ) ; } , removeClassFromLeadingIcon : ( className ) => { const leadingIconClassList = new Set ( this . state . leadingIconClassList ) ; leadingIconClassList . delete ( className ) ; this . setState ( { leadingIconClassList } ) ; } , } ; } onClick = ( e ) => { this . props . onClick ! ( e ) ; this . foundation . handleInteraction ( e . nativeEvent ) ; } ; onKeyDown = ( e ) => { this . props . onKeyDown ! ( e ) ; this . foundation . handleInteraction ( e . nativeEvent ) ; } ; handleTrailingIconClick = ( e ) => this . foundation . handleTrailingIconInteraction ( e . nativeEvent ) ; handleTransitionEnd = ( e ) => { this . props . onTransitionEnd ! ( e ) ; this . foundation . handleTransitionEnd ( e . nativeEvent ) ; } ; renderLeadingIcon = ( leadingIcon ) => { const { leadingIconClassList } = this . state ; const { className , ... otherProps } = leadingIcon . props ; const props = { className : classnames ( className , Array . from ( leadingIconClassList ) , 'mdc-chip__icon' , 'mdc-chip__icon--leading' ) , ... otherProps , } ; return React . cloneElement ( leadingIcon , props ) ; } ; renderTrailingIcon = ( trailingIcon ) => { const { className , ... otherProps } = trailingIcon . props ; const props = { className : classnames ( className , 'mdc-chip__icon' , 'mdc-chip__icon--trailing' ) , onClick : this . handleTrailingIconClick , onKeyDown : this . handleTrailingIconClick , tabIndex : 0 , role : 'button' , ... otherProps , } ; return React . cloneElement ( trailingIcon , props ) ; } ; render ( ) { const { id , className , selected , handleSelect , handleInteraction , handleRemove , handleTrailingIconInteraction , onClick , onKeyDown , onTransitionEnd , computeBoundingRect , initRipple , unbounded , shouldRemoveOnTrailingIconClick , chipCheckmark , leadingIcon , trailingIcon , label , ... otherProps } = this . props ; return ( < div tabIndex = { 0 } className = { this . classes } onClick = { this . onClick } onKeyDown = { this . onKeyDown } onTransitionEnd = { this . handleTransitionEnd } ref = { this . init } { ... otherProps } > { leadingIcon && this . renderLeadingIcon ( leadingIcon ) } { chipCheckmark } < div className = 'mdc-chip__text' > { label } < /div> { trailingIcon && this . renderTrailingIcon ( trailingIcon ) } < /div>); } } export default withRipple < ChipProps , HTMLDivElement > ( Chip ) ;