@open-mercato/ui 0.6.8-develop.6998.1.d2bc46c56c → 0.6.8-develop.7003.1.24fef49d48
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/.turbo/turbo-build.log +1 -1
- package/dist/backend/CrudForm.js +30 -19
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.generated.js +16 -2
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/inputs/ComboboxInput.js +34 -27
- package/dist/backend/inputs/ComboboxInput.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/CrudForm.tsx +41 -31
- package/src/backend/icons/lucideRegistry.generated.tsx +14 -0
- package/src/backend/inputs/ComboboxInput.tsx +11 -4
- package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +21 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { LucideIcon } from 'lucide-react';
|
|
6
6
|
import {
|
|
7
7
|
Activity,
|
|
8
|
+
AlarmClock,
|
|
8
9
|
AlertCircle,
|
|
9
10
|
AlertOctagon,
|
|
10
11
|
AlertTriangle,
|
|
@@ -44,6 +45,7 @@ import {
|
|
|
44
45
|
Clock3,
|
|
45
46
|
Coins,
|
|
46
47
|
Copy,
|
|
48
|
+
CopyX,
|
|
47
49
|
CreditCard,
|
|
48
50
|
Crown,
|
|
49
51
|
Database,
|
|
@@ -67,6 +69,7 @@ import {
|
|
|
67
69
|
GitPullRequestArrow,
|
|
68
70
|
Globe,
|
|
69
71
|
GraduationCap,
|
|
72
|
+
Hammer,
|
|
70
73
|
Hand,
|
|
71
74
|
Handshake,
|
|
72
75
|
Headphones,
|
|
@@ -82,6 +85,7 @@ import {
|
|
|
82
85
|
LineChart,
|
|
83
86
|
Link,
|
|
84
87
|
List,
|
|
88
|
+
ListX,
|
|
85
89
|
Loader,
|
|
86
90
|
Loader2,
|
|
87
91
|
Lock,
|
|
@@ -92,6 +96,7 @@ import {
|
|
|
92
96
|
MapPinned,
|
|
93
97
|
Megaphone,
|
|
94
98
|
MessageCircle,
|
|
99
|
+
MessageSquareReply,
|
|
95
100
|
MinusCircle,
|
|
96
101
|
Notebook,
|
|
97
102
|
Package,
|
|
@@ -109,6 +114,7 @@ import {
|
|
|
109
114
|
ReceiptText,
|
|
110
115
|
RefreshCcw,
|
|
111
116
|
RefreshCw,
|
|
117
|
+
Repeat2,
|
|
112
118
|
Reply,
|
|
113
119
|
RotateCcw,
|
|
114
120
|
Ruler,
|
|
@@ -156,10 +162,12 @@ import {
|
|
|
156
162
|
Wrench,
|
|
157
163
|
X,
|
|
158
164
|
XCircle,
|
|
165
|
+
ZapOff,
|
|
159
166
|
} from 'lucide-react';
|
|
160
167
|
|
|
161
168
|
export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
162
169
|
'activity': Activity,
|
|
170
|
+
'alarm-clock': AlarmClock,
|
|
163
171
|
'alert-circle': AlertCircle,
|
|
164
172
|
'alert-octagon': AlertOctagon,
|
|
165
173
|
'alert-triangle': AlertTriangle,
|
|
@@ -199,6 +207,7 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
199
207
|
'clock-3': Clock3,
|
|
200
208
|
'coins': Coins,
|
|
201
209
|
'copy': Copy,
|
|
210
|
+
'copy-x': CopyX,
|
|
202
211
|
'credit-card': CreditCard,
|
|
203
212
|
'crown': Crown,
|
|
204
213
|
'database': Database,
|
|
@@ -222,6 +231,7 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
222
231
|
'git-pull-request-arrow': GitPullRequestArrow,
|
|
223
232
|
'globe': Globe,
|
|
224
233
|
'graduation-cap': GraduationCap,
|
|
234
|
+
'hammer': Hammer,
|
|
225
235
|
'hand': Hand,
|
|
226
236
|
'handshake': Handshake,
|
|
227
237
|
'headphones': Headphones,
|
|
@@ -237,6 +247,7 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
237
247
|
'line-chart': LineChart,
|
|
238
248
|
'link': Link,
|
|
239
249
|
'list': List,
|
|
250
|
+
'list-x': ListX,
|
|
240
251
|
'loader': Loader,
|
|
241
252
|
'loader-2': Loader2,
|
|
242
253
|
'lock': Lock,
|
|
@@ -247,6 +258,7 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
247
258
|
'map-pinned': MapPinned,
|
|
248
259
|
'megaphone': Megaphone,
|
|
249
260
|
'message-circle': MessageCircle,
|
|
261
|
+
'message-square-reply': MessageSquareReply,
|
|
250
262
|
'minus-circle': MinusCircle,
|
|
251
263
|
'notebook': Notebook,
|
|
252
264
|
'package': Package,
|
|
@@ -264,6 +276,7 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
264
276
|
'receipt-text': ReceiptText,
|
|
265
277
|
'refresh-ccw': RefreshCcw,
|
|
266
278
|
'refresh-cw': RefreshCw,
|
|
279
|
+
'repeat-2': Repeat2,
|
|
267
280
|
'reply': Reply,
|
|
268
281
|
'rotate-ccw': RotateCcw,
|
|
269
282
|
'ruler': Ruler,
|
|
@@ -311,4 +324,5 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
311
324
|
'wrench': Wrench,
|
|
312
325
|
'x': X,
|
|
313
326
|
'x-circle': XCircle,
|
|
327
|
+
'zap-off': ZapOff,
|
|
314
328
|
};
|
|
@@ -82,6 +82,7 @@ export function ComboboxInput({
|
|
|
82
82
|
const t = useT()
|
|
83
83
|
const resolvedPlaceholder = placeholder ?? t('ui.inputs.comboboxInput.placeholder', 'Type to search...')
|
|
84
84
|
const loadingLabel = t('ui.inputs.comboboxInput.loading', 'Loading suggestions…')
|
|
85
|
+
const noMatchesLabel = t('ui.inputs.comboboxInput.noMatches', 'No matches found')
|
|
85
86
|
const resolvedClearLabel = clearLabel ?? t('ui.inputs.comboboxInput.clear', 'Clear value')
|
|
86
87
|
const blurCloseDelayMs = 250
|
|
87
88
|
const blurCloseMaxDelayMs = 1000
|
|
@@ -392,7 +393,9 @@ export function ComboboxInput({
|
|
|
392
393
|
}, [optionDomId, selectedIndex, showSuggestions])
|
|
393
394
|
|
|
394
395
|
const showClearButton = clearable && !disabled && (value !== '' || input !== '')
|
|
395
|
-
const listboxVisible = showSuggestions
|
|
396
|
+
const listboxVisible = showSuggestions
|
|
397
|
+
&& !disabled
|
|
398
|
+
&& (loading || filteredSuggestions.length > 0 || (touched && input.trim().length > 0))
|
|
396
399
|
|
|
397
400
|
return (
|
|
398
401
|
<div className="relative w-full">
|
|
@@ -416,7 +419,7 @@ export function ComboboxInput({
|
|
|
416
419
|
disabled={disabled}
|
|
417
420
|
role="combobox"
|
|
418
421
|
aria-expanded={listboxVisible}
|
|
419
|
-
aria-controls={listboxId}
|
|
422
|
+
aria-controls={listboxVisible && !loading && filteredSuggestions.length > 0 ? listboxId : undefined}
|
|
420
423
|
aria-autocomplete="list"
|
|
421
424
|
aria-activedescendant={listboxVisible && selectedIndex >= 0 ? optionDomId(selectedIndex) : undefined}
|
|
422
425
|
onFocus={() => {
|
|
@@ -467,9 +470,13 @@ export function ComboboxInput({
|
|
|
467
470
|
) : null}
|
|
468
471
|
|
|
469
472
|
{listboxVisible && (
|
|
470
|
-
<div
|
|
473
|
+
<div
|
|
474
|
+
className="absolute z-popover w-full mt-1 rounded-md border border-input bg-popover p-2 shadow-md max-h-48 sm:max-h-60 overflow-auto"
|
|
475
|
+
>
|
|
471
476
|
{loading && touched ? (
|
|
472
|
-
<div className="px-2 py-1.5 text-xs text-muted-foreground">{loadingLabel}</div>
|
|
477
|
+
<div className="px-2 py-1.5 text-xs text-muted-foreground" role="status">{loadingLabel}</div>
|
|
478
|
+
) : touched && !filteredSuggestions.length ? (
|
|
479
|
+
<div className="px-2 py-1.5 text-xs text-muted-foreground" role="status">{noMatchesLabel}</div>
|
|
473
480
|
) : (
|
|
474
481
|
<div id={listboxId} role="listbox" className="flex flex-col gap-1">
|
|
475
482
|
{filteredSuggestions.map((option, index) => (
|
|
@@ -5,7 +5,7 @@ jest.mock('@open-mercato/shared/lib/i18n/context', () => ({
|
|
|
5
5
|
}))
|
|
6
6
|
|
|
7
7
|
import * as React from 'react'
|
|
8
|
-
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
8
|
+
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
|
9
9
|
import { ComboboxInput } from '../ComboboxInput'
|
|
10
10
|
|
|
11
11
|
type HarnessProps = Partial<React.ComponentProps<typeof ComboboxInput>> & {
|
|
@@ -397,3 +397,23 @@ describe('ComboboxInput — eager label resolution', () => {
|
|
|
397
397
|
await waitFor(() => expect(getInput(container).value).toBe('Label-b'))
|
|
398
398
|
})
|
|
399
399
|
})
|
|
400
|
+
|
|
401
|
+
describe('ComboboxInput accessibility', () => {
|
|
402
|
+
it('exposes status text separately and reserves listbox for options', () => {
|
|
403
|
+
render(<Harness />)
|
|
404
|
+
const input = screen.getByRole('combobox')
|
|
405
|
+
|
|
406
|
+
fireEvent.focus(input)
|
|
407
|
+
fireEvent.change(input, { target: { value: 'missing' } })
|
|
408
|
+
|
|
409
|
+
expect(screen.getByText(/no matches/i)).toHaveAttribute('role', 'status')
|
|
410
|
+
expect(screen.queryByRole('listbox')).toBeNull()
|
|
411
|
+
expect(input).not.toHaveAttribute('aria-controls')
|
|
412
|
+
|
|
413
|
+
fireEvent.change(input, { target: { value: 're' } })
|
|
414
|
+
|
|
415
|
+
const listbox = screen.getByRole('listbox')
|
|
416
|
+
expect(within(listbox).getAllByRole('option')).toHaveLength(2)
|
|
417
|
+
expect(input).toHaveAttribute('aria-controls', listbox.id)
|
|
418
|
+
})
|
|
419
|
+
})
|