@konce-pt/react 0.9.0 → 0.10.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 (196) hide show
  1. package/dist/accordion/accordion.js +7 -1
  2. package/dist/accordion/llms.txt +8 -4
  3. package/dist/alert/alert.d.ts +10 -4
  4. package/dist/alert/alert.js +13 -6
  5. package/dist/alert/llms.txt +8 -6
  6. package/dist/app-shell/app-shell.js +28 -2
  7. package/dist/app-shell/llms.txt +3 -0
  8. package/dist/autocomplete/autocomplete.d.ts +7 -2
  9. package/dist/autocomplete/autocomplete.js +46 -9
  10. package/dist/autocomplete/llms.txt +12 -0
  11. package/dist/avatar/avatar.d.ts +4 -2
  12. package/dist/avatar/avatar.js +8 -3
  13. package/dist/avatar/llms.txt +9 -5
  14. package/dist/avatar-group/avatar-group.d.ts +5 -2
  15. package/dist/avatar-group/avatar-group.js +10 -2
  16. package/dist/avatar-group/llms.txt +7 -4
  17. package/dist/badge/badge.d.ts +8 -1
  18. package/dist/badge/badge.js +3 -2
  19. package/dist/badge/llms.txt +11 -5
  20. package/dist/bottom-sheet/bottom-sheet.d.ts +1 -1
  21. package/dist/bottom-sheet/bottom-sheet.js +8 -2
  22. package/dist/bottom-sheet/llms.txt +8 -2
  23. package/dist/breadcrumb/breadcrumb.js +4 -4
  24. package/dist/breadcrumb/llms.txt +12 -5
  25. package/dist/card/card.d.ts +9 -1
  26. package/dist/card/card.js +2 -2
  27. package/dist/card/llms.txt +6 -2
  28. package/dist/carousel/carousel.d.ts +8 -1
  29. package/dist/carousel/carousel.js +9 -2
  30. package/dist/carousel/llms.txt +24 -10
  31. package/dist/checkbox/checkbox.d.ts +6 -1
  32. package/dist/checkbox/checkbox.js +11 -3
  33. package/dist/checkbox/llms.txt +11 -0
  34. package/dist/chip/chip.js +10 -1
  35. package/dist/chip/llms.txt +7 -4
  36. package/dist/chips-input/chips-input.d.ts +7 -1
  37. package/dist/chips-input/chips-input.js +21 -5
  38. package/dist/chips-input/llms.txt +12 -0
  39. package/dist/color-picker/color-picker.d.ts +18 -5
  40. package/dist/color-picker/color-picker.js +48 -14
  41. package/dist/color-picker/llms.txt +14 -0
  42. package/dist/confirm/confirm-container.d.ts +5 -1
  43. package/dist/confirm/confirm-container.js +15 -1
  44. package/dist/confirm/llms.txt +4 -2
  45. package/dist/context-menu/context-menu.d.ts +5 -0
  46. package/dist/context-menu/context-menu.js +48 -4
  47. package/dist/context-menu/llms.txt +11 -4
  48. package/dist/date-range/date-range.d.ts +14 -1
  49. package/dist/date-range/date-range.js +133 -17
  50. package/dist/date-range/llms.txt +26 -3
  51. package/dist/datepicker/datepicker.d.ts +10 -1
  52. package/dist/datepicker/datepicker.js +147 -14
  53. package/dist/datepicker/llms.txt +27 -3
  54. package/dist/dialog/dialog.d.ts +4 -0
  55. package/dist/dialog/dialog.js +7 -1
  56. package/dist/dialog/llms.txt +2 -0
  57. package/dist/drawer/drawer.js +7 -2
  58. package/dist/drawer/llms.txt +10 -5
  59. package/dist/empty/empty.d.ts +6 -1
  60. package/dist/empty/empty.js +2 -2
  61. package/dist/empty/llms.txt +4 -3
  62. package/dist/fab/llms.txt +2 -1
  63. package/dist/file-upload/file-upload.d.ts +5 -0
  64. package/dist/file-upload/file-upload.js +24 -3
  65. package/dist/file-upload/llms.txt +15 -4
  66. package/dist/form-field/form-field-context.d.ts +30 -1
  67. package/dist/form-field/form-field-context.js +27 -0
  68. package/dist/form-field/form-field.js +16 -2
  69. package/dist/form-field/llms.txt +10 -1
  70. package/dist/galleria/galleria.d.ts +6 -1
  71. package/dist/galleria/galleria.js +40 -1
  72. package/dist/galleria/llms.txt +17 -8
  73. package/dist/icon/icon.d.ts +7 -1
  74. package/dist/icon/icon.js +18 -4
  75. package/dist/icon/llms.txt +5 -2
  76. package/dist/image/image.d.ts +5 -0
  77. package/dist/image/image.js +38 -2
  78. package/dist/image/llms.txt +24 -17
  79. package/dist/input/input.d.ts +1 -1
  80. package/dist/input/input.js +4 -6
  81. package/dist/input/llms.txt +5 -0
  82. package/dist/input-mask/input-mask.d.ts +1 -1
  83. package/dist/input-mask/input-mask.js +8 -6
  84. package/dist/input-mask/llms.txt +11 -0
  85. package/dist/input-number/input-number.d.ts +13 -1
  86. package/dist/input-number/input-number.js +49 -9
  87. package/dist/input-number/llms.txt +21 -3
  88. package/dist/input-otp/input-otp.d.ts +7 -2
  89. package/dist/input-otp/input-otp.js +36 -7
  90. package/dist/input-otp/llms.txt +14 -0
  91. package/dist/internal/calendar-focus.d.ts +8 -0
  92. package/dist/internal/calendar-focus.js +38 -0
  93. package/dist/internal/option-nav.d.ts +24 -0
  94. package/dist/internal/option-nav.js +80 -0
  95. package/dist/internal/roving.d.ts +9 -0
  96. package/dist/internal/roving.js +43 -0
  97. package/dist/internal/use-modal-focus.d.ts +10 -0
  98. package/dist/internal/use-modal-focus.js +57 -0
  99. package/dist/knob/knob.d.ts +5 -1
  100. package/dist/knob/knob.js +4 -2
  101. package/dist/knob/llms.txt +8 -0
  102. package/dist/listbox/listbox.d.ts +13 -1
  103. package/dist/listbox/listbox.js +89 -6
  104. package/dist/listbox/llms.txt +21 -5
  105. package/dist/megamenu/llms.txt +28 -10
  106. package/dist/megamenu/megamenu.d.ts +10 -1
  107. package/dist/megamenu/megamenu.js +116 -5
  108. package/dist/menu/llms.txt +12 -1
  109. package/dist/menu/menu-keys.d.ts +15 -0
  110. package/dist/menu/menu-keys.js +53 -0
  111. package/dist/menu/menu-rows.js +1 -1
  112. package/dist/menu/menu.js +52 -4
  113. package/dist/menubar/llms.txt +24 -7
  114. package/dist/menubar/menubar.d.ts +8 -1
  115. package/dist/menubar/menubar.js +92 -6
  116. package/dist/meter-group/llms.txt +10 -3
  117. package/dist/meter-group/meter-group.d.ts +9 -2
  118. package/dist/meter-group/meter-group.js +8 -2
  119. package/dist/order-list/llms.txt +11 -3
  120. package/dist/order-list/order-list.d.ts +5 -1
  121. package/dist/order-list/order-list.js +46 -8
  122. package/dist/panel/llms.txt +9 -6
  123. package/dist/panel/panel.js +3 -1
  124. package/dist/password/llms.txt +8 -0
  125. package/dist/password/password.d.ts +6 -1
  126. package/dist/password/password.js +6 -5
  127. package/dist/pick-list/llms.txt +12 -3
  128. package/dist/pick-list/pick-list.d.ts +5 -1
  129. package/dist/pick-list/pick-list.js +52 -5
  130. package/dist/popover/llms.txt +9 -2
  131. package/dist/popover/popover.d.ts +9 -3
  132. package/dist/popover/popover.js +41 -6
  133. package/dist/public-api.d.ts +6 -6
  134. package/dist/public-api.js +1 -1
  135. package/dist/radio-group/llms.txt +11 -0
  136. package/dist/radio-group/radio-group.d.ts +9 -1
  137. package/dist/radio-group/radio-group.js +5 -2
  138. package/dist/rating/llms.txt +18 -5
  139. package/dist/rating/rating.d.ts +21 -3
  140. package/dist/rating/rating.js +87 -12
  141. package/dist/rich-text/llms.txt +20 -0
  142. package/dist/rich-text/rich-text.d.ts +7 -1
  143. package/dist/rich-text/rich-text.js +155 -28
  144. package/dist/scroll-top/llms.txt +5 -1
  145. package/dist/scroll-top/scroll-top.js +3 -1
  146. package/dist/select/llms.txt +21 -7
  147. package/dist/select/select.d.ts +12 -1
  148. package/dist/select/select.js +110 -38
  149. package/dist/sidenav/llms.txt +6 -0
  150. package/dist/skeleton/llms.txt +3 -2
  151. package/dist/skeleton/skeleton.d.ts +1 -1
  152. package/dist/skeleton/skeleton.js +2 -2
  153. package/dist/slider/llms.txt +14 -1
  154. package/dist/slider/slider.d.ts +11 -2
  155. package/dist/slider/slider.js +15 -2
  156. package/dist/speed-dial/llms.txt +6 -2
  157. package/dist/speed-dial/speed-dial.d.ts +1 -1
  158. package/dist/speed-dial/speed-dial.js +14 -3
  159. package/dist/spinner/llms.txt +12 -8
  160. package/dist/spinner/spinner.d.ts +5 -1
  161. package/dist/spinner/spinner.js +12 -2
  162. package/dist/split-button/llms.txt +11 -3
  163. package/dist/split-button/split-button.d.ts +3 -1
  164. package/dist/split-button/split-button.js +43 -3
  165. package/dist/splitter/llms.txt +14 -4
  166. package/dist/splitter/splitter.d.ts +9 -2
  167. package/dist/splitter/splitter.js +15 -2
  168. package/dist/stepper/llms.txt +8 -4
  169. package/dist/stepper/stepper.d.ts +3 -2
  170. package/dist/stepper/stepper.js +7 -2
  171. package/dist/switch/llms.txt +10 -0
  172. package/dist/switch/switch.d.ts +1 -1
  173. package/dist/switch/switch.js +4 -2
  174. package/dist/switch-group/llms.txt +11 -0
  175. package/dist/switch-group/switch-group.d.ts +5 -1
  176. package/dist/switch-group/switch-group.js +7 -2
  177. package/dist/tabs/llms.txt +10 -4
  178. package/dist/tabs/tabs.d.ts +4 -0
  179. package/dist/tabs/tabs.js +21 -2
  180. package/dist/textarea/llms.txt +10 -0
  181. package/dist/textarea/textarea.d.ts +3 -1
  182. package/dist/textarea/textarea.js +13 -8
  183. package/dist/toast/llms.txt +21 -7
  184. package/dist/toast/toast-container.d.ts +6 -1
  185. package/dist/toast/toast-container.js +52 -1
  186. package/dist/toast/toast-store.d.ts +12 -0
  187. package/dist/toast/toast-store.js +46 -7
  188. package/dist/tooltip/llms.txt +10 -3
  189. package/dist/tooltip/tooltip.d.ts +5 -0
  190. package/dist/tooltip/tooltip.js +80 -7
  191. package/dist/tree/llms.txt +20 -8
  192. package/dist/tree/tree-keys.d.ts +27 -0
  193. package/dist/tree/tree-keys.js +39 -0
  194. package/dist/tree/tree.d.ts +12 -5
  195. package/dist/tree/tree.js +75 -29
  196. package/package.json +12 -12
