@techsio/ui-kit 0.48.0 → 0.50.0

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 (50) hide show
  1. package/dist/atoms/numeric-input.js +3 -0
  2. package/dist/molecules/combobox.js +204 -50
  3. package/dist/molecules/form-checkbox.js +12 -2
  4. package/dist/molecules/form-error-summary.js +59 -0
  5. package/dist/molecules/form-input.js +10 -1
  6. package/dist/molecules/form-numeric-input.js +9 -1
  7. package/dist/molecules/form-textarea.js +10 -1
  8. package/dist/src/atoms/numeric-input.d.ts +1 -1
  9. package/dist/src/atoms/numeric-input.d.ts.map +1 -1
  10. package/dist/src/molecules/combobox.d.ts +50 -4
  11. package/dist/src/molecules/combobox.d.ts.map +1 -1
  12. package/dist/src/molecules/form-checkbox.d.ts +2 -1
  13. package/dist/src/molecules/form-checkbox.d.ts.map +1 -1
  14. package/dist/src/molecules/form-error-summary.d.ts +29 -0
  15. package/dist/src/molecules/form-error-summary.d.ts.map +1 -0
  16. package/dist/src/molecules/form-input.d.ts +3 -3
  17. package/dist/src/molecules/form-input.d.ts.map +1 -1
  18. package/dist/src/molecules/form-numeric-input.d.ts +3 -3
  19. package/dist/src/molecules/form-numeric-input.d.ts.map +1 -1
  20. package/dist/src/molecules/form-textarea.d.ts +3 -3
  21. package/dist/src/molecules/form-textarea.d.ts.map +1 -1
  22. package/dist/src/templates/facet-filter-panel.d.ts +96 -0
  23. package/dist/src/templates/facet-filter-panel.d.ts.map +1 -0
  24. package/dist/src/templates/numeric-input.d.ts +1 -1
  25. package/dist/src/templates/search-suggestions.d.ts +23 -0
  26. package/dist/src/templates/search-suggestions.d.ts.map +1 -0
  27. package/dist/stories/changelog/changelog.stories.d.ts.map +1 -1
  28. package/dist/stories/molecules/combobox.stories.d.ts +6 -0
  29. package/dist/stories/molecules/combobox.stories.d.ts.map +1 -1
  30. package/dist/stories/molecules/form-error-summary.stories.d.ts +11 -0
  31. package/dist/stories/molecules/form-error-summary.stories.d.ts.map +1 -0
  32. package/dist/stories/templates/facet-filter-panel.stories.d.ts +17 -0
  33. package/dist/stories/templates/facet-filter-panel.stories.d.ts.map +1 -0
  34. package/dist/stories/templates/search-suggestions.stories.d.ts +119 -0
  35. package/dist/stories/templates/search-suggestions.stories.d.ts.map +1 -0
  36. package/dist/templates/facet-filter-panel.js +310 -0
  37. package/dist/templates/search-suggestions.js +14 -0
  38. package/dist/test/combobox.behavior.spec.d.ts +2 -0
  39. package/dist/test/combobox.behavior.spec.d.ts.map +1 -0
  40. package/dist/test/facet-filter-panel.spec.d.ts +2 -0
  41. package/dist/test/facet-filter-panel.spec.d.ts.map +1 -0
  42. package/dist/test/form-error-summary.ids.test.d.ts +2 -0
  43. package/dist/test/form-error-summary.ids.test.d.ts.map +1 -0
  44. package/dist/test/search-suggestions.spec.d.ts +2 -0
  45. package/dist/test/search-suggestions.spec.d.ts.map +1 -0
  46. package/package.json +1 -1
  47. package/src/tokens/components/components.css +3 -0
  48. package/src/tokens/components/molecules/_combobox.css +22 -0
  49. package/src/tokens/components/molecules/_form-error-summary.css +20 -0
  50. package/src/tokens/components/templates/_facet-filter-panel.css +43 -0
