11import * as React from 'react'
22import * as _ from 'underscore'
33import ClassNames from 'classnames'
4-
5- import { faChevronUp } from '@fortawesome/free-solid-svg-icons'
6- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4+ import { Manager , Popper , Reference } from 'react-popper'
75
86export interface ColorPickerEvent {
97 selectedValue : string
@@ -36,6 +34,7 @@ interface IProps {
3634 placeholder ?: string
3735 className ?: string
3836 value ?: string
37+ disabled ?: boolean
3938 onChange ?: ( event : ColorPickerEvent ) => void
4039}
4140
@@ -45,6 +44,9 @@ interface IState {
4544}
4645
4746export class ColorPicker extends React . Component < IProps , IState > {
47+ private _popperRef : HTMLElement | null = null
48+ private _popperUpdate : ( ( ) => Promise < any > ) | undefined
49+
4850 constructor ( props : IProps ) {
4951 super ( props )
5052
@@ -58,10 +60,14 @@ export class ColorPicker extends React.Component<IProps, IState> {
5860 this . refreshChecked ( )
5961 }
6062
61- componentDidUpdate ( prevProps : IProps ) : void {
63+ async componentDidUpdate ( prevProps : IProps ) : Promise < void > {
6264 if ( this . props . value !== prevProps . value ) {
6365 this . refreshChecked ( )
6466 }
67+
68+ if ( this . state . expanded && typeof this . _popperUpdate === 'function' ) {
69+ await this . _popperUpdate ( )
70+ }
6571 }
6672
6773 private refreshChecked ( ) {
@@ -79,51 +85,135 @@ export class ColorPicker extends React.Component<IProps, IState> {
7985 private handleChange = ( value : string ) => {
8086 this . setState ( {
8187 selectedValue : value ,
88+ expanded : false ,
8289 } )
8390
8491 if ( this . props . onChange && typeof this . props . onChange === 'function' ) {
8592 this . props . onChange ( { selectedValue : value } )
8693 }
87- this . toggleExpco ( )
8894 }
8995
90- private toggleExpco = ( ) => {
96+ private toggleExpco = async ( e : React . MouseEvent < HTMLElement > ) => {
97+ e . preventDefault ( )
98+ e . stopPropagation ( )
99+
100+ if ( this . props . disabled ) return
101+
102+ if ( typeof this . _popperUpdate === 'function' ) {
103+ await this . _popperUpdate ( )
104+ }
105+
91106 this . setState ( {
92107 expanded : ! this . state . expanded ,
93108 } )
94109 }
95110
111+ private setPopperRef = ( ref : HTMLDivElement | null , popperRef : React . Ref < any > ) => {
112+ this . _popperRef = ref
113+ if ( typeof popperRef === 'function' ) {
114+ popperRef ( ref )
115+ }
116+ }
117+
118+ private setUpdate = ( update : ( ) => Promise < any > ) => {
119+ this . _popperUpdate = update
120+ }
121+
122+ private onBlur = ( event : React . FocusEvent < HTMLDivElement > ) => {
123+ if (
124+ ! (
125+ event . relatedTarget &&
126+ event . relatedTarget instanceof HTMLElement &&
127+ this . _popperRef &&
128+ ( this . _popperRef === event . relatedTarget || this . _popperRef . contains ( event . relatedTarget ) )
129+ )
130+ ) {
131+ this . setState ( {
132+ expanded : false ,
133+ } )
134+ }
135+ }
136+
96137 render ( ) : JSX . Element {
97138 return (
98- < div
99- className = { ClassNames (
100- 'expco focusable subtle colorpicker' ,
101- {
102- 'expco-expanded' : this . state . expanded ,
103- } ,
104- this . props . className
105- ) }
106- >
107- < div className = { ClassNames ( 'expco-title focusable-main' ) } onClick = { this . toggleExpco } >
108- < div className = "color-preview" style = { { backgroundColor : this . state . selectedValue } } > </ div >
109- </ div >
110- < a className = "action-btn right expco-expand subtle" onClick = { this . toggleExpco } >
111- < FontAwesomeIcon icon = { faChevronUp } />
112- </ a >
113- < div className = "expco-body bd" >
114- { _ . values (
115- _ . mapObject ( this . props . availableOptions , ( value , key ) => {
116- return (
117- < div className = "expco-item" key = { key } >
118- < label className = "action-btn" onClick = { ( ) => this . handleChange ( value ) } >
119- < div className = "color-preview" style = { { backgroundColor : value } } > </ div >
120- </ label >
121- </ div >
122- )
123- } )
139+ < Manager >
140+ < Reference >
141+ { ( { ref } ) => (
142+ < div
143+ ref = { ref }
144+ className = { ClassNames (
145+ 'expco form-select colorpicker' ,
146+ {
147+ 'expco-expanded' : this . state . expanded ,
148+ disabled : this . props . disabled ,
149+ } ,
150+ this . props . className
151+ ) }
152+ tabIndex = { - 1 }
153+ onBlur = { this . onBlur }
154+ onClick = { this . toggleExpco }
155+ >
156+ < div className = { ClassNames ( 'expco-title focusable-main' ) } onClick = { this . toggleExpco } >
157+ < div className = "color-preview" style = { { backgroundColor : this . state . selectedValue } } > </ div >
158+ </ div >
159+ < a className = "action-btn right expco-expand" onClick = { this . toggleExpco } >
160+
161+ </ a >
162+ </ div >
124163 ) }
125- </ div >
126- </ div >
164+ </ Reference >
165+ < Popper
166+ placement = "bottom-start"
167+ modifiers = { [
168+ { name : 'flip' , enabled : false } ,
169+ { name : 'offset' , enabled : true , options : { offset : [ 0 , - 1 ] } } ,
170+ {
171+ name : 'eventListeners' ,
172+ enabled : true ,
173+ options : {
174+ scroll : this . state . expanded ,
175+ resize : this . state . expanded ,
176+ } ,
177+ } ,
178+ ] }
179+ >
180+ { ( { ref, style, placement, update } ) => {
181+ this . setUpdate ( update )
182+ return (
183+ < div
184+ ref = { ( r ) => this . setPopperRef ( r , ref ) }
185+ style = { style }
186+ data-placement = { placement }
187+ className = { ClassNames (
188+ 'expco expco-popper colorpicker' ,
189+ {
190+ 'expco-expanded' : this . state . expanded ,
191+ } ,
192+ this . props . className
193+ ) }
194+ tabIndex = { - 1 }
195+ onBlur = { this . onBlur }
196+ >
197+ { this . state . expanded && (
198+ < div className = "expco-body bd" >
199+ < div className = "expco-list" >
200+ { _ . map ( this . props . availableOptions , ( value , key ) => {
201+ return (
202+ < div className = "expco-item" key = { key } >
203+ < label className = "action-btn" onClick = { ( ) => this . handleChange ( value ) } >
204+ < div className = "color-preview" style = { { backgroundColor : value } } > </ div >
205+ </ label >
206+ </ div >
207+ )
208+ } ) }
209+ </ div >
210+ </ div >
211+ ) }
212+ </ div >
213+ )
214+ } }
215+ </ Popper >
216+ </ Manager >
127217 )
128218 }
129219}
0 commit comments