1- import React , { useState , ReactNode , useCallback } from 'react'
1+ import React , { ReactNode } from 'react'
22import ClassNames from 'classnames'
3+ import { Manager , Popper , Reference } from 'react-popper'
34
45interface IProps {
56 selectedKey : string
@@ -19,37 +20,141 @@ export function SplitDropdownItem(props: Readonly<SplitDropdownItemObj>): SplitD
1920 }
2021}
2122
22- export function SplitDropdown ( props : Readonly < IProps > ) : JSX . Element {
23- const [ expanded , setExpanded ] = useState ( false )
24- const toggleExpco = useCallback ( ( ) => setExpanded ( ( oldVal ) => ! oldVal ) , [ ] )
25-
26- function getSelected ( ) {
27- const selectedChild =
28- props . options &&
29- Array . isArray ( props . options ) &&
30- props . options . find ( ( element ) => element . key === props . selectedKey ) ?. node
31- return selectedChild ? < > { selectedChild } </ > : < div className = "expco-item" > </ div >
32- }
33-
34- return (
35- < div
36- className = { ClassNames (
37- 'expco button focusable subtle split-dropdown' ,
38- {
39- 'expco-expanded' : expanded ,
40- } ,
41- props . className
42- ) }
43- >
44- < div className = "expco-title focusable-main" > { getSelected ( ) } </ div >
45- < div className = "action-btn right expco-expand subtle" onClick = { toggleExpco } >
46-
47- </ div >
48- < div className = "expco-body bd" >
49- { props . options ?. map ( ( child , index ) => (
50- < React . Fragment key = { child . key || index } > { child . node } </ React . Fragment >
51- ) ) }
52- </ div >
53- </ div >
54- )
23+ interface IState {
24+ expanded : boolean
25+ }
26+
27+ export class SplitDropdown extends React . Component < IProps , IState > {
28+ private _popperRef : HTMLElement | null = null
29+ private _popperUpdate : ( ( ) => Promise < any > ) | undefined
30+
31+ constructor ( props : IProps ) {
32+ super ( props )
33+
34+ this . state = {
35+ expanded : false ,
36+ }
37+ }
38+
39+ async componentDidUpdate ( _prevProps : IProps ) : Promise < void > {
40+ if ( this . state . expanded && typeof this . _popperUpdate === 'function' ) {
41+ await this . _popperUpdate ( )
42+ }
43+ }
44+
45+ private toggleExpco = async ( e : React . MouseEvent < HTMLElement > ) => {
46+ e . preventDefault ( )
47+ e . stopPropagation ( )
48+
49+ if ( typeof this . _popperUpdate === 'function' ) {
50+ await this . _popperUpdate ( )
51+ }
52+
53+ this . setState ( {
54+ expanded : ! this . state . expanded ,
55+ } )
56+ }
57+
58+ private setPopperRef = ( ref : HTMLDivElement | null , popperRef : React . Ref < any > ) => {
59+ this . _popperRef = ref
60+ if ( typeof popperRef === 'function' ) {
61+ popperRef ( ref )
62+ }
63+ }
64+
65+ private setUpdate = ( update : ( ) => Promise < any > ) => {
66+ this . _popperUpdate = update
67+ }
68+
69+ private onBlur = ( event : React . FocusEvent < HTMLDivElement > ) => {
70+ if (
71+ ! (
72+ event . relatedTarget &&
73+ event . relatedTarget instanceof HTMLElement &&
74+ this . _popperRef &&
75+ ( this . _popperRef === event . relatedTarget || this . _popperRef . contains ( event . relatedTarget ) )
76+ )
77+ ) {
78+ this . setState ( {
79+ expanded : false ,
80+ } )
81+ }
82+ }
83+
84+ render ( ) : JSX . Element {
85+ const getSelected = ( ) => {
86+ const selectedChild =
87+ this . props . options &&
88+ Array . isArray ( this . props . options ) &&
89+ this . props . options . find ( ( element ) => element . key === this . props . selectedKey ) ?. node
90+ return selectedChild ? < > { selectedChild } </ > : < div className = "expco-item" > </ div >
91+ }
92+
93+ return (
94+ < Manager >
95+ < Reference >
96+ { ( { ref } ) => (
97+ < div
98+ ref = { ref }
99+ className = { ClassNames (
100+ 'expco button subtle form-select' ,
101+ {
102+ 'expco-expanded' : this . state . expanded ,
103+ } ,
104+ this . props . className
105+ ) }
106+ >
107+ < div className = "expco-title focusable-main" > { getSelected ( ) } </ div >
108+ < div className = "action-btn expco-expand subtle" onClick = { this . toggleExpco } >
109+
110+ </ div >
111+ </ div >
112+ ) }
113+ </ Reference >
114+ < Popper
115+ placement = "bottom-start"
116+ modifiers = { [
117+ { name : 'flip' , enabled : false } ,
118+ { name : 'offset' , enabled : true , options : { offset : [ 0 , - 1 ] } } ,
119+ {
120+ name : 'eventListeners' ,
121+ enabled : true ,
122+ options : {
123+ scroll : this . state . expanded ,
124+ resize : this . state . expanded ,
125+ } ,
126+ } ,
127+ ] }
128+ >
129+ { ( { ref, style, placement, update } ) => {
130+ this . setUpdate ( update )
131+ return (
132+ < div
133+ ref = { ( r ) => this . setPopperRef ( r , ref ) }
134+ style = { style }
135+ data-placement = { placement }
136+ className = { ClassNames (
137+ 'expco expco-popper split-dropdown' ,
138+ {
139+ 'expco-expanded' : this . state . expanded ,
140+ } ,
141+ this . props . className
142+ ) }
143+ tabIndex = { - 1 }
144+ onBlur = { this . onBlur }
145+ >
146+ { this . state . expanded && (
147+ < div className = "expco-body bd" >
148+ { this . props . options ?. map ( ( child , index ) => (
149+ < React . Fragment key = { child . key || index } > { child . node } </ React . Fragment >
150+ ) ) }
151+ </ div >
152+ ) }
153+ </ div >
154+ )
155+ } }
156+ </ Popper >
157+ </ Manager >
158+ )
159+ }
55160}
0 commit comments