@@ -102,6 +102,9 @@ function NumericInput({ id, name, size, disabled = false, required = false, patt
102
102
  const stringDefaultValue = void 0 !== defaultValue ? formatValue(defaultValue) : void 0;
103
103
  const service = useMachine(__rspack_external__zag_js_number_input_f1a1f439.machine, {
104
104
  id: uniqueId,
105
+ ids: id ? {
106
+ input: id
107
+ } : void 0,
105
108
  min,
106
109
  max,
107
110
  step,
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { createElement, useEffect, useId, useMemo, useState } from "react";
2
3
  import { collection as combobox_collection, connect, machine } from "@zag-js/combobox";
3
4
  import { Portal, normalizeProps, useMachine } from "@zag-js/react";
4
- import { useEffect, useId, useState } from "react";
5
5
  import { ActionIcon } from "../atoms/action-icon.js";
6
6
  import { Button } from "../atoms/button.js";
7
7
  import { Icon } from "../atoms/icon.js";
@@ -53,7 +53,7 @@ const comboboxVariants = tv({
53
53
  content: [
54
54
  "popup-surface-base",
55
55
  "w-full",
56
- "flex flex-col",
56
+ "flex flex-col overflow-hidden",
57
57
  "duration-200 ease-out motion-safe:transition-[opacity,display,translate,scale]",
58
58
  "transition-discrete",
59
59
  "starting:scale-98 starting:opacity-0",
@@ -62,7 +62,14 @@ const comboboxVariants = tv({
62
62
  "data-[state=closed]:scale-98 data-[state=closed]:opacity-0"
63
63
  ],
64
64
  list: [
65
- "m-0 flex list-none flex-col"
65
+ "m-0 flex min-h-0 list-none flex-col overflow-y-auto overscroll-contain"
66
+ ],
67
+ groupLabel: [
68
+ "combobox-popup-padding",
69
+ "text-combobox-group-label-size font-combobox-group-label text-combobox-group-fg"
70
+ ],
71
+ footer: [
72
+ "combobox-popup-padding shrink-0 border-t border-combobox-footer-border"
66
73
  ],
67
74
  itemText: [
68
75
  "min-w-0 flex-grow truncate"
@@ -83,7 +90,12 @@ const comboboxVariants = tv({
83
90
  ],
84
91
  emptyState: [
85
92
  "px-(--popup-item-x) py-(--popup-item-y)",
86
- "text-popup-item-fg-muted"
93
+ "text-combobox-status-fg"
94
+ ],
95
+ status: [
96
+ "combobox-status-gap flex items-center",
97
+ "px-(--popup-item-x) py-(--popup-item-y)",
98
+ "text-combobox-status-fg"
87
99
  ],
88
100
  triggerIndicator: [
89
101
  "text-combobox-trigger-fg-base group-hover:text-combobox-trigger-fg-hover",
@@ -142,65 +154,151 @@ const comboboxVariants = tv({
142
154
  size: "md"
143
155
  }
144
156
  });
145
- function Combobox({ id, name, label, size, placeholder = "Select option", disabled = false, readOnly = false, required = false, items = [], value, defaultValue, inputValue, multiple = false, validateStatus, helpText, showHelpTextIcon = true, noResultsMessage = 'No results found for "{inputValue}"', clearable = true, selectionBehavior = "replace", closeOnSelect = true, allowCustomValue = false, loopFocus = true, autoFocus = false, triggerIcon = "token-icon-combobox-chevron", triggerIconSize, clearIcon = "token-icon-combobox-clear", inputBehavior = "autocomplete", onChange, onInputValueChange, onOpenChange }) {
157
+ function Combobox({ id, name, label, size, placeholder = "Select option", disabled = false, readOnly = false, required = false, items, groups, value, defaultValue, inputValue, multiple = false, validateStatus, helpText, showHelpTextIcon = true, noResultsMessage = 'No results found for "{inputValue}"', clearable = true, selectionBehavior = "replace", closeOnSelect = true, allowCustomValue = false, loopFocus = true, autoFocus = false, open, defaultOpen, triggerIcon = "token-icon-combobox-chevron", triggerIconSize, clearIcon = "token-icon-combobox-clear", inputBehavior = "autocomplete", filterBehavior = "local", loading = false, loadingMessage = "Loading results", error, retryLabel = "Retry", onRetry, renderItem, onChange, onInputValueChange, onOpenChange, mode = "selection", navigate, footer, portalled = true }) {
146
158
  const generatedId = useId();
147
159
  const uniqueId = id || generatedId;
148
- const [options, setOptions] = useState(items);
160
+ const listId = `${uniqueId}-listbox`;
161
+ const helpTextId = `${uniqueId}-help`;
162
+ const navigation = "navigation" === mode;
163
+ const allItems = useMemo(()=>groups ? groups.flatMap((group)=>group.items) : items ?? [], [
164
+ groups,
165
+ items
166
+ ]);
167
+ const [options, setOptions] = useState(allItems);
149
168
  useEffect(()=>{
150
- setOptions(items);
169
+ setOptions(allItems);
151
170
  }, [
152
- items
171
+ allItems
153
172
  ]);
173
+ const displayedItems = "external" === filterBehavior ? allItems : options;
174
+ const resultsHidden = loading || Boolean(error);
154
175
  const collection = combobox_collection({
155
- items: options,
176
+ items: displayedItems,
156
177
  itemToString: (item)=>item.label,
157
178
  itemToValue: (item)=>item.value,
158
- isItemDisabled: (item)=>!!item.disabled
179
+ isItemDisabled: (item)=>Boolean(resultsHidden || item.disabled || readOnly || navigation && !item.href)
159
180
  });
160
181
  const service = useMachine(machine, {
161
182
  id: uniqueId,
162
- name,
183
+ name: navigation ? void 0 : name,
163
184
  collection,
164
185
  disabled,
165
186
  readOnly,
166
187
  closeOnSelect,
167
- selectionBehavior,
168
- allowCustomValue,
188
+ selectionBehavior: navigation ? "preserve" : selectionBehavior,
189
+ allowCustomValue: !navigation && allowCustomValue,
169
190
  autoFocus,
191
+ open,
192
+ defaultOpen,
170
193
  inputBehavior,
171
194
  loopFocus,
195
+ navigate,
196
+ composite: false,
197
+ positioning: {
198
+ strategy: portalled ? "absolute" : "fixed"
199
+ },
200
+ scrollToIndexFn: ({ getElement })=>{
201
+ getElement()?.scrollIntoView({
202
+ block: "nearest",
203
+ inline: "nearest"
204
+ });
205
+ },
206
+ invalid: "error" === validateStatus,
172
207
  ids: {
173
208
  label: `${uniqueId}-label`,
174
209
  input: `${uniqueId}-input`,
175
210
  control: `${uniqueId}-control`
176
211
  },
177
- value: value,
178
- defaultValue: defaultValue,
179
- multiple,
212
+ value: navigation ? [] : "string" == typeof value ? [
213
+ value
214
+ ] : value,
215
+ defaultValue: navigation ? void 0 : "string" == typeof defaultValue ? [
216
+ defaultValue
217
+ ] : defaultValue,
218
+ multiple: !navigation && multiple,
180
219
  inputValue,
181
220
  onValueChange: ({ value: selectedValue })=>{
182
- onChange?.(selectedValue);
221
+ if (!navigation) onChange?.(selectedValue);
183
222
  },
184
223
  onInputValueChange: ({ inputValue: newItemInputValue })=>{
185
- const filtered = items.filter((item)=>item.label.toLowerCase().includes(newItemInputValue.toLowerCase()));
186
- setOptions(filtered);
224
+ if ("local" === filterBehavior) {
225
+ const filtered = allItems.filter((item)=>item.label.toLowerCase().includes(newItemInputValue.toLowerCase()));
226
+ setOptions(filtered);
227
+ }
187
228
  onInputValueChange?.(newItemInputValue);
188
229
  },
189
230
  onOpenChange: ({ open })=>{
190
- setOptions(items);
231
+ setOptions(allItems);
191
232
  onOpenChange?.(open);
192
233
  }
193
234
  });
194
235
  const api = connect(service, normalizeProps);
195
- const inputProps = api.getInputProps();
196
- const { ...restInputProps } = inputProps;
197
- const { root, label: labelStyles, control, input, trigger, positioner, content, list, item: itemSlot, itemText, itemIndicator, emptyState, triggerIndicator } = comboboxVariants({
236
+ const { root, label: labelStyles, control, input, trigger, positioner, content, list, groupLabel, footer: footerSlot, item: itemSlot, itemText, itemIndicator, emptyState, status: statusSlot, triggerIndicator } = comboboxVariants({
198
237
  size
199
238
  });
200
239
  const hasOptions = api.collection.size > 0;
201
- const showEmptyState = !hasOptions && Boolean(api.inputValue);
240
+ const showEmptyState = !resultsHidden && !hasOptions && Boolean(api.inputValue);
241
+ let state = "idle";
242
+ if (loading) state = "loading";
243
+ else if (error) state = "error";
244
+ else if (hasOptions) state = "results";
245
+ else if (showEmptyState) state = "empty";
246
+ const visibleValues = new Set(collection.items.map((item)=>item.value));
247
+ const renderOption = (item)=>{
248
+ const itemProps = api.getItemProps({
249
+ item
250
+ });
251
+ const itemContent = renderItem ? renderItem(item) : /*#__PURE__*/ jsx("span", {
252
+ className: itemText(),
253
+ children: item.label
254
+ });
255
+ if (navigation) {
256
+ const blocked = disabled || readOnly || item.disabled || !item.href;
257
+ return /*#__PURE__*/ createElement("a", {
258
+ ...itemProps,
259
+ "aria-label": item.label,
260
+ className: itemSlot(),
261
+ href: blocked ? void 0 : item.href,
262
+ key: item.value,
263
+ onClick: (event)=>{
264
+ if (blocked) return void event.preventDefault();
265
+ itemProps.onClick?.(event);
266
+ if (navigate && !event.defaultPrevented && 0 === event.button && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) {
267
+ event.preventDefault();
268
+ navigate({
269
+ node: event.currentTarget,
270
+ href: event.currentTarget.href,
271
+ value: item.value
272
+ });
273
+ }
274
+ }
275
+ }, itemContent);
276
+ }
277
+ return /*#__PURE__*/ createElement("div", {
278
+ ...itemProps,
279
+ "aria-label": item.label,
280
+ className: itemSlot(),
281
+ key: item.value,
282
+ children: [
283
+ itemContent,
284
+ /*#__PURE__*/ jsx("span", {
285
+ ...api.getItemIndicatorProps({
286
+ item
287
+ }),
288
+ className: itemIndicator(),
289
+ children: /*#__PURE__*/ jsx(Icon, {
290
+ icon: "token-icon-check",
291
+ size: "current"
292
+ })
293
+ })
294
+ ]
295
+ });
296
+ };
202
297
  return /*#__PURE__*/ jsxs("div", {
298
+ ...api.getRootProps(),
203
299
  className: root(),
300
+ "data-mode": mode,
301
+ "data-status": state,
204
302
  children: [
205
303
  label && /*#__PURE__*/ jsx(Label, {
206
304
  className: labelStyles(),
@@ -216,20 +314,33 @@ function Combobox({ id, name, label, size, placeholder = "Select option", disabl
216
314
  children: [
217
315
  /*#__PURE__*/ jsx(Input, {
218
316
  className: input(),
219
- ...restInputProps,
220
- name: name,
317
+ ...api.getInputProps(),
318
+ "aria-controls": api.open ? listId : void 0,
319
+ "aria-describedby": helpText ? helpTextId : void 0,
320
+ name: navigation ? void 0 : name,
221
321
  placeholder: placeholder,
222
322
  required: required,
223
323
  size: size
224
324
  }),
225
- clearable && api.value.length > 0 && /*#__PURE__*/ jsx(ActionIcon, {
325
+ clearable && (navigation ? Boolean(api.inputValue) : api.value.length > 0) && /*#__PURE__*/ jsx(ActionIcon, {
226
326
  icon: clearIcon,
227
327
  size: size ?? "md",
228
328
  tone: "neutral",
229
- ...api.getClearTriggerProps()
329
+ ...api.getClearTriggerProps(),
330
+ disabled: disabled || readOnly,
331
+ hidden: false,
332
+ onClick: (event)=>{
333
+ if (navigation) {
334
+ if (disabled || readOnly) return;
335
+ api.setInputValue("");
336
+ api.focus();
337
+ } else api.getClearTriggerProps().onClick?.(event);
338
+ }
230
339
  }),
231
340
  /*#__PURE__*/ jsx(Button, {
232
341
  ...api.getTriggerProps(),
342
+ "aria-controls": api.open ? listId : void 0,
343
+ "aria-haspopup": "listbox",
233
344
  className: trigger(),
234
345
  size: "current",
235
346
  theme: "unstyled",
@@ -242,48 +353,91 @@ function Combobox({ id, name, label, size, placeholder = "Select option", disabl
242
353
  ]
243
354
  }),
244
355
  /*#__PURE__*/ jsx(Portal, {
356
+ disabled: !portalled,
245
357
  children: /*#__PURE__*/ jsx("div", {
246
358
  ...api.getPositionerProps(),
247
359
  className: positioner(),
248
360
  children: /*#__PURE__*/ jsxs("div", {
249
361
  ...api.getContentProps(),
362
+ "aria-labelledby": void 0,
250
363
  className: content(),
364
+ "data-status": state,
365
+ role: void 0,
366
+ tabIndex: void 0,
251
367
  children: [
252
- hasOptions && /*#__PURE__*/ jsx("ul", {
368
+ loading ? /*#__PURE__*/ jsxs("div", {
369
+ className: statusSlot(),
370
+ role: "status",
371
+ children: [
372
+ /*#__PURE__*/ jsx(Icon, {
373
+ icon: "token-icon-spinner",
374
+ size: "current"
375
+ }),
376
+ /*#__PURE__*/ jsx("span", {
377
+ children: loadingMessage
378
+ })
379
+ ]
380
+ }) : error ? /*#__PURE__*/ jsxs("div", {
381
+ className: statusSlot(),
382
+ role: "alert",
383
+ children: [
384
+ /*#__PURE__*/ jsx("span", {
385
+ className: "min-w-0 flex-1",
386
+ children: error
387
+ }),
388
+ onRetry && /*#__PURE__*/ jsx(Button, {
389
+ disabled: disabled || readOnly,
390
+ onClick: onRetry,
391
+ size: "sm",
392
+ variant: "secondary",
393
+ children: retryLabel
394
+ })
395
+ ]
396
+ }) : showEmptyState ? /*#__PURE__*/ jsx("div", {
397
+ className: emptyState(),
398
+ role: "status",
399
+ children: noResultsMessage.replace("{inputValue}", api.inputValue)
400
+ }) : null,
401
+ /*#__PURE__*/ jsx("div", {
253
402
  ...api.getListProps(),
403
+ "aria-busy": loading || void 0,
254
404
  className: list(),
255
- children: options.map((item)=>/*#__PURE__*/ jsxs("li", {
256
- ...api.getItemProps({
257
- item
405
+ id: listId,
406
+ children: !resultsHidden && (groups ? groups.map((group)=>{
407
+ const groupItems = group.items.filter((item)=>visibleValues.has(item.value));
408
+ if (0 === groupItems.length) return null;
409
+ return /*#__PURE__*/ createElement("div", {
410
+ ...api.getItemGroupProps({
411
+ id: group.id
258
412
  }),
259
- className: itemSlot(),
413
+ "aria-labelledby": group.label ? api.getItemGroupProps({
414
+ id: group.id
415
+ })["aria-labelledby"] : void 0,
416
+ key: group.id,
260
417
  children: [
261
- /*#__PURE__*/ jsx("span", {
262
- className: itemText(),
263
- children: item.label
264
- }),
265
- /*#__PURE__*/ jsx("span", {
266
- ...api.getItemIndicatorProps({
267
- item
418
+ group.label && /*#__PURE__*/ jsx("div", {
419
+ ...api.getItemGroupLabelProps({
420
+ htmlFor: group.id
268
421
  }),
269
- className: itemIndicator(),
270
- children: /*#__PURE__*/ jsx(Icon, {
271
- icon: "token-icon-check",
272
- size: "current"
273
- })
274
- })
422
+ className: groupLabel(),
423
+ children: group.label
424
+ }),
425
+ groupItems.map(renderOption)
275
426
  ]
276
- }, item.value))
427
+ });
428
+ }) : collection.items.map(renderOption))
277
429
  }),
278
- showEmptyState && /*#__PURE__*/ jsx("div", {
279
- className: emptyState(),
280
- children: noResultsMessage.replace("{inputValue}", api.inputValue)
430
+ footer && /*#__PURE__*/ jsx("div", {
431
+ className: footerSlot(),
432
+ "data-part": "footer",
433
+ children: footer
281
434
  })
282
435
  ]
283
436
  })
284
437
  })
285
438
  }),
286
439
  helpText && /*#__PURE__*/ jsx(StatusText, {
440
+ id: helpTextId,
287
441
  showIcon: showHelpTextIcon,
288
442
  size: size,
289
443
  status: validateStatus,
@@ -61,11 +61,19 @@ const checkboxVariants = tv({
61
61
  size: "md"
62
62
  }
63
63
  });
64
- function FormCheckbox({ id, name, value, checked, defaultChecked, indeterminate, disabled = false, required = false, readOnly = false, children, label, helpText, validateStatus = "default", showHelpTextIcon = "default" !== validateStatus, size = "md", className, onCheckedChange }) {
64
+ function FormCheckbox({ id, name, value, checked, defaultChecked, indeterminate, disabled = false, required = false, readOnly = false, children, label, helpText, "aria-describedby": ariaDescribedBy, validateStatus = "default", showHelpTextIcon = "default" !== validateStatus, size = "md", className, onCheckedChange }) {
65
65
  const generatedId = useId();
66
66
  const uniqueId = id || generatedId;
67
+ const helpTextId = useId();
68
+ const describedBy = [
69
+ ariaDescribedBy,
70
+ helpText ? helpTextId : void 0
71
+ ].filter(Boolean).join(" ") || void 0;
67
72
  const service = useMachine(machine, {
68
73
  id: uniqueId,
74
+ ids: id ? {
75
+ hiddenInput: id
76
+ } : void 0,
69
77
  name,
70
78
  value,
71
79
  checked: indeterminate ? "indeterminate" : checked,
@@ -100,7 +108,8 @@ function FormCheckbox({ id, name, value, checked, defaultChecked, indeterminate,
100
108
  }),
101
109
  /*#__PURE__*/ jsx("input", {
102
110
  className: styles.hiddenInput(),
103
- ...api.getHiddenInputProps()
111
+ ...api.getHiddenInputProps(),
112
+ "aria-describedby": describedBy
104
113
  }),
105
114
  labelContent && /*#__PURE__*/ jsxs("span", {
106
115
  className: styles.label(),
@@ -119,6 +128,7 @@ function FormCheckbox({ id, name, value, checked, defaultChecked, indeterminate,
119
128
  className: styles.textIndented(),
120
129
  "data-icon": showHelpTextIcon,
121
130
  children: /*#__PURE__*/ jsx(StatusText, {
131
+ id: helpTextId,
122
132
  showIcon: showHelpTextIcon,
123
133
  size: size,
124
134
  status: validateStatus,
@@ -0,0 +1,59 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { tv } from "../utils.js";
3
+ const formErrorSummaryVariants = tv({
4
+ slots: {
5
+ root: [
6
+ "flex flex-col gap-100",
7
+ "rounded-form-control",
8
+ "border-(length:--border-width-validation)",
9
+ "border-form-error-summary-border",
10
+ "bg-form-error-summary-bg",
11
+ "p-300"
12
+ ],
13
+ title: [
14
+ "text-label-md",
15
+ "font-medium",
16
+ "text-form-error-summary-fg"
17
+ ],
18
+ list: [
19
+ "m-0",
20
+ "flex list-none flex-col gap-100 p-0"
21
+ ],
22
+ link: [
23
+ "text-form-error-summary-fg",
24
+ "underline",
25
+ "underline-offset-4",
26
+ "focus-visible:form-error-summary-focus-ring"
27
+ ]
28
+ }
29
+ });
30
+ function FormErrorSummary({ title = "There is a problem", errors, onFieldFocus, className, ref }) {
31
+ if (0 === errors.length) return null;
32
+ const styles = formErrorSummaryVariants();
33
+ return /*#__PURE__*/ jsxs("div", {
34
+ className: styles.root({
35
+ className
36
+ }),
37
+ ref: ref,
38
+ role: "alert",
39
+ children: [
40
+ /*#__PURE__*/ jsx("h2", {
41
+ className: styles.title(),
42
+ children: title
43
+ }),
44
+ /*#__PURE__*/ jsx("ul", {
45
+ className: styles.list(),
46
+ children: errors.map((error)=>/*#__PURE__*/ jsx("li", {
47
+ children: /*#__PURE__*/ jsx("a", {
48
+ className: styles.link(),
49
+ href: `#${error.targetId}`,
50
+ onClick: ()=>onFieldFocus?.(error.targetId),
51
+ children: error.label
52
+ })
53
+ }, error.id))
54
+ })
55
+ ]
56
+ });
57
+ }
58
+ FormErrorSummary.displayName = "FormErrorSummary";
59
+ export { FormErrorSummary };
@@ -1,4 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useId } from "react";
2
3
  import { Input } from "../atoms/input.js";
3
4
  import { Label } from "../atoms/label.js";
4
5
  import { StatusText } from "../atoms/status-text.js";
@@ -26,9 +27,15 @@ function FormInputRaw({ id, label, validateStatus = "default", helpText, size =
26
27
  ]
27
28
  });
28
29
  }
29
- function FormInput({ helpText, id, validateStatus = "default", showHelpTextIcon = "default" !== validateStatus, size = "md", ...props }) {
30
+ function FormInput({ helpText, id, validateStatus = "default", showHelpTextIcon = "default" !== validateStatus, size = "md", "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, ...props }) {
31
+ const helpTextId = useId();
32
+ const describedBy = [
33
+ ariaDescribedBy,
34
+ helpText ? helpTextId : void 0
35
+ ].filter(Boolean).join(" ") || void 0;
30
36
  return /*#__PURE__*/ jsx(FormInputRaw, {
31
37
  helpText: helpText && /*#__PURE__*/ jsx(StatusText, {
38
+ id: helpTextId,
32
39
  status: validateStatus,
33
40
  showIcon: showHelpTextIcon,
34
41
  size: size,
@@ -37,6 +44,8 @@ function FormInput({ helpText, id, validateStatus = "default", showHelpTextIcon
37
44
  id: id,
38
45
  size: size,
39
46
  validateStatus: validateStatus,
47
+ "aria-invalid": "error" === validateStatus ? true : ariaInvalid,
48
+ "aria-describedby": describedBy,
40
49
  ...props
41
50
  });
42
51
  }
@@ -1,8 +1,14 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useId } from "react";
2
3
  import { Label } from "../atoms/label.js";
3
4
  import { NumericInput } from "../atoms/numeric-input.js";
4
5
  import { StatusText } from "../atoms/status-text.js";
5
- function FormNumericInput({ id, label, validateStatus = "default", helpText, showHelpTextIcon = "default" !== validateStatus, size = "md", required, disabled, children, ...numericInputProps }) {
6
+ function FormNumericInput({ id, label, validateStatus = "default", helpText, showHelpTextIcon = "default" !== validateStatus, size = "md", required, disabled, children, describedBy, ...numericInputProps }) {
7
+ const helpTextId = useId();
8
+ const mergedDescribedBy = [
9
+ describedBy,
10
+ helpText ? helpTextId : void 0
11
+ ].filter(Boolean).join(" ") || void 0;
6
12
  return /*#__PURE__*/ jsxs("div", {
7
13
  className: "flex flex-col gap-form-field-gap",
8
14
  children: [
@@ -19,10 +25,12 @@ function FormNumericInput({ id, label, validateStatus = "default", helpText, sho
19
25
  invalid: "error" === validateStatus,
20
26
  required: required,
21
27
  size: size,
28
+ describedBy: mergedDescribedBy,
22
29
  ...numericInputProps,
23
30
  children: children
24
31
  }),
25
32
  helpText && /*#__PURE__*/ jsx(StatusText, {
33
+ id: helpTextId,
26
34
  status: validateStatus,
27
35
  showIcon: showHelpTextIcon,
28
36
  size: size,
@@ -1,4 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useId } from "react";
2
3
  import { Label } from "../atoms/label.js";
3
4
  import { StatusText } from "../atoms/status-text.js";
4
5
  import { Textarea } from "../atoms/textarea.js";
@@ -25,9 +26,15 @@ function FormTextareaRaw({ id, label, validateStatus = "default", helpText, size
25
26
  ]
26
27
  });
27
28
  }
28
- function FormTextarea({ helpText, id, validateStatus = "default", showHelpTextIcon = "default" !== validateStatus, size = "md", ...props }) {
29
+ function FormTextarea({ helpText, id, validateStatus = "default", showHelpTextIcon = "default" !== validateStatus, size = "md", "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, ...props }) {
30
+ const helpTextId = useId();
31
+ const describedBy = [
32
+ ariaDescribedBy,
33
+ helpText ? helpTextId : void 0
34
+ ].filter(Boolean).join(" ") || void 0;
29
35
  return /*#__PURE__*/ jsx(FormTextareaRaw, {
30
36
  helpText: helpText && /*#__PURE__*/ jsx(StatusText, {
37
+ id: helpTextId,
31
38
  status: validateStatus,
32
39
  showIcon: showHelpTextIcon,
33
40
  size: size,
@@ -36,6 +43,8 @@ function FormTextarea({ helpText, id, validateStatus = "default", showHelpTextIc
36
43
  id: id,
37
44
  size: size,
38
45
  validateStatus: validateStatus,
46
+ "aria-invalid": "error" === validateStatus ? true : ariaInvalid,
47
+ "aria-describedby": describedBy,
39
48
  ...props
40
49
  });
41
50
  }
@@ -2,7 +2,7 @@
2
2
  * NumericInput — @techsio/ui-kit atom.
3
3
  *
4
4
  * @component NumericInput
5
- * @componentVersion v1.0.0
5
+ * @componentVersion v1.0.1
6
6
  * @skill numeric-input-usage
7
7
  * @changelog libs/ui/stories/changelog/changelog.stories.tsx
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"numeric-input.d.ts","sourceRoot":"","sources":["../../../src/atoms/numeric-input.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"numeric-input.d.ts","sourceRoot":"","sources":["../../../src/atoms/numeric-input.tsx"],"names":[],"mappings":"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"}