ywana-core8 0.1.74 → 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.
Files changed (123) hide show
  1. package/ACCORDION_EVALUATION.md +583 -0
  2. package/CHECKBOX_EVALUATION.md +273 -0
  3. package/CHIP_EVALUATION.md +542 -0
  4. package/COLOR_EVALUATION.md +524 -0
  5. package/COMPONENTS_EVALUATION.md +477 -0
  6. package/FORM_EVALUATION.md +459 -0
  7. package/HEADER_EVALUATION.md +436 -0
  8. package/ICON_EVALUATION.md +254 -0
  9. package/LIST_EVALUATION.md +574 -0
  10. package/PROGRESS_EVALUATION.md +450 -0
  11. package/RADIO_EVALUATION.md +439 -0
  12. package/RADIO_VISUAL_FIX.md +183 -0
  13. package/SECTION_IMPROVEMENTS.md +153 -0
  14. package/SWITCH_EVALUATION.md +335 -0
  15. package/SWITCH_VISUAL_FIX.md +232 -0
  16. package/TAB_EVALUATION.md +626 -0
  17. package/TEXTFIELD_EVALUATION.md +747 -0
  18. package/TOOLTIP_FIX.md +157 -0
  19. package/TREE_EVALUATION.md +708 -0
  20. package/dist/index.cjs +7900 -1615
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.css +6094 -1122
  23. package/dist/index.css.map +1 -1
  24. package/dist/index.modern.js +7929 -1645
  25. package/dist/index.modern.js.map +1 -1
  26. package/dist/index.umd.js +7900 -1615
  27. package/dist/index.umd.js.map +1 -1
  28. package/jest.config.js +24 -0
  29. package/package.json +10 -1
  30. package/src/html/accordion.css +208 -4
  31. package/src/html/accordion.example.js +390 -0
  32. package/src/html/accordion.js +284 -28
  33. package/src/html/accordion.unit.test.js +334 -0
  34. package/src/html/button.css +157 -16
  35. package/src/html/button.example.js +374 -0
  36. package/src/html/button.js +240 -60
  37. package/src/html/button.test.js +422 -0
  38. package/src/html/checkbox.css +74 -2
  39. package/src/html/checkbox.example.js +316 -0
  40. package/src/html/checkbox.js +113 -26
  41. package/src/html/checkbox.test.js +285 -0
  42. package/src/html/chip.css +230 -19
  43. package/src/html/chip.example.js +355 -0
  44. package/src/html/chip.js +321 -25
  45. package/src/html/chip.test.js +425 -0
  46. package/src/html/color.css +435 -6
  47. package/src/html/color.example.js +527 -0
  48. package/src/html/color.js +458 -9
  49. package/src/html/color.test.js +362 -4
  50. package/src/html/components.example.js +492 -0
  51. package/src/html/components_enhanced.test.js +581 -0
  52. package/src/html/form.css +70 -3
  53. package/src/html/form.example.js +385 -0
  54. package/src/html/form.js +232 -34
  55. package/src/html/form.test.js +369 -0
  56. package/src/html/header2.css +264 -0
  57. package/src/html/header2.example.js +411 -0
  58. package/src/html/header2.js +203 -0
  59. package/src/html/header2.test.js +377 -0
  60. package/src/html/icon.css +20 -2
  61. package/src/html/icon.example.js +268 -0
  62. package/src/html/icon.js +86 -16
  63. package/src/html/icon.test.js +231 -0
  64. package/src/html/index.js +1 -1
  65. package/src/html/list.css +393 -1
  66. package/src/html/list.example.js +404 -0
  67. package/src/html/list.js +583 -40
  68. package/src/html/list.test.js +383 -0
  69. package/src/html/progress.css +707 -17
  70. package/src/html/progress.example.js +424 -0
  71. package/src/html/progress.js +906 -9
  72. package/src/html/progress.test.js +313 -0
  73. package/src/html/property.css +399 -0
  74. package/src/html/property.example.js +553 -0
  75. package/src/html/property.js +393 -15
  76. package/src/html/property.test.js +351 -2
  77. package/src/html/radio-visual-test.js +289 -0
  78. package/src/html/radio.css +137 -11
  79. package/src/html/radio.example.js +389 -0
  80. package/src/html/radio.js +234 -10
  81. package/src/html/radio.test.js +318 -0
  82. package/src/html/section.example.js +99 -0
  83. package/src/html/section.js +40 -3
  84. package/src/html/section.test.js +131 -0
  85. package/src/html/selector.css +329 -3
  86. package/src/html/selector.js +369 -23
  87. package/src/html/switch-debug.js +197 -0
  88. package/src/html/switch-test-visual.js +294 -0
  89. package/src/html/switch.css +200 -0
  90. package/src/html/switch.example.js +461 -0
  91. package/src/html/switch.js +283 -23
  92. package/src/html/switch.test.js +355 -0
  93. package/src/html/tab.css +288 -0
  94. package/src/html/tab.example.js +446 -0
  95. package/src/html/tab.js +387 -22
  96. package/src/html/tab_enhanced.js +378 -0
  97. package/src/html/tab_enhanced.test.js +504 -0
  98. package/src/html/table2.css +576 -0
  99. package/src/html/table2.example.js +703 -0
  100. package/src/html/table2.js +1252 -0
  101. package/src/html/table2.migration.md +328 -0
  102. package/src/html/table2.test.js +582 -0
  103. package/src/html/text.css +375 -0
  104. package/src/html/text.js +311 -20
  105. package/src/html/textfield.js +1 -1
  106. package/src/html/textfield2.css +842 -0
  107. package/src/html/textfield2.example.js +499 -0
  108. package/src/html/textfield2.js +1130 -0
  109. package/src/html/textfield2.test.js +950 -0
  110. package/src/html/thumbnail.css +289 -2
  111. package/src/html/thumbnail.js +214 -9
  112. package/src/html/tokenfield.css +449 -1
  113. package/src/html/tokenfield.example.js +503 -0
  114. package/src/html/tokenfield.js +561 -56
  115. package/src/html/tokenfield.test.js +423 -0
  116. package/src/html/tooltip-positioning-demo.js +187 -0
  117. package/src/html/tooltip.css +25 -2
  118. package/src/html/tree.css +228 -0
  119. package/src/html/tree.example.js +475 -0
  120. package/src/html/tree.js +712 -28
  121. package/src/html/tree_enhanced.test.js +495 -0
  122. package/table2.test.js +454 -0
  123. package/src/html/button.tsx +0 -38
