|
1 | | -import classnames from 'classnames'; |
2 | 1 | import PropTypes from 'prop-types'; |
3 | | -import shortid from '../utils/shortId'; |
| 2 | +import Tree from './_Tree'; |
| 3 | +import TreeBranch from './_TreeBranch'; |
| 4 | +import TreeCol from './_TreeCol'; |
| 5 | +import TreeHead from './_TreeHead'; |
| 6 | +import TreeItem from './_TreeItem'; |
| 7 | +import TreeRow from './_TreeRow'; |
4 | 8 | import React, { Component } from 'react'; |
5 | 9 |
|
6 | | -export class TreeCol extends Component { |
7 | | - render() { |
8 | | - const { |
9 | | - children, |
10 | | - className, |
11 | | - ...rest |
12 | | - } = this.props; |
13 | | - |
14 | | - const cellClassName = classnames( |
15 | | - 'fd-tree__col', |
16 | | - className |
17 | | - ); |
18 | | - |
19 | | - return ( |
20 | | - <div |
21 | | - {...rest} |
22 | | - className={cellClassName}> |
23 | | - {children} |
24 | | - </div> |
25 | | - ); |
26 | | - } |
27 | | -} |
28 | | - |
29 | | -TreeCol.displayName = 'TreeCol'; |
30 | | - |
31 | | -TreeCol.propTypes = { |
32 | | - children: PropTypes.node, |
33 | | - className: PropTypes.string |
34 | | -}; |
35 | | - |
36 | | -export class TreeHead extends Component { |
37 | | - render() { |
38 | | - const { |
39 | | - buttonProps, |
40 | | - children, |
41 | | - className, |
42 | | - isExpanded, |
43 | | - onExpandAll, |
44 | | - ...rest |
45 | | - } = this.props; |
46 | | - |
47 | | - const headerClassName = classnames( |
48 | | - 'fd-tree', |
49 | | - 'fd-tree--header', |
50 | | - className |
51 | | - ); |
52 | | - |
53 | | - return ( |
54 | | - <div {...rest} className={headerClassName}> |
55 | | - <div className='fd-tree__row fd-tree__row--header'> |
56 | | - { |
57 | | - React.Children.map(children, (child, index) => { |
58 | | - const isFirstTreeCol = index === 0 && child.type && child.type.displayName === 'TreeCol'; |
59 | | - |
60 | | - // Add control class to first TreeCol element |
61 | | - const childClassName = classnames({ |
62 | | - 'fd-tree__col--control': isFirstTreeCol |
63 | | - }); |
64 | | - |
65 | | - // Add expand button to first TreeCol element |
66 | | - const newChildren = isFirstTreeCol ? ( |
67 | | - <div> |
68 | | - <button |
69 | | - {...buttonProps} |
70 | | - aria-label={isExpanded ? 'collapse all' : 'expand all'} |
71 | | - aria-pressed={isExpanded} |
72 | | - className='fd-tree__control' |
73 | | - onClick={onExpandAll} /> |
74 | | - {child.props && child.props.children} |
75 | | - </div> |
76 | | - ) : child.props && child.props.children; |
77 | | - |
78 | | - return React.cloneElement(child, { |
79 | | - children: newChildren, |
80 | | - className: childClassName |
81 | | - }); |
82 | | - }) |
83 | | - } |
84 | | - </div> |
85 | | - </div> |
86 | | - ); |
87 | | - } |
88 | | -} |
89 | | - |
90 | | -TreeHead.displayName = 'TreeHead'; |
91 | | - |
92 | | -TreeHead.propTypes = { |
93 | | - buttonProps: PropTypes.object, |
94 | | - children: PropTypes.node, |
95 | | - className: PropTypes.string, |
96 | | - isExpanded: PropTypes.bool, |
97 | | - onExpandAll: PropTypes.func |
98 | | -}; |
99 | | - |
100 | | -TreeHead.propDescriptions = { |
101 | | - buttonProps: 'Additional props to be spread to the header expand/collapse `<button>` element.', |
102 | | - children: 'Node(s) to render within the component. Expecting `TreeCol` components as children.', |
103 | | - isExpanded: '_INTERNAL USE ONLY._', |
104 | | - onExpandAll: '_INTERNAL USE ONLY._' |
105 | | -}; |
106 | | - |
107 | | -export class TreeRow extends Component { |
108 | | - render() { |
109 | | - const { |
110 | | - children, |
111 | | - isExpanded, |
112 | | - isParent, |
113 | | - onExpandClick, |
114 | | - rowId, |
115 | | - ...rest |
116 | | - } = this.props; |
117 | | - |
118 | | - // Render child TreeCols |
119 | | - const cells = React.Children.map(children, (child, index) => { |
120 | | - const isTreeCol = child.type && child.type.displayName === 'TreeCol'; |
121 | | - const isFirstTreeCol = index === 0 && isTreeCol; |
122 | | - |
123 | | - // Add control class to first TreeCol element |
124 | | - const className = classnames({ |
125 | | - 'fd-tree__col--control': isFirstTreeCol |
126 | | - }); |
127 | | - |
128 | | - // Add expand button to first TableCell if parent list |
129 | | - const newChildren = isFirstTreeCol && isParent ? ( |
130 | | - <div> |
131 | | - <button |
132 | | - aria-controls={rowId} |
133 | | - aria-label={isExpanded ? 'collapse' : 'expand'} |
134 | | - aria-pressed={isExpanded} |
135 | | - className='fd-tree__control' |
136 | | - onClick={onExpandClick} /> |
137 | | - {child.props && child.props.children} |
138 | | - </div> |
139 | | - ) : child.props && child.props.children; |
140 | | - |
141 | | - return isTreeCol ? |
142 | | - React.cloneElement(child, { className, children: newChildren }) : |
143 | | - null; |
144 | | - }); |
145 | | - |
146 | | - return ( |
147 | | - <div {...rest} className='fd-tree__row'> |
148 | | - {cells} |
149 | | - </div> |
150 | | - ); |
151 | | - } |
152 | | -} |
153 | | - |
154 | | -TreeRow.displayName = 'TreeRow'; |
155 | | - |
156 | | -TreeRow.propTypes = { |
157 | | - children: PropTypes.node, |
158 | | - isExpanded: PropTypes.bool, |
159 | | - isParent: PropTypes.bool, |
160 | | - rowId: PropTypes.string, |
161 | | - onExpandClick: PropTypes.func |
162 | | -}; |
163 | | - |
164 | | -TreeRow.propDescriptions = { |
165 | | - children: 'Node(s) to render within the component. Expecting `TreeCol` components as children.', |
166 | | - isExpanded: '_INTERNAL USE ONLY._', |
167 | | - isParent: '_INTERNAL USE ONLY._', |
168 | | - rowId: '_INTERNAL USE ONLY._', |
169 | | - onExpandClick: '_INTERNAL USE ONLY._' |
170 | | -}; |
171 | | - |
172 | | -export class TreeItem extends Component { |
173 | | - constructor(props) { |
174 | | - super(props); |
175 | | - |
176 | | - const { |
177 | | - rowId, |
178 | | - onExpandClick |
179 | | - } = this.props; |
180 | | - |
181 | | - // Generate unique id for row to manage expand/collapse state in parent if prop isn't given |
182 | | - this.rowId = rowId || shortid.generate(); |
183 | | - |
184 | | - // Initialize row in parent TreeView state |
185 | | - onExpandClick(this.rowId); |
186 | | - } |
187 | | - |
188 | | - render() { |
189 | | - const { |
190 | | - children, |
191 | | - expandData, |
192 | | - level, |
193 | | - onExpandClick, |
194 | | - isExpanded: isExpandedProp, |
195 | | - rowId, |
196 | | - ...rest |
197 | | - } = this.props; |
198 | | - const isExpanded = isExpandedProp || !!expandData[this.rowId]; |
199 | | - |
200 | | - // Render child TreeBranch with correct props |
201 | | - const childBranch = React.Children.map(children, (child) => { |
202 | | - const isTreeBranch = child.type && child.type.displayName === 'TreeBranch'; |
203 | | - |
204 | | - return isTreeBranch ? |
205 | | - React.cloneElement(child, { |
206 | | - expandData, |
207 | | - onExpandClick, |
208 | | - isExpanded, |
209 | | - // Increment child branch level |
210 | | - level: level + 1 |
211 | | - }) : |
212 | | - null; |
213 | | - }); |
214 | | - |
215 | | - // Render child TreeRow with correct props |
216 | | - const childRow = React.Children.map(children, (child) => { |
217 | | - const isTreeRow = child.type && child.type.displayName === 'TreeRow'; |
218 | | - |
219 | | - return isTreeRow ? |
220 | | - React.cloneElement(child, { |
221 | | - isExpanded, |
222 | | - onExpandClick: () => onExpandClick(this.rowId), |
223 | | - isParent: !!childBranch[0], |
224 | | - rowId: this.rowId |
225 | | - }) : |
226 | | - null; |
227 | | - }); |
228 | | - |
229 | | - return ( |
230 | | - <li |
231 | | - {...rest} |
232 | | - aria-expanded={isExpanded} |
233 | | - className='fd-tree__item' |
234 | | - id={this.rowId} |
235 | | - role='treeitem'> |
236 | | - {childRow} |
237 | | - {childBranch} |
238 | | - </li> |
239 | | - ); |
240 | | - } |
241 | | -} |
242 | | - |
243 | | -TreeItem.displayName = 'TreeItem'; |
244 | | - |
245 | | -TreeItem.propTypes = { |
246 | | - children: PropTypes.node, |
247 | | - expandData: PropTypes.object, |
248 | | - isExpanded: PropTypes.bool, |
249 | | - level: PropTypes.number, |
250 | | - rowId: PropTypes.string, |
251 | | - onExpandClick: PropTypes.func |
252 | | -}; |
253 | | - |
254 | | -TreeItem.defaultProps = { |
255 | | - expandData: {}, |
256 | | - level: 0, |
257 | | - onExpandClick: () => {} |
258 | | -}; |
259 | | - |
260 | | -TreeItem.propDescriptions = { |
261 | | - children: 'Node(s) to render within the component. Expecting `TreeRow` and `TreeBranch` components as children.', |
262 | | - expandData: '_INTERNAL USE ONLY._', |
263 | | - isExpanded: 'Set to *true* for expanded tree item. This variable is handled internally, but can be overridden by the consumer through this prop.', |
264 | | - level: '_INTERNAL USE ONLY._', |
265 | | - rowId: 'ID used to track the expanded/collapsed state of the row. This variable is handled internally, but can be overridden by the consumer through this prop.', |
266 | | - onExpandClick: '_INTERNAL USE ONLY._' |
267 | | -}; |
268 | | - |
269 | | -export class TreeBranch extends Component { |
270 | | - render() { |
271 | | - return <_Tree {...this.props} />; |
272 | | - } |
273 | | -} |
274 | | - |
275 | | -TreeBranch.displayName = 'TreeBranch'; |
276 | | - |
277 | | -TreeBranch.propTypes = { |
278 | | - children: PropTypes.node, |
279 | | - expandData: PropTypes.object, |
280 | | - isExpanded: PropTypes.bool, |
281 | | - level: PropTypes.number, |
282 | | - onExpandClick: PropTypes.func |
283 | | -}; |
284 | | - |
285 | | -TreeBranch.defaultProps = { |
286 | | - expandData: {} |
287 | | -}; |
288 | | - |
289 | | -TreeBranch.propDescriptions = { |
290 | | - children: 'Node(s) to render within the component. Expecting `TreeItem` components as children.', |
291 | | - expandData: '_INTERNAL USE ONLY._', |
292 | | - isExpanded: '_INTERNAL USE ONLY._', |
293 | | - level: '_INTERNAL USE ONLY._', |
294 | | - onExpandClick: '_INTERNAL USE ONLY._' |
295 | | -}; |
296 | | - |
297 | | -export class Tree extends Component { |
298 | | - render() { |
299 | | - return <_Tree {...this.props} level={0} />; |
300 | | - } |
301 | | -} |
302 | | - |
303 | | -Tree.displayName = 'Tree'; |
304 | | - |
305 | | -Tree.propTypes = { |
306 | | - children: PropTypes.node, |
307 | | - expandData: PropTypes.object, |
308 | | - isExpanded: PropTypes.bool, |
309 | | - onExpandClick: PropTypes.func |
310 | | -}; |
311 | | - |
312 | | -Tree.defaultProps = { |
313 | | - expandData: {} |
314 | | -}; |
315 | | - |
316 | | -Tree.propDescriptions = { |
317 | | - children: 'Node(s) to render within the component. Expecting `TreeItem` components as children.', |
318 | | - expandData: '_INTERNAL USE ONLY._', |
319 | | - isExpanded: '_INTERNAL USE ONLY._', |
320 | | - level: '_INTERNAL USE ONLY._', |
321 | | - onExpandClick: '_INTERNAL USE ONLY._' |
322 | | -}; |
323 | | - |
324 | | -class _Tree extends Component { |
325 | | - render() { |
326 | | - const { |
327 | | - children, |
328 | | - expandData, |
329 | | - level, |
330 | | - isExpanded, |
331 | | - onExpandClick, |
332 | | - ...rest |
333 | | - } = this.props; |
334 | | - |
335 | | - const className = classnames({ |
336 | | - 'fd-tree': level === 0, |
337 | | - 'fd-tree__group': level > 0, |
338 | | - [`fd-tree__group--sublevel-${level}`]: level > 0, |
339 | | - 'is-hidden': level > 0 && !isExpanded |
340 | | - }); |
341 | | - |
342 | | - return ( |
343 | | - <ul |
344 | | - {...rest} |
345 | | - aria-hidden={level > 0 && !isExpanded} |
346 | | - className={className} |
347 | | - role={level === 0 ? 'tree' : 'group'}> |
348 | | - { |
349 | | - React.Children.map(children, (child) => { |
350 | | - return React.cloneElement(child, { |
351 | | - expandData, |
352 | | - level, |
353 | | - onExpandClick |
354 | | - }); |
355 | | - }) |
356 | | - } |
357 | | - </ul> |
358 | | - ); |
359 | | - } |
360 | | -} |
361 | | - |
362 | | -_Tree.propTypes = { |
363 | | - expandData: PropTypes.object.isRequired, |
364 | | - level: PropTypes.number.isRequired, |
365 | | - onExpandClick: PropTypes.func.isRequired, |
366 | | - children: PropTypes.node, |
367 | | - isExpanded: PropTypes.bool |
368 | | -}; |
369 | | - |
370 | | -export class TreeView extends Component { |
| 10 | +class TreeView extends Component { |
371 | 11 | constructor(props) { |
372 | 12 | super(props); |
373 | 13 |
|
@@ -525,3 +165,12 @@ TreeView.propDescriptions = { |
525 | 165 | isExpandAll: 'Set to *true* for an expanded tree. This variable is handled internally, but can be overridden by the consumer through this prop', |
526 | 166 | onExpandChange: 'Callback that is called whenever the internal expand/collapse state changes. The argument is an an object with rowId keys and boolean values representing whether that row is expanded.' |
527 | 167 | }; |
| 168 | + |
| 169 | +TreeView.Tree = Tree; |
| 170 | +TreeView.Branch = TreeBranch; |
| 171 | +TreeView.Col = TreeCol; |
| 172 | +TreeView.Head = TreeHead; |
| 173 | +TreeView.Item = TreeItem; |
| 174 | +TreeView.Row = TreeRow; |
| 175 | + |
| 176 | +export default TreeView; |
0 commit comments