@konce-pt/react 0.8.1 → 0.8.3

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 (123) hide show
  1. package/README.md +381 -27
  2. package/dist/accordion/llms.txt +56 -0
  3. package/dist/alert/llms.txt +45 -0
  4. package/dist/app-shell/app-shell.d.ts +20 -0
  5. package/dist/app-shell/app-shell.js +24 -0
  6. package/dist/app-shell/llms.txt +59 -0
  7. package/dist/auth/auth.d.ts +1 -1
  8. package/dist/auth/llms.txt +65 -0
  9. package/dist/autocomplete/autocomplete.d.ts +1 -1
  10. package/dist/autocomplete/llms.txt +44 -0
  11. package/dist/avatar/llms.txt +42 -0
  12. package/dist/avatar-group/avatar-group.d.ts +1 -1
  13. package/dist/avatar-group/llms.txt +38 -0
  14. package/dist/badge/llms.txt +38 -0
  15. package/dist/bottom-sheet/llms.txt +43 -0
  16. package/dist/breadcrumb/breadcrumb.d.ts +1 -1
  17. package/dist/breadcrumb/llms.txt +35 -0
  18. package/dist/button/llms.txt +56 -0
  19. package/dist/button-group/button-group.d.ts +1 -1
  20. package/dist/button-group/llms.txt +40 -0
  21. package/dist/card/llms.txt +60 -0
  22. package/dist/carousel/carousel.d.ts +1 -1
  23. package/dist/carousel/llms.txt +47 -0
  24. package/dist/charts/chart-legend.d.ts +1 -1
  25. package/dist/charts/llms.txt +57 -0
  26. package/dist/checkbox/llms.txt +39 -0
  27. package/dist/chip/llms.txt +41 -0
  28. package/dist/chips-input/llms.txt +40 -0
  29. package/dist/clock/llms.txt +50 -0
  30. package/dist/color-picker/color-picker.d.ts +1 -1
  31. package/dist/color-picker/llms.txt +40 -0
  32. package/dist/confirm/llms.txt +44 -0
  33. package/dist/context-menu/context-menu.d.ts +1 -1
  34. package/dist/context-menu/llms.txt +39 -0
  35. package/dist/data-table/data-table-types.d.ts +1 -1
  36. package/dist/data-table/data-table.d.ts +2 -10
  37. package/dist/data-table/data-table.js +17 -9
  38. package/dist/data-table/llms.txt +79 -0
  39. package/dist/data-view/data-view.d.ts +1 -1
  40. package/dist/data-view/llms.txt +49 -0
  41. package/dist/date-range/llms.txt +54 -0
  42. package/dist/datepicker/llms.txt +74 -0
  43. package/dist/dialog/llms.txt +51 -0
  44. package/dist/divider/llms.txt +28 -0
  45. package/dist/drawer/llms.txt +47 -0
  46. package/dist/empty/llms.txt +35 -0
  47. package/dist/fab/llms.txt +37 -0
  48. package/dist/fieldset/llms.txt +43 -0
  49. package/dist/file-upload/llms.txt +48 -0
  50. package/dist/form-field/llms.txt +55 -0
  51. package/dist/galleria/galleria.d.ts +1 -1
  52. package/dist/galleria/llms.txt +46 -0
  53. package/dist/grid/llms.txt +110 -0
  54. package/dist/icon/icon-registry.d.ts +5 -2
  55. package/dist/icon/icon-registry.js +5 -3
  56. package/dist/icon/llms.txt +66 -0
  57. package/dist/icon-button/llms.txt +38 -0
  58. package/dist/icons-entry/public-api.d.ts +5 -1
  59. package/dist/icons-entry/public-api.js +7 -1
  60. package/dist/image/llms.txt +46 -0
  61. package/dist/input/llms.txt +54 -0
  62. package/dist/input-mask/llms.txt +37 -0
  63. package/dist/input-number/llms.txt +44 -0
  64. package/dist/input-otp/llms.txt +43 -0
  65. package/dist/internal/use-breakpoint.js +18 -10
  66. package/dist/knob/llms.txt +46 -0
  67. package/dist/listbox/listbox.d.ts +1 -1
  68. package/dist/listbox/llms.txt +39 -0
  69. package/dist/map/llms.txt +108 -0
  70. package/dist/megamenu/llms.txt +48 -0
  71. package/dist/megamenu/megamenu.d.ts +1 -1
  72. package/dist/menu/llms.txt +56 -0
  73. package/dist/menu/menu-item.d.ts +1 -1
  74. package/dist/menu/menu-rows.d.ts +1 -1
  75. package/dist/menu/menu.d.ts +1 -1
  76. package/dist/menu/menu.js +12 -1
  77. package/dist/menubar/llms.txt +45 -0
  78. package/dist/menubar/menubar.d.ts +1 -1
  79. package/dist/meter-group/llms.txt +47 -0
  80. package/dist/meter-group/meter-group.d.ts +1 -1
  81. package/dist/order-list/llms.txt +41 -0
  82. package/dist/paginator/llms.txt +48 -0
  83. package/dist/paginator/paginator.d.ts +1 -1
  84. package/dist/paginator/paginator.js +7 -1
  85. package/dist/panel/llms.txt +55 -0
  86. package/dist/password/llms.txt +45 -0
  87. package/dist/pick-list/llms.txt +52 -0
  88. package/dist/popover/llms.txt +42 -0
  89. package/dist/progress/llms.txt +47 -0
  90. package/dist/public-api.d.ts +2 -1
  91. package/dist/public-api.js +1 -1
  92. package/dist/radio-group/llms.txt +40 -0
  93. package/dist/radio-group/radio-group.d.ts +1 -1
  94. package/dist/rating/llms.txt +43 -0
  95. package/dist/rich-text/llms.txt +61 -0
  96. package/dist/rich-text/rich-text.d.ts +1 -1
  97. package/dist/roadmap/llms.txt +82 -0
  98. package/dist/scroll-top/llms.txt +41 -0
  99. package/dist/select/llms.txt +56 -0
  100. package/dist/select/select.d.ts +1 -1
  101. package/dist/sidenav/llms.txt +53 -0
  102. package/dist/skeleton/llms.txt +33 -0
  103. package/dist/slider/llms.txt +50 -0
  104. package/dist/speed-dial/llms.txt +46 -0
  105. package/dist/speed-dial/speed-dial.d.ts +1 -1
  106. package/dist/spinner/llms.txt +33 -0
  107. package/dist/split-button/llms.txt +54 -0
  108. package/dist/split-button/split-button.d.ts +1 -1
  109. package/dist/splitter/llms.txt +44 -0
  110. package/dist/stepper/llms.txt +64 -0
  111. package/dist/switch/llms.txt +42 -0
  112. package/dist/switch-group/llms.txt +43 -0
  113. package/dist/switch-group/switch-group.d.ts +1 -1
  114. package/dist/tabs/llms.txt +48 -0
  115. package/dist/textarea/llms.txt +40 -0
  116. package/dist/timeline/llms.txt +53 -0
  117. package/dist/timeline/timeline.d.ts +1 -1
  118. package/dist/toast/llms.txt +52 -0
  119. package/dist/toolbar/llms.txt +45 -0
  120. package/dist/tooltip/llms.txt +42 -0
  121. package/dist/tree/llms.txt +45 -0
  122. package/dist/tree/tree.d.ts +2 -2
  123. package/package.json +11 -11