package/src/html/list.js CHANGED
@@ -1,23 +1,248 @@
1
- import React, { Fragment } from 'react'
2
- import { Icon } from './icon';
3
- import { Text } from './text';
1
+ import React, { Fragment, useState, useEffect, useCallback, useMemo, useRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { Icon } from './icon'
4
+ import { Text } from './text'
5
+ import { CircularProgress } from './progress'
6
+ import { TextField } from './textfield'
4
7
  import "./list.css"
5
8
 
6
9
  /**
7
- * List
10
+ * Enhanced List component with improved functionality while maintaining 100% compatibility
8
11
  */
9
12
  export const List = (props) => {
13
+ const {
14
+ items = [],
15
+ children,
16
+ selected,
17
+ onSelect,
18
+ groupBy,
19
+ groupRenderer,
20
+ // New enhanced props (all optional for compatibility)
21
+ loading = false,
22
+ empty = false,
23
+ emptyMessage = "No items found",
24
+ emptyIcon = "inbox",
25
+ searchable = false,
26
+ searchPlaceholder = "Search...",
27
+ searchBy = ['line1', 'line2'],
28
+ sortable = false,
29
+ sortBy,
30
+ sortDirection = 'asc',
31
+ onSort,
32
+ multiSelect = false,
33
+ onMultiSelect,
34
+ dense = false,
35
+ disabled = false,
36
+ animated = true,
37
+ virtualized = false,
38
+ itemHeight = 48,
39
+ maxHeight,
40
+ className,
41
+ style,
42
+ ariaLabel,
43
+ role = 'list',
44
+ ...restProps
45
+ } = props
10
46
 
11
- const { items = [], children, selected, onSelect, groupBy, groupRenderer } = props
47
+ const [searchTerm, setSearchTerm] = useState('')
48
+ const [sortConfig, setSortConfig] = useState({
49
+ key: sortBy,
50
+ direction: sortDirection
51
+ })
52
+ const listRef = useRef(null)
12
53
 
13
- function select(id) {
14
- if (onSelect) onSelect(id)
54
+ // Validate props
55
+ if (!Array.isArray(items)) {
56
+ console.warn('List component: items prop must be an array')
15
57
  }
16
58
 
17
- return groupBy ? <GroupedList {...props} onSelect={select} /> : (
18
- <div className="list">
19
- <ul>
20
- {items.map(item => <ListItem key={item.id} item={item} selected={selected} onSelect={select} />)}
59
+ // Handle selection (maintaining original behavior)
60
+ const handleSelect = useCallback((id, event) => {
61
+ if (disabled) return
62
+
63
+ if (multiSelect && event?.ctrlKey) {
64
+ // Multi-select with Ctrl key
65
+ const currentSelected = Array.isArray(selected) ? selected : [selected].filter(Boolean)
66
+ const newSelected = currentSelected.includes(id)
67
+ ? currentSelected.filter(s => s !== id)
68
+ : [...currentSelected, id]
69
+
70
+ if (onMultiSelect) {
71
+ onMultiSelect(newSelected)
72
+ } else if (onSelect) {
73
+ onSelect(newSelected)
74
+ }
75
+ } else {
76
+ // Single select (original behavior)
77
+ if (onSelect) onSelect(id)
78
+ }
79
+ }, [disabled, multiSelect, selected, onSelect, onMultiSelect])
80
+
81
+ // Handle search
82
+ const handleSearch = useCallback((searchId, value) => {
83
+ setSearchTerm(value)
84
+ }, [])
85
+
86
+ // Filter items by search
87
+ const filteredItems = useMemo(() => {
88
+ if (!searchable || !searchTerm.trim()) return items
89
+
90
+ return items.filter(item => {
91
+ const searchText = searchBy
92
+ .map(field => item[field] || '')
93
+ .join(' ')
94
+ .toLowerCase()
95
+ return searchText.includes(searchTerm.toLowerCase())
96
+ })
97
+ }, [items, searchable, searchTerm, searchBy])
98
+
99
+ // Sort items
100
+ const sortedItems = useMemo(() => {
101
+ if (!sortable || !sortConfig.key) return filteredItems
102
+
103
+ return [...filteredItems].sort((a, b) => {
104
+ const aValue = a[sortConfig.key] || ''
105
+ const bValue = b[sortConfig.key] || ''
106
+
107
+ if (sortConfig.direction === 'asc') {
108
+ return aValue.toString().localeCompare(bValue.toString())
109
+ } else {
110
+ return bValue.toString().localeCompare(aValue.toString())
111
+ }
112
+ })
113
+ }, [filteredItems, sortable, sortConfig])
114
+
115
+ // Handle sort
116
+ const handleSort = useCallback((key) => {
117
+ if (!sortable) return
118
+
119
+ const direction = sortConfig.key === key && sortConfig.direction === 'asc' ? 'desc' : 'asc'
120
+ const newConfig = { key, direction }
121
+
122
+ setSortConfig(newConfig)
123
+ if (onSort) onSort(newConfig)
124
+ }, [sortable, sortConfig, onSort])
125
+
126
+ // Generate CSS classes
127
+ const cssClasses = [
128
+ 'list',
129
+ dense && 'list--dense',
130
+ disabled && 'list--disabled',
131
+ animated && 'list--animated',
132
+ loading && 'list--loading',
133
+ className
134
+ ].filter(Boolean).join(' ')
135
+
136
+ // Accessibility attributes
137
+ const ariaAttributes = {
138
+ 'aria-label': ariaLabel || 'List',
139
+ 'aria-disabled': disabled,
140
+ 'aria-busy': loading,
141
+ role: role
142
+ }
143
+
144
+ // Show loading state
145
+ if (loading) {
146
+ return (
147
+ <div className={cssClasses} style={style} {...ariaAttributes} {...restProps}>
148
+ <div className="list__loading">
149
+ <CircularProgress size="medium" />
150
+ <Text>Loading...</Text>
151
+ </div>
152
+ {children}
153
+ </div>
154
+ )
155
+ }
156
+
157
+ // Show empty state
158
+ if (empty || sortedItems.length === 0) {
159
+ return (
160
+ <div className={cssClasses} style={style} {...ariaAttributes} {...restProps}>
161
+ {searchable && (
162
+ <div className="list__search">
163
+ <TextField
164
+ id="list-search"
165
+ placeholder={searchPlaceholder}
166
+ value={searchTerm}
167
+ onChange={handleSearch}
168
+ icon="search"
169
+ outlined={true}
170
+ size="small"
171
+ />
172
+ </div>
173
+ )}
174
+ <div className="list__empty">
175
+ <Icon icon={emptyIcon} size="large" />
176
+ <Text>{emptyMessage}</Text>
177
+ </div>
178
+ {children}
179
+ </div>
180
+ )
181
+ }
182
+
183
+ // Render grouped or normal list
184
+ return groupBy ? (
185
+ <GroupedList
186
+ {...props}
187
+ items={sortedItems}
188
+ onSelect={handleSelect}
189
+ searchTerm={searchTerm}
190
+ onSearch={handleSearch}
191
+ onSort={handleSort}
192
+ sortConfig={sortConfig}
193
+ cssClasses={cssClasses}
194
+ ariaAttributes={ariaAttributes}
195
+ />
196
+ ) : (
197
+ <div className={cssClasses} style={style} ref={listRef} {...ariaAttributes} {...restProps}>
198
+ {searchable && (
199
+ <div className="list__search">
200
+ <TextField
201
+ id="list-search"
202
+ placeholder={searchPlaceholder}
203
+ value={searchTerm}
204
+ onChange={handleSearch}
205
+ icon="search"
206
+ outlined={true}
207
+ size="small"
208
+ />
209
+ </div>
210
+ )}
211
+
212
+ {sortable && sortBy && (
213
+ <div className="list__sort">
214
+ <button
215
+ className="list__sort-button"
216
+ onClick={() => handleSort(sortBy)}
217
+ aria-label={`Sort by ${sortBy}`}
218
+ >
219
+ <Text>{sortBy}</Text>
220
+ <Icon
221
+ icon={sortConfig.direction === 'asc' ? 'arrow_upward' : 'arrow_downward'}
222
+ size="small"
223
+ />
224
+ </button>
225
+ </div>
226
+ )}
227
+
228
+ <ul
229
+ className="list__items"
230
+ style={{ maxHeight: maxHeight }}
231
+ role="listbox"
232
+ aria-multiselectable={multiSelect}
233
+ >
234
+ {sortedItems.map(item => (
235
+ <ListItem
236
+ key={item.id}
237
+ item={item}
238
+ selected={selected}
239
+ onSelect={handleSelect}
240
+ multiSelect={multiSelect}
241
+ dense={dense}
242
+ disabled={disabled}
243
+ animated={animated}
244
+ />
245
+ ))}
21
246
  </ul>
22
247
  {children}
23
248
  </div>
@@ -25,31 +250,126 @@ export const List = (props) => {
25
250
  }
26
251
 
27
252
  /**
28
- * Grouped List
253
+ * Enhanced Grouped List with improved functionality
29
254
  */
30
255
  const GroupedList = (props) => {
31
- const { items = [], children, selected, onSelect, groupBy, groupRenderer } = props
256
+ const {
257
+ items = [],
258
+ children,
259
+ selected,
260
+ onSelect,
261
+ groupBy,
262
+ groupRenderer,
263
+ searchTerm,
264
+ onSearch,
265
+ searchable = false,
266
+ searchPlaceholder = "Search...",
267
+ multiSelect = false,
268
+ dense = false,
269
+ disabled = false,
270
+ animated = true,
271
+ cssClasses,
272
+ ariaAttributes,
273
+ style,
274
+ ...restProps
275
+ } = props
32
276
 
33
- const groups = items.reduce((groups, item) => {
34
- let group = groups.find(g => g.name === item[groupBy])
35
- if (!group) {
36
- group = { name: item[groupBy], items: [] }
37
- groups.push(group)
38
- }
39
- group.items.push(item)
40
- return groups
277
+ const [collapsedGroups, setCollapsedGroups] = useState(new Set())
278
+
279
+ // Group items (maintaining original logic)
280
+ const groups = useMemo(() => {
281
+ return items.reduce((groups, item) => {
282
+ let group = groups.find(g => g.name === item[groupBy])
283
+ if (!group) {
284
+ group = { name: item[groupBy], items: [] }
285
+ groups.push(group)
286
+ }
287
+ group.items.push(item)
288
+ return groups
289
+ }, [])
290
+ }, [items, groupBy])
291
+
292
+ // Handle group collapse/expand
293
+ const handleGroupToggle = useCallback((groupName) => {
294
+ setCollapsedGroups(prev => {
295
+ const next = new Set(prev)
296
+ if (next.has(groupName)) {
297
+ next.delete(groupName)
298
+ } else {
299
+ next.add(groupName)
300
+ }
301
+ return next
302
+ })
41
303
  }, [])
42
304
 
43
305
  return (
44
- <div className="list grouped">
306
+ <div className={`${cssClasses} grouped`} style={style} {...ariaAttributes} {...restProps}>
307
+ {searchable && (
308
+ <div className="list__search">
309
+ <TextField
310
+ id="grouped-list-search"
311
+ placeholder={searchPlaceholder}
312
+ value={searchTerm}
313
+ onChange={onSearch}
314
+ icon="search"
315
+ outlined={true}
316
+ size="small"
317
+ />
318
+ </div>
319
+ )}
320
+
45
321
  {groups.map(group => {
322
+ const isCollapsed = collapsedGroups.has(group.name)
46
323
  const groupTitle = groupRenderer ? groupRenderer(group) : <Text>{group.name}</Text>
324
+
47
325
  return (
48
326
  <Fragment key={group.name}>
49
- <header key={`${group.name}-header`}>{groupTitle}</header>
50
- <ul key={`${group.name}-ul`}>
51
- {group.items.map(item => <ListItem key={item.id} item={item} selected={selected} onSelect={onSelect} />)}
52
- </ul>
327
+ <header
328
+ className="list__group-header"
329
+ onClick={() => handleGroupToggle(group.name)}
330
+ role="button"
331
+ tabIndex={0}
332
+ aria-expanded={!isCollapsed}
333
+ aria-controls={`group-${group.name}`}
334
+ onKeyDown={(e) => {
335
+ if (e.key === 'Enter' || e.key === ' ') {
336
+ e.preventDefault()
337
+ handleGroupToggle(group.name)
338
+ }
339
+ }}
340
+ >
341
+ <Icon
342
+ icon={isCollapsed ? 'expand_more' : 'expand_less'}
343
+ size="small"
344
+ className="list__group-toggle"
345
+ />
346
+ {groupTitle}
347
+ <span className="list__group-count">
348
+ ({group.items.length})
349
+ </span>
350
+ </header>
351
+
352
+ {!isCollapsed && (
353
+ <ul
354
+ id={`group-${group.name}`}
355
+ className="list__group-items"
356
+ role="group"
357
+ aria-labelledby={`group-header-${group.name}`}
358
+ >
359
+ {group.items.map(item => (
360
+ <ListItem
361
+ key={item.id}
362
+ item={item}
363
+ selected={selected}
364
+ onSelect={onSelect}
365
+ multiSelect={multiSelect}
366
+ dense={dense}
367
+ disabled={disabled}
368
+ animated={animated}
369
+ />
370
+ ))}
371
+ </ul>
372
+ )}
53
373
  </Fragment>
54
374
  )
55
375
  })}
@@ -59,25 +379,248 @@ const GroupedList = (props) => {
59
379
  }
60
380
 
61
381
  /**
62
- * List Item
382
+ * Enhanced List Item with improved functionality and accessibility
63
383
  */
64
- const ListItem = ({ item, selected, onSelect }) => {
65
- const { id, icon, iconTooltip, line1, line2, meta } = item
384
+ const ListItem = ({
385
+ item,
386
+ selected,
387
+ onSelect,
388
+ multiSelect = false,
389
+ dense = false,
390
+ disabled = false,
391
+ animated = true
392
+ }) => {
393
+ const {
394
+ id,
395
+ icon,
396
+ iconTooltip,
397
+ line1,
398
+ line2,
399
+ meta,
400
+ avatar,
401
+ badge,
402
+ actions,
403
+ disabled: itemDisabled = false,
404
+ ...itemProps
405
+ } = item
406
+
407
+ const isItemDisabled = disabled || itemDisabled
408
+ const isSelected = Array.isArray(selected) ? selected.includes(id) : selected === id
409
+
410
+ // Handle selection with keyboard and mouse support
411
+ const handleSelect = useCallback((event) => {
412
+ if (isItemDisabled) return
413
+
414
+ event.preventDefault()
415
+ if (onSelect) onSelect(id, event)
416
+ }, [isItemDisabled, onSelect, id])
417
+
418
+ // Handle keyboard navigation
419
+ const handleKeyDown = useCallback((event) => {
420
+ if (isItemDisabled) return
421
+
422
+ switch (event.key) {
423
+ case 'Enter':
424
+ case ' ':
425
+ event.preventDefault()
426
+ if (onSelect) onSelect(id, event)
427
+ break
428
+ case 'ArrowDown':
429
+ case 'ArrowUp':
430
+ // Allow parent to handle navigation
431
+ break
432
+ default:
433
+ break
434
+ }
435
+ }, [isItemDisabled, onSelect, id])
66
436
 
67
- function select() {
68
- if (onSelect) onSelect(id)
437
+ // Generate CSS classes
438
+ const cssClasses = [
439
+ 'list__item',
440
+ isSelected && 'list__item--selected',
441
+ isItemDisabled && 'list__item--disabled',
442
+ dense && 'list__item--dense',
443
+ animated && 'list__item--animated',
444
+ multiSelect && 'list__item--multi-select'
445
+ ].filter(Boolean).join(' ')
446
+
447
+ // Accessibility attributes
448
+ const ariaAttributes = {
449
+ 'aria-selected': isSelected,
450
+ 'aria-disabled': isItemDisabled,
451
+ 'aria-label': typeof line1 === 'string' ? line1 : 'List item',
452
+ role: 'option',
453
+ tabIndex: isItemDisabled ? -1 : 0
69
454
  }
70
455
 
71
- const isSelected = Array.isArray(selected) ? selected.includes(id) : selected === id
72
- const style = isSelected ? "selected" : ""
73
456
  return (
74
- <li className={`${style}`} onClick={select}>
75
- {icon ? <Icon icon={icon} size="small" tooltip={iconTooltip} /> : null}
76
- <main>
77
- <div className="primary-line"><Text>{line1}</Text></div>
78
- {line2 ? <div className="secondary-line"><Text>{line2}</Text></div> : null}
457
+ <li
458
+ className={cssClasses}
459
+ onClick={handleSelect}
460
+ onKeyDown={handleKeyDown}
461
+ {...ariaAttributes}
462
+ {...itemProps}
463
+ >
464
+ {/* Selection indicator for multi-select */}
465
+ {multiSelect && (
466
+ <div className="list__item-selector">
467
+ <Icon
468
+ icon={isSelected ? 'check_box' : 'check_box_outline_blank'}
469
+ size="small"
470
+ className="list__item-checkbox"
471
+ />
472
+ </div>
473
+ )}
474
+
475
+ {/* Avatar or Icon */}
476
+ {avatar ? (
477
+ <div className="list__item-avatar">
478
+ {typeof avatar === 'string' ? (
479
+ <img src={avatar} alt="" className="list__item-avatar-img" />
480
+ ) : (
481
+ avatar
482
+ )}
483
+ </div>
484
+ ) : icon ? (
485
+ <div className="list__item-icon">
486
+ <Icon
487
+ icon={icon}
488
+ size="small"
489
+ tooltip={iconTooltip}
490
+ disabled={isItemDisabled}
491
+ />
492
+ </div>
493
+ ) : null}
494
+
495
+ {/* Main content */}
496
+ <main className="list__item-content">
497
+ <div className="list__item-primary">
498
+ <Text className="list__item-line1">{line1}</Text>
499
+ {badge && (
500
+ <span className="list__item-badge">
501
+ {typeof badge === 'string' ? <Text size="small">{badge}</Text> : badge}
502
+ </span>
503
+ )}
504
+ </div>
505
+ {line2 && (
506
+ <div className="list__item-secondary">
507
+ <Text className="list__item-line2" size="small">{line2}</Text>
508
+ </div>
509
+ )}
79
510
  </main>
80
- {meta ? <div className="meta">{meta}</div> : null}
511
+
512
+ {/* Meta information */}
513
+ {meta && (
514
+ <div className="list__item-meta">
515
+ {typeof meta === 'string' ? <Text size="small">{meta}</Text> : meta}
516
+ </div>
517
+ )}
518
+
519
+ {/* Actions */}
520
+ {actions && (
521
+ <div className="list__item-actions" role="toolbar">
522
+ {actions}
523
+ </div>
524
+ )}
81
525
  </li>
82
526
  )
83
- }
527
+ }
528
+
529
+ // PropTypes for List component
530
+ List.propTypes = {
531
+ /** Array of items to display */
532
+ items: PropTypes.arrayOf(PropTypes.shape({
533
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
534
+ line1: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
535
+ line2: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
536
+ icon: PropTypes.string,
537
+ iconTooltip: PropTypes.string,
538
+ avatar: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
539
+ badge: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
540
+ meta: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
541
+ actions: PropTypes.node,
542
+ disabled: PropTypes.bool
543
+ })).isRequired,
544
+ /** Child elements */
545
+ children: PropTypes.node,
546
+ /** Selected item ID(s) */
547
+ selected: PropTypes.oneOfType([
548
+ PropTypes.string,
549
+ PropTypes.number,
550
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))
551
+ ]),
552
+ /** Selection callback */
553
+ onSelect: PropTypes.func,
554
+ /** Group items by this property */
555
+ groupBy: PropTypes.string,
556
+ /** Custom group renderer */
557
+ groupRenderer: PropTypes.func,
558
+ /** Loading state */
559
+ loading: PropTypes.bool,
560
+ /** Empty state */
561
+ empty: PropTypes.bool,
562
+ /** Empty state message */
563
+ emptyMessage: PropTypes.string,
564
+ /** Empty state icon */
565
+ emptyIcon: PropTypes.string,
566
+ /** Enable search functionality */
567
+ searchable: PropTypes.bool,
568
+ /** Search input placeholder */
569
+ searchPlaceholder: PropTypes.string,
570
+ /** Properties to search by */
571
+ searchBy: PropTypes.arrayOf(PropTypes.string),
572
+ /** Enable sorting */
573
+ sortable: PropTypes.bool,
574
+ /** Property to sort by */
575
+ sortBy: PropTypes.string,
576
+ /** Sort direction */
577
+ sortDirection: PropTypes.oneOf(['asc', 'desc']),
578
+ /** Sort callback */
579
+ onSort: PropTypes.func,
580
+ /** Enable multi-selection */
581
+ multiSelect: PropTypes.bool,
582
+ /** Multi-selection callback */
583
+ onMultiSelect: PropTypes.func,
584
+ /** Dense layout */
585
+ dense: PropTypes.bool,
586
+ /** Disabled state */
587
+ disabled: PropTypes.bool,
588
+ /** Enable animations */
589
+ animated: PropTypes.bool,
590
+ /** Enable virtualization for large lists */
591
+ virtualized: PropTypes.bool,
592
+ /** Item height for virtualization */
593
+ itemHeight: PropTypes.number,
594
+ /** Maximum height of list */
595
+ maxHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
596
+ /** Additional CSS classes */
597
+ className: PropTypes.string,
598
+ /** Inline styles */
599
+ style: PropTypes.object,
600
+ /** ARIA label */
601
+ ariaLabel: PropTypes.string,
602
+ /** ARIA role */
603
+ role: PropTypes.string
604
+ }
605
+
606
+ List.defaultProps = {
607
+ items: [],
608
+ loading: false,
609
+ empty: false,
610
+ emptyMessage: "No items found",
611
+ emptyIcon: "inbox",
612
+ searchable: false,
613
+ searchPlaceholder: "Search...",
614
+ searchBy: ['line1', 'line2'],
615
+ sortable: false,
616
+ sortDirection: 'asc',
617
+ multiSelect: false,
618
+ dense: false,
619
+ disabled: false,
620
+ animated: true,
621
+ virtualized: false,
622
+ itemHeight: 48,
623
+ role: 'list'
624
+ }
625
+
626
+ export default List