ywana-core8 0.1.75 → 0.1.77

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 (122) 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 +10893 -1969
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.css +7768 -1096
  23. package/dist/index.css.map +1 -1
  24. package/dist/index.modern.js +10921 -2005
  25. package/dist/index.modern.js.map +1 -1
  26. package/dist/index.umd.js +10893 -1969
  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 +4 -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 +289 -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/textfield2.css +841 -0
  106. package/src/html/textfield2.example.js +1370 -0
  107. package/src/html/textfield2.js +1143 -0
  108. package/src/html/textfield2.test.js +950 -0
  109. package/src/html/thumbnail.css +289 -2
  110. package/src/html/thumbnail.js +214 -9
  111. package/src/html/tokenfield.css +449 -1
  112. package/src/html/tokenfield.example.js +503 -0
  113. package/src/html/tokenfield.js +561 -56
  114. package/src/html/tokenfield.test.js +423 -0
  115. package/src/html/tooltip-positioning-demo.js +187 -0
  116. package/src/html/tooltip.css +25 -2
  117. package/src/html/tree.css +240 -10
  118. package/src/html/tree.example.js +475 -0
  119. package/src/html/tree.js +714 -28
  120. package/src/html/tree_enhanced.test.js +495 -0
  121. package/table2.test.js +454 -0
  122. package/src/html/button.tsx +0 -38