@@ -6,7 +6,9 @@ Import: `import { KptBadge } from '@konce-pt/react';`
6
6
  ## DOM
7
7
  <span class="kpt-badge-host">
8
8
  …children…
9
- <span class="kpt-badge kpt-badge--overlay" data-variant="danger" data-size="md">8</span>
9
+ <span class="kpt-badge kpt-badge--overlay" data-variant="danger" data-size="md">
10
+ <span aria-hidden="true">8</span><span class="kpt-badge__label">8 unread</span>
11
+ </span>
10
12
  </span>
11
13
 
12
14
  ## Props
@@ -16,6 +18,8 @@ Import: `import { KptBadge } from '@konce-pt/react';`
16
18
  - `dot`: boolean — a bare status dot, no number
17
19
  - `overlay`: boolean — laid over the `children`
18
20
  - `max`: number (default 99) — above it the counter reads `99+`
21
+ - `label`: string — the meaning for screen readers (“3 unread”, “online”); the visible text is then `aria-hidden`
22
+ - `hideZero`: boolean — hides the badge when `value` is `0` or `null` (not with `dot`)
19
23
  - `className`, `ref` and every other `<span>` prop go to the host
20
24
 
21
25
  ## Overlay
@@ -30,9 +34,11 @@ it on the host rather than on the badge.
30
34
  <span>Notifications <KptBadge value={4} size="sm" variant="success" /></span>
31
35
 
32
36
  ## Tokens
33
- The semantic colour pairs of each variant (`--kpt-color-primary` / `--kpt-color-on-primary`,
34
- `-success`, `-danger`, `-warning`, `-info`, `--kpt-color-muted` / `--kpt-color-on-muted`).
37
+ Background `--kpt-color-{primary,success,danger,warning,info}` with the text in the matching `*-contrast` token (`--kpt-color-on-primary` for `primary`) — the same pairs as `kpt-button`; `neutral` is `--kpt-color-muted` / `--kpt-color-on-muted`; the overlay ring comes from `--kpt-color-surface`.
35
38
 
36
39
  ## Accessibility
