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
@@ -0,0 +1,203 @@
|
|
1
|
+
import React, { useCallback } from 'react'
|
2
|
+
import PropTypes from 'prop-types'
|
3
|
+
import { Icon } from './icon'
|
4
|
+
import { Text } from './text'
|
5
|
+
import './header2.css'
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Enhanced Header component with improved accessibility and robustness
|
9
|
+
* Maintains 100% visual compatibility with original Header
|
10
|
+
*/
|
11
|
+
export const Header2 = (props) => {
|
12
|
+
const {
|
13
|
+
title,
|
14
|
+
icon,
|
15
|
+
iconSrc,
|
16
|
+
img,
|
17
|
+
caption = false,
|
18
|
+
prominent = false,
|
19
|
+
dense = false,
|
20
|
+
primary = false,
|
21
|
+
secondary = false,
|
22
|
+
clickable = false,
|
23
|
+
disabled = false,
|
24
|
+
loading = false,
|
25
|
+
className,
|
26
|
+
children,
|
27
|
+
action,
|
28
|
+
ariaLabel,
|
29
|
+
role = 'banner',
|
30
|
+
onClick,
|
31
|
+
onIconClick,
|
32
|
+
...restProps
|
33
|
+
} = props
|
34
|
+
|
35
|
+
// Validate props
|
36
|
+
if (!title && !icon && !iconSrc && !ariaLabel) {
|
37
|
+
console.warn('Header2 component: title, icon, iconSrc, or ariaLabel should be provided for accessibility')
|
38
|
+
}
|
39
|
+
|
40
|
+
// Handle icon click
|
41
|
+
const handleIconClick = useCallback((event) => {
|
42
|
+
if (disabled || loading) return
|
43
|
+
|
44
|
+
if (onIconClick) {
|
45
|
+
onIconClick(event)
|
46
|
+
} else if (action) {
|
47
|
+
action(event)
|
48
|
+
}
|
49
|
+
}, [disabled, loading, onIconClick, action])
|
50
|
+
|
51
|
+
// Handle header click
|
52
|
+
const handleClick = useCallback((event) => {
|
53
|
+
if (disabled || loading) return
|
54
|
+
|
55
|
+
if (onClick) {
|
56
|
+
onClick(event)
|
57
|
+
}
|
58
|
+
}, [disabled, loading, onClick])
|
59
|
+
|
60
|
+
// Generate CSS classes (maintaining exact same logic as original)
|
61
|
+
const captionClass = caption ? 'caption' : ''
|
62
|
+
const prominentClass = prominent ? 'prominent' : ''
|
63
|
+
const denseClass = dense ? 'dense' : ''
|
64
|
+
let themeClass = primary ? 'primary' : ''
|
65
|
+
themeClass = secondary ? 'secondary' : themeClass
|
66
|
+
|
67
|
+
const safeClassName = className || ''
|
68
|
+
const cssClasses = [
|
69
|
+
'header2',
|
70
|
+
captionClass,
|
71
|
+
prominentClass,
|
72
|
+
denseClass,
|
73
|
+
themeClass,
|
74
|
+
disabled && 'disabled',
|
75
|
+
loading && 'loading',
|
76
|
+
safeClassName
|
77
|
+
].filter(Boolean).join(' ')
|
78
|
+
|
79
|
+
// Generate icon element (maintaining exact same logic as original)
|
80
|
+
let iconElement = null
|
81
|
+
if (icon) {
|
82
|
+
iconElement = (
|
83
|
+
<Icon
|
84
|
+
icon={icon}
|
85
|
+
clickable={clickable && !disabled && !loading}
|
86
|
+
action={handleIconClick}
|
87
|
+
disabled={disabled}
|
88
|
+
ariaLabel={`${icon} icon`}
|
89
|
+
/>
|
90
|
+
)
|
91
|
+
} else if (iconSrc) {
|
92
|
+
iconElement = (
|
93
|
+
<img
|
94
|
+
className="header2-icon"
|
95
|
+
src={iconSrc}
|
96
|
+
alt={ariaLabel || title || 'Header icon'}
|
97
|
+
onClick={clickable && !disabled && !loading ? handleIconClick : undefined}
|
98
|
+
style={{
|
99
|
+
cursor: clickable && !disabled && !loading ? 'pointer' : 'default',
|
100
|
+
opacity: disabled ? 0.6 : 1
|
101
|
+
}}
|
102
|
+
/>
|
103
|
+
)
|
104
|
+
}
|
105
|
+
|
106
|
+
// Generate background style (maintaining exact same logic as original)
|
107
|
+
const backgroundStyle = img ? { backgroundImage: `url(${img})` } : {}
|
108
|
+
|
109
|
+
// Generate title element (maintaining exact same logic as original)
|
110
|
+
const titleElement = title ? (
|
111
|
+
<label className="header2-title">
|
112
|
+
<Text>{title}</Text>
|
113
|
+
</label>
|
114
|
+
) : null
|
115
|
+
|
116
|
+
// Accessibility attributes
|
117
|
+
const ariaAttributes = {
|
118
|
+
'aria-label': ariaLabel || (typeof title === 'string' ? title : 'Header'),
|
119
|
+
'aria-disabled': disabled,
|
120
|
+
'aria-busy': loading,
|
121
|
+
role: role
|
122
|
+
}
|
123
|
+
|
124
|
+
return (
|
125
|
+
<header
|
126
|
+
className={cssClasses}
|
127
|
+
style={backgroundStyle}
|
128
|
+
onClick={onClick ? handleClick : undefined}
|
129
|
+
{...ariaAttributes}
|
130
|
+
{...restProps}
|
131
|
+
>
|
132
|
+
{iconElement}
|
133
|
+
{titleElement}
|
134
|
+
<span className="header2-actions" role="toolbar" aria-label="Header actions">
|
135
|
+
{loading && (
|
136
|
+
<Icon
|
137
|
+
icon="hourglass_empty"
|
138
|
+
className="header2-loading"
|
139
|
+
ariaLabel="Loading"
|
140
|
+
/>
|
141
|
+
)}
|
142
|
+
{children}
|
143
|
+
</span>
|
144
|
+
</header>
|
145
|
+
)
|
146
|
+
}
|
147
|
+
|
148
|
+
// PropTypes for Header2
|
149
|
+
Header2.propTypes = {
|
150
|
+
/** Header title text */
|
151
|
+
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
152
|
+
/** Material icon name */
|
153
|
+
icon: PropTypes.string,
|
154
|
+
/** Icon image source URL */
|
155
|
+
iconSrc: PropTypes.string,
|
156
|
+
/** Background image URL */
|
157
|
+
img: PropTypes.string,
|
158
|
+
/** Whether header has caption style (4rem height) */
|
159
|
+
caption: PropTypes.bool,
|
160
|
+
/** Whether header has prominent style (12.8rem height) */
|
161
|
+
prominent: PropTypes.bool,
|
162
|
+
/** Whether header has dense style (2.4rem height) */
|
163
|
+
dense: PropTypes.bool,
|
164
|
+
/** Whether header has primary theme */
|
165
|
+
primary: PropTypes.bool,
|
166
|
+
/** Whether header has secondary theme */
|
167
|
+
secondary: PropTypes.bool,
|
168
|
+
/** Whether icon is clickable */
|
169
|
+
clickable: PropTypes.bool,
|
170
|
+
/** Whether header is disabled */
|
171
|
+
disabled: PropTypes.bool,
|
172
|
+
/** Whether header is in loading state */
|
173
|
+
loading: PropTypes.bool,
|
174
|
+
/** Additional CSS classes */
|
175
|
+
className: PropTypes.string,
|
176
|
+
/** Action elements (buttons, etc.) */
|
177
|
+
children: PropTypes.node,
|
178
|
+
/** Icon click handler (legacy support) */
|
179
|
+
action: PropTypes.func,
|
180
|
+
/** ARIA label for accessibility */
|
181
|
+
ariaLabel: PropTypes.string,
|
182
|
+
/** ARIA role */
|
183
|
+
role: PropTypes.string,
|
184
|
+
/** Header click handler */
|
185
|
+
onClick: PropTypes.func,
|
186
|
+
/** Icon click handler */
|
187
|
+
onIconClick: PropTypes.func
|
188
|
+
}
|
189
|
+
|
190
|
+
Header2.defaultProps = {
|
191
|
+
caption: false,
|
192
|
+
prominent: false,
|
193
|
+
dense: false,
|
194
|
+
primary: false,
|
195
|
+
secondary: false,
|
196
|
+
clickable: false,
|
197
|
+
disabled: false,
|
198
|
+
loading: false,
|
199
|
+
role: 'banner',
|
200
|
+
className: ''
|
201
|
+
}
|
202
|
+
|
203
|
+
export default Header2
|
@@ -0,0 +1,377 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { Header2 } from './header2'
|
3
|
+
|
4
|
+
// Pruebas unitarias para el componente Header2
|
5
|
+
describe('Header2 Component', () => {
|
6
|
+
// Mock de los componentes dependientes
|
7
|
+
const mockText = jest.fn()
|
8
|
+
const mockIcon = jest.fn()
|
9
|
+
|
10
|
+
beforeEach(() => {
|
11
|
+
jest.clearAllMocks()
|
12
|
+
|
13
|
+
// Mock del componente Text
|
14
|
+
jest.doMock('./text', () => ({
|
15
|
+
Text: mockText
|
16
|
+
}))
|
17
|
+
|
18
|
+
// Mock del componente Icon
|
19
|
+
jest.doMock('./icon', () => ({
|
20
|
+
Icon: mockIcon
|
21
|
+
}))
|
22
|
+
|
23
|
+
// Mock de console.warn para las pruebas
|
24
|
+
jest.spyOn(console, 'warn').mockImplementation(() => {})
|
25
|
+
})
|
26
|
+
|
27
|
+
afterEach(() => {
|
28
|
+
console.warn.mockRestore()
|
29
|
+
})
|
30
|
+
|
31
|
+
test('component exports correctly', () => {
|
32
|
+
expect(Header2).toBeDefined()
|
33
|
+
expect(typeof Header2).toBe('function')
|
34
|
+
})
|
35
|
+
|
36
|
+
test('component has correct PropTypes', () => {
|
37
|
+
expect(Header2.propTypes).toBeDefined()
|
38
|
+
expect(Header2.propTypes.title).toBeDefined()
|
39
|
+
expect(Header2.propTypes.icon).toBeDefined()
|
40
|
+
expect(Header2.propTypes.iconSrc).toBeDefined()
|
41
|
+
expect(Header2.propTypes.img).toBeDefined()
|
42
|
+
expect(Header2.propTypes.caption).toBeDefined()
|
43
|
+
expect(Header2.propTypes.prominent).toBeDefined()
|
44
|
+
expect(Header2.propTypes.dense).toBeDefined()
|
45
|
+
expect(Header2.propTypes.primary).toBeDefined()
|
46
|
+
expect(Header2.propTypes.secondary).toBeDefined()
|
47
|
+
expect(Header2.propTypes.clickable).toBeDefined()
|
48
|
+
expect(Header2.propTypes.disabled).toBeDefined()
|
49
|
+
expect(Header2.propTypes.loading).toBeDefined()
|
50
|
+
expect(Header2.propTypes.className).toBeDefined()
|
51
|
+
expect(Header2.propTypes.children).toBeDefined()
|
52
|
+
expect(Header2.propTypes.action).toBeDefined()
|
53
|
+
expect(Header2.propTypes.ariaLabel).toBeDefined()
|
54
|
+
expect(Header2.propTypes.role).toBeDefined()
|
55
|
+
expect(Header2.propTypes.onClick).toBeDefined()
|
56
|
+
expect(Header2.propTypes.onIconClick).toBeDefined()
|
57
|
+
})
|
58
|
+
|
59
|
+
test('component has correct defaultProps', () => {
|
60
|
+
expect(Header2.defaultProps).toBeDefined()
|
61
|
+
expect(Header2.defaultProps.caption).toBe(false)
|
62
|
+
expect(Header2.defaultProps.prominent).toBe(false)
|
63
|
+
expect(Header2.defaultProps.dense).toBe(false)
|
64
|
+
expect(Header2.defaultProps.primary).toBe(false)
|
65
|
+
expect(Header2.defaultProps.secondary).toBe(false)
|
66
|
+
expect(Header2.defaultProps.clickable).toBe(false)
|
67
|
+
expect(Header2.defaultProps.disabled).toBe(false)
|
68
|
+
expect(Header2.defaultProps.loading).toBe(false)
|
69
|
+
expect(Header2.defaultProps.role).toBe('banner')
|
70
|
+
expect(Header2.defaultProps.className).toBe('')
|
71
|
+
})
|
72
|
+
|
73
|
+
test('warns when no accessibility props are provided', () => {
|
74
|
+
const validateAccessibility = (title, icon, iconSrc, ariaLabel) => {
|
75
|
+
if (!title && !icon && !iconSrc && !ariaLabel) {
|
76
|
+
console.warn('Header2 component: title, icon, iconSrc, or ariaLabel should be provided for accessibility')
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
validateAccessibility(null, null, null, null)
|
81
|
+
expect(console.warn).toHaveBeenCalledWith('Header2 component: title, icon, iconSrc, or ariaLabel should be provided for accessibility')
|
82
|
+
|
83
|
+
console.warn.mockClear()
|
84
|
+
validateAccessibility('Test Title', null, null, null)
|
85
|
+
expect(console.warn).not.toHaveBeenCalled()
|
86
|
+
})
|
87
|
+
|
88
|
+
test('CSS classes generation maintains original logic', () => {
|
89
|
+
const generateClasses = (caption, prominent, dense, primary, secondary, disabled, loading, className) => {
|
90
|
+
const captionClass = caption ? 'caption' : ''
|
91
|
+
const prominentClass = prominent ? 'prominent' : ''
|
92
|
+
const denseClass = dense ? 'dense' : ''
|
93
|
+
let themeClass = primary ? 'primary' : ''
|
94
|
+
themeClass = secondary ? 'secondary' : themeClass
|
95
|
+
|
96
|
+
return [
|
97
|
+
'header2',
|
98
|
+
captionClass,
|
99
|
+
prominentClass,
|
100
|
+
denseClass,
|
101
|
+
themeClass,
|
102
|
+
disabled && 'disabled',
|
103
|
+
loading && 'loading',
|
104
|
+
className || ''
|
105
|
+
].filter(Boolean).join(' ')
|
106
|
+
}
|
107
|
+
|
108
|
+
// Test basic header
|
109
|
+
expect(generateClasses(false, false, false, false, false, false, false, ''))
|
110
|
+
.toBe('header2')
|
111
|
+
|
112
|
+
// Test caption variant
|
113
|
+
expect(generateClasses(true, false, false, false, false, false, false, ''))
|
114
|
+
.toBe('header2 caption')
|
115
|
+
|
116
|
+
// Test prominent variant
|
117
|
+
expect(generateClasses(false, true, false, false, false, false, false, ''))
|
118
|
+
.toBe('header2 prominent')
|
119
|
+
|
120
|
+
// Test dense variant
|
121
|
+
expect(generateClasses(false, false, true, false, false, false, false, ''))
|
122
|
+
.toBe('header2 dense')
|
123
|
+
|
124
|
+
// Test primary theme
|
125
|
+
expect(generateClasses(false, false, false, true, false, false, false, ''))
|
126
|
+
.toBe('header2 primary')
|
127
|
+
|
128
|
+
// Test secondary theme (overrides primary)
|
129
|
+
expect(generateClasses(false, false, false, true, true, false, false, ''))
|
130
|
+
.toBe('header2 secondary')
|
131
|
+
|
132
|
+
// Test disabled state
|
133
|
+
expect(generateClasses(false, false, false, false, false, true, false, ''))
|
134
|
+
.toBe('header2 disabled')
|
135
|
+
|
136
|
+
// Test loading state
|
137
|
+
expect(generateClasses(false, false, false, false, false, false, true, ''))
|
138
|
+
.toBe('header2 loading')
|
139
|
+
|
140
|
+
// Test custom className
|
141
|
+
expect(generateClasses(false, false, false, false, false, false, false, 'custom-class'))
|
142
|
+
.toBe('header2 custom-class')
|
143
|
+
|
144
|
+
// Test combination
|
145
|
+
expect(generateClasses(true, false, false, true, false, false, false, 'custom'))
|
146
|
+
.toBe('header2 caption primary custom')
|
147
|
+
})
|
148
|
+
|
149
|
+
test('icon element generation maintains original logic', () => {
|
150
|
+
const generateIconElement = (icon, iconSrc, clickable, disabled, loading, handleIconClick, ariaLabel, title) => {
|
151
|
+
if (icon) {
|
152
|
+
return {
|
153
|
+
type: 'Icon',
|
154
|
+
props: {
|
155
|
+
icon: icon,
|
156
|
+
clickable: clickable && !disabled && !loading,
|
157
|
+
action: handleIconClick,
|
158
|
+
disabled: disabled,
|
159
|
+
ariaLabel: `${icon} icon`
|
160
|
+
}
|
161
|
+
}
|
162
|
+
} else if (iconSrc) {
|
163
|
+
return {
|
164
|
+
type: 'img',
|
165
|
+
props: {
|
166
|
+
className: 'header2-icon',
|
167
|
+
src: iconSrc,
|
168
|
+
alt: ariaLabel || title || 'Header icon',
|
169
|
+
onClick: clickable && !disabled && !loading ? handleIconClick : undefined,
|
170
|
+
style: {
|
171
|
+
cursor: clickable && !disabled && !loading ? 'pointer' : 'default',
|
172
|
+
opacity: disabled ? 0.6 : 1
|
173
|
+
}
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
return null
|
178
|
+
}
|
179
|
+
|
180
|
+
const mockHandler = jest.fn()
|
181
|
+
|
182
|
+
// Test with icon
|
183
|
+
const iconResult = generateIconElement('home', null, true, false, false, mockHandler, null, null)
|
184
|
+
expect(iconResult.type).toBe('Icon')
|
185
|
+
expect(iconResult.props.icon).toBe('home')
|
186
|
+
expect(iconResult.props.clickable).toBe(true)
|
187
|
+
expect(iconResult.props.ariaLabel).toBe('home icon')
|
188
|
+
|
189
|
+
// Test with iconSrc
|
190
|
+
const imgResult = generateIconElement(null, '/icon.png', true, false, false, mockHandler, 'Custom label', 'Title')
|
191
|
+
expect(imgResult.type).toBe('img')
|
192
|
+
expect(imgResult.props.src).toBe('/icon.png')
|
193
|
+
expect(imgResult.props.alt).toBe('Custom label')
|
194
|
+
expect(imgResult.props.style.cursor).toBe('pointer')
|
195
|
+
|
196
|
+
// Test disabled state
|
197
|
+
const disabledResult = generateIconElement('home', null, true, true, false, mockHandler, null, null)
|
198
|
+
expect(disabledResult.props.clickable).toBe(false)
|
199
|
+
|
200
|
+
// Test loading state
|
201
|
+
const loadingResult = generateIconElement('home', null, true, false, true, mockHandler, null, null)
|
202
|
+
expect(loadingResult.props.clickable).toBe(false)
|
203
|
+
|
204
|
+
// Test no icon
|
205
|
+
const noIconResult = generateIconElement(null, null, true, false, false, mockHandler, null, null)
|
206
|
+
expect(noIconResult).toBeNull()
|
207
|
+
})
|
208
|
+
|
209
|
+
test('background style generation maintains original logic', () => {
|
210
|
+
const generateBackgroundStyle = (img) => {
|
211
|
+
return img ? { backgroundImage: `url(${img})` } : {}
|
212
|
+
}
|
213
|
+
|
214
|
+
expect(generateBackgroundStyle(null)).toEqual({})
|
215
|
+
expect(generateBackgroundStyle('')).toEqual({})
|
216
|
+
expect(generateBackgroundStyle('/bg.jpg')).toEqual({ backgroundImage: 'url(/bg.jpg)' })
|
217
|
+
expect(generateBackgroundStyle('https://example.com/bg.png')).toEqual({ backgroundImage: 'url(https://example.com/bg.png)' })
|
218
|
+
})
|
219
|
+
|
220
|
+
test('accessibility attributes generation works correctly', () => {
|
221
|
+
const generateAriaAttributes = (ariaLabel, title, disabled, loading, role) => {
|
222
|
+
return {
|
223
|
+
'aria-label': ariaLabel || (typeof title === 'string' ? title : 'Header'),
|
224
|
+
'aria-disabled': disabled,
|
225
|
+
'aria-busy': loading,
|
226
|
+
role: role
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
// Test with string title
|
231
|
+
const result1 = generateAriaAttributes(null, 'Test Title', false, false, 'banner')
|
232
|
+
expect(result1['aria-label']).toBe('Test Title')
|
233
|
+
expect(result1['aria-disabled']).toBe(false)
|
234
|
+
expect(result1['aria-busy']).toBe(false)
|
235
|
+
expect(result1.role).toBe('banner')
|
236
|
+
|
237
|
+
// Test with custom aria-label
|
238
|
+
const result2 = generateAriaAttributes('Custom Label', 'Test Title', false, false, 'banner')
|
239
|
+
expect(result2['aria-label']).toBe('Custom Label')
|
240
|
+
|
241
|
+
// Test with non-string title
|
242
|
+
const result3 = generateAriaAttributes(null, { type: 'span', children: 'Complex Title' }, false, false, 'banner')
|
243
|
+
expect(result3['aria-label']).toBe('Header')
|
244
|
+
|
245
|
+
// Test disabled state
|
246
|
+
const result4 = generateAriaAttributes(null, 'Test Title', true, false, 'banner')
|
247
|
+
expect(result4['aria-disabled']).toBe(true)
|
248
|
+
|
249
|
+
// Test loading state
|
250
|
+
const result5 = generateAriaAttributes(null, 'Test Title', false, true, 'banner')
|
251
|
+
expect(result5['aria-busy']).toBe(true)
|
252
|
+
|
253
|
+
// Test custom role
|
254
|
+
const result6 = generateAriaAttributes(null, 'Test Title', false, false, 'heading')
|
255
|
+
expect(result6.role).toBe('heading')
|
256
|
+
})
|
257
|
+
|
258
|
+
test('icon click handling works correctly', () => {
|
259
|
+
const mockOnIconClick = jest.fn()
|
260
|
+
const mockAction = jest.fn()
|
261
|
+
|
262
|
+
const simulateIconClick = (disabled, loading, onIconClick, action) => {
|
263
|
+
if (disabled || loading) return false
|
264
|
+
|
265
|
+
if (onIconClick) {
|
266
|
+
onIconClick({})
|
267
|
+
return true
|
268
|
+
} else if (action) {
|
269
|
+
action({})
|
270
|
+
return true
|
271
|
+
}
|
272
|
+
return false
|
273
|
+
}
|
274
|
+
|
275
|
+
// Normal click with onIconClick
|
276
|
+
expect(simulateIconClick(false, false, mockOnIconClick, null)).toBe(true)
|
277
|
+
expect(mockOnIconClick).toHaveBeenCalled()
|
278
|
+
|
279
|
+
mockOnIconClick.mockClear()
|
280
|
+
|
281
|
+
// Normal click with action (fallback)
|
282
|
+
expect(simulateIconClick(false, false, null, mockAction)).toBe(true)
|
283
|
+
expect(mockAction).toHaveBeenCalled()
|
284
|
+
|
285
|
+
mockAction.mockClear()
|
286
|
+
|
287
|
+
// Disabled click
|
288
|
+
expect(simulateIconClick(true, false, mockOnIconClick, null)).toBe(false)
|
289
|
+
expect(mockOnIconClick).not.toHaveBeenCalled()
|
290
|
+
|
291
|
+
// Loading click
|
292
|
+
expect(simulateIconClick(false, true, mockOnIconClick, null)).toBe(false)
|
293
|
+
expect(mockOnIconClick).not.toHaveBeenCalled()
|
294
|
+
|
295
|
+
// No handlers
|
296
|
+
expect(simulateIconClick(false, false, null, null)).toBe(false)
|
297
|
+
})
|
298
|
+
|
299
|
+
test('header click handling works correctly', () => {
|
300
|
+
const mockOnClick = jest.fn()
|
301
|
+
|
302
|
+
const simulateHeaderClick = (disabled, loading, onClick) => {
|
303
|
+
if (disabled || loading) return false
|
304
|
+
|
305
|
+
if (onClick) {
|
306
|
+
onClick({})
|
307
|
+
return true
|
308
|
+
}
|
309
|
+
return false
|
310
|
+
}
|
311
|
+
|
312
|
+
// Normal click
|
313
|
+
expect(simulateHeaderClick(false, false, mockOnClick)).toBe(true)
|
314
|
+
expect(mockOnClick).toHaveBeenCalled()
|
315
|
+
|
316
|
+
mockOnClick.mockClear()
|
317
|
+
|
318
|
+
// Disabled click
|
319
|
+
expect(simulateHeaderClick(true, false, mockOnClick)).toBe(false)
|
320
|
+
expect(mockOnClick).not.toHaveBeenCalled()
|
321
|
+
|
322
|
+
// Loading click
|
323
|
+
expect(simulateHeaderClick(false, true, mockOnClick)).toBe(false)
|
324
|
+
expect(mockOnClick).not.toHaveBeenCalled()
|
325
|
+
|
326
|
+
// No handler
|
327
|
+
expect(simulateHeaderClick(false, false, null)).toBe(false)
|
328
|
+
})
|
329
|
+
|
330
|
+
test('title element generation maintains original logic', () => {
|
331
|
+
const generateTitleElement = (title) => {
|
332
|
+
return title ? {
|
333
|
+
type: 'label',
|
334
|
+
className: 'header2-title',
|
335
|
+
children: { type: 'Text', children: title }
|
336
|
+
} : null
|
337
|
+
}
|
338
|
+
|
339
|
+
expect(generateTitleElement(null)).toBeNull()
|
340
|
+
expect(generateTitleElement('')).toBeNull()
|
341
|
+
|
342
|
+
const result = generateTitleElement('Test Title')
|
343
|
+
expect(result.type).toBe('label')
|
344
|
+
expect(result.className).toBe('header2-title')
|
345
|
+
expect(result.children.children).toBe('Test Title')
|
346
|
+
})
|
347
|
+
|
348
|
+
test('theme class logic maintains original behavior', () => {
|
349
|
+
const generateThemeClass = (primary, secondary) => {
|
350
|
+
let themeClass = primary ? 'primary' : ''
|
351
|
+
themeClass = secondary ? 'secondary' : themeClass
|
352
|
+
return themeClass
|
353
|
+
}
|
354
|
+
|
355
|
+
expect(generateThemeClass(false, false)).toBe('')
|
356
|
+
expect(generateThemeClass(true, false)).toBe('primary')
|
357
|
+
expect(generateThemeClass(false, true)).toBe('secondary')
|
358
|
+
expect(generateThemeClass(true, true)).toBe('secondary') // secondary overrides primary
|
359
|
+
})
|
360
|
+
|
361
|
+
test('maintains exact same height calculations as original', () => {
|
362
|
+
const getHeaderHeight = (caption, prominent, dense) => {
|
363
|
+
if (prominent) return '12.8rem'
|
364
|
+
if (caption) return '4rem'
|
365
|
+
if (dense) return '2.4rem'
|
366
|
+
return '3rem' // min-height for normal
|
367
|
+
}
|
368
|
+
|
369
|
+
expect(getHeaderHeight(false, false, false)).toBe('3rem')
|
370
|
+
expect(getHeaderHeight(true, false, false)).toBe('4rem')
|
371
|
+
expect(getHeaderHeight(false, true, false)).toBe('12.8rem')
|
372
|
+
expect(getHeaderHeight(false, false, true)).toBe('2.4rem')
|
373
|
+
|
374
|
+
// Prominent takes precedence
|
375
|
+
expect(getHeaderHeight(true, true, true)).toBe('12.8rem')
|
376
|
+
})
|
377
|
+
})
|
package/src/html/icon.css
CHANGED
@@ -29,15 +29,33 @@
|
|
29
29
|
}
|
30
30
|
|
31
31
|
.icon.disabled {
|
32
|
-
color: var(--text-color-lighter)
|
32
|
+
color: var(--text-color-lighter);
|
33
|
+
cursor: not-allowed;
|
34
|
+
}
|
35
|
+
|
36
|
+
.icon.clickable {
|
37
|
+
cursor: pointer;
|
33
38
|
}
|
34
39
|
|
35
40
|
.icon.clickable:hover {
|
36
41
|
background-color: rgba(100,100,100,.1);
|
37
|
-
cursor: pointer;
|
38
42
|
border-radius: 100%;
|
39
43
|
}
|
40
44
|
|
45
|
+
.icon.clickable:focus {
|
46
|
+
outline: 2px solid var(--primary-color);
|
47
|
+
outline-offset: 2px;
|
48
|
+
border-radius: 100%;
|
49
|
+
}
|
50
|
+
|
51
|
+
.icon.clickable.disabled {
|
52
|
+
cursor: not-allowed;
|
53
|
+
}
|
54
|
+
|
55
|
+
.icon.clickable.disabled:hover {
|
56
|
+
background-color: transparent;
|
57
|
+
}
|
58
|
+
|
41
59
|
.icon.small {
|
42
60
|
width: 2rem;
|
43
61
|
height: 2rem;
|