@w5-ui/react 0.5.0 → 0.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@w5-ui/react",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "React 18 components for the Dash UI design system - ~170 dashboard-focused components with full type defs and SSR support. Part of Webber's design systems.",
5
5
  "keywords": [
6
6
  "react",
@@ -87,6 +87,7 @@ export function ColumnToggle({ columns, visible, onChange }: ColumnToggleProps)
87
87
  <label className="col-toggle__label">
88
88
  <input
89
89
  type="checkbox"
90
+ aria-label={col.label}
90
91
  className="col-toggle__check"
91
92
  checked={visible.has(col.key)}
92
93
  disabled={col.required}
@@ -110,6 +110,7 @@ export function CommandPalette({
110
110
  className="cp-input"
111
111
  type="text"
112
112
  role="combobox"
113
+ aria-label={placeholder}
113
114
  aria-expanded={filtered.length > 0}
114
115
  aria-autocomplete="list"
115
116
  aria-controls={listId}
@@ -103,7 +103,7 @@ export function ContextMenu({
103
103
  >
104
104
  {items.map((entry, i) => {
105
105
  if ('separator' in entry) {
106
- return <li key={i} role="separator" className="ctx-menu-sep" />
106
+ return <li key={i} role="separator" aria-hidden="true" className="ctx-menu-sep" />
107
107
  }
108
108
  const ai = actionItems.indexOf(entry)
109
109
  return (
@@ -145,8 +145,23 @@ export function DateRangePicker({
145
145
  setPicking(null)
146
146
  }
147
147
  }
148
+ // Escape closes the dialog and restores focus to the trigger. Handled
149
+ // at the document level (rather than a JSX onKeyDown on the dialog) so
150
+ // it works regardless of focus position inside the popover.
151
+ function onKey(e: KeyboardEvent) {
152
+ if (e.key === 'Escape') {
153
+ e.preventDefault()
154
+ setOpen(false)
155
+ setPicking(null)
156
+ triggerRef.current?.focus()
157
+ }
158
+ }
148
159
  document.addEventListener('mousedown', handler)
149
- return () => document.removeEventListener('mousedown', handler)
160
+ document.addEventListener('keydown', onKey)
161
+ return () => {
162
+ document.removeEventListener('mousedown', handler)
163
+ document.removeEventListener('keydown', onKey)
164
+ }
150
165
  }, [open])
151
166
 
152
167
  function renderCalendar(grid: Date[], gridMonth: number, gridYear: number) {
@@ -302,14 +317,6 @@ export function DateRangePicker({
302
317
  aria-modal="true"
303
318
  aria-label="Select date range"
304
319
  className="drp-cal"
305
- onKeyDown={(e) => {
306
- if (e.key === 'Escape') {
307
- e.preventDefault()
308
- setOpen(false)
309
- setPicking(null)
310
- triggerRef.current?.focus()
311
- }
312
- }}
313
320
  >
314
321
  <div className="drp-months">
315
322
  {renderCalendar(leftGrid, viewMonth, viewYear)}
@@ -22,6 +22,7 @@ export function ExpandableRow({
22
22
  <button
23
23
  type="button"
24
24
  className="exp-toggle"
25
+ aria-label={open ? 'Collapse row' : 'Expand row'}
25
26
  aria-expanded={open}
26
27
  aria-controls={detailId}
27
28
  onClick={() => setOpen((o) => !o)}
@@ -98,6 +98,7 @@ export function NumberInput({
98
98
  id={inputId}
99
99
  type="number"
100
100
  role="spinbutton"
101
+ aria-label={label}
101
102
  aria-valuenow={value}
102
103
  aria-valuemin={min}
103
104
  aria-valuemax={max}
@@ -89,6 +89,7 @@ export function PasswordInput({
89
89
  <input
90
90
  id={inputId}
91
91
  type={shown ? 'text' : 'password'}
92
+ aria-label={label}
92
93
  className="pwd-input__field"
93
94
  value={value}
94
95
  placeholder={placeholder}
@@ -49,6 +49,7 @@ export function RadioGroup({
49
49
  <input
50
50
  type="radio"
51
51
  id={optId}
52
+ aria-labelledby={`${optId}-label`}
52
53
  name={groupName}
53
54
  value={opt.value}
54
55
  checked={value !== undefined ? value === opt.value : undefined}
@@ -57,7 +58,7 @@ export function RadioGroup({
57
58
  onChange={() => onChange?.(opt.value)}
58
59
  className="radio"
59
60
  />
60
- <span className="radio-text">
61
+ <span className="radio-text" id={`${optId}-label`}>
61
62
  <span>{opt.label}</span>
62
63
  {opt.description && <span className="radio-description">{opt.description}</span>}
63
64
  </span>
@@ -61,7 +61,13 @@ export function TagInput({
61
61
  }
62
62
 
63
63
  return (
64
- <div className={`tag-input-wrapper${className ? ` ${className}` : ''}`}>
64
+ // Click-to-focus lives on the presentational wrapper rather than the
65
+ // role="group" element, so a non-interactive ARIA role never carries a
66
+ // mouse handler.
67
+ <div
68
+ className={`tag-input-wrapper${className ? ` ${className}` : ''}`}
69
+ onClick={() => inputRef.current?.focus()}
70
+ >
65
71
  {label && (
66
72
  <label htmlFor={inputId} className="tag-input__label">
67
73
  {label}
@@ -72,7 +78,6 @@ export function TagInput({
72
78
  role="group"
73
79
  aria-label={label ?? 'Tags'}
74
80
  className={`tag-input${disabled ? ' tag-input--disabled' : ''}`}
75
- onClick={() => inputRef.current?.focus()}
76
81
  >
77
82
  {tags.map((t, i) => (
78
83
  <Tag key={t} label={t} onRemove={disabled ? undefined : () => removeTag(i)} />