@@ -0,0 +1,423 @@
1
+ import React from 'react'
2
+ import { TokenField } from './tokenfield'
3
+
4
+ // Pruebas unitarias para el componente TokenField mejorado
5
+ describe('Enhanced TokenField Component', () => {
6
+ // Mock de los componentes dependientes
7
+ const mockIcon = jest.fn()
8
+ const mockText = jest.fn()
9
+ const mockDropDown = jest.fn()
10
+
11
+ beforeEach(() => {
12
+ jest.clearAllMocks()
13
+
14
+ // Mock de componentes
15
+ jest.doMock('./icon', () => ({ Icon: mockIcon }))
16
+ jest.doMock('./text', () => ({ Text: mockText }))
17
+ jest.doMock('./textfield', () => ({ DropDown: mockDropDown }))
18
+
19
+ // Mock de console.warn
20
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
21
+ })
22
+
23
+ afterEach(() => {
24
+ console.warn.mockRestore()
25
+ })
26
+
27
+ // TokenField Component Tests
28
+ describe('TokenField Component', () => {
29
+ test('component exports correctly', () => {
30
+ expect(TokenField).toBeDefined()
31
+ expect(typeof TokenField).toBe('function')
32
+ })
33
+
34
+ test('component has correct PropTypes', () => {
35
+ expect(TokenField.propTypes).toBeDefined()
36
+ expect(TokenField.propTypes.id).toBeDefined()
37
+ expect(TokenField.propTypes.label).toBeDefined()
38
+ expect(TokenField.propTypes.tokens).toBeDefined()
39
+ expect(TokenField.propTypes.readOnly).toBeDefined()
40
+ expect(TokenField.propTypes.options).toBeDefined()
41
+ expect(TokenField.propTypes.predictive).toBeDefined()
42
+ expect(TokenField.propTypes.onChange).toBeDefined()
43
+ expect(TokenField.propTypes.disabled).toBeDefined()
44
+ expect(TokenField.propTypes.maxTokens).toBeDefined()
45
+ expect(TokenField.propTypes.minTokens).toBeDefined()
46
+ expect(TokenField.propTypes.allowDuplicates).toBeDefined()
47
+ expect(TokenField.propTypes.validateToken).toBeDefined()
48
+ expect(TokenField.propTypes.tokenSeparators).toBeDefined()
49
+ })
50
+
51
+ test('component has correct defaultProps', () => {
52
+ expect(TokenField.defaultProps).toBeDefined()
53
+ expect(TokenField.defaultProps.tokens).toEqual([])
54
+ expect(TokenField.defaultProps.predictive).toBe(true)
55
+ expect(TokenField.defaultProps.disabled).toBe(false)
56
+ expect(TokenField.defaultProps.required).toBe(false)
57
+ expect(TokenField.defaultProps.placeholder).toBe("Add token...")
58
+ expect(TokenField.defaultProps.minTokens).toBe(0)
59
+ expect(TokenField.defaultProps.allowDuplicates).toBe(true)
60
+ expect(TokenField.defaultProps.tokenSeparators).toEqual([',', ';', '\n'])
61
+ expect(TokenField.defaultProps.size).toBe('medium')
62
+ expect(TokenField.defaultProps.variant).toBe('default')
63
+ })
64
+
65
+ test('validates props correctly', () => {
66
+ const validateProps = (tokens, maxTokens, minTokens) => {
67
+ if (!Array.isArray(tokens)) {
68
+ console.warn('TokenField: tokens prop must be an array')
69
+ }
70
+ if (maxTokens && tokens.length > maxTokens) {
71
+ console.warn(`TokenField: tokens count (${tokens.length}) exceeds maxTokens (${maxTokens})`)
72
+ }
73
+ if (minTokens && tokens.length < minTokens) {
74
+ console.warn(`TokenField: tokens count (${tokens.length}) is below minTokens (${minTokens})`)
75
+ }
76
+ }
77
+
78
+ // Invalid tokens prop
79
+ validateProps('not an array', null, 0)
80
+ expect(console.warn).toHaveBeenCalledWith('TokenField: tokens prop must be an array')
81
+
82
+ console.warn.mockClear()
83
+
84
+ // Exceeds max tokens
85
+ validateProps(['token1', 'token2', 'token3'], 2, 0)
86
+ expect(console.warn).toHaveBeenCalledWith('TokenField: tokens count (3) exceeds maxTokens (2)')
87
+
88
+ console.warn.mockClear()
89
+
90
+ // Below min tokens
91
+ validateProps(['token1'], null, 2)
92
+ expect(console.warn).toHaveBeenCalledWith('TokenField: tokens count (1) is below minTokens (2)')
93
+
94
+ console.warn.mockClear()
95
+
96
+ // Valid props
97
+ validateProps(['token1', 'token2'], 3, 1)
98
+ expect(console.warn).not.toHaveBeenCalled()
99
+ })
100
+
101
+ test('validates token correctly', () => {
102
+ const validateTokenValue = (tokenValue, allowDuplicates, tokens, maxTokens, validateToken) => {
103
+ if (!tokenValue || (typeof tokenValue === 'string' && tokenValue.trim() === '')) {
104
+ return { isValid: false, error: 'Token cannot be empty' }
105
+ }
106
+
107
+ // Check duplicates
108
+ if (!allowDuplicates && tokens.includes(tokenValue)) {
109
+ return { isValid: false, error: 'Duplicate token not allowed' }
110
+ }
111
+
112
+ // Check max tokens
113
+ if (maxTokens && tokens.length >= maxTokens) {
114
+ return { isValid: false, error: `Maximum ${maxTokens} tokens allowed` }
115
+ }
116
+
117
+ // Custom validation
118
+ if (validateToken) {
119
+ const customValidation = validateToken(tokenValue, tokens)
120
+ if (customValidation && !customValidation.isValid) {
121
+ return customValidation
122
+ }
123
+ }
124
+
125
+ return { isValid: true }
126
+ }
127
+
128
+ // Empty token
129
+ expect(validateTokenValue('', true, [], null, null))
130
+ .toEqual({ isValid: false, error: 'Token cannot be empty' })
131
+
132
+ // Duplicate token
133
+ expect(validateTokenValue('token1', false, ['token1'], null, null))
134
+ .toEqual({ isValid: false, error: 'Duplicate token not allowed' })
135
+
136
+ // Max tokens reached
137
+ expect(validateTokenValue('token3', true, ['token1', 'token2'], 2, null))
138
+ .toEqual({ isValid: false, error: 'Maximum 2 tokens allowed' })
139
+
140
+ // Custom validation failure
141
+ const customValidator = (token) => ({ isValid: false, error: 'Custom error' })
142
+ expect(validateTokenValue('token1', true, [], null, customValidator))
143
+ .toEqual({ isValid: false, error: 'Custom error' })
144
+
145
+ // Valid token
146
+ expect(validateTokenValue('token1', true, [], null, null))
147
+ .toEqual({ isValid: true })
148
+ })
149
+
150
+ test('handles remove correctly', () => {
151
+ const mockOnChange = jest.fn()
152
+ const mockOnTokenRemove = jest.fn()
153
+
154
+ const remove = (index, disabled, readOnly, tokens, minTokens, id, onChange, onTokenRemove) => {
155
+ if (disabled || readOnly) return
156
+
157
+ const next = tokens.slice()
158
+ const removedToken = next.splice(index, 1)[0]
159
+
160
+ // Check minimum tokens
161
+ if (minTokens && next.length < minTokens) {
162
+ console.warn(`TokenField: Cannot remove token. Minimum ${minTokens} tokens required.`)
163
+ return
164
+ }
165
+
166
+ if (onChange) onChange(id, next)
167
+ if (onTokenRemove) onTokenRemove(removedToken, index)
168
+ }
169
+
170
+ const tokens = ['token1', 'token2', 'token3']
171
+
172
+ // Normal remove
173
+ remove(1, false, false, tokens, 0, 'field1', mockOnChange, mockOnTokenRemove)
174
+ expect(mockOnChange).toHaveBeenCalledWith('field1', ['token1', 'token3'])
175
+ expect(mockOnTokenRemove).toHaveBeenCalledWith('token2', 1)
176
+
177
+ mockOnChange.mockClear()
178
+ mockOnTokenRemove.mockClear()
179
+
180
+ // Disabled remove
181
+ remove(1, true, false, tokens, 0, 'field1', mockOnChange, mockOnTokenRemove)
182
+ expect(mockOnChange).not.toHaveBeenCalled()
183
+
184
+ // ReadOnly remove
185
+ remove(1, false, true, tokens, 0, 'field1', mockOnChange, mockOnTokenRemove)
186
+ expect(mockOnChange).not.toHaveBeenCalled()
187
+
188
+ // Min tokens prevent remove
189
+ remove(0, false, false, ['token1'], 1, 'field1', mockOnChange, mockOnTokenRemove)
190
+ expect(console.warn).toHaveBeenCalledWith('TokenField: Cannot remove token. Minimum 1 tokens required.')
191
+ expect(mockOnChange).not.toHaveBeenCalled()
192
+ })
193
+
194
+ test('handles add token correctly', () => {
195
+ const mockOnChange = jest.fn()
196
+ const mockOnTokenAdd = jest.fn()
197
+ const mockOnValidationError = jest.fn()
198
+
199
+ const addToken = (tokenValue, disabled, readOnly, tokens, maxTokens, allowDuplicates, validateToken, id, onChange, onTokenAdd, onValidationError) => {
200
+ if (disabled || readOnly) return false
201
+
202
+ // Validation logic
203
+ if (!tokenValue || tokenValue.trim() === '') {
204
+ const error = 'Token cannot be empty'
205
+ if (onValidationError) onValidationError(error, tokenValue)
206
+ return false
207
+ }
208
+
209
+ if (!allowDuplicates && tokens.includes(tokenValue)) {
210
+ const error = 'Duplicate token not allowed'
211
+ if (onValidationError) onValidationError(error, tokenValue)
212
+ return false
213
+ }
214
+
215
+ if (maxTokens && tokens.length >= maxTokens) {
216
+ const error = `Maximum ${maxTokens} tokens allowed`
217
+ if (onValidationError) onValidationError(error, tokenValue)
218
+ return false
219
+ }
220
+
221
+ const next = Array.isArray(tokens) ? tokens.concat(tokenValue) : [tokenValue]
222
+ if (onChange) onChange(id, next)
223
+ if (onTokenAdd) onTokenAdd(tokenValue, tokens.length)
224
+
225
+ return true
226
+ }
227
+
228
+ // Normal add
229
+ expect(addToken('newToken', false, false, ['token1'], null, true, null, 'field1', mockOnChange, mockOnTokenAdd, mockOnValidationError))
230
+ .toBe(true)
231
+ expect(mockOnChange).toHaveBeenCalledWith('field1', ['token1', 'newToken'])
232
+ expect(mockOnTokenAdd).toHaveBeenCalledWith('newToken', 1)
233
+
234
+ mockOnChange.mockClear()
235
+ mockOnTokenAdd.mockClear()
236
+
237
+ // Disabled add
238
+ expect(addToken('newToken', true, false, ['token1'], null, true, null, 'field1', mockOnChange, mockOnTokenAdd, mockOnValidationError))
239
+ .toBe(false)
240
+ expect(mockOnChange).not.toHaveBeenCalled()
241
+
242
+ // Duplicate add
243
+ expect(addToken('token1', false, false, ['token1'], null, false, null, 'field1', mockOnChange, mockOnTokenAdd, mockOnValidationError))
244
+ .toBe(false)
245
+ expect(mockOnValidationError).toHaveBeenCalledWith('Duplicate token not allowed', 'token1')
246
+
247
+ // Max tokens reached
248
+ expect(addToken('newToken', false, false, ['token1', 'token2'], 2, true, null, 'field1', mockOnChange, mockOnTokenAdd, mockOnValidationError))
249
+ .toBe(false)
250
+ expect(mockOnValidationError).toHaveBeenCalledWith('Maximum 2 tokens allowed', 'newToken')
251
+ })
252
+
253
+ test('handles keyboard events correctly', () => {
254
+ const mockAddToken = jest.fn().mockReturnValue(true)
255
+ const mockRemove = jest.fn()
256
+ const mockSetValue = jest.fn()
257
+
258
+ const onEnter = (event, disabled, readOnly, tokenSeparators, value, tokens, addToken, remove, setValue) => {
259
+ if (disabled || readOnly) return
260
+
261
+ // Handle separators
262
+ if (tokenSeparators.includes(event.key) || event.key === 'Enter') {
263
+ event.preventDefault()
264
+ event.stopPropagation()
265
+
266
+ const token = event.target.value.trim()
267
+ if (token && token.length > 0) {
268
+ if (addToken(token)) {
269
+ setValue('')
270
+ }
271
+ }
272
+ return
273
+ }
274
+
275
+ // Handle backspace to remove last token
276
+ if (value === '' && tokens.length > 0 && event.key === 'Backspace') {
277
+ event.preventDefault()
278
+ remove(tokens.length - 1)
279
+ return
280
+ }
281
+ }
282
+
283
+ const mockEvent = {
284
+ preventDefault: jest.fn(),
285
+ stopPropagation: jest.fn(),
286
+ target: { value: 'newToken' },
287
+ key: 'Enter'
288
+ }
289
+
290
+ // Enter key
291
+ onEnter(mockEvent, false, false, [',', ';', '\n'], 'currentValue', ['token1'], mockAddToken, mockRemove, mockSetValue)
292
+ expect(mockEvent.preventDefault).toHaveBeenCalled()
293
+ expect(mockEvent.stopPropagation).toHaveBeenCalled()
294
+ expect(mockAddToken).toHaveBeenCalledWith('newToken')
295
+ expect(mockSetValue).toHaveBeenCalledWith('')
296
+
297
+ mockAddToken.mockClear()
298
+ mockEvent.preventDefault.mockClear()
299
+
300
+ // Backspace with empty value
301
+ const backspaceEvent = { ...mockEvent, key: 'Backspace', target: { value: '' } }
302
+ onEnter(backspaceEvent, false, false, [',', ';', '\n'], '', ['token1'], mockAddToken, mockRemove, mockSetValue)
303
+ expect(backspaceEvent.preventDefault).toHaveBeenCalled()
304
+ expect(mockRemove).toHaveBeenCalledWith(0)
305
+
306
+ // Disabled state
307
+ onEnter(mockEvent, true, false, [',', ';', '\n'], 'currentValue', ['token1'], mockAddToken, mockRemove, mockSetValue)
308
+ expect(mockAddToken).not.toHaveBeenCalled()
309
+ })
310
+
311
+ test('filters options correctly', () => {
312
+ const getFilteredOptions = (options, tokens, searchable, value, sortable) => {
313
+ if (!options) return []
314
+
315
+ const tks = tokens || []
316
+ const usedValues = tks.map(token => token)
317
+ let filteredOptions = options.filter(option => !usedValues.includes(option.value))
318
+
319
+ if (searchable && value) {
320
+ filteredOptions = filteredOptions.filter(option =>
321
+ option.label?.toLowerCase().includes(value.toLowerCase()) ||
322
+ option.value?.toString().toLowerCase().includes(value.toLowerCase())
323
+ )
324
+ }
325
+
326
+ if (sortable) {
327
+ filteredOptions.sort((a, b) => {
328
+ if (!a.label || !b.label) return 0
329
+ try {
330
+ return a.label.localeCompare(b.label)
331
+ } catch (error) {
332
+ return 0
333
+ }
334
+ })
335
+ }
336
+
337
+ return filteredOptions
338
+ }
339
+
340
+ const options = [
341
+ { label: 'Apple', value: 'apple' },
342
+ { label: 'Banana', value: 'banana' },
343
+ { label: 'Cherry', value: 'cherry' }
344
+ ]
345
+
346
+ // Filter used tokens
347
+ expect(getFilteredOptions(options, ['apple'], false, '', false))
348
+ .toEqual([
349
+ { label: 'Banana', value: 'banana' },
350
+ { label: 'Cherry', value: 'cherry' }
351
+ ])
352
+
353
+ // Search filter
354
+ expect(getFilteredOptions(options, [], true, 'app', false))
355
+ .toEqual([{ label: 'Apple', value: 'apple' }])
356
+
357
+ // Sort options
358
+ const unsortedOptions = [
359
+ { label: 'Zebra', value: 'zebra' },
360
+ { label: 'Apple', value: 'apple' }
361
+ ]
362
+ expect(getFilteredOptions(unsortedOptions, [], false, '', true))
363
+ .toEqual([
364
+ { label: 'Apple', value: 'apple' },
365
+ { label: 'Zebra', value: 'zebra' }
366
+ ])
367
+ })
368
+
369
+ test('generates CSS classes correctly', () => {
370
+ const generateClasses = (size, variant, disabled, readOnly, required, isFocused, error, className) => {
371
+ return [
372
+ 'tokenField',
373
+ `tokenField--${size}`,
374
+ `tokenField--${variant}`,
375
+ disabled && 'tokenField--disabled',
376
+ readOnly && 'tokenField--readonly',
377
+ required && 'tokenField--required',
378
+ isFocused && 'tokenField--focused',
379
+ error && 'tokenField--error',
380
+ className
381
+ ].filter(Boolean).join(' ')
382
+ }
383
+
384
+ expect(generateClasses('medium', 'default', false, false, false, false, null, ''))
385
+ .toBe('tokenField tokenField--medium tokenField--default')
386
+
387
+ expect(generateClasses('large', 'outlined', true, true, true, true, 'Error message', 'custom'))
388
+ .toBe('tokenField tokenField--large tokenField--outlined tokenField--disabled tokenField--readonly tokenField--required tokenField--focused tokenField--error custom')
389
+ })
390
+
391
+ test('handles clear all correctly', () => {
392
+ const mockOnChange = jest.fn()
393
+ const mockOnClear = jest.fn()
394
+ const mockSetValue = jest.fn()
395
+
396
+ const handleClear = (disabled, readOnly, minTokens, id, onChange, onClear, setValue) => {
397
+ if (disabled || readOnly || minTokens > 0) return
398
+
399
+ if (onChange) onChange(id, [])
400
+ if (onClear) onClear()
401
+ setValue('')
402
+ }
403
+
404
+ // Normal clear
405
+ handleClear(false, false, 0, 'field1', mockOnChange, mockOnClear, mockSetValue)
406
+ expect(mockOnChange).toHaveBeenCalledWith('field1', [])
407
+ expect(mockOnClear).toHaveBeenCalled()
408
+ expect(mockSetValue).toHaveBeenCalledWith('')
409
+
410
+ mockOnChange.mockClear()
411
+ mockOnClear.mockClear()
412
+ mockSetValue.mockClear()
413
+
414
+ // Disabled clear
415
+ handleClear(true, false, 0, 'field1', mockOnChange, mockOnClear, mockSetValue)
416
+ expect(mockOnChange).not.toHaveBeenCalled()
417
+
418
+ // Min tokens prevent clear
419
+ handleClear(false, false, 1, 'field1', mockOnChange, mockOnClear, mockSetValue)
420
+ expect(mockOnChange).not.toHaveBeenCalled()
421
+ })
422
+ })
423
+ })
@@ -0,0 +1,187 @@
1
+ import React from 'react'
2
+ import { Section } from './section'
3
+ import { Icon } from './icon'
4
+ import { Button } from './button'
5
+
6
+ /**
7
+ * Demostración de la corrección del posicionamiento de tooltips
8
+ */
9
+ export const TooltipPositioningDemo = () => {
10
+ return (
11
+ <div style={{ padding: '2rem', maxWidth: '800px' }}>
12
+ <h1>Corrección del Posicionamiento de Tooltips</h1>
13
+
14
+ <div style={{
15
+ background: '#f8f9fa',
16
+ padding: '1rem',
17
+ borderRadius: '8px',
18
+ marginBottom: '2rem',
19
+ border: '1px solid #e9ecef'
20
+ }}>
21
+ <h3>🐛 Problema Identificado:</h3>
22
+ <p>Los tooltips se posicionaban encima del icono, dificultando hacer clic en él.</p>
23
+
24
+ <h3>✅ Solución Implementada:</h3>
25
+ <ul>
26
+ <li><strong>Posicionamiento mejorado:</strong> <code>top: '-3rem', left: '-2rem'</code></li>
27
+ <li><strong>pointer-events: none</strong> en el tooltip para evitar interferencia</li>
28
+ <li><strong>z-index: 1000</strong> para asegurar visibilidad</li>
29
+ <li><strong>Animación suave</strong> con delay para evitar tooltips accidentales</li>
30
+ <li><strong>Tamaño compacto</strong> y sombra sutil para mejor UX</li>
31
+ </ul>
32
+ </div>
33
+
34
+ {/* Ejemplo de sección con tooltip corregido */}
35
+ <Section
36
+ title="Sección con Tooltip Corregido"
37
+ icon="bug_report"
38
+ open={false}
39
+ >
40
+ <div style={{ padding: '1rem' }}>
41
+ <p>✅ <strong>Tooltip posicionado correctamente</strong></p>
42
+ <p>Ahora el tooltip aparece arriba y a la izquierda del icono, sin interferir con los clics.</p>
43
+
44
+ <div style={{
45
+ background: '#e8f5e8',
46
+ padding: '1rem',
47
+ borderRadius: '4px',
48
+ margin: '1rem 0'
49
+ }}>
50
+ <strong>Prueba la funcionalidad:</strong>
51
+ <ol>
52
+ <li>Haz hover sobre el icono de expansión/contracción</li>
53
+ <li>Observa que el tooltip aparece sin tapar el icono</li>
54
+ <li>Haz clic en el icono - debería funcionar sin problemas</li>
55
+ <li>El tooltip desaparece al hacer clic</li>
56
+ </ol>
57
+ </div>
58
+ </div>
59
+ </Section>
60
+
61
+ {/* Comparación con otros iconos */}
62
+ <Section
63
+ title="Comparación con Otros Iconos"
64
+ icon="compare"
65
+ open={true}
66
+ >
67
+ <div style={{ padding: '1rem' }}>
68
+ <p>Otros iconos en el sistema también se benefician de estas mejoras:</p>
69
+
70
+ <div style={{
71
+ display: 'flex',
72
+ gap: '1rem',
73
+ alignItems: 'center',
74
+ margin: '1rem 0'
75
+ }}>
76
+ <Icon
77
+ icon="info"
78
+ clickable
79
+ tooltip={{
80
+ text: 'Información',
81
+ top: '-3rem',
82
+ left: '-1rem'
83
+ }}
84
+ />
85
+ <Icon
86
+ icon="settings"
87
+ clickable
88
+ tooltip={{
89
+ text: 'Configuración',
90
+ top: '-3rem',
91
+ left: '-2rem'
92
+ }}
93
+ />
94
+ <Icon
95
+ icon="help"
96
+ clickable
97
+ tooltip={{
98
+ text: 'Ayuda',
99
+ top: '-3rem',
100
+ left: '-1rem'
101
+ }}
102
+ />
103
+ <Button
104
+ label="Botón con Tooltip"
105
+ icon="star"
106
+ outlined
107
+ />
108
+ </div>
109
+ </div>
110
+ </Section>
111
+
112
+ {/* Mejoras técnicas */}
113
+ <Section
114
+ title="Mejoras Técnicas Implementadas"
115
+ icon="engineering"
116
+ open={true}
117
+ >
118
+ <div style={{ padding: '1rem' }}>
119
+ <h4>CSS Mejorado:</h4>
120
+ <pre style={{
121
+ background: '#f8f9fa',
122
+ padding: '1rem',
123
+ borderRadius: '4px',
124
+ fontSize: '0.9rem',
125
+ overflow: 'auto'
126
+ }}>
127
+ {`.tooltip-text {
128
+ pointer-events: none; /* No interfiere con clics */
129
+ z-index: 1000; /* Siempre visible */
130
+ white-space: nowrap; /* Texto en una línea */
131
+ font-size: 0.8rem; /* Tamaño compacto */
132
+ }
133
+
134
+ .tooltip:hover .tooltip-text {
135
+ animation: tooltip-fade-in 0.2s ease-in-out;
136
+ animation-delay: 0.5s; /* Evita tooltips accidentales */
137
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
138
+ }`}
139
+ </pre>
140
+
141
+ <h4>Posicionamiento Específico:</h4>
142
+ <pre style={{
143
+ background: '#f8f9fa',
144
+ padding: '1rem',
145
+ borderRadius: '4px',
146
+ fontSize: '0.9rem'
147
+ }}>
148
+ {`tooltip={{
149
+ text: isOpen ? 'Contraer sección' : 'Expandir sección',
150
+ top: '-3rem', // Arriba del icono
151
+ left: '-2rem' // Ligeramente a la izquierda
152
+ }}`}
153
+ </pre>
154
+ </div>
155
+ </Section>
156
+
157
+ {/* Pruebas actualizadas */}
158
+ <Section
159
+ title="Pruebas Actualizadas"
160
+ icon="verified"
161
+ open={false}
162
+ >
163
+ <div style={{ padding: '1rem' }}>
164
+ <p>Las pruebas unitarias se actualizaron para verificar:</p>
165
+ <ul>
166
+ <li>✅ Texto del tooltip correcto según el estado</li>
167
+ <li>✅ Posicionamiento que evita superposición</li>
168
+ <li>✅ Configuración completa del tooltip</li>
169
+ </ul>
170
+
171
+ <p>Ejecuta las pruebas con:</p>
172
+ <code style={{
173
+ background: '#f8f9fa',
174
+ padding: '0.5rem',
175
+ borderRadius: '4px',
176
+ display: 'block',
177
+ margin: '0.5rem 0'
178
+ }}>
179
+ npm test -- --testPathPattern=section.test.js --watchAll=false
180
+ </code>
181
+ </div>
182
+ </Section>
183
+ </div>
184
+ )
185
+ }
186
+
187
+ export default TooltipPositioningDemo
@@ -5,7 +5,10 @@
5
5
 
