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.
- 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/textfield.js +1 -1
- 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
@@ -0,0 +1,378 @@
|
|
1
|
+
import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'
|
2
|
+
import { Fragment } from 'react'
|
3
|
+
import PropTypes from 'prop-types'
|
4
|
+
import { Icon } from './icon'
|
5
|
+
import { Text, TEXTFORMATS } from './text'
|
6
|
+
import './tab.css'
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Enhanced Tabs component with improved functionality while maintaining 100% compatibility
|
10
|
+
*/
|
11
|
+
export const Tabs = (props) => {
|
12
|
+
const {
|
13
|
+
children,
|
14
|
+
selected,
|
15
|
+
onChange,
|
16
|
+
fillLeft = false,
|
17
|
+
fillRight = true,
|
18
|
+
// New enhanced props (all optional for compatibility)
|
19
|
+
orientation = 'horizontal',
|
20
|
+
variant = 'standard',
|
21
|
+
scrollable = false,
|
22
|
+
centered = false,
|
23
|
+
disabled = false,
|
24
|
+
animated = true,
|
25
|
+
persistent = false,
|
26
|
+
persistKey,
|
27
|
+
beforeChange,
|
28
|
+
className,
|
29
|
+
style,
|
30
|
+
ariaLabel,
|
31
|
+
...restProps
|
32
|
+
} = props
|
33
|
+
|
34
|
+
const tabsRef = useRef(null)
|
35
|
+
|
36
|
+
// Validate props
|
37
|
+
if (children && !React.Children.count(children)) {
|
38
|
+
console.warn('Tabs component: children prop should contain Tab components')
|
39
|
+
}
|
40
|
+
|
41
|
+
// Handle persistent tabs (remember selected tab)
|
42
|
+
useEffect(() => {
|
43
|
+
if (persistent && persistKey && typeof Storage !== 'undefined') {
|
44
|
+
const savedTab = localStorage.getItem(`tabs-${persistKey}`)
|
45
|
+
if (savedTab !== null && onChange) {
|
46
|
+
const parsedTab = isNaN(savedTab) ? savedTab : parseInt(savedTab)
|
47
|
+
if (parsedTab !== selected) {
|
48
|
+
onChange(parsedTab)
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}, [persistent, persistKey])
|
53
|
+
|
54
|
+
// Save selected tab to localStorage
|
55
|
+
useEffect(() => {
|
56
|
+
if (persistent && persistKey && typeof Storage !== 'undefined' && selected !== undefined) {
|
57
|
+
localStorage.setItem(`tabs-${persistKey}`, selected.toString())
|
58
|
+
}
|
59
|
+
}, [selected, persistent, persistKey])
|
60
|
+
|
61
|
+
// Enhanced select function with validation
|
62
|
+
const handleSelect = useCallback(async (id, index) => {
|
63
|
+
if (disabled) return
|
64
|
+
|
65
|
+
// Call beforeChange hook if provided
|
66
|
+
if (beforeChange) {
|
67
|
+
try {
|
68
|
+
const canChange = await beforeChange(id || index, selected)
|
69
|
+
if (canChange === false) return
|
70
|
+
} catch (error) {
|
71
|
+
console.warn('Tabs beforeChange hook error:', error)
|
72
|
+
return
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
if (onChange) onChange(id || index)
|
77
|
+
}, [disabled, beforeChange, onChange, selected])
|
78
|
+
|
79
|
+
// Process children (maintaining original logic)
|
80
|
+
const notNullChildren = React.Children.toArray(children).filter(child => child !== null)
|
81
|
+
const tabs = notNullChildren.map((child, index) => {
|
82
|
+
return React.cloneElement(child, {
|
83
|
+
selected: index === selected || selected === child.props.id,
|
84
|
+
onSelect: (id) => handleSelect(id, index),
|
85
|
+
// Pass enhanced props to Tab
|
86
|
+
orientation,
|
87
|
+
variant,
|
88
|
+
disabled: disabled || child.props.disabled,
|
89
|
+
animated,
|
90
|
+
tabIndex: index,
|
91
|
+
totalTabs: notNullChildren.length
|
92
|
+
})
|
93
|
+
})
|
94
|
+
|
95
|
+
// Generate CSS classes
|
96
|
+
const cssClasses = [
|
97
|
+
'tabs',
|
98
|
+
`tabs--${orientation}`,
|
99
|
+
`tabs--${variant}`,
|
100
|
+
scrollable && 'tabs--scrollable',
|
101
|
+
centered && 'tabs--centered',
|
102
|
+
disabled && 'tabs--disabled',
|
103
|
+
animated && 'tabs--animated',
|
104
|
+
className
|
105
|
+
].filter(Boolean).join(' ')
|
106
|
+
|
107
|
+
// Accessibility attributes
|
108
|
+
const ariaAttributes = {
|
109
|
+
'aria-label': ariaLabel || 'Tabs',
|
110
|
+
'aria-disabled': disabled,
|
111
|
+
'aria-orientation': orientation,
|
112
|
+
role: 'tablist'
|
113
|
+
}
|
114
|
+
|
115
|
+
// Handle keyboard navigation
|
116
|
+
const handleKeyDown = useCallback((event) => {
|
117
|
+
if (disabled) return
|
118
|
+
|
119
|
+
const currentIndex = typeof selected === 'number' ? selected :
|
120
|
+
notNullChildren.findIndex(child => child.props.id === selected)
|
121
|
+
|
122
|
+
let newIndex = currentIndex
|
123
|
+
|
124
|
+
switch (event.key) {
|
125
|
+
case 'ArrowLeft':
|
126
|
+
case 'ArrowUp':
|
127
|
+
event.preventDefault()
|
128
|
+
newIndex = currentIndex > 0 ? currentIndex - 1 : notNullChildren.length - 1
|
129
|
+
break
|
130
|
+
case 'ArrowRight':
|
131
|
+
case 'ArrowDown':
|
132
|
+
event.preventDefault()
|
133
|
+
newIndex = currentIndex < notNullChildren.length - 1 ? currentIndex + 1 : 0
|
134
|
+
break
|
135
|
+
case 'Home':
|
136
|
+
event.preventDefault()
|
137
|
+
newIndex = 0
|
138
|
+
break
|
139
|
+
case 'End':
|
140
|
+
event.preventDefault()
|
141
|
+
newIndex = notNullChildren.length - 1
|
142
|
+
break
|
143
|
+
default:
|
144
|
+
return
|
145
|
+
}
|
146
|
+
|
147
|
+
const targetChild = notNullChildren[newIndex]
|
148
|
+
if (targetChild && !targetChild.props.disabled) {
|
149
|
+
handleSelect(targetChild.props.id, newIndex)
|
150
|
+
}
|
151
|
+
}, [disabled, selected, notNullChildren, handleSelect])
|
152
|
+
|
153
|
+
return (
|
154
|
+
<div
|
155
|
+
className={cssClasses}
|
156
|
+
style={style}
|
157
|
+
ref={tabsRef}
|
158
|
+
onKeyDown={handleKeyDown}
|
159
|
+
{...ariaAttributes}
|
160
|
+
{...restProps}
|
161
|
+
>
|
162
|
+
{fillLeft && <div className="tab-filler" />}
|
163
|
+
{tabs}
|
164
|
+
{fillRight && <div className="tab-filler" />}
|
165
|
+
</div>
|
166
|
+
)
|
167
|
+
}
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Enhanced Tab component with improved functionality while maintaining 100% compatibility
|
171
|
+
*/
|
172
|
+
export const Tab = (props) => {
|
173
|
+
const {
|
174
|
+
id,
|
175
|
+
icon,
|
176
|
+
label,
|
177
|
+
selected,
|
178
|
+
actions,
|
179
|
+
onSelect,
|
180
|
+
// New enhanced props (all optional for compatibility)
|
181
|
+
disabled = false,
|
182
|
+
closeable = false,
|
183
|
+
onClose,
|
184
|
+
badge,
|
185
|
+
tooltip,
|
186
|
+
orientation = 'horizontal',
|
187
|
+
variant = 'standard',
|
188
|
+
animated = true,
|
189
|
+
tabIndex,
|
190
|
+
totalTabs,
|
191
|
+
className,
|
192
|
+
style,
|
193
|
+
...restProps
|
194
|
+
} = props
|
195
|
+
|
196
|
+
const tabRef = useRef(null)
|
197
|
+
|
198
|
+
// Handle selection (maintaining original behavior)
|
199
|
+
const handleSelect = useCallback((event) => {
|
200
|
+
if (disabled) return
|
201
|
+
|
202
|
+
event.preventDefault()
|
203
|
+
if (onSelect) onSelect(id)
|
204
|
+
}, [disabled, onSelect, id])
|
205
|
+
|
206
|
+
// Handle close
|
207
|
+
const handleClose = useCallback((event) => {
|
208
|
+
event.stopPropagation()
|
209
|
+
if (onClose) onClose(id)
|
210
|
+
}, [onClose, id])
|
211
|
+
|
212
|
+
// Handle keyboard interaction
|
213
|
+
const handleKeyDown = useCallback((event) => {
|
214
|
+
if (disabled) return
|
215
|
+
|
216
|
+
switch (event.key) {
|
217
|
+
case 'Enter':
|
218
|
+
case ' ':
|
219
|
+
event.preventDefault()
|
220
|
+
if (onSelect) onSelect(id)
|
221
|
+
break
|
222
|
+
case 'Delete':
|
223
|
+
case 'Backspace':
|
224
|
+
if (closeable && onClose) {
|
225
|
+
event.preventDefault()
|
226
|
+
onClose(id)
|
227
|
+
}
|
228
|
+
break
|
229
|
+
default:
|
230
|
+
break
|
231
|
+
}
|
232
|
+
}, [disabled, onSelect, id, closeable, onClose])
|
233
|
+
|
234
|
+
// Generate CSS classes
|
235
|
+
const cssClasses = [
|
236
|
+
'tab',
|
237
|
+
selected && 'selected',
|
238
|
+
disabled && 'tab--disabled',
|
239
|
+
closeable && 'tab--closeable',
|
240
|
+
animated && 'tab--animated',
|
241
|
+
`tab--${orientation}`,
|
242
|
+
`tab--${variant}`,
|
243
|
+
className
|
244
|
+
].filter(Boolean).join(' ')
|
245
|
+
|
246
|
+
// Accessibility attributes
|
247
|
+
const ariaAttributes = {
|
248
|
+
'aria-selected': selected,
|
249
|
+
'aria-disabled': disabled,
|
250
|
+
'aria-controls': `tabpanel-${id || tabIndex}`,
|
251
|
+
'aria-label': typeof label === 'string' ? label : `Tab ${(tabIndex || 0) + 1}`,
|
252
|
+
role: 'tab',
|
253
|
+
tabIndex: selected ? 0 : -1,
|
254
|
+
id: `tab-${id || tabIndex}`
|
255
|
+
}
|
256
|
+
|
257
|
+
// Render label with proper formatting (maintaining original logic)
|
258
|
+
const labelTxt = label ? <Text format={TEXTFORMATS.STRING}>{label}</Text> : null
|
259
|
+
|
260
|
+
return (
|
261
|
+
<div
|
262
|
+
className={cssClasses}
|
263
|
+
style={style}
|
264
|
+
onClick={handleSelect}
|
265
|
+
onKeyDown={handleKeyDown}
|
266
|
+
ref={tabRef}
|
267
|
+
title={tooltip}
|
268
|
+
{...ariaAttributes}
|
269
|
+
{...restProps}
|
270
|
+
>
|
271
|
+
{/* Icon (maintaining original structure) */}
|
272
|
+
{icon && <Icon icon={icon} size="small" disabled={disabled} />}
|
273
|
+
|
274
|
+
{/* Label with badge */}
|
275
|
+
<div className="tab__content">
|
276
|
+
{labelTxt}
|
277
|
+
{badge && (
|
278
|
+
<span className="tab__badge">
|
279
|
+
{typeof badge === 'number' && badge > 99 ? '99+' : badge}
|
280
|
+
</span>
|
281
|
+
)}
|
282
|
+
</div>
|
283
|
+
|
284
|
+
{/* Actions (maintaining original structure) */}
|
285
|
+
{actions && <div className="tab__actions">{actions}</div>}
|
286
|
+
|
287
|
+
{/* Close button */}
|
288
|
+
{closeable && (
|
289
|
+
<button
|
290
|
+
className="tab__close"
|
291
|
+
onClick={handleClose}
|
292
|
+
aria-label={`Close ${typeof label === 'string' ? label : 'tab'}`}
|
293
|
+
tabIndex={-1}
|
294
|
+
>
|
295
|
+
<Icon icon="close" size="small" />
|
296
|
+
</button>
|
297
|
+
)}
|
298
|
+
</div>
|
299
|
+
)
|
300
|
+
}
|
301
|
+
|
302
|
+
/**
|
303
|
+
* Enhanced Stack component with improved functionality while maintaining 100% compatibility
|
304
|
+
*/
|
305
|
+
export const Stack = (props) => {
|
306
|
+
const {
|
307
|
+
selected = 0,
|
308
|
+
children,
|
309
|
+
// New enhanced props (all optional for compatibility)
|
310
|
+
lazy = false,
|
311
|
+
keepMounted = false,
|
312
|
+
animated = true,
|
313
|
+
className,
|
314
|
+
style,
|
315
|
+
...restProps
|
316
|
+
} = props
|
317
|
+
|
318
|
+
const [mountedTabs, setMountedTabs] = useState(new Set([selected]))
|
319
|
+
|
320
|
+
// Track which tabs have been mounted for lazy loading
|
321
|
+
useEffect(() => {
|
322
|
+
if (lazy || keepMounted) {
|
323
|
+
setMountedTabs(prev => new Set([...prev, selected]))
|
324
|
+
}
|
325
|
+
}, [selected, lazy, keepMounted])
|
326
|
+
|
327
|
+
const notNullChildren = React.Children.toArray(children).filter(child => child !== null)
|
328
|
+
|
329
|
+
// Generate CSS classes
|
330
|
+
const cssClasses = [
|
331
|
+
'stack',
|
332
|
+
animated && 'stack--animated',
|
333
|
+
className
|
334
|
+
].filter(Boolean).join(' ')
|
335
|
+
|
336
|
+
if (lazy) {
|
337
|
+
// Lazy loading: only render mounted tabs
|
338
|
+
return (
|
339
|
+
<div className={cssClasses} style={style} {...restProps}>
|
340
|
+
{notNullChildren.map((child, index) => {
|
341
|
+
const isSelected = index === selected
|
342
|
+
const shouldRender = mountedTabs.has(index) || isSelected
|
343
|
+
|
344
|
+
if (!shouldRender) return null
|
345
|
+
|
346
|
+
return (
|
347
|
+
<div
|
348
|
+
key={index}
|
349
|
+
className={`stack__panel ${isSelected ? 'stack__panel--active' : 'stack__panel--hidden'}`}
|
350
|
+
role="tabpanel"
|
351
|
+
aria-labelledby={`tab-${index}`}
|
352
|
+
id={`tabpanel-${index}`}
|
353
|
+
hidden={!isSelected}
|
354
|
+
>
|
355
|
+
{child}
|
356
|
+
</div>
|
357
|
+
)
|
358
|
+
})}
|
359
|
+
</div>
|
360
|
+
)
|
361
|
+
}
|
362
|
+
|
363
|
+
// Original behavior: render selected child only
|
364
|
+
const child = notNullChildren.filter((child, index) => index === selected)[0]
|
365
|
+
|
366
|
+
return (
|
367
|
+
<div
|
368
|
+
className={cssClasses}
|
369
|
+
style={style}
|
370
|
+
role="tabpanel"
|
371
|
+
aria-labelledby={`tab-${selected}`}
|
372
|
+
id={`tabpanel-${selected}`}
|
373
|
+
{...restProps}
|
374
|
+
>
|
375
|
+
{child}
|
376
|
+
</div>
|
377
|
+
)
|
378
|
+
}
|