@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.
- package/README.md +381 -27
- package/dist/accordion/llms.txt +56 -0
- package/dist/alert/llms.txt +45 -0
- package/dist/app-shell/app-shell.d.ts +20 -0
- package/dist/app-shell/app-shell.js +24 -0
- package/dist/app-shell/llms.txt +59 -0
- package/dist/auth/auth.d.ts +1 -1
- package/dist/auth/llms.txt +65 -0
- package/dist/autocomplete/autocomplete.d.ts +1 -1
- package/dist/autocomplete/llms.txt +44 -0
- package/dist/avatar/llms.txt +42 -0
- package/dist/avatar-group/avatar-group.d.ts +1 -1
- package/dist/avatar-group/llms.txt +38 -0
- package/dist/badge/llms.txt +38 -0
- package/dist/bottom-sheet/llms.txt +43 -0
- package/dist/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/breadcrumb/llms.txt +35 -0
- package/dist/button/llms.txt +56 -0
- package/dist/button-group/button-group.d.ts +1 -1
- package/dist/button-group/llms.txt +40 -0
- package/dist/card/llms.txt +60 -0
- package/dist/carousel/carousel.d.ts +1 -1
- package/dist/carousel/llms.txt +47 -0
- package/dist/charts/chart-legend.d.ts +1 -1
- package/dist/charts/llms.txt +57 -0
- package/dist/checkbox/llms.txt +39 -0
- package/dist/chip/llms.txt +41 -0
- package/dist/chips-input/llms.txt +40 -0
- package/dist/clock/llms.txt +50 -0
- package/dist/color-picker/color-picker.d.ts +1 -1
- package/dist/color-picker/llms.txt +40 -0
- package/dist/confirm/llms.txt +44 -0
- package/dist/context-menu/context-menu.d.ts +1 -1
- package/dist/context-menu/llms.txt +39 -0
- package/dist/data-table/data-table-types.d.ts +1 -1
- package/dist/data-table/data-table.d.ts +2 -10
- package/dist/data-table/data-table.js +17 -9
- package/dist/data-table/llms.txt +79 -0
- package/dist/data-view/data-view.d.ts +1 -1
- package/dist/data-view/llms.txt +49 -0
- package/dist/date-range/llms.txt +54 -0
- package/dist/datepicker/llms.txt +74 -0
- package/dist/dialog/llms.txt +51 -0
- package/dist/divider/llms.txt +28 -0
- package/dist/drawer/llms.txt +47 -0
- package/dist/empty/llms.txt +35 -0
- package/dist/fab/llms.txt +37 -0
- package/dist/fieldset/llms.txt +43 -0
- package/dist/file-upload/llms.txt +48 -0
- package/dist/form-field/llms.txt +55 -0
- package/dist/galleria/galleria.d.ts +1 -1
- package/dist/galleria/llms.txt +46 -0
- package/dist/grid/llms.txt +110 -0
- package/dist/icon/icon-registry.d.ts +5 -2
- package/dist/icon/icon-registry.js +5 -3
- package/dist/icon/llms.txt +66 -0
- package/dist/icon-button/llms.txt +38 -0
- package/dist/icons-entry/public-api.d.ts +5 -1
- package/dist/icons-entry/public-api.js +7 -1
- package/dist/image/llms.txt +46 -0
- package/dist/input/llms.txt +54 -0
- package/dist/input-mask/llms.txt +37 -0
- package/dist/input-number/llms.txt +44 -0
- package/dist/input-otp/llms.txt +43 -0
- package/dist/internal/use-breakpoint.js +18 -10
- package/dist/knob/llms.txt +46 -0
- package/dist/listbox/listbox.d.ts +1 -1
- package/dist/listbox/llms.txt +39 -0
- package/dist/map/llms.txt +108 -0
- package/dist/megamenu/llms.txt +48 -0
- package/dist/megamenu/megamenu.d.ts +1 -1
- package/dist/menu/llms.txt +56 -0
- package/dist/menu/menu-item.d.ts +1 -1
- package/dist/menu/menu-rows.d.ts +1 -1
- package/dist/menu/menu.d.ts +1 -1
- package/dist/menu/menu.js +12 -1
- package/dist/menubar/llms.txt +45 -0
- package/dist/menubar/menubar.d.ts +1 -1
- package/dist/meter-group/llms.txt +47 -0
- package/dist/meter-group/meter-group.d.ts +1 -1
- package/dist/order-list/llms.txt +41 -0
- package/dist/paginator/llms.txt +48 -0
- package/dist/paginator/paginator.d.ts +1 -1
- package/dist/paginator/paginator.js +7 -1
- package/dist/panel/llms.txt +55 -0
- package/dist/password/llms.txt +45 -0
- package/dist/pick-list/llms.txt +52 -0
- package/dist/popover/llms.txt +42 -0
- package/dist/progress/llms.txt +47 -0
- package/dist/public-api.d.ts +2 -1
- package/dist/public-api.js +1 -1
- package/dist/radio-group/llms.txt +40 -0
- package/dist/radio-group/radio-group.d.ts +1 -1
- package/dist/rating/llms.txt +43 -0
- package/dist/rich-text/llms.txt +61 -0
- package/dist/rich-text/rich-text.d.ts +1 -1
- package/dist/roadmap/llms.txt +82 -0
- package/dist/scroll-top/llms.txt +41 -0
- package/dist/select/llms.txt +56 -0
- package/dist/select/select.d.ts +1 -1
- package/dist/sidenav/llms.txt +53 -0
- package/dist/skeleton/llms.txt +33 -0
- package/dist/slider/llms.txt +50 -0
- package/dist/speed-dial/llms.txt +46 -0
- package/dist/speed-dial/speed-dial.d.ts +1 -1
- package/dist/spinner/llms.txt +33 -0
- package/dist/split-button/llms.txt +54 -0
- package/dist/split-button/split-button.d.ts +1 -1
- package/dist/splitter/llms.txt +44 -0
- package/dist/stepper/llms.txt +64 -0
- package/dist/switch/llms.txt +42 -0
- package/dist/switch-group/llms.txt +43 -0
- package/dist/switch-group/switch-group.d.ts +1 -1
- package/dist/tabs/llms.txt +48 -0
- package/dist/textarea/llms.txt +40 -0
- package/dist/timeline/llms.txt +53 -0
- package/dist/timeline/timeline.d.ts +1 -1
- package/dist/toast/llms.txt +52 -0
- package/dist/toolbar/llms.txt +45 -0
- package/dist/tooltip/llms.txt +42 -0
- package/dist/tree/llms.txt +45 -0
- package/dist/tree/tree.d.ts +2 -2
- 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
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|