6
6
  .tooltip-text {
7
7
  display: none;
8
- z-index: 10;
8
+ z-index: 1000;
9
+ pointer-events: none; /* Evita que el tooltip interfiera con los clics */
10
+ white-space: nowrap; /* Evita que el texto se rompa en múltiples líneas */
11
+ font-size: 0.8rem; /* Tamaño de fuente más pequeño */
9
12
  }
10
13
 
11
14
  .tooltip:hover .tooltip-text {
@@ -14,6 +17,26 @@
14
17
  border: solid 1px var(--divider-color);
15
18
  background-color: var(--text-color-light);
16
19
  color: var(--paper-color);
17
- padding: .5rem;
20
+ padding: .4rem .6rem; /* Padding más compacto */
18
21
  border-radius: 4px;
22
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); /* Sombra sutil */
23
+ animation: tooltip-fade-in 0.2s ease-in-out; /* Animación suave */
24
+ }
25
+
26
+ /* Animación de aparición del tooltip */
27
+ @keyframes tooltip-fade-in {
28
+ from {
29
+ opacity: 0;
30
+ transform: translateY(4px);
31
+ }
32
+ to {
33
+ opacity: 1;
34
+ transform: translateY(0);
35
+ }
36
+ }
37
+
38
+ /* Delay para evitar tooltips accidentales */
39
+ .tooltip:hover .tooltip-text {
40
+ animation-delay: 0.5s;
41
+ animation-fill-mode: both;
19
42
  }