@@ -0,0 +1,66 @@
1
+ # KptIcon (@konce-pt/react)
2
+
3
+ An inline SVG icon (Tabler Icons — MIT). The size follows `font-size` (1em) or the `size` prop;
4
+ the colour comes from `currentColor`.
5
+ Import: `import { KptIcon } from '@konce-pt/react';`
6
+
7
+ ## DOM
8
+ <span class="kpt-icon-host" aria-hidden="true">
9
+ <svg class="kpt-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">…</svg>
10
+ </span>
11
+
12
+ ## Props
13
+ - `name`: string — the icon name from the registry (required)
14
+ - `size`: string — a CSS length (e.g. '1.25rem'); by default inherited from `font-size`
15
+ - `className`, `ref` and every other `<span>` prop go to the host
16
+
17
+ ## The registry
18
+ The built-in set (84 icons) works with no setup. It covers both what the library's own components
19
+ draw and the staples of an application shell — sun, moon, bell, settings, users, user-circle,
20
+ logout, layout-dashboard, home, activity, chart-bar, arrow-up-right, brand-github and the like —
21
+ so a typical admin panel never needs the full set.
22
+
23
+ The full Tabler set (5130 icons) is opt-in, and there are two paths. `@konce-pt/react/icons`
24
+ exposes every icon as its own export, so importing by name lets the bundler keep only what you
25
+ used:
26
+
27
+ // main.tsx — production: two icons reach the bundle.
28
+ import { LayoutDashboard, Rocket } from '@konce-pt/react/icons';
29
+ import { registerKptIcons } from '@konce-pt/react';
30
+
31
+ registerKptIcons([LayoutDashboard, Rocket]);
32
+
33
+ // main.tsx — prototype: every icon, about 1.2 MB.
34
+ import { registerKptTablerIcons } from '@konce-pt/react/icons';
35
+
36
+ registerKptTablerIcons();
37
+
38
+ `registerKptIcons` is the one that shrinks: it takes the data as an argument and imports nothing
39
+ from the Tabler subpath, so the bundle follows your imports. Passing the namespace object
40
+ (`import * as tabler`) registers everything — a namespace handed to a function blocks static
41
+ analysis, so it is the explicit form of the prototype path, not a smaller one.
42
+
43
+ Export names are PascalCase because `kebab-case` is not a legal identifier and camelCase would
44
+ collide with keywords (`function`, `package`, `switch`). Each icon carries its own registry name,
45
+ so `BrandHtml5` registers as `brand-html5`.
46
+
47
+ The registry is a module singleton, not a context: icons are global to the application, so there is
48
+ nothing to provide. `KptIcon` subscribes to it, so icons registered after the first render appear
49
+ without a manual refresh. Register your own with `registerKptIcons({ name: '<path … />' })`.
50
+
51
+ An unknown name renders an empty host rather than throwing — a missing icon never takes the page
52
+ down with it.
53
+
54
+ ## Security
55
+ Registry content reaches `innerHTML`; `registerKptIcons` sanitizes it to the SVG geometry
56
+ allowlist, but call it only with developer-controlled, build-time content, never straight from a
57
+ CMS, an API or an upload.
58
+
59
+ ## Examples
60
+ <KptIcon name="menu" />
61
+ <KptIcon name="check" size="1.25rem" />
62
+ <KptButton><KptIcon name="phone" /> Call</KptButton>
63
+
64
+ ## Accessibility
65
+ The host carries `aria-hidden="true"` — an icon is decoration. Put the accessible name on the
66
+ control around it (`aria-label` on the button), not on the icon.
@@ -0,0 +1,38 @@
1
+ # KptIconButton (@konce-pt/react)
2
+
3
+ An icon-only button. Import: `import { KptIconButton } from '@konce-pt/react';`
4
+
5
+ ## DOM
6
+ <span class="kpt-icon-button-host">
7
+ <button class="kpt-icon-button" type="button" data-variant="text" data-size="md" data-shape="round" aria-label="Settings">
8
+ …icon…
9
+ </button>
10
+ </span>
11
+
12
+ ## Props
13
+ - `variant`: the same union as `KptButton` — 'filled' | 'tonal' | 'outline' | 'text' | 'danger'
14
+ (default 'text')
15
+ - `size`: 'sm' | 'md' | 'lg' (default 'md')
16
+ - `shape`: 'round' | 'square' (default 'round')
17
+ - `loading`: boolean — a spinner instead of the icon, sets `aria-busy` and disables the button
18
+ - `aria-label`: string — REQUIRED by the type
19
+ - `hostProps` — props for the `.kpt-icon-button-host` wrapper
20
+ - `className`, `ref` and every other `<button>` prop go to the inner `<button>`
21
+
22
+ ## The label is required by the type
23
+ `aria-label` is not optional in `KptIconButtonProps`: a button with no text has no accessible name,
24
+ and that is the most common accessibility bug in this family. TypeScript refuses the component
25
+ without it, so the mistake cannot reach the browser.
26
+
27
+ ## Examples
28
+ <KptIconButton aria-label="Settings"><KptIcon name="menu" /></KptIconButton>
29
+ <KptIconButton variant="filled" shape="square" aria-label="Delete"><KptIcon name="x" /></KptIconButton>
30
+ <KptIconButton loading aria-label="Saving" />
31
+
32
+ ## Tokens
33
+ The same button tokens as `KptButton` (`--kpt-button-*` for each variant), plus the size and shape
34
+ geometry from the shared stylesheet.
35
+
36
+ ## Accessibility
37
+ A native `<button>` with a mandatory `aria-label`. In `loading` the icon is replaced by a spinner
38
+ and the button is disabled with `aria-busy="true"`.
@@ -1,9 +1,13 @@
1
- export { KPT_TABLER_ICONS, KPT_TABLER_ICON_NAMES } from '@konce-pt/icons/tabler';
1
+ export * from '@konce-pt/icons/tabler';
2
2
  /**
3
3
  * Rejestruje pełny zestaw Tabler Icons (5130), dzięki czemu `KptIcon` może użyć dowolnej
4
4
  * ikony po nazwie. Wywołaj raz, w wejściu aplikacji — rejestr jest globalny, więc nie ma
5
5
  * potrzeby owijać drzewa żadnym providerem.
6
6
  *
7
+ * Wciąga komplet (~1,2 MB), więc to ścieżka prototypowa. W produkcji zaimportuj ikony
8
+ * po nazwie i podaj je do `registerKptIcons([...])` z rdzenia `@konce-pt/react` — ta funkcja
9
+ * nie importuje pełnego zestawu, więc bundler zostawia wyłącznie użyte ikony.
10
+ *
7
11
  * @example
8
12
  * // main.tsx
9
13
  * import { registerKptTablerIcons } from '@konce-pt/react/icons';
@@ -4,12 +4,18 @@
4
4
  */
