@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.
- package/dist/accordion/accordion.js +7 -1
- package/dist/accordion/llms.txt +8 -4
- package/dist/alert/alert.d.ts +10 -4
- package/dist/alert/alert.js +13 -6
- package/dist/alert/llms.txt +8 -6
- package/dist/app-shell/app-shell.js +28 -2
- package/dist/app-shell/llms.txt +3 -0
- package/dist/autocomplete/autocomplete.d.ts +7 -2
- package/dist/autocomplete/autocomplete.js +46 -9
- package/dist/autocomplete/llms.txt +12 -0
- package/dist/avatar/avatar.d.ts +4 -2
- package/dist/avatar/avatar.js +8 -3
- package/dist/avatar/llms.txt +9 -5
- package/dist/avatar-group/avatar-group.d.ts +5 -2
- package/dist/avatar-group/avatar-group.js +10 -2
- package/dist/avatar-group/llms.txt +7 -4
- package/dist/badge/badge.d.ts +8 -1
- package/dist/badge/badge.js +3 -2
- package/dist/badge/llms.txt +11 -5
- package/dist/bottom-sheet/bottom-sheet.d.ts +1 -1
- package/dist/bottom-sheet/bottom-sheet.js +8 -2
- package/dist/bottom-sheet/llms.txt +8 -2
- package/dist/breadcrumb/breadcrumb.js +4 -4
- package/dist/breadcrumb/llms.txt +12 -5
- package/dist/card/card.d.ts +9 -1
- package/dist/card/card.js +2 -2
- package/dist/card/llms.txt +6 -2
- package/dist/carousel/carousel.d.ts +8 -1
- package/dist/carousel/carousel.js +9 -2
- package/dist/carousel/llms.txt +24 -10
- package/dist/checkbox/checkbox.d.ts +6 -1
- package/dist/checkbox/checkbox.js +11 -3
- package/dist/checkbox/llms.txt +11 -0
- package/dist/chip/chip.js +10 -1
- package/dist/chip/llms.txt +7 -4
- package/dist/chips-input/chips-input.d.ts +7 -1
- package/dist/chips-input/chips-input.js +21 -5
- package/dist/chips-input/llms.txt +12 -0
- package/dist/color-picker/color-picker.d.ts +18 -5
- package/dist/color-picker/color-picker.js +48 -14
- package/dist/color-picker/llms.txt +14 -0
- package/dist/confirm/confirm-container.d.ts +5 -1
- package/dist/confirm/confirm-container.js +15 -1
- package/dist/confirm/llms.txt +4 -2
- package/dist/context-menu/context-menu.d.ts +5 -0
- package/dist/context-menu/context-menu.js +48 -4
- package/dist/context-menu/llms.txt +11 -4
- package/dist/date-range/date-range.d.ts +14 -1
- package/dist/date-range/date-range.js +133 -17
- package/dist/date-range/llms.txt +26 -3
- package/dist/datepicker/datepicker.d.ts +10 -1
- package/dist/datepicker/datepicker.js +147 -14
- package/dist/datepicker/llms.txt +27 -3
- package/dist/dialog/dialog.d.ts +4 -0
- package/dist/dialog/dialog.js +7 -1
- package/dist/dialog/llms.txt +2 -0
- package/dist/drawer/drawer.js +7 -2
- package/dist/drawer/llms.txt +10 -5
- package/dist/empty/empty.d.ts +6 -1
- package/dist/empty/empty.js +2 -2
- package/dist/empty/llms.txt +4 -3
- package/dist/fab/llms.txt +2 -1
- package/dist/file-upload/file-upload.d.ts +5 -0
- package/dist/file-upload/file-upload.js +24 -3
- package/dist/file-upload/llms.txt +15 -4
- package/dist/form-field/form-field-context.d.ts +30 -1
- package/dist/form-field/form-field-context.js +27 -0
- package/dist/form-field/form-field.js +16 -2
- package/dist/form-field/llms.txt +10 -1
- package/dist/galleria/galleria.d.ts +6 -1
- package/dist/galleria/galleria.js +40 -1
- package/dist/galleria/llms.txt +17 -8
- package/dist/icon/icon.d.ts +7 -1
- package/dist/icon/icon.js +18 -4
- package/dist/icon/llms.txt +5 -2
- package/dist/image/image.d.ts +5 -0
- package/dist/image/image.js +38 -2
- package/dist/image/llms.txt +24 -17
- package/dist/input/input.d.ts +1 -1
- package/dist/input/input.js +4 -6
- package/dist/input/llms.txt +5 -0
- package/dist/input-mask/input-mask.d.ts +1 -1
- package/dist/input-mask/input-mask.js +8 -6
- package/dist/input-mask/llms.txt +11 -0
- package/dist/input-number/input-number.d.ts +13 -1
- package/dist/input-number/input-number.js +49 -9
- package/dist/input-number/llms.txt +21 -3
- package/dist/input-otp/input-otp.d.ts +7 -2
- package/dist/input-otp/input-otp.js +36 -7
- package/dist/input-otp/llms.txt +14 -0
- package/dist/internal/calendar-focus.d.ts +8 -0
- package/dist/internal/calendar-focus.js +38 -0
- package/dist/internal/option-nav.d.ts +24 -0
- package/dist/internal/option-nav.js +80 -0
- package/dist/internal/roving.d.ts +9 -0
- package/dist/internal/roving.js +43 -0
- package/dist/internal/use-modal-focus.d.ts +10 -0
- package/dist/internal/use-modal-focus.js +57 -0
- package/dist/knob/knob.d.ts +5 -1
- package/dist/knob/knob.js +4 -2
- package/dist/knob/llms.txt +8 -0
- package/dist/listbox/listbox.d.ts +13 -1
- package/dist/listbox/listbox.js +89 -6
- package/dist/listbox/llms.txt +21 -5
- package/dist/megamenu/llms.txt +28 -10
- package/dist/megamenu/megamenu.d.ts +10 -1
- package/dist/megamenu/megamenu.js +116 -5
- package/dist/menu/llms.txt +12 -1
- package/dist/menu/menu-keys.d.ts +15 -0
- package/dist/menu/menu-keys.js +53 -0
- package/dist/menu/menu-rows.js +1 -1
- package/dist/menu/menu.js +52 -4
- package/dist/menubar/llms.txt +24 -7
- package/dist/menubar/menubar.d.ts +8 -1
- package/dist/menubar/menubar.js +92 -6
- package/dist/meter-group/llms.txt +10 -3
- package/dist/meter-group/meter-group.d.ts +9 -2
- package/dist/meter-group/meter-group.js +8 -2
- package/dist/order-list/llms.txt +11 -3
- package/dist/order-list/order-list.d.ts +5 -1
- package/dist/order-list/order-list.js +46 -8
- package/dist/panel/llms.txt +9 -6
- package/dist/panel/panel.js +3 -1
- package/dist/password/llms.txt +8 -0
- package/dist/password/password.d.ts +6 -1
- package/dist/password/password.js +6 -5
- package/dist/pick-list/llms.txt +12 -3
- package/dist/pick-list/pick-list.d.ts +5 -1
- package/dist/pick-list/pick-list.js +52 -5
- package/dist/popover/llms.txt +9 -2
- package/dist/popover/popover.d.ts +9 -3
- package/dist/popover/popover.js +41 -6
- package/dist/public-api.d.ts +6 -6
- package/dist/public-api.js +1 -1
- package/dist/radio-group/llms.txt +11 -0
- package/dist/radio-group/radio-group.d.ts +9 -1
- package/dist/radio-group/radio-group.js +5 -2
- package/dist/rating/llms.txt +18 -5
- package/dist/rating/rating.d.ts +21 -3
- package/dist/rating/rating.js +87 -12
- package/dist/rich-text/llms.txt +20 -0
- package/dist/rich-text/rich-text.d.ts +7 -1
- package/dist/rich-text/rich-text.js +155 -28
- package/dist/scroll-top/llms.txt +5 -1
- package/dist/scroll-top/scroll-top.js +3 -1
- package/dist/select/llms.txt +21 -7
- package/dist/select/select.d.ts +12 -1
- package/dist/select/select.js +110 -38
- package/dist/sidenav/llms.txt +6 -0
- package/dist/skeleton/llms.txt +3 -2
- package/dist/skeleton/skeleton.d.ts +1 -1
- package/dist/skeleton/skeleton.js +2 -2
- package/dist/slider/llms.txt +14 -1
- package/dist/slider/slider.d.ts +11 -2
- package/dist/slider/slider.js +15 -2
- package/dist/speed-dial/llms.txt +6 -2
- package/dist/speed-dial/speed-dial.d.ts +1 -1
- package/dist/speed-dial/speed-dial.js +14 -3
- package/dist/spinner/llms.txt +12 -8
- package/dist/spinner/spinner.d.ts +5 -1
- package/dist/spinner/spinner.js +12 -2
- package/dist/split-button/llms.txt +11 -3
- package/dist/split-button/split-button.d.ts +3 -1
- package/dist/split-button/split-button.js +43 -3
- package/dist/splitter/llms.txt +14 -4
- package/dist/splitter/splitter.d.ts +9 -2
- package/dist/splitter/splitter.js +15 -2
- package/dist/stepper/llms.txt +8 -4
- package/dist/stepper/stepper.d.ts +3 -2
- package/dist/stepper/stepper.js +7 -2
- package/dist/switch/llms.txt +10 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +4 -2
- package/dist/switch-group/llms.txt +11 -0
- package/dist/switch-group/switch-group.d.ts +5 -1
- package/dist/switch-group/switch-group.js +7 -2
- package/dist/tabs/llms.txt +10 -4
- package/dist/tabs/tabs.d.ts +4 -0
- package/dist/tabs/tabs.js +21 -2
- package/dist/textarea/llms.txt +10 -0
- package/dist/textarea/textarea.d.ts +3 -1
- package/dist/textarea/textarea.js +13 -8
- package/dist/toast/llms.txt +21 -7
- package/dist/toast/toast-container.d.ts +6 -1
- package/dist/toast/toast-container.js +52 -1
- package/dist/toast/toast-store.d.ts +12 -0
- package/dist/toast/toast-store.js +46 -7
- package/dist/tooltip/llms.txt +10 -3
- package/dist/tooltip/tooltip.d.ts +5 -0
- package/dist/tooltip/tooltip.js +80 -7
- package/dist/tree/llms.txt +20 -8
- package/dist/tree/tree-keys.d.ts +27 -0
- package/dist/tree/tree-keys.js +39 -0
- package/dist/tree/tree.d.ts +12 -5
- package/dist/tree/tree.js +75 -29
- package/package.json +12 -12
package/dist/badge/llms.txt
CHANGED
|
@@ -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">
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
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
|
-
|
|
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"
|
|
43
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
}
|
package/dist/breadcrumb/llms.txt
CHANGED
|
@@ -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
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
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>`
|
|
35
|
-
screen reader
|
|
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.
|
package/dist/card/card.d.ts
CHANGED
|
@@ -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';
|
package/dist/card/llms.txt
CHANGED
|
@@ -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
|
|
60
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/carousel/llms.txt
CHANGED
|
@@ -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"
|
|
11
|
-
|
|
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"
|
|
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
|
|
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
|
|
47
|
-
|
|
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 }),
|
|
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
|
}
|
package/dist/checkbox/llms.txt
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/chip/llms.txt
CHANGED
|
@@ -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"
|
|
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
|
|
50
|
-
|
|
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
|
|
31
|
-
|
|
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: [
|
|
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
|
|
2
|
-
|
|
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
|
-
/**
|
|
8
|
-
|
|
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;
|