ywana-core8 0.1.75 → 0.1.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ACCORDION_EVALUATION.md +583 -0
- package/CHECKBOX_EVALUATION.md +273 -0
- package/CHIP_EVALUATION.md +542 -0
- package/COLOR_EVALUATION.md +524 -0
- package/COMPONENTS_EVALUATION.md +477 -0
- package/FORM_EVALUATION.md +459 -0
- package/HEADER_EVALUATION.md +436 -0
- package/ICON_EVALUATION.md +254 -0
- package/LIST_EVALUATION.md +574 -0
- package/PROGRESS_EVALUATION.md +450 -0
- package/RADIO_EVALUATION.md +439 -0
- package/RADIO_VISUAL_FIX.md +183 -0
- package/SECTION_IMPROVEMENTS.md +153 -0
- package/SWITCH_EVALUATION.md +335 -0
- package/SWITCH_VISUAL_FIX.md +232 -0
- package/TAB_EVALUATION.md +626 -0
- package/TEXTFIELD_EVALUATION.md +747 -0
- package/TOOLTIP_FIX.md +157 -0
- package/TREE_EVALUATION.md +708 -0
- package/dist/index.cjs +7900 -1615
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +6094 -1122
- package/dist/index.css.map +1 -1
- package/dist/index.modern.js +7929 -1645
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +7900 -1615
- package/dist/index.umd.js.map +1 -1
- package/jest.config.js +24 -0
- package/package.json +10 -1
- package/src/html/accordion.css +208 -4
- package/src/html/accordion.example.js +390 -0
- package/src/html/accordion.js +284 -28
- package/src/html/accordion.unit.test.js +334 -0
- package/src/html/button.css +157 -16
- package/src/html/button.example.js +374 -0
- package/src/html/button.js +240 -60
- package/src/html/button.test.js +422 -0
- package/src/html/checkbox.css +74 -2
- package/src/html/checkbox.example.js +316 -0
- package/src/html/checkbox.js +113 -26
- package/src/html/checkbox.test.js +285 -0
- package/src/html/chip.css +230 -19
- package/src/html/chip.example.js +355 -0
- package/src/html/chip.js +321 -25
- package/src/html/chip.test.js +425 -0
- package/src/html/color.css +435 -6
- package/src/html/color.example.js +527 -0
- package/src/html/color.js +458 -9
- package/src/html/color.test.js +362 -4
- package/src/html/components.example.js +492 -0
- package/src/html/components_enhanced.test.js +581 -0
- package/src/html/form.css +70 -3
- package/src/html/form.example.js +385 -0
- package/src/html/form.js +232 -34
- package/src/html/form.test.js +369 -0
- package/src/html/header2.css +264 -0
- package/src/html/header2.example.js +411 -0
- package/src/html/header2.js +203 -0
- package/src/html/header2.test.js +377 -0
- package/src/html/icon.css +20 -2
- package/src/html/icon.example.js +268 -0
- package/src/html/icon.js +86 -16
- package/src/html/icon.test.js +231 -0
- package/src/html/index.js +1 -1
- package/src/html/list.css +393 -1
- package/src/html/list.example.js +404 -0
- package/src/html/list.js +583 -40
- package/src/html/list.test.js +383 -0
- package/src/html/progress.css +707 -17
- package/src/html/progress.example.js +424 -0
- package/src/html/progress.js +906 -9
- package/src/html/progress.test.js +313 -0
- package/src/html/property.css +399 -0
- package/src/html/property.example.js +553 -0
- package/src/html/property.js +393 -15
- package/src/html/property.test.js +351 -2
- package/src/html/radio-visual-test.js +289 -0
- package/src/html/radio.css +137 -11
- package/src/html/radio.example.js +389 -0
- package/src/html/radio.js +234 -10
- package/src/html/radio.test.js +318 -0
- package/src/html/section.example.js +99 -0
- package/src/html/section.js +40 -3
- package/src/html/section.test.js +131 -0
- package/src/html/selector.css +329 -3
- package/src/html/selector.js +369 -23
- package/src/html/switch-debug.js +197 -0
- package/src/html/switch-test-visual.js +294 -0
- package/src/html/switch.css +200 -0
- package/src/html/switch.example.js +461 -0
- package/src/html/switch.js +283 -23
- package/src/html/switch.test.js +355 -0
- package/src/html/tab.css +288 -0
- package/src/html/tab.example.js +446 -0
- package/src/html/tab.js +387 -22
- package/src/html/tab_enhanced.js +378 -0
- package/src/html/tab_enhanced.test.js +504 -0
- package/src/html/table2.css +576 -0
- package/src/html/table2.example.js +703 -0
- package/src/html/table2.js +1252 -0
- package/src/html/table2.migration.md +328 -0
- package/src/html/table2.test.js +582 -0
- package/src/html/text.css +375 -0
- package/src/html/text.js +311 -20
- package/src/html/textfield2.css +842 -0
- package/src/html/textfield2.example.js +499 -0
- package/src/html/textfield2.js +1130 -0
- package/src/html/textfield2.test.js +950 -0
- package/src/html/thumbnail.css +289 -2
- package/src/html/thumbnail.js +214 -9
- package/src/html/tokenfield.css +449 -1
- package/src/html/tokenfield.example.js +503 -0
- package/src/html/tokenfield.js +561 -56
- package/src/html/tokenfield.test.js +423 -0
- package/src/html/tooltip-positioning-demo.js +187 -0
- package/src/html/tooltip.css +25 -2
- package/src/html/tree.css +228 -0
- package/src/html/tree.example.js +475 -0
- package/src/html/tree.js +712 -28
- package/src/html/tree_enhanced.test.js +495 -0
- package/table2.test.js +454 -0
- package/src/html/button.tsx +0 -38
package/src/html/tree.js
CHANGED
@@ -1,67 +1,751 @@
|
|
1
|
-
import React from 'react'
|
1
|
+
import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'
|
2
|
+
import PropTypes from 'prop-types'
|
2
3
|
import { Icon } from './icon'
|
3
4
|
import { Text, TEXTFORMATS } from './text'
|
5
|
+
import { TextField } from './textfield'
|
4
6
|
import './tree.css'
|
5
7
|
|
6
8
|
/**
|
7
|
-
* Tree
|
9
|
+
* Enhanced Tree component with improved functionality while maintaining 100% compatibility
|
8
10
|
*/
|
9
|
-
export const Tree = (
|
11
|
+
export const Tree = (props) => {
|
12
|
+
const {
|
13
|
+
nodes = [],
|
14
|
+
children,
|
15
|
+
// New enhanced props (all optional for compatibility)
|
16
|
+
searchable = false,
|
17
|
+
searchPlaceholder = "Search...",
|
18
|
+
searchBy = ['label'],
|
19
|
+
filterable = false,
|
20
|
+
sortable = false,
|
21
|
+
sortBy = 'label',
|
22
|
+
sortDirection = 'asc',
|
23
|
+
multiSelect = false,
|
24
|
+
onMultiSelect,
|
25
|
+
expandAll = false,
|
26
|
+
collapseAll = false,
|
27
|
+
onExpandAll,
|
28
|
+
onCollapseAll,
|
29
|
+
disabled = false,
|
30
|
+
loading = false,
|
31
|
+
empty = false,
|
32
|
+
emptyMessage = "No items found",
|
33
|
+
emptyIcon = "folder_open",
|
34
|
+
className,
|
35
|
+
style,
|
36
|
+
ariaLabel,
|
37
|
+
...restProps
|
38
|
+
} = props
|
39
|
+
|
40
|
+
const [searchTerm, setSearchTerm] = useState('')
|
41
|
+
const [selectedItems, setSelectedItems] = useState([])
|
42
|
+
const [expandedNodes, setExpandedNodes] = useState(new Set())
|
43
|
+
const treeRef = useRef(null)
|
44
|
+
|
45
|
+
// Validate props
|
46
|
+
if (children && !React.Children.count(children) && nodes.length === 0) {
|
47
|
+
console.warn('Tree component: should contain TreeNode components or nodes prop')
|
48
|
+
}
|
49
|
+
|
50
|
+
// Handle search
|
51
|
+
const handleSearch = useCallback((searchId, value) => {
|
52
|
+
setSearchTerm(value)
|
53
|
+
}, [])
|
54
|
+
|
55
|
+
// Handle multi-selection
|
56
|
+
const handleMultiSelect = useCallback((id, selected) => {
|
57
|
+
if (!multiSelect) return
|
58
|
+
|
59
|
+
setSelectedItems(prev => {
|
60
|
+
const newSelected = selected
|
61
|
+
? [...prev, id]
|
62
|
+
: prev.filter(item => item !== id)
|
63
|
+
|
64
|
+
if (onMultiSelect) {
|
65
|
+
onMultiSelect(newSelected)
|
66
|
+
}
|
67
|
+
return newSelected
|
68
|
+
})
|
69
|
+
}, [multiSelect, onMultiSelect])
|
70
|
+
|
71
|
+
// Handle expand/collapse all
|
72
|
+
const handleExpandAll = useCallback(() => {
|
73
|
+
if (onExpandAll) onExpandAll()
|
74
|
+
// Implementation would depend on tree structure
|
75
|
+
}, [onExpandAll])
|
76
|
+
|
77
|
+
const handleCollapseAll = useCallback(() => {
|
78
|
+
if (onCollapseAll) onCollapseAll()
|
79
|
+
// Implementation would depend on tree structure
|
80
|
+
}, [onCollapseAll])
|
81
|
+
|
82
|
+
// Generate CSS classes
|
83
|
+
const cssClasses = [
|
84
|
+
'tree',
|
85
|
+
disabled && 'tree--disabled',
|
86
|
+
loading && 'tree--loading',
|
87
|
+
searchable && 'tree--searchable',
|
88
|
+
multiSelect && 'tree--multi-select',
|
89
|
+
className
|
90
|
+
].filter(Boolean).join(' ')
|
91
|
+
|
92
|
+
// Accessibility attributes
|
93
|
+
const ariaAttributes = {
|
94
|
+
'aria-label': ariaLabel || 'Tree',
|
95
|
+
'aria-disabled': disabled,
|
96
|
+
'aria-busy': loading,
|
97
|
+
role: 'tree'
|
98
|
+
}
|
99
|
+
|
100
|
+
// Show loading state
|
101
|
+
if (loading) {
|
102
|
+
return (
|
103
|
+
<div className={cssClasses} style={style} {...ariaAttributes} {...restProps}>
|
104
|
+
<div className="tree__loading">
|
105
|
+
<Icon icon="hourglass_empty" size="medium" />
|
106
|
+
<Text>Loading...</Text>
|
107
|
+
</div>
|
108
|
+
</div>
|
109
|
+
)
|
110
|
+
}
|
111
|
+
|
112
|
+
// Show empty state
|
113
|
+
if (empty || (!children && nodes.length === 0)) {
|
114
|
+
return (
|
115
|
+
<div className={cssClasses} style={style} {...ariaAttributes} {...restProps}>
|
116
|
+
{searchable && (
|
117
|
+
<div className="tree__search">
|
118
|
+
<TextField
|
119
|
+
id="tree-search"
|
120
|
+
placeholder={searchPlaceholder}
|
121
|
+
value={searchTerm}
|
122
|
+
onChange={handleSearch}
|
123
|
+
icon="search"
|
124
|
+
outlined={true}
|
125
|
+
size="small"
|
126
|
+
/>
|
127
|
+
</div>
|
128
|
+
)}
|
129
|
+
<div className="tree__empty">
|
130
|
+
<Icon icon={emptyIcon} size="large" />
|
131
|
+
<Text>{emptyMessage}</Text>
|
132
|
+
</div>
|
133
|
+
</div>
|
134
|
+
)
|
135
|
+
}
|
136
|
+
|
10
137
|
return (
|
11
|
-
<div
|
12
|
-
{
|
13
|
-
{
|
138
|
+
<div
|
139
|
+
className={cssClasses}
|
140
|
+
style={style}
|
141
|
+
ref={treeRef}
|
142
|
+
{...ariaAttributes}
|
143
|
+
{...restProps}
|
144
|
+
>
|
145
|
+
{searchable && (
|
146
|
+
<div className="tree__search">
|
147
|
+
<TextField
|
148
|
+
id="tree-search"
|
149
|
+
placeholder={searchPlaceholder}
|
150
|
+
value={searchTerm}
|
151
|
+
onChange={handleSearch}
|
152
|
+
icon="search"
|
153
|
+
outlined={true}
|
154
|
+
size="small"
|
155
|
+
/>
|
156
|
+
</div>
|
157
|
+
)}
|
158
|
+
|
159
|
+
{(expandAll || collapseAll) && (
|
160
|
+
<div className="tree__controls">
|
161
|
+
{expandAll && (
|
162
|
+
<button
|
163
|
+
className="tree__control-button"
|
164
|
+
onClick={handleExpandAll}
|
165
|
+
aria-label="Expand all nodes"
|
166
|
+
>
|
167
|
+
<Icon icon="unfold_more" size="small" />
|
168
|
+
<Text size="small">Expand All</Text>
|
169
|
+
</button>
|
170
|
+
)}
|
171
|
+
{collapseAll && (
|
172
|
+
<button
|
173
|
+
className="tree__control-button"
|
174
|
+
onClick={handleCollapseAll}
|
175
|
+
aria-label="Collapse all nodes"
|
176
|
+
>
|
177
|
+
<Icon icon="unfold_less" size="small" />
|
178
|
+
<Text size="small">Collapse All</Text>
|
179
|
+
</button>
|
180
|
+
)}
|
181
|
+
</div>
|
182
|
+
)}
|
183
|
+
|
184
|
+
<div className="tree__content">
|
185
|
+
{nodes}
|
186
|
+
{children}
|
187
|
+
</div>
|
14
188
|
</div>
|
15
189
|
)
|
16
190
|
}
|
17
191
|
|
18
192
|
/**
|
19
|
-
*
|
193
|
+
* Enhanced TreeNode component with improved functionality while maintaining 100% compatibility
|
20
194
|
*/
|
21
|
-
export const TreeNode = (
|
195
|
+
export const TreeNode = (props) => {
|
196
|
+
const {
|
197
|
+
id,
|
198
|
+
icon = 'folder',
|
199
|
+
label,
|
200
|
+
tooltip,
|
201
|
+
open = false,
|
202
|
+
children,
|
203
|
+
actions,
|
204
|
+
onSelect,
|
205
|
+
// New enhanced props (all optional for compatibility)
|
206
|
+
disabled = false,
|
207
|
+
draggable = false,
|
208
|
+
onDragStart,
|
209
|
+
onDragEnd,
|
210
|
+
onDrop,
|
211
|
+
expandable = true,
|
212
|
+
level = 0,
|
213
|
+
hasChildren = true,
|
214
|
+
loading = false,
|
215
|
+
badge,
|
216
|
+
className,
|
217
|
+
style,
|
218
|
+
...restProps
|
219
|
+
} = props
|
22
220
|
|
23
|
-
const
|
221
|
+
const [isOpen, setIsOpen] = useState(open)
|
222
|
+
const [isDragging, setIsDragging] = useState(false)
|
223
|
+
const nodeRef = useRef(null)
|
224
|
+
|
225
|
+
// Sync with open prop
|
226
|
+
useEffect(() => {
|
227
|
+
setIsOpen(open)
|
228
|
+
}, [open])
|
24
229
|
|
25
|
-
|
230
|
+
// Handle selection (maintaining original behavior)
|
231
|
+
const handleSelect = useCallback((event) => {
|
232
|
+
if (disabled) return
|
233
|
+
|
234
|
+
event.stopPropagation()
|
26
235
|
if (onSelect) onSelect(id)
|
236
|
+
}, [disabled, onSelect, id])
|
237
|
+
|
238
|
+
// Handle toggle
|
239
|
+
const handleToggle = useCallback((event) => {
|
240
|
+
if (disabled || !expandable) return
|
241
|
+
|
242
|
+
event.preventDefault()
|
243
|
+
setIsOpen(prev => !prev)
|
244
|
+
}, [disabled, expandable])
|
245
|
+
|
246
|
+
// Handle keyboard interaction
|
247
|
+
const handleKeyDown = useCallback((event) => {
|
248
|
+
if (disabled) return
|
249
|
+
|
250
|
+
switch (event.key) {
|
251
|
+
case 'Enter':
|
252
|
+
case ' ':
|
253
|
+
event.preventDefault()
|
254
|
+
if (onSelect) onSelect(id)
|
255
|
+
break
|
256
|
+
case 'ArrowRight':
|
257
|
+
if (!isOpen && hasChildren) {
|
258
|
+
event.preventDefault()
|
259
|
+
setIsOpen(true)
|
260
|
+
}
|
261
|
+
break
|
262
|
+
case 'ArrowLeft':
|
263
|
+
if (isOpen && hasChildren) {
|
264
|
+
event.preventDefault()
|
265
|
+
setIsOpen(false)
|
266
|
+
}
|
267
|
+
break
|
268
|
+
default:
|
269
|
+
break
|
270
|
+
}
|
271
|
+
}, [disabled, onSelect, id, isOpen, hasChildren])
|
272
|
+
|
273
|
+
// Handle drag and drop
|
274
|
+
const handleDragStart = useCallback((event) => {
|
275
|
+
if (!draggable || disabled) return
|
276
|
+
|
277
|
+
setIsDragging(true)
|
278
|
+
event.dataTransfer.setData('text/plain', id)
|
279
|
+
if (onDragStart) onDragStart(id, event)
|
280
|
+
}, [draggable, disabled, id, onDragStart])
|
281
|
+
|
282
|
+
const handleDragEnd = useCallback((event) => {
|
283
|
+
setIsDragging(false)
|
284
|
+
if (onDragEnd) onDragEnd(id, event)
|
285
|
+
}, [id, onDragEnd])
|
286
|
+
|
287
|
+
const handleDrop = useCallback((event) => {
|
288
|
+
event.preventDefault()
|
289
|
+
const draggedId = event.dataTransfer.getData('text/plain')
|
290
|
+
if (onDrop && draggedId !== id) {
|
291
|
+
onDrop(draggedId, id, event)
|
292
|
+
}
|
293
|
+
}, [id, onDrop])
|
294
|
+
|
295
|
+
const handleDragOver = useCallback((event) => {
|
296
|
+
if (draggable) {
|
297
|
+
event.preventDefault()
|
298
|
+
}
|
299
|
+
}, [draggable])
|
300
|
+
|
301
|
+
// Generate CSS classes
|
302
|
+
const cssClasses = [
|
303
|
+
'tree-node',
|
304
|
+
disabled && 'tree-node--disabled',
|
305
|
+
isDragging && 'tree-node--dragging',
|
306
|
+
!hasChildren && 'tree-node--leaf',
|
307
|
+
loading && 'tree-node--loading',
|
308
|
+
className
|
309
|
+
].filter(Boolean).join(' ')
|
310
|
+
|
311
|
+
// Accessibility attributes
|
312
|
+
const ariaAttributes = {
|
313
|
+
'aria-expanded': hasChildren ? isOpen : undefined,
|
314
|
+
'aria-disabled': disabled,
|
315
|
+
'aria-level': level + 1,
|
316
|
+
'aria-label': typeof label === 'string' ? label : `Tree node ${id || ''}`,
|
317
|
+
role: 'treeitem',
|
318
|
+
tabIndex: disabled ? -1 : 0
|
27
319
|
}
|
28
320
|
|
321
|
+
// Render label with proper formatting (maintaining original logic)
|
322
|
+
const labelTxt = label ? <Text format={TEXTFORMATS.STRING}>{label}</Text> : null
|
29
323
|
const clickable = onSelect ? "clickable" : ""
|
30
324
|
|
31
325
|
return (
|
32
|
-
<details
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
326
|
+
<details
|
327
|
+
className={cssClasses}
|
328
|
+
open={isOpen}
|
329
|
+
style={style}
|
330
|
+
ref={nodeRef}
|
331
|
+
draggable={draggable && !disabled}
|
332
|
+
onDragStart={handleDragStart}
|
333
|
+
onDragEnd={handleDragEnd}
|
334
|
+
onDrop={handleDrop}
|
335
|
+
onDragOver={handleDragOver}
|
336
|
+
{...restProps}
|
337
|
+
>
|
338
|
+
<summary
|
339
|
+
className="tree-item"
|
340
|
+
onClick={handleToggle}
|
341
|
+
onKeyDown={handleKeyDown}
|
342
|
+
{...ariaAttributes}
|
343
|
+
title={tooltip}
|
344
|
+
>
|
345
|
+
{/* Expand/collapse indicator */}
|
346
|
+
{hasChildren && expandable && (
|
347
|
+
<div className="tree-node__toggle">
|
348
|
+
<Icon
|
349
|
+
icon={isOpen ? 'expand_less' : 'expand_more'}
|
350
|
+
size="small"
|
351
|
+
className="tree-node__toggle-icon"
|
352
|
+
/>
|
353
|
+
</div>
|
354
|
+
)}
|
355
|
+
|
356
|
+
{/* Icon (maintaining original structure) */}
|
357
|
+
{icon && (
|
358
|
+
<div className="tree-node__icon">
|
359
|
+
<Icon
|
360
|
+
icon={icon}
|
361
|
+
size="small"
|
362
|
+
disabled={disabled}
|
363
|
+
/>
|
364
|
+
</div>
|
365
|
+
)}
|
366
|
+
|
367
|
+
{/* Loading indicator */}
|
368
|
+
{loading && (
|
369
|
+
<div className="tree-node__loading">
|
370
|
+
<Icon icon="hourglass_empty" size="small" />
|
371
|
+
</div>
|
372
|
+
)}
|
373
|
+
|
374
|
+
{/* Label with badge */}
|
375
|
+
<div
|
376
|
+
className={`label ${clickable}`}
|
377
|
+
onClick={handleSelect}
|
378
|
+
>
|
379
|
+
{labelTxt}
|
380
|
+
{badge && (
|
381
|
+
<span className="tree-node__badge">
|
382
|
+
{typeof badge === 'number' && badge > 99 ? '99+' : badge}
|
383
|
+
</span>
|
384
|
+
)}
|
385
|
+
</div>
|
386
|
+
|
387
|
+
{/* Actions (maintaining original structure) */}
|
388
|
+
{actions && (
|
389
|
+
<div className="actions">
|
390
|
+
{actions}
|
391
|
+
</div>
|
392
|
+
)}
|
37
393
|
</summary>
|
38
|
-
|
394
|
+
|
395
|
+
{/* Children */}
|
396
|
+
{hasChildren && isOpen && (
|
397
|
+
<div className="tree-node__children" role="group">
|
398
|
+
{children}
|
399
|
+
</div>
|
400
|
+
)}
|
39
401
|
</details>
|
40
402
|
)
|
41
403
|
}
|
42
404
|
|
43
405
|
/**
|
44
|
-
*
|
406
|
+
* Enhanced TreeItem component with improved functionality while maintaining 100% compatibility
|
45
407
|
*/
|
46
|
-
export const TreeItem = (
|
408
|
+
export const TreeItem = (props) => {
|
409
|
+
const {
|
410
|
+
id,
|
411
|
+
icon = 'description',
|
412
|
+
label,
|
413
|
+
actions,
|
414
|
+
onSelect,
|
415
|
+
selected = false,
|
416
|
+
onCheck,
|
417
|
+
checked = false,
|
418
|
+
// New enhanced props (all optional for compatibility)
|
419
|
+
disabled = false,
|
420
|
+
draggable = false,
|
421
|
+
onDragStart,
|
422
|
+
onDragEnd,
|
423
|
+
onDrop,
|
424
|
+
level = 0,
|
425
|
+
badge,
|
426
|
+
tooltip,
|
427
|
+
className,
|
428
|
+
style,
|
429
|
+
...restProps
|
430
|
+
} = props
|
431
|
+
|
432
|
+
const [isDragging, setIsDragging] = useState(false)
|
433
|
+
const itemRef = useRef(null)
|
47
434
|
|
48
|
-
|
435
|
+
// Handle selection (maintaining original behavior)
|
436
|
+
const handleSelect = useCallback((event) => {
|
437
|
+
if (disabled) return
|
438
|
+
|
439
|
+
event.preventDefault()
|
49
440
|
if (onSelect) onSelect(id)
|
50
|
-
}
|
441
|
+
}, [disabled, onSelect, id])
|
51
442
|
|
52
|
-
|
443
|
+
// Handle checkbox (maintaining original behavior)
|
444
|
+
const handleCheck = useCallback((event) => {
|
445
|
+
if (disabled) return
|
446
|
+
|
447
|
+
event.stopPropagation()
|
53
448
|
if (onCheck) onCheck(id, event.target.checked)
|
449
|
+
}, [disabled, onCheck, id])
|
450
|
+
|
451
|
+
// Handle keyboard interaction
|
452
|
+
const handleKeyDown = useCallback((event) => {
|
453
|
+
if (disabled) return
|
454
|
+
|
455
|
+
switch (event.key) {
|
456
|
+
case 'Enter':
|
457
|
+
case ' ':
|
458
|
+
event.preventDefault()
|
459
|
+
if (onSelect) onSelect(id)
|
460
|
+
break
|
461
|
+
case 'ArrowUp':
|
462
|
+
case 'ArrowDown':
|
463
|
+
// Allow parent to handle navigation
|
464
|
+
break
|
465
|
+
default:
|
466
|
+
break
|
467
|
+
}
|
468
|
+
}, [disabled, onSelect, id])
|
469
|
+
|
470
|
+
// Handle drag and drop
|
471
|
+
const handleDragStart = useCallback((event) => {
|
472
|
+
if (!draggable || disabled) return
|
473
|
+
|
474
|
+
setIsDragging(true)
|
475
|
+
event.dataTransfer.setData('text/plain', id)
|
476
|
+
if (onDragStart) onDragStart(id, event)
|
477
|
+
}, [draggable, disabled, id, onDragStart])
|
478
|
+
|
479
|
+
const handleDragEnd = useCallback((event) => {
|
480
|
+
setIsDragging(false)
|
481
|
+
if (onDragEnd) onDragEnd(id, event)
|
482
|
+
}, [id, onDragEnd])
|
483
|
+
|
484
|
+
const handleDrop = useCallback((event) => {
|
485
|
+
event.preventDefault()
|
486
|
+
const draggedId = event.dataTransfer.getData('text/plain')
|
487
|
+
if (onDrop && draggedId !== id) {
|
488
|
+
onDrop(draggedId, id, event)
|
489
|
+
}
|
490
|
+
}, [id, onDrop])
|
491
|
+
|
492
|
+
const handleDragOver = useCallback((event) => {
|
493
|
+
if (draggable) {
|
494
|
+
event.preventDefault()
|
495
|
+
}
|
496
|
+
}, [draggable])
|
497
|
+
|
498
|
+
// Generate CSS classes
|
499
|
+
const cssClasses = [
|
500
|
+
'tree-item',
|
501
|
+
'final',
|
502
|
+
selected && 'selected',
|
503
|
+
disabled && 'tree-item--disabled',
|
504
|
+
isDragging && 'tree-item--dragging',
|
505
|
+
className
|
506
|
+
].filter(Boolean).join(' ')
|
507
|
+
|
508
|
+
// Accessibility attributes
|
509
|
+
const ariaAttributes = {
|
510
|
+
'aria-selected': selected,
|
511
|
+
'aria-disabled': disabled,
|
512
|
+
'aria-level': level + 1,
|
513
|
+
'aria-label': typeof label === 'string' ? label : `Tree item ${id || ''}`,
|
514
|
+
role: 'treeitem',
|
515
|
+
tabIndex: disabled ? -1 : 0
|
54
516
|
}
|
55
517
|
|
56
|
-
|
518
|
+
// Render label with proper formatting (maintaining original logic)
|
57
519
|
const labelTxt = label ? <Text format={TEXTFORMATS.STRING}>{label}</Text> : null
|
58
520
|
|
59
521
|
return (
|
60
|
-
<div
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
522
|
+
<div
|
523
|
+
className={cssClasses}
|
524
|
+
style={style}
|
525
|
+
onClick={handleSelect}
|
526
|
+
onKeyDown={handleKeyDown}
|
527
|
+
ref={itemRef}
|
528
|
+
draggable={draggable && !disabled}
|
529
|
+
onDragStart={handleDragStart}
|
530
|
+
onDragEnd={handleDragEnd}
|
531
|
+
onDrop={handleDrop}
|
532
|
+
onDragOver={handleDragOver}
|
533
|
+
title={tooltip}
|
534
|
+
{...ariaAttributes}
|
535
|
+
{...restProps}
|
536
|
+
>
|
537
|
+
{/* Checkbox (maintaining original structure) */}
|
538
|
+
{onCheck && (
|
539
|
+
<div className="tree-item__checkbox">
|
540
|
+
<input
|
541
|
+
type="checkbox"
|
542
|
+
checked={checked}
|
543
|
+
onChange={handleCheck}
|
544
|
+
disabled={disabled}
|
545
|
+
aria-label={`Select ${typeof label === 'string' ? label : 'item'}`}
|
546
|
+
/>
|
547
|
+
</div>
|
548
|
+
)}
|
549
|
+
|
550
|
+
{/* Icon (maintaining original structure) */}
|
551
|
+
<div className="tree-item__icon">
|
552
|
+
<Icon
|
553
|
+
icon={icon}
|
554
|
+
size="small"
|
555
|
+
disabled={disabled}
|
556
|
+
/>
|
557
|
+
</div>
|
558
|
+
|
559
|
+
{/* Label with badge */}
|
560
|
+
<div className="label">
|
561
|
+
{labelTxt}
|
562
|
+
{badge && (
|
563
|
+
<span className="tree-item__badge">
|
564
|
+
{typeof badge === 'number' && badge > 99 ? '99+' : badge}
|
565
|
+
</span>
|
566
|
+
)}
|
567
|
+
</div>
|
568
|
+
|
569
|
+
{/* Actions (maintaining original structure) */}
|
570
|
+
{actions && (
|
571
|
+
<div className="actions">
|
572
|
+
{actions}
|
573
|
+
</div>
|
574
|
+
)}
|
65
575
|
</div>
|
66
576
|
)
|
67
|
-
}
|
577
|
+
}
|
578
|
+
|
579
|
+
// PropTypes for Tree component
|
580
|
+
Tree.propTypes = {
|
581
|
+
/** Array of tree nodes */
|
582
|
+
nodes: PropTypes.array,
|
583
|
+
/** Child TreeNode components */
|
584
|
+
children: PropTypes.node,
|
585
|
+
/** Enable search functionality */
|
586
|
+
searchable: PropTypes.bool,
|
587
|
+
/** Search input placeholder */
|
588
|
+
searchPlaceholder: PropTypes.string,
|
589
|
+
/** Properties to search by */
|
590
|
+
searchBy: PropTypes.arrayOf(PropTypes.string),
|
591
|
+
/** Enable filtering */
|
592
|
+
filterable: PropTypes.bool,
|
593
|
+
/** Enable sorting */
|
594
|
+
sortable: PropTypes.bool,
|
595
|
+
/** Property to sort by */
|
596
|
+
sortBy: PropTypes.string,
|
597
|
+
/** Sort direction */
|
598
|
+
sortDirection: PropTypes.oneOf(['asc', 'desc']),
|
599
|
+
/** Enable multi-selection */
|
600
|
+
multiSelect: PropTypes.bool,
|
601
|
+
/** Multi-selection callback */
|
602
|
+
onMultiSelect: PropTypes.func,
|
603
|
+
/** Show expand all button */
|
604
|
+
expandAll: PropTypes.bool,
|
605
|
+
/** Show collapse all button */
|
606
|
+
collapseAll: PropTypes.bool,
|
607
|
+
/** Expand all callback */
|
608
|
+
onExpandAll: PropTypes.func,
|
609
|
+
/** Collapse all callback */
|
610
|
+
onCollapseAll: PropTypes.func,
|
611
|
+
/** Disabled state */
|
612
|
+
disabled: PropTypes.bool,
|
613
|
+
/** Loading state */
|
614
|
+
loading: PropTypes.bool,
|
615
|
+
/** Empty state */
|
616
|
+
empty: PropTypes.bool,
|
617
|
+
/** Empty state message */
|
618
|
+
emptyMessage: PropTypes.string,
|
619
|
+
/** Empty state icon */
|
620
|
+
emptyIcon: PropTypes.string,
|
621
|
+
/** Additional CSS classes */
|
622
|
+
className: PropTypes.string,
|
623
|
+
/** Inline styles */
|
624
|
+
style: PropTypes.object,
|
625
|
+
/** ARIA label */
|
626
|
+
ariaLabel: PropTypes.string
|
627
|
+
}
|
628
|
+
|
629
|
+
Tree.defaultProps = {
|
630
|
+
nodes: [],
|
631
|
+
searchable: false,
|
632
|
+
searchPlaceholder: "Search...",
|
633
|
+
searchBy: ['label'],
|
634
|
+
filterable: false,
|
635
|
+
sortable: false,
|
636
|
+
sortDirection: 'asc',
|
637
|
+
multiSelect: false,
|
638
|
+
expandAll: false,
|
639
|
+
collapseAll: false,
|
640
|
+
disabled: false,
|
641
|
+
loading: false,
|
642
|
+
empty: false,
|
643
|
+
emptyMessage: "No items found",
|
644
|
+
emptyIcon: "folder_open"
|
645
|
+
}
|
646
|
+
|
647
|
+
// PropTypes for TreeNode component
|
648
|
+
TreeNode.propTypes = {
|
649
|
+
/** Node identifier */
|
650
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
651
|
+
/** Icon name */
|
652
|
+
icon: PropTypes.string,
|
653
|
+
/** Node label */
|
654
|
+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
655
|
+
/** Tooltip text */
|
656
|
+
tooltip: PropTypes.string,
|
657
|
+
/** Initially open state */
|
658
|
+
open: PropTypes.bool,
|
659
|
+
/** Child nodes */
|
660
|
+
children: PropTypes.node,
|
661
|
+
/** Action elements */
|
662
|
+
actions: PropTypes.node,
|
663
|
+
/** Selection callback */
|
664
|
+
onSelect: PropTypes.func,
|
665
|
+
/** Disabled state */
|
666
|
+
disabled: PropTypes.bool,
|
667
|
+
/** Draggable state */
|
668
|
+
draggable: PropTypes.bool,
|
669
|
+
/** Drag start callback */
|
670
|
+
onDragStart: PropTypes.func,
|
671
|
+
/** Drag end callback */
|
672
|
+
onDragEnd: PropTypes.func,
|
673
|
+
/** Drop callback */
|
674
|
+
onDrop: PropTypes.func,
|
675
|
+
/** Can be expanded */
|
676
|
+
expandable: PropTypes.bool,
|
677
|
+
/** Nesting level */
|
678
|
+
level: PropTypes.number,
|
679
|
+
/** Has child nodes */
|
680
|
+
hasChildren: PropTypes.bool,
|
681
|
+
/** Loading state */
|
682
|
+
loading: PropTypes.bool,
|
683
|
+
/** Badge content */
|
684
|
+
badge: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.node]),
|
685
|
+
/** Additional CSS classes */
|
686
|
+
className: PropTypes.string,
|
687
|
+
/** Inline styles */
|
688
|
+
style: PropTypes.object
|
689
|
+
}
|
690
|
+
|
691
|
+
TreeNode.defaultProps = {
|
692
|
+
icon: 'folder',
|
693
|
+
open: false,
|
694
|
+
disabled: false,
|
695
|
+
draggable: false,
|
696
|
+
expandable: true,
|
697
|
+
level: 0,
|
698
|
+
hasChildren: true,
|
699
|
+
loading: false
|
700
|
+
}
|
701
|
+
|
702
|
+
// PropTypes for TreeItem component
|
703
|
+
TreeItem.propTypes = {
|
704
|
+
/** Item identifier */
|
705
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
706
|
+
/** Icon name */
|
707
|
+
icon: PropTypes.string,
|
708
|
+
/** Item label */
|
709
|
+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
710
|
+
/** Action elements */
|
711
|
+
actions: PropTypes.node,
|
712
|
+
/** Selection callback */
|
713
|
+
onSelect: PropTypes.func,
|
714
|
+
/** Selected state */
|
715
|
+
selected: PropTypes.bool,
|
716
|
+
/** Check callback */
|
717
|
+
onCheck: PropTypes.func,
|
718
|
+
/** Checked state */
|
719
|
+
checked: PropTypes.bool,
|
720
|
+
/** Disabled state */
|
721
|
+
disabled: PropTypes.bool,
|
722
|
+
/** Draggable state */
|
723
|
+
draggable: PropTypes.bool,
|
724
|
+
/** Drag start callback */
|
725
|
+
onDragStart: PropTypes.func,
|
726
|
+
/** Drag end callback */
|
727
|
+
onDragEnd: PropTypes.func,
|
728
|
+
/** Drop callback */
|
729
|
+
onDrop: PropTypes.func,
|
730
|
+
/** Nesting level */
|
731
|
+
level: PropTypes.number,
|
732
|
+
/** Badge content */
|
733
|
+
badge: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.node]),
|
734
|
+
/** Tooltip text */
|
735
|
+
tooltip: PropTypes.string,
|
736
|
+
/** Additional CSS classes */
|
737
|
+
className: PropTypes.string,
|
738
|
+
/** Inline styles */
|
739
|
+
style: PropTypes.object
|
740
|
+
}
|
741
|
+
|
742
|
+
TreeItem.defaultProps = {
|
743
|
+
icon: 'description',
|
744
|
+
selected: false,
|
745
|
+
checked: false,
|
746
|
+
disabled: false,
|
747
|
+
draggable: false,
|
748
|
+
level: 0
|
749
|
+
}
|
750
|
+
|
751
|
+
export default Tree
|