5
5
  import { KPT_TABLER_ICONS } from '@konce-pt/icons/tabler';
6
6
  import { registerKptIcons } from "../icon/icon-registry.js";
7
- export { KPT_TABLER_ICONS, KPT_TABLER_ICON_NAMES } from '@konce-pt/icons/tabler';
7
+ // `export *` udostępnia 5130 ikon jako osobne eksporty (import po nazwie wchodzi do bundla
8
+ // pojedynczo) obok dwóch eksportów zbiorczych, które wciągają komplet.
9
+ export * from '@konce-pt/icons/tabler';
8
10
  /**
9
11
  * Rejestruje pełny zestaw Tabler Icons (5130), dzięki czemu `KptIcon` może użyć dowolnej
10
12
  * ikony po nazwie. Wywołaj raz, w wejściu aplikacji — rejestr jest globalny, więc nie ma
11
13
  * potrzeby owijać drzewa żadnym providerem.
12
14
  *
15
+ * Wciąga komplet (~1,2 MB), więc to ścieżka prototypowa. W produkcji zaimportuj ikony
16
+ * po nazwie i podaj je do `registerKptIcons([...])` z rdzenia `@konce-pt/react` — ta funkcja
17
+ * nie importuje pełnego zestawu, więc bundler zostawia wyłącznie użyte ikony.
18
+ *
13
19
  * @example
14
20
  * // main.tsx
15
21
  * import { registerKptTablerIcons } from '@konce-pt/react/icons';
@@ -0,0 +1,46 @@
1
+ # KptImage (@konce-pt/react)
2
+
3
+ An image with an optional full-screen preview.
4
+ Import: `import { KptImage } from '@konce-pt/react';`
5
+
6
+ ## DOM
7
+ <div class="kpt-image-host">
8
+ <div class="kpt-image kpt-image--preview" style="width:240px">
9
+ <img class="kpt-image__img" src="…" alt="…">
10
+ <div class="kpt-image__mask" aria-hidden="true">…zoom-in…</div>
11
+ </div>
12
+ <!-- open: -->
13
+ <div class="kpt-image__overlay">
14
+ <button class="kpt-image__close">…</button>
15
+ <img class="kpt-image__full" src="…" alt="…">
16
+ </div>
17
+ </div>
18
+
19
+ ## Props
20
+ - `src`: string (required); `alt`: string
21
+ - `width`: string | null — a CSS length, e.g. `'240px'`
22
+ - `preview`: boolean — a click opens the overlay
23
+ - `className`, `ref` and every other `<div>` prop go to the host
24
+
25
+ ## The overlay closes around the photo, not on it
26
+ The click on `.kpt-image__full` stops propagating, so only the dark area around the image closes
27
+ the preview — clicking the photo itself (to look closer, to drag it) must not dismiss it. The × is
28
+ there for the explicit way out.
29
+
30
+ ## The mask is decorative
31
+ The zoom-in overlay is `aria-hidden`: it repeats what the cursor (`zoom-in`) already says and would
32
+ only add noise for a screen reader.
33
+
34
+ ## Examples
35
+ <KptImage src="/photo.jpg" alt="Photo" preview width="240px" />
36
+ <KptImage src="/logo.svg" alt="Logo" />
37
+
38
+ ## i18n
39
+ The close button label comes from `common.close`.
40
+
41
+ ## Tokens
42
+ The radius `--kpt-radius-md`, the mask and the overlay are black at 40% / 80% via `color-mix`.
43
+
44
+ ## Accessibility
45
+ `alt` goes to both the thumbnail and the full-size image; the close button is labelled from the
46
+ dictionary.
@@ -0,0 +1,54 @@
1
+ # KptInput (@konce-pt/react)
2
+
3
+ The Koncept UI text field. It renders a native `<input>` inside a `.kpt-input-host` wrapper.
4
+ Import: `import { KptInput } from '@konce-pt/react';`
5
+
6
+ ## DOM
7
+ <span class="kpt-input-host">
8
+ <input class="kpt-input__field" id="…" type="text">
9
+ </span>
10
+
11
+ ## Props
12
+ - `type`: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url' | 'time' (default 'text')
13
+ - `value` / `defaultValue` — controlled or uncontrolled, exactly like a native input
14
+ - `onValueChange(value)` — the value alone; `onChange` still fires with the full event
15
+ - `onTouch()` — fires on blur, the moment the field becomes touched
16
+ - `required`: boolean — see the note below
17
+ - `invalid`: boolean, `touched`: boolean — the error state
18
+ - `hostProps` — props for the `.kpt-input-host` wrapper
19
+ - `className`, `ref`, `name`, `placeholder`, `autoComplete` and every other `<input>` prop go to
20
+ the inner `<input>`
21
+
22
+ ## Controlled and uncontrolled
23
+ Pass `value` and the field is controlled. Pass `defaultValue` (or nothing) and the value belongs
24
+ to the DOM, so a native `<form>`, `<form action={…}>`, a form reset and React Hook Form's
25
+ `register()` all work without an adapter — there is no internal mirror of the value to fight with.
26
+
27
+ ## The error state
28
+ `aria-invalid="true"` is set when `invalid` is true. Pass `touched` as well to reproduce the
29
+ Angular port's gate — the error then shows only after the field has been touched, like the native
30
+ `:user-invalid`. With `touched` omitted there is nothing to gate on, so `invalid` decides alone;
31
+ that is the common case in React, where the form library already knows when an error is ready.
32
+ The style hangs off `aria-invalid`, never off a class.
33
+
34
+ ## `required` is `aria-required`
35
+ The native `required` attribute is deliberately NOT set: it would block the submit event and start
36
+ the browser's own validation next to yours. Screen readers still announce the field as required.
37
+
38
+ ## Inside KptFormField
39
+ The field takes its `id` from the wrapper through context, so `label[for]` is wired with no
40
+ configuration. An explicit `id` prop still wins.
41
+
42
+ ## Examples
43
+ <KptInput name="email" type="email" placeholder="jan@firma.pl" />
44
+ <KptInput value={name} onValueChange={setName} />
45
+ <KptFormField label="E-mail" error={err}>
46
+ <KptInput type="email" invalid={!!err} onTouch={markTouched} />
47
+ </KptFormField>
48
+
49
+ ## Tokens
50
+ `--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}`.
51
+
52
+ ## Accessibility
53
+ A native `<input>` — full keyboard and screen reader support. `aria-required` marks the field as
54
+ required, `aria-invalid` marks it as erroneous, and the focus ring comes from `@konce-pt/styles`.
@@ -0,0 +1,37 @@
1
+ # KptInputMask (@konce-pt/react)
2
+
3
+ A text field with a character mask.
4
+ Import: `import { KptInputMask } from '@konce-pt/react';`
5
+
6
+ ## DOM
7
+ <span class="kpt-input-mask-host">
8
+ <input class="kpt-input-mask__input" type="text" placeholder="(999) 999-99-99">
9
+ </span>
10
+
11
+ ## Props
12
+ - `value` / `defaultValue` / `onValueChange` — the formatted text
13
+ - `mask`: string — `9` a digit, `a` a letter, `*` alphanumeric; everything else is a literal
14
+ - `placeholder`: string — defaults to the mask itself, which doubles as a hint
15
+ - `invalid`, `touched`, `onTouch()`
16
+ - `className`, `ref` and every other `<input>` prop go to the input
17
+
18
+ ## The mask rewrites, it does not patch
19
+ Every keystroke is re-formatted from scratch: only characters matching the next token are taken
20
+ from the input, literals are inserted by the mask. Typing the formatted form and pasting the raw
21
+ digits therefore produce the same result, and deleting a literal cannot leave a hole in the
22
+ pattern. Characters that match nothing are skipped rather than rejected, so stray letters around a
23
+ number still format.
24
+
25
+ Like the OTP field, the DOM is synced by hand when the mask drops a character — the value did not
26
+ change, so React would not re-render and the character would linger on screen.
27
+
28
+ ## Examples
29
+ <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
30
+ <KptInputMask mask="aa-9999" value={code} onValueChange={setCode} />
31
+
32
+ ## Tokens
33
+ `--kpt-form-field-{bg,text,placeholder,border,border-focus,border-error,radius}`.
34
+
35
+ ## Accessibility
36
+ A native text input; the mask doubles as the placeholder, so the expected shape is visible before
37
+ typing starts.
@@ -0,0 +1,44 @@
1
+ # KptInputNumber (@konce-pt/react)
2
+
3
+ A number field with stepper arrows. Import: `import { KptInputNumber } from '@konce-pt/react';`
4
+
5
+ ## DOM
6
+ <div class="kpt-input-number-host">
7
+ <div class="kpt-input-number">
8
+ <button class="kpt-input-number__btn" aria-label="Decrease">…</button>
9
+ <input class="kpt-input-number__input" type="text" inputmode="decimal" value="12 zł">
10
+ <button class="kpt-input-number__btn" aria-label="Increase">…</button>
11
+ </div>
12
+ </div>
13
+
14
+ ## Props
15
+ - `value` / `defaultValue` / `onValueChange` — `number | null`
16
+ - `min`, `max`, `step` (default 1)
17
+ - `placeholder`, `suffix` — text shown after the number, e.g. ' szt.' or 'ms'
18
+ - `showButtons`: boolean (default true) — `false` leaves the bare field
19
+ - `disabled`, `invalid`, `touched`, `onTouch()`
20
+ - `locale`, `dictionary` — i18n overrides for this component only
21
+
22
+ ## Null is a value
23
+ The value is `number | null`, not `number`: an empty field means "no value", which is not the same
24
+ as zero. Clearing the input yields `null`, and the stepper starts from `min` (or 0) when it has
25
+ nothing to step from.
26
+
27
+ ## A text input, not a number input
28
+ The field is `type="text"` with `inputmode="decimal"`, so the suffix can live inside it and a comma
29
+ works as the decimal separator — typing "12,5 zł" parses correctly. A native `type="number"` would
30
+ reject both. Values out of range are clamped on input, not rejected.
31
+
32
+ ## i18n
33
+ The stepper labels come from `inputNumber.decrement` and `inputNumber.increment`.
34
+
35
+ ## Examples
36
+ <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} />
37
+ <KptInputNumber value={price} onValueChange={setPrice} showButtons={false} suffix=" zł" />
38
+
39
+ ## Tokens
40
+ `--kpt-form-field-{bg,text,border,border-focus,border-error,radius}`.
41
+
42
+ ## Accessibility
43
+ The stepper buttons carry labels from the dictionary and go `disabled` at the limits, so the
44
+ keyboard never lands on a control that cannot do anything.
@@ -0,0 +1,43 @@
1
+ # KptInputOtp (@konce-pt/react)
2
+
3
+ A one-time code field (OTP / PIN) — N separate boxes, auto-advance and paste.
4
+ Import: `import { KptInputOtp } from '@konce-pt/react';`
5
+
6
+ ## DOM
7
+ <div class="kpt-input-otp-host">
8
+ <div class="kpt-input-otp">
9
+ <input class="kpt-input-otp__cell" type="text" inputmode="numeric" maxlength="1" value="1">
10
+ …N cells…
11
+ </div>
12
+ </div>
13
+
14
+ ## Props
15
+ - `value` / `defaultValue` / `onValueChange` — one string, the characters joined
16
+ - `length`: number (default 6)
17
+ - `integerOnly`: boolean — digits only, and a numeric keyboard on mobile
18
+ - `disabled`, `invalid`, `touched`, `onTouch()`
19
+
20
+ ## One string, N boxes
21
+ The value is a single string while the UI is N inputs, so a gap in the middle has to be
22
+ expressible. Writing pads with spaces, replaces the character and trims trailing spaces — that way
23
+ a code with a hole round-trips and a single character does not turn into one followed by blanks.
24
+
25
+ The DOM of a cell is also synced by hand after a rejected character: the value did not change, so
26
+ React has nothing to re-render, and the typed character would otherwise stay on screen.
27
+
28
+ ## Keyboard and paste
29
+ A character moves focus to the next cell; Backspace in an empty cell moves back; ArrowLeft and
30
+ ArrowRight walk the cells; focus selects the content so typing overwrites. Pasting fills the whole
31
+ code at once — with `integerOnly` everything non-numeric is stripped first, so a copied
32
+ "123-456" still lands correctly.
33
+
34
+ ## Examples
35
+ <KptInputOtp value={code} onValueChange={setCode} length={6} integerOnly />
36
+ <KptInputOtp value={code} onValueChange={setCode} length={4} />
37
+
38
+ ## Tokens
39
+ `--kpt-form-field-{bg,text,border,border-focus,border-error,radius}`.
40
+
41
+ ## Accessibility
42
+ Native inputs with `inputmode` matched to the allowed characters, so a phone shows the numeric
43
+ keypad for a PIN instead of the full keyboard.
@@ -18,20 +18,28 @@ export const KPT_BREAKPOINTS = {
18
18
  * komponenty — tak samo jak memoizacja w `KptBreakpointObserver` po stronie Angulara.
19
19
  */