37
- The badge is text inside the host, so a screen reader reads it together with the content it marks.
38
- For a counter that has to be announced on its own, add your own `aria-label` on the host.
40
+ On its own a badge is just a number, and a `dot` is nothing. `label` gives it a meaning — a visually
41
+ hidden `.kpt-badge__label` that screen readers read instead of the visible text. Without `label`, put
42
+ the meaning into the name of the control the badge sits on. Count changes are not announced. The
43
+ `success`, `warning` and `info` variants draw the text in `--kpt-color-surface`; check the contrast
44
+ in your theme, especially for `warning`.
@@ -1,4 +1,4 @@
1
- import type { ComponentPropsWithRef, ReactNode } from 'react';
1
+ import { type ComponentPropsWithRef, type ReactNode } from 'react';
2
2
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
3
  export interface KptBottomSheetProps extends ComponentPropsWithRef<'div'>, KptI18nOverrides {
4
4
  open: boolean;
@@ -1,5 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
2
3
  import { cn } from "../internal/cn.js";
4
+ import { useKptId } from "../internal/use-id.js";
5
+ import { useModalFocus } from "../internal/use-modal-focus.js";
3
6
  import { useKptMessages } from "../i18n/i18n-context.js";
4
7
  /**
5
8
  * Arkusz wysuwany z dołu ekranu.
@@ -15,8 +18,11 @@ import { useKptMessages } from "../i18n/i18n-context.js";
15
18
  export function KptBottomSheet({ open, onOpenChange, header = '', closeOnBackdrop = true, locale, dictionary, className, children, ...rest }) {
16
19
  const { t } = useKptMessages({ locale, dictionary });
17
20
  const close = () => onOpenChange(false);
18
- return (_jsxs("div", { className: cn('kpt-bottom-sheet-host', className), ...rest, children: [_jsx("div", { className: cn('kpt-bottom-sheet__backdrop', open && 'is-open'), onClick: () => closeOnBackdrop && close() }), _jsxs("div", { className: cn('kpt-bottom-sheet__panel', open && 'is-open'), role: "dialog", "aria-modal": "true", tabIndex: -1, onKeyDown: (event) => {
21
+ const panelRef = useRef(null);
22
+ const titleId = useKptId(undefined, 'kpt-bottom-sheet-title');
23
+ useModalFocus(open, panelRef);
24
+ return (_jsxs("div", { className: cn('kpt-bottom-sheet-host', className), ...rest, children: [_jsx("div", { className: cn('kpt-bottom-sheet__backdrop', open && 'is-open'), onClick: () => closeOnBackdrop && close() }), _jsxs("div", { ref: panelRef, className: cn('kpt-bottom-sheet__panel', open && 'is-open'), role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, tabIndex: -1, onKeyDown: (event) => {
19
25
  if (event.key === 'Escape')
20
26
  close();
21
- }, children: [_jsx("button", { type: "button", className: "kpt-bottom-sheet__handle", onClick: close, "aria-label": t('common.close') }), header ? _jsx("div", { className: "kpt-bottom-sheet__header", children: header }) : null, _jsx("div", { className: "kpt-bottom-sheet__body", children: children })] })] }));
27
+ }, children: [_jsx("button", { type: "button", className: "kpt-bottom-sheet__handle", onClick: close, "aria-label": t('common.close') }), header ? (_jsx("div", { className: "kpt-bottom-sheet__header", id: titleId, children: header })) : null, _jsx("div", { className: "kpt-bottom-sheet__body", children: children })] })] }));
22
28
  }
@@ -31,6 +31,12 @@ The panel and the backdrop are `position: fixed` and the host is `display: conte
31
31
  needs no portal — the same DOM as the Angular port. A transformed ancestor would break the fixed
32
32
  positioning there too.
33
33
 
34
+ ## Focus and keyboard
35
+ Opening moves focus into the panel (its first focusable element, else the panel
36
+ itself) and traps it there — `Tab` / `Shift+Tab` wrap inside, as `aria-modal="true"` promises.
37
+ Closing returns focus to the element that had it before. `Escape` closes the panel. The panel
38
+ is named by its `header` through `aria-labelledby`. The shared helper is `internal/use-modal-focus.ts` (the Angular port has a twin on the CDK focus trap).
39
+
34
40
  ## Examples
35
41
  <KptButton onClick={() => setOpen(true)}>Share</KptButton>
36
42
  <KptBottomSheet open={open} onOpenChange={setOpen} header="Share">…</KptBottomSheet>
@@ -39,5 +45,5 @@ positioning there too.
39
45
  The panel `--kpt-color-surface-raised`, the top radius `--kpt-radius-lg`, `--kpt-elevation-4`.
40
46
 
41
47
  ## Accessibility
42
- `role="dialog"` with `aria-modal`; Escape and the handle close the sheet, and the handle is a real
43
- button labelled from `common.close`.
48
+ `role="dialog"`, `aria-modal` and `aria-labelledby` pointing at the header; the handle is a real button
49
+ labelled from `common.close`. Focus handling — see "Focus and keyboard" above.
@@ -13,8 +13,8 @@ import { KptIcon } from "../icon/icon.js";
13
13
  */
14
14
  export function KptBreadcrumb({ items = [], locale, dictionary, className, ...rest }) {
15
15
  const { t } = useKptMessages({ locale, dictionary });
16
- return (_jsx("div", { className: cn('kpt-breadcrumb-host', className), ...rest, children: _jsx("nav", { className: "kpt-breadcrumb", "aria-label": t('breadcrumb.label'), children: items.map((item, index) => {
17
- const last = index === items.length - 1;
18
- return (_jsxs("span", { style: { display: 'contents' }, children: [item.url && !last ? (_jsx("a", { className: "kpt-breadcrumb__link", href: item.url, children: item.label })) : (_jsx("span", { className: "kpt-breadcrumb__current", "aria-current": last ? 'page' : undefined, children: item.label })), last ? null : _jsx(KptIcon, { name: "chevron-right", className: "kpt-breadcrumb__sep" })] }, index));
19
- }) }) }));
16
+ return (_jsx("div", { className: cn('kpt-breadcrumb-host', className), ...rest, children: _jsx("nav", { className: "kpt-breadcrumb", "aria-label": t('breadcrumb.label'), children: _jsx("ol", { className: "kpt-breadcrumb__list", children: items.map((item, index) => {
17
+ const last = index === items.length - 1;
18
+ return (_jsxs("li", { className: "kpt-breadcrumb__item", children: [item.url && !last ? (_jsx("a", { className: "kpt-breadcrumb__link", href: item.url, children: item.label })) : (_jsx("span", { className: "kpt-breadcrumb__current", "aria-current": last ? 'page' : undefined, children: item.label })), last ? null : _jsx(KptIcon, { name: "chevron-right", className: "kpt-breadcrumb__sep" })] }, index));
19
+ }) }) }) }));
20
20
  }
@@ -5,9 +5,15 @@ A breadcrumb trail. Import: `import { KptBreadcrumb } from '@konce-pt/react';`
5
5
  ## DOM
6
6
  <div class="kpt-breadcrumb-host">
7
7
  <nav class="kpt-breadcrumb" aria-label="breadcrumb">
8
- <a class="kpt-breadcrumb__link" href="/">Home</a>
9
- <span class="kpt-icon-host kpt-breadcrumb__sep">…</span>
10
- <span class="kpt-breadcrumb__current" aria-current="page">Showcase</span>
8
+ <ol class="kpt-breadcrumb__list">
9
+ <li class="kpt-breadcrumb__item">
10
+ <a class="kpt-breadcrumb__link" href="/">Home</a>
11
+ <span class="kpt-icon-host kpt-breadcrumb__sep">…</span>
12
+ </li>
13
+ <li class="kpt-breadcrumb__item">
14
+ <span class="kpt-breadcrumb__current" aria-current="page">Showcase</span>
15
+ </li>
16
+ </ol>
11
17
  </nav>
12
18
  </div>
13
19
 
@@ -31,5 +37,6 @@ The nav label comes from `breadcrumb.label` in the dictionary.
31
37
  (the links).
32
38
 
33
39
  ## Accessibility
34
- A `<nav>` with a label from the dictionary; the current page carries `aria-current="page"`, so a
35
- screen reader can tell the trail from the position in it.
40
+ A `<nav>` landmark labelled from `breadcrumb.label`, holding an ordered list (`<ol>`/`<li>`), so a
41
+ screen reader announces how many levels the trail has. The current page carries
42
+ `aria-current="page"`; separators are icons hidden from assistive technology.
@@ -1,10 +1,18 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
2
  export type KptCardVariant = 'elevated' | 'outlined';
3
+ /** Poziom nagłówka karty w strukturze strony. */
4
+ export type KptCardHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
3
5
  export interface KptCardProps extends ComponentPropsWithRef<'div'> {
4
6
  /** Wariant wizualny. */
5
7
  variant?: KptCardVariant;
6
8
  /** Opcjonalny tekst nagłówka. Dla bogatszego nagłówka użyj `<KptCard.Header>`. */
7
9
  header?: string;
10
+ /**
11
+ * Robi z `header` nagłówek strony (`role="heading"` z `aria-level`), żeby karta pojawiła się
12
+ * w strukturze dokumentu. Bez tego `header` to sam tekst — część kart (np. kafelki statystyk)
13
+ * nie powinna zaśmiecać spisu nagłówków.
14
+ */
15
+ headingLevel?: KptCardHeadingLevel;
8
16
  /** Body bez wewnętrznego paddingu — dla układów zarządzających własnymi sekcjami. */
9
17
  flush?: boolean;
10
18
  }
@@ -21,7 +29,7 @@ export interface KptCardProps extends ComponentPropsWithRef<'div'> {
21
29
  * <KptCard.Footer><KptButton>Zapisz</KptButton></KptCard.Footer>
22
30
  * </KptCard>
23
31
  */
24
- export declare function KptCard({ variant, header, flush, className, children, ...rest }: Readonly<KptCardProps>): import("react").JSX.Element;
32
+ export declare function KptCard({ variant, header, headingLevel, flush, className, children, ...rest }: Readonly<KptCardProps>): import("react").JSX.Element;
25
33
  export declare namespace KptCard {
26
34
  var Header: typeof KptCardHeader;
27
35
  var Footer: typeof KptCardFooter;
package/dist/card/card.js CHANGED
@@ -20,7 +20,7 @@ import { cn } from "../internal/cn.js";
20
20
  * <KptCard.Footer><KptButton>Zapisz</KptButton></KptCard.Footer>
21
21
  * </KptCard>
22
22
  */
23
- export function KptCard({ variant = 'elevated', header, flush = false, className, children, ...rest }) {
23
+ export function KptCard({ variant = 'elevated', header, headingLevel, flush = false, className, children, ...rest }) {
24
24
  const headers = [];
25
25
  const footers = [];
26
26
  const body = [];
@@ -33,7 +33,7 @@ export function KptCard({ variant = 'elevated', header, flush = false, className
33
33
  else
34
34
  body.push(child);
35
35
  });
36
- return (_jsxs("div", { className: cn('kpt-card', className), "data-variant": variant, ...rest, children: [header ? _jsx("div", { className: "kpt-card__header", children: header }) : null, headers, _jsx("div", { className: cn('kpt-card__body', flush && 'kpt-card__body--flush'), children: body }), footers] }));
36
+ return (_jsxs("div", { className: cn('kpt-card', className), "data-variant": variant, ...rest, children: [header ? (_jsx("div", { className: "kpt-card__header", role: headingLevel ? 'heading' : undefined, "aria-level": headingLevel, children: header })) : null, headers, _jsx("div", { className: cn('kpt-card__body', flush && 'kpt-card__body--flush'), children: body }), footers] }));
37
37
  }
38
38
  function slotOf(child) {
39
39
  return child.type?.kptCardSlot ?? 'body';
@@ -15,6 +15,8 @@ Import: `import { KptCard } from '@konce-pt/react';`
15
15
  ## Props
16
16
  - `variant`: 'elevated' | 'outlined' (default 'elevated')
17
17
  - `header`: string — optional header text; use `<KptCard.Header>` for richer content
18
+ - `headingLevel`: 1–6 — makes `header` a heading of that level (`role="heading"` + `aria-level`)
19
+ - `headingLevel`: 1–6 — makes `header` a heading of that level (`role="heading"` + `aria-level`)
18
20
  - `flush`: boolean — a body with no padding (for rich layouts that manage their own sections)
19
21
  - `className`, `ref` and every other `<div>` prop go to the card root
20
22
 
@@ -56,5 +58,7 @@ An overlapping avatar is a class you apply to your own element: `className="kpt-
56
58
  `--kpt-card-{bg,fg,border,radius,padding,shadow}`.
57
59
 
58
60
  ## Accessibility
59
- The card is a plain container with no role of its own — give it heading semantics through your
60
- own content when it needs them.
61
+ The card is a plain container with no role of its own. `header` is styled text by default — set
62
+ `headingLevel` (1–6) to make it a heading (`role="heading"` + `aria-level`) when the card should
63
+ appear in the page outline; leave it off for tiles that would only clutter it. Give media images a
64
+ meaningful `alt`, or `alt=""` when decorative.
@@ -7,6 +7,8 @@ export interface KptCarouselProps<T> extends Omit<ComponentPropsWithRef<'div'>,
7
7
  onActiveIndexChange?: (index: number) => void;
8
8
  /** Szablon slajdu — odpowiednik `<ng-template let-item let-i="index">` z Angulara. */
9
9
  renderSlide: (item: T, index: number) => ReactNode;
10
+ /** Nazwa regionu dla czytników ekranu, np. „Nowości”; domyślnie `carousel.label`. */
11
+ label?: string;
10
12
  }
11
13
  /**
12
14
  * Karuzela slajdów. Dane przez `items`, wygląd slajdu przez `renderSlide`.
@@ -14,7 +16,12 @@ export interface KptCarouselProps<T> extends Omit<ComponentPropsWithRef<'div'>,
14
16
  * Tor przesuwa się jednym `translateX` o wielokrotność 100%, więc slajdy jadą płynnie, a każdy
15
17
  * zostaje zamontowany — przewijanie nie gubi stanu treści.
16
18
  *
19
+ * Semantyka według wzorca WAI-ARIA „carousel” (bez autoodtwarzania): region z
20
+ * `aria-roledescription="karuzela"`, każdy slajd to grupa „Slajd 2 z 4”, a slajdy poza widokiem są
21
+ * `inert` — `Tab` i czytnik ich nie widzą. Zmiana slajdu jest ogłaszana uprzejmie (`aria-live`
22
+ * na torze). Strzałki na końcach dostają `aria-disabled` zamiast `disabled`, żeby nie zgubić fokusu.
23
+ *
17
24
  * @example
18
25
  * <KptCarousel items={photos} renderSlide={(photo) => <img src={photo} style={{ width: '100%' }} />} />
19
26
  */
20
- export declare function KptCarousel<T>({ items, activeIndex, defaultActiveIndex, onActiveIndexChange, renderSlide, locale, dictionary, className, ...rest }: Readonly<KptCarouselProps<T>>): import("react").JSX.Element;
27
+ export declare function KptCarousel<T>({ items, activeIndex, defaultActiveIndex, onActiveIndexChange, renderSlide, label, locale, dictionary, className, ...rest }: Readonly<KptCarouselProps<T>>): import("react").JSX.Element;
@@ -9,10 +9,15 @@ import { KptIcon } from "../icon/icon.js";
9
9
  * Tor przesuwa się jednym `translateX` o wielokrotność 100%, więc slajdy jadą płynnie, a każdy
10
10
  * zostaje zamontowany — przewijanie nie gubi stanu treści.
11
11
  *
12
+ * Semantyka według wzorca WAI-ARIA „carousel” (bez autoodtwarzania): region z
13
+ * `aria-roledescription="karuzela"`, każdy slajd to grupa „Slajd 2 z 4”, a slajdy poza widokiem są
14
+ * `inert` — `Tab` i czytnik ich nie widzą. Zmiana slajdu jest ogłaszana uprzejmie (`aria-live`
15
+ * na torze). Strzałki na końcach dostają `aria-disabled` zamiast `disabled`, żeby nie zgubić fokusu.
16
+ *
12
17
  * @example
13
18
  * <KptCarousel items={photos} renderSlide={(photo) => <img src={photo} style={{ width: '100%' }} />} />
14
19
  */
15
- export function KptCarousel({ items = [], activeIndex, defaultActiveIndex = 0, onActiveIndexChange, renderSlide, locale, dictionary, className, ...rest }) {
20
+ export function KptCarousel({ items = [], activeIndex, defaultActiveIndex = 0, onActiveIndexChange, renderSlide, label, locale, dictionary, className, ...rest }) {
16
21
  const { t } = useKptMessages({ locale, dictionary });
17
22
  const [active, setActive] = useControllableState({
18
23
  value: activeIndex,
@@ -20,5 +25,7 @@ export function KptCarousel({ items = [], activeIndex, defaultActiveIndex = 0, o
20
25
  onChange: onActiveIndexChange,
21
26
  });
22
27
  const goTo = (index) => setActive(Math.min(Math.max(index, 0), items.length - 1));
23
- return (_jsx("div", { className: cn('kpt-carousel-host', className), ...rest, children: _jsxs("div", { className: "kpt-carousel", children: [_jsx("div", { className: "kpt-carousel__viewport", children: _jsx("div", { className: "kpt-carousel__track", style: { transform: `translateX(-${active * 100}%)` }, children: items.map((item, index) => (_jsx("div", { className: "kpt-carousel__slide", children: renderSlide(item, index) }, index))) }) }), _jsx("button", { type: "button", className: "kpt-carousel__nav kpt-carousel__nav--prev", disabled: active === 0, "aria-label": t('common.previous'), onClick: () => goTo(active - 1), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsx("button", { type: "button", className: "kpt-carousel__nav kpt-carousel__nav--next", disabled: active >= items.length - 1, "aria-label": t('common.next'), onClick: () => goTo(active + 1), children: _jsx(KptIcon, { name: "chevron-right" }) }), _jsx("div", { className: "kpt-carousel__dots", children: items.map((_, index) => (_jsx("button", { type: "button", className: cn('kpt-carousel__dot', index === active && 'is-active'), "aria-label": t('carousel.slide', { index: index + 1 }), onClick: () => goTo(index) }, index))) })] }) }));
28
+ const atStart = active <= 0;
29
+ const atEnd = active >= items.length - 1;
30
+ return (_jsx("div", { className: cn('kpt-carousel-host', className), ...rest, children: _jsxs("div", { className: "kpt-carousel", role: "region", "aria-roledescription": t('carousel.roleCarousel'), "aria-label": label || t('carousel.label'), children: [_jsx("div", { className: "kpt-carousel__viewport", children: _jsx("div", { className: "kpt-carousel__track", "aria-live": "polite", style: { transform: `translateX(calc(var(--_kpt-carousel-dir, 1) * -${active * 100}%))` }, children: items.map((item, index) => (_jsx("div", { className: "kpt-carousel__slide", role: "group", "aria-roledescription": t('carousel.roleSlide'), "aria-label": t('carousel.slideOf', { index: index + 1, total: items.length }), inert: index !== active, "aria-hidden": index === active ? undefined : true, children: renderSlide(item, index) }, index))) }) }), _jsx("button", { type: "button", className: "kpt-carousel__nav kpt-carousel__nav--prev", "aria-disabled": atStart || undefined, "aria-label": t('common.previous'), onClick: () => goTo(active - 1), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsx("button", { type: "button", className: "kpt-carousel__nav kpt-carousel__nav--next", "aria-disabled": atEnd || undefined, "aria-label": t('common.next'), onClick: () => goTo(active + 1), children: _jsx(KptIcon, { name: "chevron-right" }) }), _jsx("div", { className: "kpt-carousel__dots", children: items.map((_, index) => (_jsx("button", { type: "button", className: cn('kpt-carousel__dot', index === active && 'is-active'), "aria-current": index === active ? 'true' : undefined, "aria-label": t('carousel.slide', { index: index + 1 }), onClick: () => goTo(index) }, index))) })] }) }));
24
31
  }
@@ -5,15 +5,21 @@ Import: `import { KptCarousel } from '@konce-pt/react';`
5
5
 
6
6
  ## DOM
7
7
  <div class="kpt-carousel-host">
8
- <div class="kpt-carousel">
8
+ <div class="kpt-carousel" role="region" aria-roledescription="carousel" aria-label="Carousel">
9
9
  <div class="kpt-carousel__viewport">
10
- <div class="kpt-carousel__track" style="transform:translateX(-100%)">
11
- <div class="kpt-carousel__slide">…</div>…
10
+ <div class="kpt-carousel__track" aria-live="polite"
11
+ style="transform:translateX(calc(var(--_kpt-carousel-dir, 1) * -100%))">
12
+ <div class="kpt-carousel__slide" role="group" aria-roledescription="slide"
13
+ aria-label="Slide 1 of 3" inert aria-hidden="true">…</div>
14
+ <div class="kpt-carousel__slide" role="group" aria-roledescription="slide"
15
+ aria-label="Slide 2 of 3">…</div>…
12
16
  </div>
13
17
  </div>
14
- <button class="kpt-carousel__nav kpt-carousel__nav--prev">…</button>
15
- <button class="kpt-carousel__nav kpt-carousel__nav--next">…</button>
16
- <div class="kpt-carousel__dots"><button class="kpt-carousel__dot is-active"></button>…</div>
18
+ <button class="kpt-carousel__nav kpt-carousel__nav--prev" aria-label="Previous">…</button>
19
+ <button class="kpt-carousel__nav kpt-carousel__nav--next" aria-label="Next">…</button>
20
+ <div class="kpt-carousel__dots">
21
+ <button class="kpt-carousel__dot is-active" aria-current="true" aria-label="Slide 2"></button>…
22
+ </div>
17
23
  </div>
18
24
  </div>
19
25
 
@@ -21,6 +27,7 @@ Import: `import { KptCarousel } from '@konce-pt/react';`
21
27
  - `items`: `T[]`
22
28
  - `activeIndex` / `defaultActiveIndex` / `onActiveIndexChange`
23
29
  - `renderSlide(item, index)` — required
30
+ - `label`: string — the region name; empty = `carousel.label`
24
31
  - `className`, `ref` and every other `<div>` prop go to the host
25
32
 
26
33
  ## The render prop replaces the template
@@ -37,11 +44,18 @@ paging does not lose state inside a slide (a form, a playing video, a scroll pos
37
44
  <KptCarousel items={slides} activeIndex={index} onActiveIndexChange={setIndex} renderSlide={renderSlide} />
38
45
 
39
46
  ## i18n
40
- The arrows use `common.previous` / `common.next`, the dots `carousel.slide` (`{index}`).
47
+ The arrows use `common.previous` / `common.next`, the dots `carousel.slide` (`{index}`); the region `carousel.label`, `carousel.roleCarousel`, the slides `carousel.slideOf` (`{index}`, `{total}`) and `carousel.roleSlide`.
41
48
 
42
49
  ## Tokens
43
- The navigation and dots `--kpt-color-primary`, the arrow background `--kpt-color-surface-raised`.
50
+ The active dot `--kpt-color-primary`; the arrows `--kpt-color-surface` (80% via `color-mix`, solid on hover) with `--kpt-elevation-2`; the slide transition `--kpt-motion-duration-base`.
44
51
 
45
52
  ## Accessibility
46
- The arrows are disabled at the ends instead of wrapping around, and every dot is a labelled button,
47
- so a slide can be reached directly from the keyboard.
53
+ The WAI-ARIA carousel pattern without autoplay. The root is `role="region"` with
54
+ `aria-roledescription` from `carousel.roleCarousel` (“carousel”) and a name from `label` or
55
+ `carousel.label`. Each slide is `role="group"` with `aria-roledescription` from `carousel.roleSlide`
56
+ and a name from `carousel.slideOf` (“Slide 2 of 4”); slides outside the view are `inert` and
57
+ `aria-hidden`, so Tab never lands on an invisible link. The track is `aria-live="polite"`, so the
58
+ slide that comes in is announced. The arrows use `aria-disabled` at the ends instead of `disabled`,
59
+ so they keep the focus; the active dot carries `aria-current`, and every dot has a 24 px target
60
+ (WCAG 2.5.8) with an 8 px visible dot. The transition is off under `prefers-reduced-motion`; in RTL
61
+ the track and the chevrons follow the reading direction (`:dir(rtl)`).
@@ -5,6 +5,11 @@ export interface KptCheckboxProps extends Omit<ComponentPropsWithRef<'input'>, '
5
5
  label?: string;
6
6
  /** Oznacza pole jako wymagane atrybutem `aria-required`, a NIE natywnym `required`. */
7
7
  required?: boolean;
8
+ /**
9
+ * Stan pośredni („część zaznaczona”) — dla pola „zaznacz wszystko” nad listą. To właściwość DOM,
10
+ * nie atrybut, więc ustawia ją komponent; czytnik ogłasza „częściowo zaznaczone”.
11
+ */
12
+ indeterminate?: boolean;
8
13
  /** Pole jest nieprawidłowe. Widoczne dopiero, gdy `touched` też jest prawdziwe. */
9
14
  invalid?: boolean;
10
15
  /** Czy pole było dotknięte; pominięty = stan błędu pokazuje się od razu po `invalid`. */
@@ -30,4 +35,4 @@ export interface KptCheckboxProps extends Omit<ComponentPropsWithRef<'input'>, '
30
35
  * <KptCheckbox name="terms" label="Akceptuję regulamin" />
31
36
  * <KptCheckbox checked={on} onCheckedChange={setOn} />
32
37
  */
33
- export declare function KptCheckbox({ label, required, invalid, touched, onCheckedChange, onTouch, hostProps, className, disabled, onChange, onBlur, children, 'aria-invalid': ariaInvalid, ...rest }: Readonly<KptCheckboxProps>): import("react").JSX.Element;
38
+ export declare function KptCheckbox({ label, required, invalid, touched, onCheckedChange, onTouch, hostProps, className, disabled, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, indeterminate, ref, ...rest }: Readonly<KptCheckboxProps>): import("react").JSX.Element;
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback } from 'react';
2
+ import { useCallback, useEffect, useRef } from 'react';
3
+ import { composeRefs } from "../internal/compose-refs.js";
3
4
  import { cn } from "../internal/cn.js";
5
+ import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
4
6
  /**
5
7
  * Pole wyboru (checkbox). Cały układ niesie `<label>`, więc kliknięcie w etykietę przełącza pole
6
8
  * bez `htmlFor` — natywny `<input>` w środku odpowiada za semantykę i klawiaturę.
@@ -14,7 +16,13 @@ import { cn } from "../internal/cn.js";
14
16
  * <KptCheckbox name="terms" label="Akceptuję regulamin" />
15
17
  * <KptCheckbox checked={on} onCheckedChange={setOn} />
16
18
  */
17
- export function KptCheckbox({ label = '', required = false, invalid = false, touched, onCheckedChange, onTouch, hostProps, className, disabled = false, onChange, onBlur, children, 'aria-invalid': ariaInvalid, ...rest }) {
19
+ export function KptCheckbox({ label = '', required = false, invalid = false, touched, onCheckedChange, onTouch, hostProps, className, disabled = false, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, indeterminate = false, ref, ...rest }) {
20
+ const inputRef = useRef(null);
21
+ useEffect(() => {
22
+ if (inputRef.current)
23
+ inputRef.current.indeterminate = indeterminate;
24
+ }, [indeterminate]);
25
+ const field = useKptFieldA11y(id, 'kpt-checkbox');
18
26
  const { className: hostClassName, ...hostRest } = hostProps ?? {};
19
27
  const showInvalid = invalid && (touched ?? true);
20
28
  const handleChange = useCallback((event) => {
@@ -25,5 +33,5 @@ export function KptCheckbox({ label = '', required = false, invalid = false, tou
25
33
  onBlur?.(event);
26
34
  onTouch?.();
27
35
  }, [onBlur, onTouch]);
28
- return (_jsx("span", { className: cn('kpt-checkbox-host', hostClassName), ...hostRest, children: _jsxs("label", { className: cn('kpt-checkbox', disabled && 'kpt-checkbox--disabled', className), children: [_jsx("input", { type: "checkbox", className: "kpt-checkbox__input", disabled: disabled, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }), _jsx("span", { className: "kpt-checkbox__box", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 16 16", className: "kpt-checkbox__check", children: _jsx("path", { d: "M13 4.5 6.5 11 3 7.5" }) }) }), label ? _jsx("span", { className: "kpt-checkbox__label", children: label }) : null, children] }) }));
36
+ return (_jsx("span", { className: cn('kpt-checkbox-host', hostClassName), ...hostRest, children: _jsxs("label", { className: cn('kpt-checkbox', disabled && 'kpt-checkbox--disabled', className), children: [_jsx("input", { type: "checkbox", className: "kpt-checkbox__input", ref: composeRefs(inputRef, ref), id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), disabled: disabled, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }), _jsxs("span", { className: "kpt-checkbox__box", "aria-hidden": "true", children: [_jsx("svg", { viewBox: "0 0 16 16", className: "kpt-checkbox__check", children: _jsx("path", { d: "M13 4.5 6.5 11 3 7.5" }) }), _jsx("svg", { viewBox: "0 0 16 16", className: "kpt-checkbox__dash", children: _jsx("path", { d: "M4 8h8" }) })] }), label ? _jsx("span", { className: "kpt-checkbox__label", children: label }) : null, children] }) }));
29
37
  }
@@ -29,6 +29,17 @@ Import: `import { KptCheckbox } from '@konce-pt/react';`
29
29
  <KptCheckbox checked={on} onCheckedChange={setOn} />
30
30
  <KptCheckbox label="Newsletter" invalid={!!err} touched={blurred} />
31
31
 
32
+ ## Screen reader and keyboard
33
+ `indeterminate`: boolean — the "some selected" state for a "select all" box (a dash in the box;
34
+ screen readers say "partially checked"). It is a DOM property, so the component sets it; the browser
35
+ clears it on click, so set it to `false` in `onCheckedChange`.
36
+
37
+ ## Label and description
38
+ `id` goes to the native field, not the host; without one it comes from `KptFormField` or is
39
+ generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
40
+ added to `aria-describedby` (merged with your own). Without a wrapper, name it with `aria-label`.
41
+ Inside `kpt-form-field` the box gets both names: the wrapper label and its own `label`.
42
+
32
43
  ## Tokens
33
44
  `--kpt-form-field-border`, `--kpt-color-primary` (the checked box), `--kpt-color-on-primary`
34
45
  (the tick), `--kpt-form-field-border-error`.
package/dist/chip/chip.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../internal/cn.js";
3
+ import { useKptId } from "../internal/use-id.js";
3
4
  import { KptIcon } from "../icon/icon.js";
4
5
  import { useKptMessages } from "../i18n/i18n-context.js";
5
6
  /**
@@ -13,5 +14,13 @@ import { useKptMessages } from "../i18n/i18n-context.js";
13
14
  export function KptChip({ variant = 'neutral', textTransform = 'none', removable = false, disabled = false, onRemove, locale, dictionary, hostProps, className, children, ...rest }) {
14
15
  const { t } = useKptMessages({ locale, dictionary });
15
16
  const { className: hostClassName, ...hostRest } = hostProps ?? {};
16
- return (_jsx("span", { className: cn('kpt-chip-host', hostClassName), ...hostRest, children: _jsxs("span", { className: cn('kpt-chip', disabled && 'kpt-chip--disabled', className), "data-variant": variant, "data-text-transform": textTransform === 'none' ? undefined : textTransform, ...rest, children: [children, removable ? (_jsx("button", { type: "button", className: "kpt-chip__remove", disabled: disabled, onClick: onRemove, "aria-label": t('common.remove'), children: _jsx(KptIcon, { name: "x" }) })) : null] }) }));
17
+ const uid = useKptId(undefined, 'kpt-chip');
18
+ const labelId = `${uid}-label`;
19
+ const removeId = `${uid}-remove`;
20
+ return (_jsx("span", { className: cn('kpt-chip-host', hostClassName), ...hostRest, children: _jsxs("span", { className: cn('kpt-chip', disabled && 'kpt-chip--disabled', className), "data-variant": variant, "data-text-transform": textTransform === 'none' ? undefined : textTransform, ...rest, children: [_jsx("span", { className: "kpt-chip__label", id: labelId, children: children }), removable ? (_jsx("button", { type: "button", className: "kpt-chip__remove", id: removeId, disabled: disabled, onClick: onRemove, onKeyDown: (event) => {
21
+ if (event.key !== 'Backspace' && event.key !== 'Delete')
22
+ return;
23
+ event.preventDefault();
24
+ onRemove?.();
25
+ }, "aria-label": t('common.remove'), "aria-labelledby": `${removeId} ${labelId}`, children: _jsx(KptIcon, { name: "x" }) })) : null] }) }));
17
26
  }
@@ -6,8 +6,9 @@ Import: `import { KptChip } from '@konce-pt/react';`
6
6
  ## DOM
7
7
  <span class="kpt-chip-host">
8
8
  <span class="kpt-chip" data-variant="primary">
9
- React
10
- <button type="button" class="kpt-chip__remove" aria-label="Remove">…icon…</button>
9
+ <span class="kpt-chip__label" id="kpt-chip-r1-label">React</span>
10
+ <button type="button" class="kpt-chip__remove" id="kpt-chip-r1-remove" aria-label="Remove"
11
+ aria-labelledby="kpt-chip-r1-remove kpt-chip-r1-label">…icon…</button>
11
12
  </span>
12
13
  </span>
13
14
 
@@ -46,5 +47,7 @@ spelling it out in the content is the exact route.
46
47
  (`--kpt-color-primary` / `-subtle`, `-success`, `-danger`, `-warning`, `-info`).
47
48
 
48
49
  ## Accessibility
49
- The remove button is a native `<button>` with `aria-label`, so it is reachable by keyboard and goes
50
- `disabled` together with the chip.
50
+ The label sits in `.kpt-chip__label`, and the remove button is named by `aria-labelledby` from
51
+ itself plus that label — “Remove Angular” — so with several chips a screen reader user hears which
52
+ one goes. `Backspace` and `Delete` on the button work like a click. After removal focus falls back
53
+ to the page; move it to the next chip yourself.
@@ -7,6 +7,12 @@ export interface KptChipsInputProps extends Omit<ComponentPropsWithRef<'div'>, '
7
7
  /** Placeholder; pominięty = `chipsInput.placeholder` ze słownika. */
8
8
  placeholder?: string;
9
9
  allowDuplicates?: boolean;
10
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
11
+ id?: string;
12
+ /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
13
+ ariaLabel?: string | null;
14
+ /** Oznacza pole jako wymagane atrybutem `aria-required`. */
15
+ required?: boolean;
10
16
  disabled?: boolean;
11
17
  invalid?: boolean;
12
18
  touched?: boolean;
@@ -22,4 +28,4 @@ export interface KptChipsInputProps extends Omit<ComponentPropsWithRef<'div'>, '
22
28
  * @example
23
29
  * <KptChipsInput value={tags} onValueChange={setTags} placeholder="Dodaj tag…" />
24
30
  */
25
- export declare function KptChipsInput({ value, defaultValue, onValueChange, placeholder, allowDuplicates, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptChipsInputProps>): import("react").JSX.Element;
31
+ export declare function KptChipsInput({ value, defaultValue, onValueChange, placeholder, allowDuplicates, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptChipsInputProps>): import("react").JSX.Element;
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef } from 'react';
2
+ import { useRef, useState } from 'react';
3
3
  import { cn } from "../internal/cn.js";
4
4
  import { useControllableState } from "../internal/use-controllable-state.js";
5
5
  import { useKptMessages } from "../i18n/i18n-context.js";
6
6
  import { KptChip } from "../chip/chip.js";
7
+ import { useKptFieldA11y } from "../form-field/form-field-context.js";
7
8
  /**
8
9
  * Pole tagów — dodawanie Enterem albo przecinkiem, usuwanie Backspace lub ×.
9
10
  *
@@ -14,8 +15,9 @@ import { KptChip } from "../chip/chip.js";
14
15
  * @example
15
16
  * <KptChipsInput value={tags} onValueChange={setTags} placeholder="Dodaj tag…" />
16
17
  */
17
- export function KptChipsInput({ value, defaultValue = [], onValueChange, placeholder, allowDuplicates = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
18
+ export function KptChipsInput({ value, defaultValue = [], onValueChange, placeholder, allowDuplicates = false, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
18
19
  const { t } = useKptMessages({ locale, dictionary });
20
+ const field = useKptFieldA11y(id, 'kpt-chips-input');
19
21
  const inputRef = useRef(null);
20
22
  const [chips, setChips] = useControllableState({
21
23
  value,
@@ -23,16 +25,26 @@ export function KptChipsInput({ value, defaultValue = [], onValueChange, placeho
23
25
  onChange: onValueChange,
24
26
  });
25
27
  const showInvalid = invalid && (touched ?? true);
28
+ /** Komunikat regionu `polite`: dodanie, usunięcie albo odrzucony duplikat. */
29
+ const [announcement, setAnnouncement] = useState('');
26
30
  const commit = (el) => {
27
31
  const text = el.value.trim();
28
32
  if (!text)
29
33
  return;
30
- if (allowDuplicates || !chips.includes(text))
31
- setChips([...chips, text]);
34
+ if (!allowDuplicates && chips.includes(text)) {
35
+ // Duplikat zostaje w polu (do poprawy), a czytnik słyszy, dlaczego nic nie przybyło.
36
+ setAnnouncement(t('chipsInput.duplicate', { value: text }));
37
+ return;
38
+ }
39
+ setChips([...chips, text]);
40
+ setAnnouncement(t('chipsInput.added', { value: text }));
32
41
  el.value = '';
33
42
  };
34
43
  const removeAt = (index) => {
44
+ const removed = chips[index];
35
45
  setChips(chips.filter((_, i) => i !== index));
46
+ if (removed !== undefined)
47
+ setAnnouncement(t('chipsInput.removed', { value: removed }));
36
48
  onTouch?.();
37
49
  };
38
50
  const onKeyDown = (event) => {
@@ -50,5 +62,9 @@ export function KptChipsInput({ value, defaultValue = [], onValueChange, placeho
50
62
  commit(event.currentTarget);
51
63
  onTouch?.();
52
64
  };
53
- return (_jsx("div", { className: cn('kpt-chips-input-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-chips-input', disabled && 'kpt-chips-input--disabled', showInvalid && 'kpt-chips-input--invalid'), children: [chips.map((chip, index) => (_jsx(KptChip, { variant: "primary", removable: !disabled, onRemove: () => removeAt(index), children: chip }, index))), _jsx("input", { ref: inputRef, className: "kpt-chips-input__field", type: "text", placeholder: chips.length ? '' : (placeholder ?? t('chipsInput.placeholder')), disabled: disabled, onKeyDown: onKeyDown, onBlur: onBlur })] }) }));
65
+ return (_jsx("div", { className: cn('kpt-chips-input-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-chips-input', disabled && 'kpt-chips-input--disabled', showInvalid && 'kpt-chips-input--invalid'), children: [_jsx("span", { className: "kpt-chips-input__list", role: "list", children: chips.map((chip, index) => (_jsx("span", { className: "kpt-chips-input__item", role: "listitem", children: _jsx(KptChip, { variant: "primary", removable: !disabled, onRemove: () => {
66
+ removeAt(index);
67
+ // Przycisk usuwania znika razem z tagiem — fokus wraca do pola, nie na <body>.
68
+ inputRef.current?.focus();
69
+ }, children: chip }) }, index))) }), _jsx("input", { ref: inputRef, className: "kpt-chips-input__field", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-describedby": field.describedBy, type: "text", placeholder: chips.length ? '' : (placeholder ?? t('chipsInput.placeholder')), disabled: disabled, onKeyDown: onKeyDown, onBlur: onBlur }), _jsx("span", { className: "kpt-chips-input__live", "aria-live": "polite", children: announcement })] }) }));
54
70
  }
@@ -32,6 +32,18 @@ lost by clicking away.
32
32
  <KptChipsInput value={tags} onValueChange={setTags} placeholder="Add a tag…" />
33
33
  <KptChipsInput value={tags} onValueChange={setTags} allowDuplicates />
34
34
 
35
+ ## Screen reader and keyboard
36
+ The tags are a list (`role="list"`/`listitem`), so a screen reader hears how many there are. Adding,
37
+ removing (also with `Backspace`) and a rejected duplicate are announced by a polite live region
38
+ (`chipsInput.added`/`removed`/`duplicate`); a duplicate stays in the field to be corrected instead
39
+ of vanishing. After removing a tag with its `×`, focus returns to the text field.
40
+
41
+ ## Label and description
42
+ `id` goes to the native field, not the host; without one it comes from `KptFormField` or is
43
+ generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
44
+ added to `aria-describedby` (merged with your own). Without a wrapper, name it with `ariaLabel`.
45
+ New: `id`, `ariaLabel`, `required` and `aria-invalid` on the text field.
46
+
35
47
  ## Tokens
36
48
  `--kpt-form-field-{bg,border,border-focus,border-error,radius}` plus the chip tokens.
37
49
 
@@ -1,11 +1,24 @@
1
- import type { ComponentPropsWithRef } from 'react';
2
- export interface KptColorPickerProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'> {
1
+ import { type ComponentPropsWithRef } from 'react';
2
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
+ /** Gotowy kolor z nazwą — nazwa trafia do `aria-label` próbki. */
4
+ export interface KptColorPreset {
5
+ value: string;
6
+ label: string;
7
+ }
8
+ export interface KptColorPickerProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
3
9
  /** Kolor jako hex, np. '#3b82f6'. */
4
10
  value?: string;
5
11
  defaultValue?: string;
6
12
  onValueChange?: (value: string) => void;
7
- /** Paleta szybkiego wyboru. */
8
- presets?: readonly string[];
13
+ /**
14
+ * Paleta szybkiego wyboru: kody albo `{ value, label }`. Kod sam w sobie niewiele mówi
15
+ * czytnikowi („#3b82f6”), więc własną paletę warto nazwać; domyślne kolory mają nazwy z i18n.
16
+ */
17
+ presets?: readonly (string | KptColorPreset)[];
18
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
19
+ id?: string;
20
+ /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
21
+ ariaLabel?: string | null;
9
22
  disabled?: boolean;
10
23
  invalid?: boolean;
11
24
  touched?: boolean;
@@ -20,4 +33,4 @@ export interface KptColorPickerProps extends Omit<ComponentPropsWithRef<'div'>,
20
33
  * @example
21
34
  * <KptColorPicker value={color} onValueChange={setColor} />
22
35
  */
23
- export declare function KptColorPicker({ value, defaultValue, onValueChange, presets, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptColorPickerProps>): import("react").JSX.Element;
36
+ export declare function KptColorPicker({ value, defaultValue, onValueChange, presets, id, ariaLabel, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptColorPickerProps>): import("react").JSX.Element;