|
1 | | -import React, { Component } from 'react'; |
2 | | -import PropTypes from 'prop-types'; |
3 | | -import { IoIosArrowDown, IoIosArrowForward } from 'react-icons/io'; |
4 | | - |
5 | | -/** |
6 | | - * This Component was adapted from https://www.npmjs.com/package/react-collapsible. |
7 | | - */ |
8 | | -class Collapsible extends Component { |
9 | | - constructor(props) { |
10 | | - super(props); |
11 | | - |
12 | | - this.timeout = undefined; |
13 | | - |
14 | | - // Bind class methods |
15 | | - this.handleTriggerClick = this.handleTriggerClick.bind(this); |
16 | | - this.handleTransitionEnd = this.handleTransitionEnd.bind(this); |
17 | | - this.continueOpenCollapsible = this.continueOpenCollapsible.bind(this); |
18 | | - this.setInnerRef = this.setInnerRef.bind(this); |
19 | | - |
20 | | - // Defaults the dropdown to be closed |
21 | | - if (props.open) { |
22 | | - this.state = { |
23 | | - isClosed: false, |
24 | | - shouldSwitchAutoOnNextCycle: false, |
25 | | - height: 'auto', |
26 | | - transition: 'none', |
27 | | - hasBeenOpened: true, |
28 | | - overflow: props.overflowWhenOpen, |
29 | | - inTransition: false, |
30 | | - }; |
31 | | - } else { |
32 | | - this.state = { |
33 | | - isClosed: true, |
34 | | - shouldSwitchAutoOnNextCycle: false, |
35 | | - height: 0, |
36 | | - transition: `height ${props.transitionTime}ms ${props.easing}`, |
37 | | - hasBeenOpened: false, |
38 | | - overflow: 'hidden', |
39 | | - inTransition: false, |
40 | | - }; |
41 | | - } |
42 | | - } |
43 | | - |
44 | | - componentDidUpdate(prevProps, prevState) { |
45 | | - if (this.state.shouldOpenOnNextCycle) { |
46 | | - this.continueOpenCollapsible(); |
47 | | - } |
48 | | - |
49 | | - if ( |
50 | | - prevState.height === 'auto' && |
51 | | - this.state.shouldSwitchAutoOnNextCycle === true |
52 | | - ) { |
53 | | - window.clearTimeout(this.timeout); |
54 | | - this.timeout = window.setTimeout(() => { |
55 | | - // Set small timeout to ensure a true re-render |
56 | | - this.setState({ |
57 | | - height: 0, |
58 | | - overflow: 'hidden', |
59 | | - isClosed: true, |
60 | | - shouldSwitchAutoOnNextCycle: false, |
61 | | - }); |
62 | | - }, 50); |
63 | | - } |
64 | | - |
65 | | - // If there has been a change in the open prop (controlled by accordion) |
66 | | - if (prevProps.open !== this.props.open) { |
67 | | - if (this.props.open === true) { |
68 | | - this.openCollapsible(); |
69 | | - this.props.onOpening(); |
70 | | - } else { |
71 | | - this.closeCollapsible(); |
72 | | - this.props.onClosing(); |
73 | | - } |
74 | | - } |
75 | | - } |
76 | | - |
77 | | - componentWillUnmount() { |
78 | | - window.clearTimeout(this.timeout); |
79 | | - } |
80 | | - |
81 | | - closeCollapsible() { |
82 | | - this.setState({ |
83 | | - shouldSwitchAutoOnNextCycle: true, |
84 | | - height: this.innerRef.scrollHeight, |
85 | | - transition: `height ${ |
86 | | - this.props.transitionCloseTime |
87 | | - ? this.props.transitionCloseTime |
88 | | - : this.props.transitionTime |
89 | | - }ms ${this.props.easing}`, |
90 | | - inTransition: true, |
91 | | - }); |
92 | | - } |
93 | | - |
94 | | - openCollapsible() { |
95 | | - this.setState({ |
96 | | - inTransition: true, |
97 | | - shouldOpenOnNextCycle: true, |
98 | | - }); |
99 | | - } |
100 | | - |
101 | | - continueOpenCollapsible() { |
102 | | - this.setState({ |
103 | | - height: this.innerRef.scrollHeight, |
104 | | - transition: `height ${this.props.transitionTime}ms ${this.props.easing}`, |
105 | | - isClosed: false, |
106 | | - hasBeenOpened: true, |
107 | | - inTransition: true, |
108 | | - shouldOpenOnNextCycle: false, |
109 | | - }); |
110 | | - } |
111 | | - |
112 | | - handleTriggerClick(event) { |
113 | | - if (this.props.triggerDisabled || this.state.inTransition) { |
114 | | - return; |
115 | | - } |
116 | | - |
117 | | - event.preventDefault(); |
118 | | - |
119 | | - if (this.props.handleTriggerClick) { |
120 | | - this.props.handleTriggerClick(this.props.accordionPosition); |
121 | | - } else { |
122 | | - if (this.state.isClosed === true) { |
123 | | - this.openCollapsible(); |
124 | | - this.props.onOpening(); |
125 | | - this.props.onTriggerOpening(); |
126 | | - } else { |
127 | | - this.closeCollapsible(); |
128 | | - this.props.onClosing(); |
129 | | - this.props.onTriggerClosing(); |
130 | | - } |
131 | | - } |
132 | | - } |
133 | | - |
134 | | - renderNonClickableTriggerElement() { |
135 | | - if ( |
136 | | - this.props.triggerSibling && |
137 | | - typeof this.props.triggerSibling === 'string' |
138 | | - ) { |
139 | | - return ( |
140 | | - <span className={`${this.props.classParentString}__trigger-sibling`}> |
141 | | - {this.props.triggerSibling} |
142 | | - </span> |
143 | | - ); |
144 | | - } else if ( |
145 | | - this.props.triggerSibling && |
146 | | - typeof this.props.triggerSibling === 'function' |
147 | | - ) { |
148 | | - return this.props.triggerSibling(); |
149 | | - } else if (this.props.triggerSibling) { |
150 | | - return <this.props.triggerSibling />; |
151 | | - } |
152 | | - |
153 | | - return null; |
154 | | - } |
155 | | - |
156 | | - handleTransitionEnd(e) { |
157 | | - // only handle transitions that origin from the container of this component |
158 | | - if (e.target !== this.innerRef) { |
159 | | - return; |
160 | | - } |
161 | | - // Switch to height auto to make the container responsive |
162 | | - if (!this.state.isClosed) { |
163 | | - this.setState({ |
164 | | - height: 'auto', |
165 | | - overflow: this.props.overflowWhenOpen, |
166 | | - inTransition: false, |
167 | | - }); |
168 | | - this.props.onOpen(); |
169 | | - } else { |
170 | | - this.setState({ inTransition: false }); |
171 | | - this.props.onClose(); |
172 | | - } |
173 | | - } |
174 | | - |
175 | | - setInnerRef(ref) { |
176 | | - this.innerRef = ref; |
177 | | - } |
178 | | - |
179 | | - render() { |
180 | | - const dropdownStyle = { |
181 | | - height: this.state.height, |
182 | | - WebkitTransition: this.state.transition, |
183 | | - msTransition: this.state.transition, |
184 | | - transition: this.state.transition, |
185 | | - overflow: this.state.overflow, |
186 | | - }; |
187 | | - |
188 | | - var openClass = this.state.isClosed ? 'is-closed' : 'is-open'; |
189 | | - var disabledClass = this.props.triggerDisabled ? 'is-disabled' : ''; |
190 | | - |
191 | | - //If user wants different text when tray is open |
192 | | - var trigger = |
193 | | - this.state.isClosed === false && this.props.triggerWhenOpen !== undefined |
194 | | - ? this.props.triggerWhenOpen |
195 | | - : this.props.trigger; |
196 | | - |
197 | | - const ContentContainerElement = this.props.contentContainerTagName; |
198 | | - |
199 | | - // If user wants a trigger wrapping element different than 'span' |
200 | | - const TriggerElement = this.props.triggerTagName; |
201 | | - |
202 | | - // Don't render children until the first opening of the Collapsible if lazy rendering is enabled |
203 | | - var children = |
204 | | - this.props.lazyRender && |
205 | | - !this.state.hasBeenOpened && |
206 | | - this.state.isClosed && |
207 | | - !this.state.inTransition |
208 | | - ? null |
209 | | - : this.props.children; |
210 | | - |
211 | | - // Construct CSS classes strings |
212 | | - const triggerClassString = `${ |
213 | | - this.props.classParentString |
214 | | - }__trigger ${openClass} ${disabledClass} ${ |
215 | | - this.state.isClosed |
216 | | - ? this.props.triggerClassName |
217 | | - : this.props.triggerOpenedClassName |
218 | | - }`; |
219 | | - const parentClassString = `${this.props.classParentString} ${ |
220 | | - this.state.isClosed ? this.props.className : this.props.openedClassName |
221 | | - }`; |
222 | | - const outerClassString = `${this.props.classParentString}__contentOuter ${this.props.contentOuterClassName}`; |
223 | | - const innerClassString = `${this.props.classParentString}__contentInner ${this.props.contentInnerClassName}`; |
224 | | - |
225 | | - return ( |
226 | | - <ContentContainerElement |
227 | | - className={parentClassString.trim()} |
228 | | - {...this.props.containerElementProps} |
229 | | - > |
230 | | - <TriggerElement |
231 | | - className={triggerClassString.trim()} |
232 | | - onClick={this.handleTriggerClick} |
233 | | - style={this.props.triggerStyle && this.props.triggerStyle} |
234 | | - onKeyPress={(event) => { |
235 | | - const { key } = event; |
236 | | - if ( |
237 | | - (key === ' ' && |
238 | | - this.props.triggerTagName.toLowerCase() !== 'button') || |
239 | | - key === 'Enter' |
240 | | - ) { |
241 | | - this.handleTriggerClick(event); |
242 | | - } |
243 | | - }} |
244 | | - tabIndex={this.props.tabIndex && this.props.tabIndex} |
245 | | - {...this.props.triggerElementProps} |
246 | | - > |
247 | | - <div className="collapsible-header"> |
248 | | - {trigger} |
249 | | - <IoIosArrowDown |
250 | | - style={{ |
251 | | - display: this.state.isClosed ? 'none' : 'block', |
252 | | - }} |
253 | | - /> |
254 | | - <IoIosArrowForward |
255 | | - style={{ |
256 | | - display: this.state.isClosed ? 'block' : 'none', |
257 | | - }} |
258 | | - /> |
259 | | - </div> |
260 | | - </TriggerElement> |
261 | | - |
262 | | - {this.renderNonClickableTriggerElement()} |
263 | | - |
264 | | - <div |
265 | | - className={outerClassString.trim()} |
266 | | - style={dropdownStyle} |
267 | | - onTransitionEnd={this.handleTransitionEnd} |
268 | | - ref={this.setInnerRef} |
269 | | - > |
270 | | - <div className={innerClassString.trim()}>{children}</div> |
271 | | - </div> |
272 | | - </ContentContainerElement> |
273 | | - ); |
274 | | - } |
| 1 | +import React, { Fragment } from "react"; |
| 2 | +import Collapsible from "react-collapsible"; |
| 3 | +import { IoIosArrowDown, IoIosArrowForward } from "react-icons/io"; |
| 4 | + |
| 5 | +export default function CollapsibleWrapper({ trigger, children, ...props }) { |
| 6 | + const triggerComponent = ( |
| 7 | + <Fragment> |
| 8 | + {trigger} |
| 9 | + <IoIosArrowDown className="arrow-down" /> |
| 10 | + <IoIosArrowForward className="arrow-up" /> |
| 11 | + </Fragment> |
| 12 | + ); |
| 13 | + return ( |
| 14 | + <Collapsible trigger={triggerComponent} {...props}> |
| 15 | + {children} |
| 16 | + </Collapsible> |
| 17 | + ); |
275 | 18 | } |
276 | | - |
277 | | -Collapsible.propTypes = { |
278 | | - transitionTime: PropTypes.number, |
279 | | - transitionCloseTime: PropTypes.number, |
280 | | - triggerTagName: PropTypes.string, |
281 | | - easing: PropTypes.string, |
282 | | - open: PropTypes.bool, |
283 | | - containerElementProps: PropTypes.object, |
284 | | - triggerElementProps: PropTypes.object, |
285 | | - classParentString: PropTypes.string, |
286 | | - openedClassName: PropTypes.string, |
287 | | - triggerStyle: PropTypes.object, |
288 | | - triggerClassName: PropTypes.string, |
289 | | - triggerOpenedClassName: PropTypes.string, |
290 | | - contentOuterClassName: PropTypes.string, |
291 | | - contentInnerClassName: PropTypes.string, |
292 | | - accordionPosition: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), |
293 | | - handleTriggerClick: PropTypes.func, |
294 | | - onOpen: PropTypes.func, |
295 | | - onClose: PropTypes.func, |
296 | | - onOpening: PropTypes.func, |
297 | | - onClosing: PropTypes.func, |
298 | | - onTriggerOpening: PropTypes.func, |
299 | | - onTriggerClosing: PropTypes.func, |
300 | | - trigger: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), |
301 | | - triggerWhenOpen: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), |
302 | | - triggerDisabled: PropTypes.bool, |
303 | | - lazyRender: PropTypes.bool, |
304 | | - overflowWhenOpen: PropTypes.oneOf([ |
305 | | - 'hidden', |
306 | | - 'visible', |
307 | | - 'auto', |
308 | | - 'scroll', |
309 | | - 'inherit', |
310 | | - 'initial', |
311 | | - 'unset', |
312 | | - ]), |
313 | | - triggerSibling: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), |
314 | | - tabIndex: PropTypes.number, |
315 | | - contentContainerTagName: PropTypes.string, |
316 | | -}; |
317 | | - |
318 | | -Collapsible.defaultProps = { |
319 | | - transitionTime: 400, |
320 | | - transitionCloseTime: null, |
321 | | - triggerTagName: 'span', |
322 | | - easing: 'linear', |
323 | | - open: false, |
324 | | - classParentString: 'Collapsible', |
325 | | - triggerDisabled: false, |
326 | | - lazyRender: false, |
327 | | - overflowWhenOpen: 'hidden', |
328 | | - openedClassName: '', |
329 | | - triggerStyle: null, |
330 | | - triggerClassName: '', |
331 | | - triggerOpenedClassName: '', |
332 | | - contentOuterClassName: '', |
333 | | - contentInnerClassName: '', |
334 | | - className: '', |
335 | | - triggerSibling: null, |
336 | | - onOpen: () => {}, |
337 | | - onClose: () => {}, |
338 | | - onOpening: () => {}, |
339 | | - onClosing: () => {}, |
340 | | - onTriggerOpening: () => {}, |
341 | | - onTriggerClosing: () => {}, |
342 | | - tabIndex: null, |
343 | | - contentContainerTagName: 'div', |
344 | | -}; |
345 | | - |
346 | | -export default Collapsible; |
0 commit comments