20
20
  const stores = new Map();
21
+ /**
22
+ * Zastępczy magazyn dla środowisk bez `matchMedia`. Jedna instancja, bo `useSyncExternalStore`
23
+ * przepina nasłuch przy każdej zmianie tożsamości `subscribe`.
24
+ */
25
+ const UNSUPPORTED = { subscribe: () => () => { }, getSnapshot: () => false };
21
26
  function storeFor(query) {
22
27
  const cached = stores.get(query);
23
28
  if (cached)
24
29
  return cached;
25
- const list = typeof window === 'undefined' ? null : window.matchMedia(query);
26
- const store = list
27
- ? {
28
- subscribe: (onChange) => {
29
- list.addEventListener('change', onChange);
30
- return () => list.removeEventListener('change', onChange);
31
- },
32
- getSnapshot: () => list.matches,
33
- }
34
- : { subscribe: () => () => { }, getSnapshot: () => false };
30
+ // Sam brak `window` nie wystarcza: jsdom go dostarcza, ale bez `matchMedia` — bez sprawdzenia
31
+ // metody każdy test dotykający `KptAppShell` wywala się na TypeError. Wyniku nie zapisujemy
32
+ // w cache'u, żeby stub podstawiony później (typowy `setup` testów) został podchwycony.
33
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
34
+ return UNSUPPORTED;
35
+ const list = window.matchMedia(query);
36
+ const store = {
37
+ subscribe: (onChange) => {
38
+ list.addEventListener('change', onChange);
39
+ return () => list.removeEventListener('change', onChange);
40
+ },
41
+ getSnapshot: () => list.matches,
42
+ };
35
43
  stores.set(query, store);
36
44
  return store;
37
45
  }
@@ -0,0 +1,46 @@
1
+ # KptKnob (@konce-pt/react)
2
+
3
+ A knob — pick a value along an SVG arc, by dragging or with the arrow keys.
4
+ Import: `import { KptKnob } from '@konce-pt/react';`
5
+
6
+ ## DOM
7
+ <div class="kpt-knob-host">
8
+ <div class="kpt-knob" role="slider" tabindex="0" aria-valuenow="40">
9
+ <svg viewBox="0 0 96 96">
10
+ <path class="kpt-knob__range" d="M …" />
11
+ <path class="kpt-knob__value" d="M …" />
12
+ <text class="kpt-knob__text">40%</text>
13
+ </svg>
14
+ </div>
15
+ </div>
16
+
17
+ ## Props
18
+ - `value` / `defaultValue` / `onValueChange` — the number
19
+ - `min` (0), `max` (100), `step` (1)
20
+ - `size`: px (96) — the side of the SVG square; `stroke`: px (10) — arc thickness
21
+ - `suffix`: string appended to the value in the middle; `showValue`: boolean (default true)
22
+ - `disabled`, `invalid`, `touched`, `onTouch()`
23
+ - `className`, `ref` and every other `<div>` prop go to the host
24
+
25
+ ## The gap at the bottom
26
+ The arc is a full circle minus a 45° gap at the bottom, so its start and end are distinguishable.
27
+ A pointer inside that gap has no value of its own — it is snapped to the nearer end, otherwise
28
+ dragging across the bottom would jump between minimum and maximum.
29
+
30
+ ## Dragging listens on the window
31
+ `pointermove` / `pointerup` are attached to `window`, not to the knob: the pointer leaves the
32
+ circle during almost every drag, and the value must keep following it. Both listeners are removed
33
+ on `pointerup`, which is also where `onTouch` fires — one touch per drag, not per pixel.
34
+
35
+ ## Examples
36
+ <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
37
+ <KptKnob value={level} onValueChange={setLevel} size={64} stroke={6} showValue={false} />
38
+
39
+ ## Tokens
40
+ The background arc `--kpt-color-border`, the value `--kpt-color-primary`, the text
41
+ `--kpt-color-on-surface`.
42
+
43
+ ## Accessibility
44
+ `role="slider"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow`; arrows step by `step`,
45
+ Home and End jump to the bounds. `disabled` sets `aria-disabled` and drops the knob out of the
46
+ tab order.
@@ -2,7 +2,7 @@ import { type ComponentPropsWithRef } from 'react';
2
2
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
3
  import type { KptOption } from '../shared/types.ts';
4
4
  export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
5
- options?: KptOption<T>[];
5
+ options?: readonly KptOption<T>[];
6
6
  value?: T | T[] | null;
7
7
  defaultValue?: T | T[] | null;
8
8
  onValueChange?: (value: T | T[] | null) => void;
@@ -0,0 +1,39 @@
1
+ # KptListbox (@konce-pt/react)
2
+
3
+ A selection list shown in full, with no dropdown. Single or multiple selection, optional filter.
4
+ Import: `import { KptListbox } from '@konce-pt/react';`
5
+
6
+ ## DOM
7
+ <div class="kpt-listbox-host">
8
+ <div class="kpt-listbox" role="listbox" aria-multiselectable="false">
9
+ <div class="kpt-listbox__filter">…icon + input…</div>
10
+ <div class="kpt-listbox__items">
11
+ <button class="kpt-listbox__item is-selected" role="option" aria-selected="true">Admin</button>
12
+ </div>
13
+ </div>
14
+ </div>
15
+
16
+ ## Props
17
+ - `options`: `KptOption<T>[]`
18
+ - `value` / `defaultValue` / `onValueChange` — `T` (single) or `T[]` (multiple)
19
+ - `multiple`, `filter`, `disabled`, `invalid`, `touched`, `onTouch()`
20
+ - `locale`, `dictionary` — i18n overrides for this component only
21
+
22
+ ## Clicking the selection clears it
23
+ In single mode clicking the already selected option deselects it. A listbox is often a filter, and a
24
+ filter has to be clearable without a separate button.
25
+
26
+ ## i18n
27
+ `listbox.search` (the filter placeholder) and `listbox.empty` (nothing matched).
28
+
29
+ ## Examples
30
+ <KptListbox options={roles} value={role} onValueChange={setRole} filter />
31
+ <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
32
+
33
+ ## Tokens
34
+ `--kpt-color-surface`, `--kpt-color-border`, `--kpt-color-primary-subtle` (the selected row),
35
+ `--kpt-form-field-border-error`.
36
+
37
+ ## Accessibility
38
+ `role="listbox"` with `aria-multiselectable`; every option is a native button with
39
+ `role="option"` and `aria-selected`, so the keyboard walks the list without extra handling.
@@ -0,0 +1,108 @@
1
+ # KptMap (@konce-pt/react/map)
2
+
3
+ A map with configurable markers: categories carrying a colour and an icon, clustering with a count,
4
+ a details popup whose content depends on the category, a ready-made place card, a legend, an address
5
+ search and adding locations with the right mouse button. The engine (Leaflet + OpenStreetMap or
6
+ Google) comes from `@konce-pt/map`; the component knows nothing about it beyond the `KptMapEngine`
7
+ interface.
8
+
9
+ Import: `import { KptMap, createLeafletEngine } from '@konce-pt/react/map';`
10
+
11
+ ## Requirements
12
+ 1. The **optional** `leaflet` dependency — the library does not install it: `pnpm add leaflet`
13
+ 2. Its stylesheet: `import 'leaflet/dist/leaflet.css';`
14
+ 3. An engine factory on the `engine` prop: `engine={() => createLeafletEngine()}`
15
+
16
+ Leaflet is loaded with a dynamic `import()`, so an app that never renders a map does not download
17
+ it. Without the package the component still imports and compiles — only rendering the map ends with
18
+ a readable message.
19
+
20
+ ## Props
21
+ - `locations`: `readonly KptMapLocation<T>[]` — **required**
22
+ - `categories`: `readonly KptMapCategory[]` — **required**
23
+ - `engine`: `KptMapEngineFactory` — **required**; a factory, not an instance, because every map
24
+ needs its own object. Read once, at mount.
25
+ - `country`: string = `'PL'` — the initial view preset; an unknown code falls back to Poland
26
+ - `view`, `bounds` — an explicit view or extent; they override `country`
27
+ - `restrictToCountry`: boolean — blocks panning outside the country's bounds
28
+ - `cluster`: boolean = true, `clusterOptions`, `clusterColorBy`: `'dominant' | 'none'`
29
+ - `legend`, `legendCounts`: boolean = true
30
+ - `hiddenCategories` / `defaultHiddenCategories` / `onHiddenCategoriesChange` — the ids hidden in
31
+ the legend, controlled or uncontrolled
32
+ - `showUserLocation`, `followUser`: boolean; `geolocation`: `KptMapGeolocationPort | null`
33
+ - `allowCreate`: boolean — the "Add location" menu under the right mouse button
34
+ - `search`: boolean, `searchPlaceholder`, `searchOptions`, `searchZoom`, `searchMarker`
35
+ - `geocoder`: `KptMapGeocoderPort | null` — this map's geocoder; it feeds both the search and the
36
+ address suggestions in the create form
37
+ - `popupLayout`: `'plain' | 'card'`; `distanceFrom`: `KptLatLng | null`
38
+ - `height`: string = `'480px'`; `tileUrl`, `attribution` (ignored by the Google engine)
39
+ - `renderPopup`, `renderCreateForm` — see below
40
+ - `className`, `ref` and every other `<div>` prop go to the host
41
+
42
+ ## Events
43
+ `onLocationClick`, `onClusterClick`, `onLocationCreate`, `onSearchSelect`, `onViewChange`,
44
+ `onUserPositionChange`, `onUserLocationError`.
45
+
46
+ `onLocationCreate` gives `{ at, categoryId, value }` — the component appends **nothing** to
47
+ `locations`; the application is the source of truth.
48
+
49
+ ## Render props instead of templates
50
+ The Angular port picks a popup template per category (`kptMapPopup category="…"`). React has one
51
+ prop and a `switch`: `renderPopup({ location, data, category, distance, close })` — the category
52
+ travels in the context, and the type of `data` follows the map's generic without an extra input.
53
+
54
+ <KptMap
55
+ engine={engine}
56
+ locations={locations}
57
+ categories={categories}
58
+ renderPopup={({ location, data, category }) =>
59
+ category?.id === 'dc' ? <ChargerPopup data={data} /> : <strong>{location.title}</strong>
60
+ }
61
+ />
62
+
63
+ `renderCreateForm({ at, category, submit, close })` replaces the schema-built form the same way.
64
+
65
+ ## Ports are props, not DI
66
+ Angular injects `KPT_MAP_ENGINE`, `KPT_MAP_GEOCODER` and `KPT_MAP_GEOLOCATION`. React has no
67
+ injector, so all three arrive as props — and the geocoder was already per-map in Angular for a
68
+ licensing reason (a Google geocoder may only be paired with a Google map), so nothing is lost.
69
+
70
+ The ports themselves are promise-shaped rather than `Observable`-shaped: `reverse()`, `search()`
71
+ and `resolve()` return promises and take an `AbortSignal`, which is what `fetch` speaks anyway.
72
+
73
+ import { createNominatimGeocoder, browserGeolocation } from '@konce-pt/react/map';
74
+
75
+ <KptMap geocoder={createNominatimGeocoder()} geolocation={browserGeolocation()} … />
76
+
77
+ `createGeocoderStub()` and `geolocationStub(at)` cover demos and tests.
78
+
79
+ ## Mounting is serialised
80
+ A map engine marks its container as taken for the life of the map, and React's strict mode mounts
81
+ every effect twice in a row. The component therefore chains mount and teardown on one promise: the
82
+ second mount waits for the first teardown to finish, instead of failing with "Map container is
83
+ already initialized".
84
+
85
+ ## Sub-components
86
+ - `KptMapLegend` — the legend on its own, when you want it somewhere other than under the map
87
+ (`hiddenCategories` instead of Angular's `hidden`: `hidden` on a DOM element is a boolean and
88
+ would clash with the `<div>` prop)
89
+ - `KptMapPoiCard` — the ready-made place card for `popupLayout="card"`
90
+ - `KptMapSearch` — the address field alone
91
+ - `KptMapCreateDialog`, `KptMapHoursField`, `KptMapImageField` — the schema-built form and its two
92
+ non-trivial field types
93
+
94
+ ## Validation without a form framework
95
+ The create dialog is built from `category.fields`. The Angular port stands on Signal Forms; here the
96
+ model is plain state and the rules are a map of functions beside it. The contract is the same:
97
+ declared once with the schema, and the "do not shout in red" gate lives in presentation — an error
98
+ shows after the field is touched or after a failed submit.
99
+
100
+ ## Tokens
101
+ `--kpt-map-height`, `--kpt-map-popup-width`, `--kpt-map-popup-width-card`,
102
+ `--kpt-map-popup-max-height`, `--kpt-map-poi-media-radius`, plus the semantic colour roles used by
103
+ the categories.
104
+
105
+ ## Accessibility
106
+ Zoom and "my location" are real buttons with labels; the popup is a `role="dialog"` with the
107
+ location's title; the create menu is a `role="menu"`; the search field is a combobox with
108
+ `aria-activedescendant` and a polite status line. Marker markup carries `role="img"` and a label.