@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
|
@@ -2,8 +2,10 @@ import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
3
|
import type { KptMenuItem } from '../menu/menu-item.ts';
|
|
4
4
|
import type { KptButtonVariant } from '../button/button.tsx';
|
|
5
|
+
/** Warianty, które mają styl — podzbiór `KptButtonVariant`. */
|
|
6
|
+
export type KptSplitButtonVariant = Extract<KptButtonVariant, 'filled' | 'tonal' | 'outline' | 'text'>;
|
|
5
7
|
export interface KptSplitButtonProps<T> extends Omit<ComponentPropsWithRef<'span'>, 'onSelect'>, KptI18nOverrides {
|
|
6
|
-
variant?:
|
|
8
|
+
variant?: KptSplitButtonVariant;
|
|
7
9
|
items?: readonly KptMenuItem<T>[];
|
|
8
10
|
disabled?: boolean;
|
|
9
11
|
/** Kliknięcie akcji głównej. */
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useRef } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { KptOverlay } from "../internal/overlay.js";
|
|
5
5
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
6
|
+
import { useKptId } from "../internal/use-id.js";
|
|
6
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
7
8
|
import { KptIcon } from "../icon/icon.js";
|
|
8
9
|
import { MenuRows } from "../menu/menu-rows.js";
|
|
10
|
+
import { focusMenuEdge, handleMenuKeydown } from "../menu/menu-keys.js";
|
|
9
11
|
/**
|
|
10
12
|
* Przycisk dzielony — akcja główna plus rozwijane menu dodatkowych akcji.
|
|
11
13
|
*
|
|
@@ -18,17 +20,55 @@ import { MenuRows } from "../menu/menu-rows.js";
|
|
|
18
20
|
export function KptSplitButton({ variant = 'filled', items = [], disabled = false, onPrimaryClick, onSelect, open, defaultOpen = false, onOpenChange, locale, dictionary, className, children, ...rest }) {
|
|
19
21
|
const { t } = useKptMessages({ locale, dictionary });
|
|
20
22
|
const toggleRef = useRef(null);
|
|
23
|
+
const panelRef = useRef(null);
|
|
24
|
+
/** Którą pozycję skupić po otwarciu — strzałka w górę na przycisku otwiera „od końca”. */
|
|
25
|
+
const focusEdge = useRef('first');
|
|
26
|
+
const panelId = useKptId(undefined, 'kpt-split-button-menu');
|
|
21
27
|
const [isOpen, setOpen] = useControllableState({
|
|
22
28
|
value: open,
|
|
23
29
|
defaultValue: defaultOpen,
|
|
24
30
|
onChange: onOpenChange,
|
|
25
31
|
});
|
|
26
32
|
const close = useCallback(() => setOpen(false), [setOpen]);
|
|
33
|
+
// Po otwarciu fokus na pierwszą (albo ostatnią) pozycję. Klatkę później: `KptOverlay` trzyma
|
|
34
|
+
// panel w `visibility: hidden`, dopóki go nie zmierzy, a niewidocznego elementu nie da się skupić.
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!isOpen)
|
|
37
|
+
return;
|
|
38
|
+
const frame = requestAnimationFrame(() => {
|
|
39
|
+
if (panelRef.current)
|
|
40
|
+
focusMenuEdge(panelRef.current, focusEdge.current);
|
|
41
|
+
});
|
|
42
|
+
return () => cancelAnimationFrame(frame);
|
|
43
|
+
}, [isOpen]);
|
|
44
|
+
/** Fokus wraca na strzałkę — inaczej po zniknięciu panelu spadłby na `<body>`. */
|
|
45
|
+
const closeAndRestoreFocus = () => {
|
|
46
|
+
close();
|
|
47
|
+
toggleRef.current?.focus();
|
|
48
|
+
};
|
|
49
|
+
const onToggleKeyDown = (event) => {
|
|
50
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')
|
|
51
|
+
return;
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
focusEdge.current = event.key === 'ArrowUp' ? 'last' : 'first';
|
|
54
|
+
if (isOpen && panelRef.current)
|
|
55
|
+
focusMenuEdge(panelRef.current, focusEdge.current);
|
|
56
|
+
else
|
|
57
|
+
setOpen(true);
|
|
58
|
+
};
|
|
59
|
+
const onPanelKeyDown = (event) => {
|
|
60
|
+
if (panelRef.current && handleMenuKeydown(panelRef.current, event.nativeEvent) === 'close') {
|
|
61
|
+
closeAndRestoreFocus();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
27
64
|
const pick = (item) => {
|
|
28
65
|
if (item.disabled)
|
|
29
66
|
return;
|
|
30
67
|
onSelect?.(item);
|
|
31
|
-
|
|
68
|
+
closeAndRestoreFocus();
|
|
32
69
|
};
|
|
33
|
-
return (_jsxs("span", { className: cn('kpt-split-button-host', className), ...rest, children: [_jsxs("div", { className: "kpt-split-button", "data-variant": variant, children: [_jsx("button", { className: "kpt-split-button__main", type: "button", disabled: disabled, onClick: onPrimaryClick, children: children }), _jsx("button", { ref: toggleRef, className: "kpt-split-button__toggle", type: "button", disabled: disabled, onClick: () =>
|
|
70
|
+
return (_jsxs("span", { className: cn('kpt-split-button-host', className), ...rest, children: [_jsxs("div", { className: "kpt-split-button", "data-variant": variant, children: [_jsx("button", { className: "kpt-split-button__main", type: "button", disabled: disabled, onClick: onPrimaryClick, children: children }), _jsx("button", { ref: toggleRef, className: "kpt-split-button__toggle", type: "button", disabled: disabled, onClick: () => {
|
|
71
|
+
focusEdge.current = 'first';
|
|
72
|
+
setOpen(!isOpen);
|
|
73
|
+
}, onKeyDown: onToggleKeyDown, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": isOpen ? panelId : undefined, "aria-label": t('splitButton.more'), children: _jsx(KptIcon, { name: "chevron-down" }) })] }), _jsx(KptOverlay, { open: isOpen, anchor: toggleRef.current, onClose: close, align: "end", children: _jsx("div", { ref: panelRef, id: panelId, className: "kpt-split-button__panel", role: "menu", onKeyDown: onPanelKeyDown, children: _jsx(MenuRows, { items: items, block: "kpt-split-button", onPick: pick }) }) })] }));
|
|
34
74
|
}
|
package/dist/splitter/llms.txt
CHANGED
|
@@ -6,8 +6,10 @@ Import: `import { KptSplitter } from '@konce-pt/react';`
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-splitter-host">
|
|
8
8
|
<div class="kpt-splitter" data-orientation="horizontal">
|
|
9
|
-
<div class="kpt-splitter__pane" style="flex-basis:40%">…</div>
|
|
10
|
-
<div class="kpt-splitter__gutter" role="separator" tabindex="0" aria-
|
|
9
|
+
<div class="kpt-splitter__pane" id="kpt-splitter-start-r1" style="flex-basis:40%">…</div>
|
|
10
|
+
<div class="kpt-splitter__gutter" role="separator" tabindex="0" aria-orientation="vertical"
|
|
11
|
+
aria-valuenow="40" aria-valuemin="10" aria-valuemax="90"
|
|
12
|
+
aria-controls="kpt-splitter-start-r1" aria-label="Resize panes"></div>
|
|
11
13
|
<div class="kpt-splitter__pane" style="flex-basis:60%">…</div>
|
|
12
14
|
</div>
|
|
13
15
|
</div>
|
|
@@ -16,6 +18,10 @@ Import: `import { KptSplitter } from '@konce-pt/react';`
|
|
|
16
18
|
- `orientation`: `'horizontal'` (default) | `'vertical'`
|
|
17
19
|
- `startSize` / `defaultStartSize` / `onStartSizeChange` — the first pane's size in percent
|
|
18
20
|
- `minSize`: number (percent, default 10) — neither pane goes below it
|
|
21
|
+
- `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
|
|
22
|
+
- `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
|
|
23
|
+
- `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
|
|
24
|
+
- `ariaLabel`: string — the handle's name; empty = `splitter.resize` from the dictionary
|
|
19
25
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
20
26
|
|
|
21
27
|
## Panes
|
|
@@ -40,5 +46,9 @@ The handle `--kpt-color-border` → `--kpt-color-primary` on hover and focus; th
|
|
|
40
46
|
`--kpt-color-border` with `--kpt-radius-md`.
|
|
41
47
|
|
|
42
48
|
## Accessibility
|
|
43
|
-
The handle is `role="separator"`
|
|
44
|
-
|
|
49
|
+
The handle is a focusable `role="separator"` (the WAI-ARIA window splitter): `aria-valuenow`,
|
|
50
|
+
`aria-valuemin`/`aria-valuemax` (from `minSize`), `aria-controls` pointing at the first pane, and a
|
|
51
|
+
name from `ariaLabel` or `splitter.resize` in the dictionary. Arrow keys resize by 2%, `Home`/`End`
|
|
52
|
+
jump to the minimum/maximum. `aria-orientation` describes the handle's line, not the layout —
|
|
53
|
+
side-by-side panes have a vertical handle. The host takes `height: 100%`, so a height set on the
|
|
54
|
+
parent reaches the panes.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
2
3
|
export type KptSplitterOrientation = 'horizontal' | 'vertical';
|
|
3
4
|
export interface KptSplitterPaneProps {
|
|
4
5
|
children?: ReactNode;
|
|
@@ -12,7 +13,7 @@ export declare function KptSplitterEnd(_props: Readonly<KptSplitterPaneProps>):
|
|
|
12
13
|
export declare namespace KptSplitterEnd {
|
|
13
14
|
var kptSplitterSlot: "end";
|
|
14
15
|
}
|
|
15
|
-
export interface KptSplitterProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange'
|
|
16
|
+
export interface KptSplitterProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange'>, KptI18nOverrides {
|
|
16
17
|
orientation?: KptSplitterOrientation;
|
|
17
18
|
/** Rozmiar pierwszego panelu w procentach (0–100). */
|
|
18
19
|
startSize?: number;
|
|
@@ -20,19 +21,25 @@ export interface KptSplitterProps extends Omit<ComponentPropsWithRef<'div'>, 'on
|
|
|
20
21
|
onStartSizeChange?: (size: number) => void;
|
|
21
22
|
/** Minimalny rozmiar panelu w procentach. */
|
|
22
23
|
minSize?: number;
|
|
24
|
+
/** Nazwa uchwytu dla czytników ekranu; domyślnie `splitter.resize` ze słownika. */
|
|
25
|
+
ariaLabel?: string;
|
|
23
26
|
children?: ReactNode;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Dwa panele z przeciąganym uchwytem. `horizontal` stawia je obok siebie, `vertical` jeden pod
|
|
27
30
|
* drugim.
|
|
28
31
|
*
|
|
32
|
+
* Uchwyt to `role="separator"` z wartością (wzorzec WAI-ARIA „window splitter”): strzałki
|
|
33
|
+
* przesuwają go o 2%, `Home`/`End` dosuwają do minimum i maksimum. `aria-orientation` opisuje
|
|
34
|
+
* linię uchwytu, nie układ — przy panelach obok siebie linia jest pionowa.
|
|
35
|
+
*
|
|
29
36
|
* @example
|
|
30
37
|
* <KptSplitter startSize={size} onStartSizeChange={setSize}>
|
|
31
38
|
* <KptSplitter.Start>Lewy</KptSplitter.Start>
|
|
32
39
|
* <KptSplitter.End>Prawy</KptSplitter.End>
|
|
33
40
|
* </KptSplitter>
|
|
34
41
|
*/
|
|
35
|
-
export declare function KptSplitter({ orientation, startSize, defaultStartSize, onStartSizeChange, minSize, className, children, ...rest }: Readonly<KptSplitterProps>): import("react").JSX.Element;
|
|
42
|
+
export declare function KptSplitter({ orientation, startSize, defaultStartSize, onStartSizeChange, minSize, ariaLabel, locale, dictionary, className, children, ...rest }: Readonly<KptSplitterProps>): import("react").JSX.Element;
|
|
36
43
|
export declare namespace KptSplitter {
|
|
37
44
|
var Start: typeof KptSplitterStart;
|
|
38
45
|
var End: typeof KptSplitterEnd;
|
|
@@ -2,6 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Children, isValidElement, useRef, } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
|
+
import { useKptId } from "../internal/use-id.js";
|
|
6
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
7
|
/** Panel splittera. Umieszczaj jako `<KptSplitter.Start>` i `<KptSplitter.End>`. */
|
|
6
8
|
export function KptSplitterStart(_props) {
|
|
7
9
|
// Panel jest nośnikiem treści: `KptSplitter` czyta jego dzieci i sam opakowuje je w `__pane`,
|
|
@@ -17,14 +19,20 @@ KptSplitterEnd.kptSplitterSlot = 'end';
|
|
|
17
19
|
* Dwa panele z przeciąganym uchwytem. `horizontal` stawia je obok siebie, `vertical` jeden pod
|
|
18
20
|
* drugim.
|
|
19
21
|
*
|
|
22
|
+
* Uchwyt to `role="separator"` z wartością (wzorzec WAI-ARIA „window splitter”): strzałki
|
|
23
|
+
* przesuwają go o 2%, `Home`/`End` dosuwają do minimum i maksimum. `aria-orientation` opisuje
|
|
24
|
+
* linię uchwytu, nie układ — przy panelach obok siebie linia jest pionowa.
|
|
25
|
+
*
|
|
20
26
|
* @example
|
|
21
27
|
* <KptSplitter startSize={size} onStartSizeChange={setSize}>
|
|
22
28
|
* <KptSplitter.Start>Lewy</KptSplitter.Start>
|
|
23
29
|
* <KptSplitter.End>Prawy</KptSplitter.End>
|
|
24
30
|
* </KptSplitter>
|
|
25
31
|
*/
|
|
26
|
-
export function KptSplitter({ orientation = 'horizontal', startSize, defaultStartSize = 50, onStartSizeChange, minSize = 10, className, children, ...rest }) {
|
|
32
|
+
export function KptSplitter({ orientation = 'horizontal', startSize, defaultStartSize = 50, onStartSizeChange, minSize = 10, ariaLabel, locale, dictionary, className, children, ...rest }) {
|
|
27
33
|
const containerRef = useRef(null);
|
|
34
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
35
|
+
const startId = useKptId(undefined, 'kpt-splitter-start');
|
|
28
36
|
const [size, setSize] = useControllableState({
|
|
29
37
|
value: startSize,
|
|
30
38
|
defaultValue: defaultStartSize,
|
|
@@ -64,6 +72,11 @@ export function KptSplitter({ orientation = 'horizontal', startSize, defaultStar
|
|
|
64
72
|
window.addEventListener('pointerup', up);
|
|
65
73
|
};
|
|
66
74
|
const onKeyDown = (event) => {
|
|
75
|
+
if (event.key === 'Home' || event.key === 'End') {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
setSize(clamp(event.key === 'Home' ? 0 : 100));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
67
80
|
const dir = event.key === 'ArrowLeft' || event.key === 'ArrowUp'
|
|
68
81
|
? -2
|
|
69
82
|
: event.key === 'ArrowRight' || event.key === 'ArrowDown'
|
|
@@ -74,7 +87,7 @@ export function KptSplitter({ orientation = 'horizontal', startSize, defaultStar
|
|
|
74
87
|
event.preventDefault();
|
|
75
88
|
setSize(clamp(size + dir));
|
|
76
89
|
};
|
|
77
|
-
return (_jsx("div", { className: cn('kpt-splitter-host', className), ...rest, children: _jsxs("div", { ref: containerRef, className: "kpt-splitter", "data-orientation": orientation, children: [_jsx("div", { className: "kpt-splitter__pane", style: { flexBasis: `${size}%` }, children: start }), _jsx("div", { className: "kpt-splitter__gutter", role: "separator", tabIndex: 0, "aria-orientation": orientation, "aria-valuenow": size, onPointerDown: onPointerDown, onKeyDown: onKeyDown }), _jsx("div", { className: "kpt-splitter__pane", style: { flexBasis: `${100 - size}%` }, children: end })] }) }));
|
|
90
|
+
return (_jsx("div", { className: cn('kpt-splitter-host', className), ...rest, children: _jsxs("div", { ref: containerRef, className: "kpt-splitter", "data-orientation": orientation, children: [_jsx("div", { className: "kpt-splitter__pane", id: startId, style: { flexBasis: `${size}%` }, children: start }), _jsx("div", { className: "kpt-splitter__gutter", role: "separator", tabIndex: 0, "aria-orientation": orientation === 'horizontal' ? 'vertical' : 'horizontal', "aria-valuenow": size, "aria-valuemin": minSize, "aria-valuemax": 100 - minSize, "aria-controls": startId, "aria-label": ariaLabel || t('splitter.resize'), onPointerDown: onPointerDown, onKeyDown: onKeyDown }), _jsx("div", { className: "kpt-splitter__pane", style: { flexBasis: `${100 - size}%` }, children: end })] }) }));
|
|
78
91
|
}
|
|
79
92
|
KptSplitter.Start = KptSplitterStart;
|
|
80
93
|
KptSplitter.End = KptSplitterEnd;
|
package/dist/stepper/llms.txt
CHANGED
|
@@ -7,8 +7,8 @@ Import: `import { KptStepper, useKptStepper } from '@konce-pt/react';`
|
|
|
7
7
|
<div class="kpt-stepper-host" style="--kpt-anim-duration:500ms; --kpt-anim-dir:1">
|
|
8
8
|
<ol class="kpt-stepper__header">
|
|
9
9
|
<li class="kpt-stepper__step is-done">
|
|
10
|
-
<button class="kpt-stepper__marker">✓</button>
|
|
11
|
-
<span class="kpt-stepper__label">Account</span>
|
|
10
|
+
<button class="kpt-stepper__marker" aria-label="Step 1: Account, completed">✓</button>
|
|
11
|
+
<span class="kpt-stepper__label" aria-hidden="true">Account</span>
|
|
12
12
|
<span class="kpt-stepper__connector"></span>
|
|
13
13
|
</li>
|
|
14
14
|
…
|
|
@@ -60,5 +60,9 @@ The active and completed step: `--kpt-color-primary` (marker, connector); the re
|
|
|
60
60
|
`--kpt-color-border-strong` and `--kpt-color-on-surface-muted`.
|
|
61
61
|
|
|
62
62
|
## Accessibility
|
|
63
|
-
The header is an `<ol>` of real buttons
|
|
64
|
-
marker
|
|
63
|
+
The header is an `<ol>` of real buttons, so every step is reachable with the keyboard and the list
|
|
64
|
+
announces how many there are. Each marker is named from the dictionary — `stepper.step`
|
|
65
|
+
(“Step 2: Delivery”) or `stepper.stepDone` (“…, completed”) — because the visible marker is only a
|
|
66
|
+
number or a check; the visible label is `aria-hidden` so it is not read twice. The active marker
|
|
67
|
+
carries `aria-current="step"`. After `next()` focus stays where it was — move it into the new step
|
|
68
|
+
yourself when the step is long.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
2
3
|
export interface KptStepperContextValue {
|
|
3
4
|
activeIndex: number;
|
|
4
5
|
/** Liczba kroków — pozwala treści kroku ukryć „Dalej" na ostatnim. */
|
|
@@ -27,7 +28,7 @@ export declare function KptStep(_props: Readonly<KptStepProps>): ReactNode;
|
|
|
27
28
|
export declare namespace KptStep {
|
|
28
29
|
var kptStepperSlot: "step";
|
|
29
30
|
}
|
|
30
|
-
export interface KptStepperProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange'
|
|
31
|
+
export interface KptStepperProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange'>, KptI18nOverrides {
|
|
31
32
|
activeIndex?: number;
|
|
32
33
|
defaultActiveIndex?: number;
|
|
33
34
|
onActiveIndexChange?: (index: number) => void;
|
|
@@ -45,7 +46,7 @@ export interface KptStepperProps extends Omit<ComponentPropsWithRef<'div'>, 'onC
|
|
|
45
46
|
* <KptStepper.Step label="Profil">…</KptStepper.Step>
|
|
46
47
|
* </KptStepper>
|
|
47
48
|
*/
|
|
48
|
-
export declare function KptStepper({ activeIndex, defaultActiveIndex, onActiveIndexChange, animated, animationDuration, className, style, children, ...rest }: Readonly<KptStepperProps>): import("react").JSX.Element;
|
|
49
|
+
export declare function KptStepper({ activeIndex, defaultActiveIndex, onActiveIndexChange, animated, animationDuration, locale, dictionary, className, style, children, ...rest }: Readonly<KptStepperProps>): import("react").JSX.Element;
|
|
49
50
|
export declare namespace KptStepper {
|
|
50
51
|
var Step: typeof KptStep;
|
|
51
52
|
}
|
package/dist/stepper/stepper.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Children, createContext, isValidElement, useContext, useState, } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
6
|
import { KptIcon } from "../icon/icon.js";
|
|
6
7
|
const StepperContext = createContext(null);
|
|
7
8
|
/**
|
|
@@ -36,7 +37,8 @@ KptStep.kptStepperSlot = 'step';
|
|
|
36
37
|
* <KptStepper.Step label="Profil">…</KptStepper.Step>
|
|
37
38
|
* </KptStepper>
|
|
38
39
|
*/
|
|
39
|
-
export function KptStepper({ activeIndex, defaultActiveIndex = 0, onActiveIndexChange, animated = true, animationDuration = 500, className, style, children, ...rest }) {
|
|
40
|
+
export function KptStepper({ activeIndex, defaultActiveIndex = 0, onActiveIndexChange, animated = true, animationDuration = 500, locale, dictionary, className, style, children, ...rest }) {
|
|
41
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
40
42
|
const [active, setActive] = useControllableState({
|
|
41
43
|
value: activeIndex,
|
|
42
44
|
defaultValue: defaultActiveIndex,
|
|
@@ -66,6 +68,9 @@ export function KptStepper({ activeIndex, defaultActiveIndex = 0, onActiveIndexC
|
|
|
66
68
|
['--kpt-anim-duration']: `${animated ? animationDuration : 0}ms`,
|
|
67
69
|
['--kpt-anim-dir']: direction,
|
|
68
70
|
...style,
|
|
69
|
-
}, ...rest, children: [_jsx("ol", { className: "kpt-stepper__header", children: steps.map((step, index) => (_jsxs("li", { className: cn('kpt-stepper__step', index === active && 'is-active', index < active && 'is-done'), children: [_jsx("button", { type: "button", className: "kpt-stepper__marker", "aria-current": index === active ? 'step' : undefined,
|
|
71
|
+
}, ...rest, children: [_jsx("ol", { className: "kpt-stepper__header", children: steps.map((step, index) => (_jsxs("li", { className: cn('kpt-stepper__step', index === active && 'is-active', index < active && 'is-done'), children: [_jsx("button", { type: "button", className: "kpt-stepper__marker", "aria-current": index === active ? 'step' : undefined, "aria-label": t(index < active ? 'stepper.stepDone' : 'stepper.step', {
|
|
72
|
+
index: index + 1,
|
|
73
|
+
label: step.label,
|
|
74
|
+
}), onClick: () => goTo(index), children: index < active ? _jsx(KptIcon, { name: "check" }) : index + 1 }), _jsx("span", { className: "kpt-stepper__label", "aria-hidden": "true", children: step.label }), index < steps.length - 1 ? _jsx("span", { className: "kpt-stepper__connector" }) : null] }, index))) }), _jsx(StepperContext.Provider, { value: context, children: _jsx("div", { className: "kpt-stepper__body", children: steps[active] ? (_jsx("div", { className: "kpt-stepper__panel", children: steps[active].children }, active)) : null }) })] }));
|
|
70
75
|
}
|
|
71
76
|
KptStepper.Step = KptStep;
|
package/dist/switch/llms.txt
CHANGED
|
@@ -33,6 +33,16 @@ show one.
|
|
|
33
33
|
<KptSwitch checked={on} onCheckedChange={setOn} onLabel="On" offLabel="Off" />
|
|
34
34
|
<KptSwitch checked={on} onCheckedChange={setOn} label="Dark mode" labelPosition="start" />
|
|
35
35
|
|
|
36
|
+
## Screen reader and keyboard
|
|
37
|
+
The `onLabel`/`offLabel` text is `aria-hidden`: `role="switch"` already announces the state, and
|
|
38
|
+
text inside the `<label>` would change the switch's name on every toggle.
|
|
39
|
+
|
|
40
|
+
## Label and description
|
|
41
|
+
`id` goes to the native field, not the host; without one it comes from `KptFormField` or is
|
|
42
|
+
generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
|
|
43
|
+
added to `aria-describedby` (merged with your own). Without a wrapper, name it with `aria-label`.
|
|
44
|
+
Inside `kpt-form-field` the switch gets both names: the wrapper label and its own `label`.
|
|
45
|
+
|
|
36
46
|
## Tokens
|
|
37
47
|
`--kpt-color-primary` (the on track), `--kpt-color-muted` (the off track), `--kpt-color-surface`
|
|
38
48
|
(the thumb), `--kpt-motion-duration-fast`.
|
package/dist/switch/switch.d.ts
CHANGED
|
@@ -36,4 +36,4 @@ export interface KptSwitchProps extends Omit<ComponentPropsWithRef<'input'>, 'ty
|
|
|
36
36
|
* <KptSwitch name="notifications" label="Powiadomienia" />
|
|
37
37
|
* <KptSwitch checked={on} onCheckedChange={setOn} onLabel="Włączony" offLabel="Wyłączony" />
|
|
38
38
|
*/
|
|
39
|
-
export declare function KptSwitch({ label, labelPosition, onLabel, offLabel, required, invalid, touched, onCheckedChange, onTouch, hostProps, className, disabled, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, ...rest }: Readonly<KptSwitchProps>): import("react").JSX.Element;
|
|
39
|
+
export declare function KptSwitch({ label, labelPosition, onLabel, offLabel, required, invalid, touched, onCheckedChange, onTouch, hostProps, className, disabled, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, ...rest }: Readonly<KptSwitchProps>): import("react").JSX.Element;
|
package/dist/switch/switch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
4
5
|
/**
|
|
5
6
|
* Przełącznik (switch/toggle). Pod spodem natywny `<input type="checkbox" role="switch">`,
|
|
6
7
|
* więc klawiatura i czytniki ekranu działają bez dodatkowej obsługi.
|
|
@@ -14,7 +15,8 @@ import { cn } from "../internal/cn.js";
|
|
|
14
15
|
* <KptSwitch name="notifications" label="Powiadomienia" />
|
|
15
16
|
* <KptSwitch checked={on} onCheckedChange={setOn} onLabel="Włączony" offLabel="Wyłączony" />
|
|
16
17
|
*/
|
|
17
|
-
export function KptSwitch({ label = '', labelPosition = 'end', onLabel = '', offLabel = '', required = false, invalid = false, touched, onCheckedChange, onTouch, hostProps, className, disabled = false, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, ...rest }) {
|
|
18
|
+
export function KptSwitch({ label = '', labelPosition = 'end', onLabel = '', offLabel = '', required = false, invalid = false, touched, onCheckedChange, onTouch, hostProps, className, disabled = false, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, ...rest }) {
|
|
19
|
+
const field = useKptFieldA11y(id, 'kpt-switch');
|
|
18
20
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
19
21
|
const showInvalid = invalid && (touched ?? true);
|
|
20
22
|
const hasStateLabels = !!(onLabel || offLabel);
|
|
@@ -26,5 +28,5 @@ export function KptSwitch({ label = '', labelPosition = 'end', onLabel = '', off
|
|
|
26
28
|
onBlur?.(event);
|
|
27
29
|
onTouch?.();
|
|
28
30
|
}, [onBlur, onTouch]);
|
|
29
|
-
return (_jsx("span", { className: cn('kpt-switch-host', hostClassName), ...hostRest, children: _jsxs("label", { className: cn('kpt-switch', disabled && 'kpt-switch--disabled', className), children: [label && labelPosition === 'start' ? (_jsx("span", { className: "kpt-switch__label kpt-switch__label--start", children: label })) : null, _jsx("input", { type: "checkbox", role: "switch", className: "kpt-switch__input", checked: checked, disabled: disabled, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }), _jsx("span", { className: "kpt-switch__track", "aria-hidden": "true", children: _jsx("span", { className: "kpt-switch__thumb" }) }), label && labelPosition === 'end' ? _jsx("span", { className: "kpt-switch__label", children: label }) : null, hasStateLabels ? (_jsx("span", { className: cn('kpt-switch__state', checked && 'kpt-switch__state--on'), children: checked ? onLabel : offLabel })) : null, children] }) }));
|
|
31
|
+
return (_jsx("span", { className: cn('kpt-switch-host', hostClassName), ...hostRest, children: _jsxs("label", { className: cn('kpt-switch', disabled && 'kpt-switch--disabled', className), children: [label && labelPosition === 'start' ? (_jsx("span", { className: "kpt-switch__label kpt-switch__label--start", children: label })) : null, _jsx("input", { type: "checkbox", role: "switch", className: "kpt-switch__input", id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), checked: checked, disabled: disabled, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }), _jsx("span", { className: "kpt-switch__track", "aria-hidden": "true", children: _jsx("span", { className: "kpt-switch__thumb" }) }), label && labelPosition === 'end' ? _jsx("span", { className: "kpt-switch__label", children: label }) : null, hasStateLabels ? (_jsx("span", { className: cn('kpt-switch__state', checked && 'kpt-switch__state--on'), "aria-hidden": "true", children: checked ? onLabel : offLabel })) : null, children] }) }));
|
|
30
32
|
}
|
|
@@ -34,6 +34,17 @@ re-applies the controlled `checked` and the switch snaps back on.
|
|
|
34
34
|
<KptSwitchGroup multiple value={channels} onValueChange={setChannels} options={options} />
|
|
35
35
|
<KptSwitchGroup orientation="horizontal" value={mode} onValueChange={setMode} options={options} />
|
|
36
36
|
|
|
37
|
+
## Screen reader and keyboard
|
|
38
|
+
`aria-invalid` is not supported on `role="group"`, so the error state is carried by each switch. In
|
|
39
|
+
single mode the switches are mutually exclusive and nothing announces that turning one on turned
|
|
40
|
+
another off — for a single choice prefer `KptRadioGroup`.
|
|
41
|
+
|
|
42
|
+
## Label and description
|
|
43
|
+
`id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
|
|
44
|
+
generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
|
|
45
|
+
`aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
|
|
46
|
+
is added to `aria-describedby`. New: `id` and `ariaLabel` on the `group`.
|
|
47
|
+
|
|
37
48
|
## Tokens
|
|
38
49
|
Layout only: `--kpt-space-2` (vertical gap) and `--kpt-space-4` (horizontal). The look comes from
|
|
39
50
|
`KptSwitch` and its tokens.
|
|
@@ -11,6 +11,10 @@ export interface KptSwitchGroupProps<T> extends Omit<ComponentPropsWithRef<'div'
|
|
|
11
11
|
orientation?: 'vertical' | 'horizontal';
|
|
12
12
|
/** Czy wyłączenie aktywnej opcji zeruje wartość. Dotyczy tylko trybu pojedynczego. */
|
|
13
13
|
allowDeselect?: boolean;
|
|
14
|
+
/** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
|
|
15
|
+
id?: string;
|
|
16
|
+
/** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
|
|
17
|
+
ariaLabel?: string | null;
|
|
14
18
|
disabled?: boolean;
|
|
15
19
|
invalid?: boolean;
|
|
16
20
|
touched?: boolean;
|
|
@@ -26,4 +30,4 @@ export interface KptSwitchGroupProps<T> extends Omit<ComponentPropsWithRef<'div'
|
|
|
26
30
|
* <KptSwitchGroup options={plans} value={plan} onValueChange={setPlan} />
|
|
27
31
|
* <KptSwitchGroup multiple options={tags} value={selected} onValueChange={setSelected} />
|
|
28
32
|
*/
|
|
29
|
-
export declare function KptSwitchGroup<T>({ options, value, defaultValue, onValueChange, multiple, orientation, allowDeselect, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptSwitchGroupProps<T>>): import("react").JSX.Element;
|
|
33
|
+
export declare function KptSwitchGroup<T>({ options, value, defaultValue, onValueChange, multiple, orientation, allowDeselect, id, ariaLabel, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptSwitchGroupProps<T>>): import("react").JSX.Element;
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { cn } from "../internal/cn.js";
|
|
3
3
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
4
|
import { KptSwitch } from "../switch/switch.js";
|
|
5
|
+
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
5
6
|
/**
|
|
6
7
|
* Grupa przełączników. Domyślnie wzajemnie wykluczających się — włączenie jednego wyłącza
|
|
7
8
|
* pozostałe; z `multiple` wartością jest tablica zaznaczonych opcji.
|
|
@@ -12,7 +13,9 @@ import { KptSwitch } from "../switch/switch.js";
|
|
|
12
13
|
* <KptSwitchGroup options={plans} value={plan} onValueChange={setPlan} />
|
|
13
14
|
* <KptSwitchGroup multiple options={tags} value={selected} onValueChange={setSelected} />
|
|
14
15
|
*/
|
|
15
|
-
export function KptSwitchGroup({ options = [], value, defaultValue = null, onValueChange, multiple = false, orientation = 'vertical', allowDeselect = true, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
16
|
+
export function KptSwitchGroup({ options = [], value, defaultValue = null, onValueChange, multiple = false, orientation = 'vertical', allowDeselect = true, id, ariaLabel = null, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
17
|
+
/** Grupa nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
|
|
18
|
+
const field = useKptFieldA11y(id, 'kpt-switch-group');
|
|
16
19
|
const [current, setCurrent] = useControllableState({
|
|
17
20
|
value,
|
|
18
21
|
defaultValue,
|
|
@@ -43,5 +46,7 @@ export function KptSwitchGroup({ options = [], value, defaultValue = null, onVal
|
|
|
43
46
|
// Bez `allowDeselect` nie robimy nic: `checked` jest wyliczane z wartości, a ta się nie
|
|
44
47
|
// zmieniła, więc przełącznik sam wraca do włączonego przy najbliższym renderze.
|
|
45
48
|
};
|
|
46
|
-
return (_jsx("div", { className: cn('kpt-switch-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-switch-group', orientation === 'horizontal' && 'kpt-switch-group--horizontal'), role: "group", "aria-
|
|
49
|
+
return (_jsx("div", { className: cn('kpt-switch-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-switch-group', orientation === 'horizontal' && 'kpt-switch-group--horizontal'), role: "group", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, children: options.map((option) => (_jsx(KptSwitch, { hostProps: { className: 'kpt-switch-group__item' }, checked: isChecked(option.value), label: option.label, disabled: disabled || option.disabled,
|
|
50
|
+
// `aria-invalid` nie działa na `role="group"` — stan błędu niesie każdy przełącznik.
|
|
51
|
+
invalid: showInvalid, onCheckedChange: (checked) => toggle(option, checked), onTouch: onTouch }, String(option.value)))) }) }));
|
|
47
52
|
}
|
package/dist/tabs/llms.txt
CHANGED
|
@@ -6,11 +6,14 @@ Import: `import { KptTabs } from '@konce-pt/react';`
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-tabs-host" style="--kpt-anim-duration: 500ms; --kpt-anim-dir: 1">
|
|
8
8
|
<div class="kpt-tabs__header" role="tablist">
|
|
9
|
-
<button class="kpt-tabs__tab is-active" role="tab"
|
|
10
|
-
|
|
9
|
+
<button class="kpt-tabs__tab is-active" role="tab" id="kpt-tabs-r1-tab-0"
|
|
10
|
+
aria-selected="true" aria-controls="kpt-tabs-r1-panel" tabindex="0">General</button>
|
|
11
|
+
<button class="kpt-tabs__tab" role="tab" id="kpt-tabs-r1-tab-1"
|
|
12
|
+
aria-selected="false" tabindex="-1">Advanced</button>
|
|
11
13
|
<span class="kpt-tabs__ink" style="width: 95px; transform: translateX(85px)"></span>
|
|
12
14
|
</div>
|
|
13
|
-
<div class="kpt-tabs__body" role="tabpanel"
|
|
15
|
+
<div class="kpt-tabs__body" role="tabpanel" tabindex="0" id="kpt-tabs-r1-panel"
|
|
16
|
+
aria-labelledby="kpt-tabs-r1-tab-0">
|
|
14
17
|
<div class="kpt-tabs__panel">…active tab content…</div>
|
|
15
18
|
</div>
|
|
16
19
|
</div>
|
|
@@ -45,4 +48,7 @@ schedule another render and the component would loop.
|
|
|
45
48
|
|
|
46
49
|
## Accessibility
|
|
47
50
|
The header is `role="tablist"` with `role="tab"` buttons carrying `aria-selected`; the body is
|
|
48
|
-
`role="tabpanel"
|
|
51
|
+
`role="tabpanel"`, named by the active tab (`aria-labelledby`), which points back at it with
|
|
52
|
+
`aria-controls`. The tab row is a single `Tab` stop (roving `tabindex`): `ArrowLeft`/`ArrowRight`
|
|
53
|
+
move between tabs, wrapping at the ends, `Home`/`End` jump to the first/last one, and disabled tabs
|
|
54
|
+
are skipped. Moving activates the tab straight away; the next `Tab` goes to the panel.
|
package/dist/tabs/tabs.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface KptTabsProps extends Omit<ComponentPropsWithRef<'div'>, 'onChan
|
|
|
23
23
|
/**
|
|
24
24
|
* Zakładki — przełączanie widoków, treść renderowana leniwie.
|
|
25
25
|
*
|
|
26
|
+
* Klawiatura według wzorca WAI-ARIA „tabs”: rząd zakładek to jeden przystanek Tab, strzałki
|
|
27
|
+
* w lewo/prawo oraz `Home`/`End` przechodzą po zakładkach (z pominięciem wyłączonych) i od razu je
|
|
28
|
+
* aktywują, a kolejny `Tab` prowadzi do panelu.
|
|
29
|
+
*
|
|
26
30
|
* Podkreślenie aktywnej zakładki (`__ink`) jest mierzone z DOM przycisku, nie liczone z indeksu:
|
|
27
31
|
* zakładki mają różne szerokości, a etykiety zmieniają się z językiem.
|
|
28
32
|
*
|
package/dist/tabs/tabs.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Children, isValidElement, useCallback, useLayoutEffect, useRef, useState, } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { nextRovingIndex, rovingStop } from "../internal/roving.js";
|
|
4
5
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
6
|
+
import { useKptId } from "../internal/use-id.js";
|
|
5
7
|
/**
|
|
6
8
|
* Pojedyncza zakładka. Umieszczaj w `<KptTabs>`. Treść renderuje się leniwie — dopiero gdy
|
|
7
9
|
* zakładka jest aktywna, tak samo jak szablon `kpt-tab` w porcie Angulara.
|
|
@@ -15,6 +17,10 @@ KptTab.kptTabsSlot = 'tab';
|
|
|
15
17
|
/**
|
|
16
18
|
* Zakładki — przełączanie widoków, treść renderowana leniwie.
|
|
17
19
|
*
|
|
20
|
+
* Klawiatura według wzorca WAI-ARIA „tabs”: rząd zakładek to jeden przystanek Tab, strzałki
|
|
21
|
+
* w lewo/prawo oraz `Home`/`End` przechodzą po zakładkach (z pominięciem wyłączonych) i od razu je
|
|
22
|
+
* aktywują, a kolejny `Tab` prowadzi do panelu.
|
|
23
|
+
*
|
|
18
24
|
* Podkreślenie aktywnej zakładki (`__ink`) jest mierzone z DOM przycisku, nie liczone z indeksu:
|
|
19
25
|
* zakładki mają różne szerokości, a etykiety zmieniają się z językiem.
|
|
20
26
|
*
|
|
@@ -33,6 +39,7 @@ export function KptTabs({ activeIndex, defaultActiveIndex = 0, onActiveIndexChan
|
|
|
33
39
|
const [direction, setDirection] = useState(1);
|
|
34
40
|
const [ink, setInk] = useState({ x: 0, w: 0 });
|
|
35
41
|
const buttonsRef = useRef([]);
|
|
42
|
+
const baseId = useKptId(undefined, 'kpt-tabs');
|
|
36
43
|
const tabs = [];
|
|
37
44
|
const contents = [];
|
|
38
45
|
Children.forEach(children, (child) => {
|
|
@@ -57,18 +64,30 @@ export function KptTabs({ activeIndex, defaultActiveIndex = 0, onActiveIndexChan
|
|
|
57
64
|
window.addEventListener('resize', measureInk);
|
|
58
65
|
return () => window.removeEventListener('resize', measureInk);
|
|
59
66
|
}, [measureInk]);
|
|
67
|
+
const disabled = tabs.map((tab) => !!tab.disabled);
|
|
68
|
+
// Jedyna zakładka w kolejności Tab: aktywna, a gdy ta jest wyłączona — pierwsza dostępna.
|
|
69
|
+
const tabStop = rovingStop(disabled, active);
|
|
60
70
|
const select = (index) => {
|
|
61
71
|
if (tabs[index]?.disabled)
|
|
62
72
|
return;
|
|
63
73
|
setDirection(index >= active ? 1 : -1);
|
|
64
74
|
setActive(index);
|
|
65
75
|
};
|
|
76
|
+
const onKeyDown = (event) => {
|
|
77
|
+
const current = buttonsRef.current.indexOf(document.activeElement);
|
|
78
|
+
const next = nextRovingIndex(disabled, current < 0 ? tabStop : current, event.key);
|
|
79
|
+
if (next === null)
|
|
80
|
+
return;
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
select(next);
|
|
83
|
+
buttonsRef.current[next]?.focus();
|
|
84
|
+
};
|
|
66
85
|
return (_jsxs("div", { className: cn('kpt-tabs-host', className), style: {
|
|
67
86
|
['--kpt-anim-duration']: `${animated ? animationDuration : 0}ms`,
|
|
68
87
|
['--kpt-anim-dir']: direction,
|
|
69
88
|
...style,
|
|
70
|
-
}, ...rest, children: [_jsxs("div", { className: "kpt-tabs__header", role: "tablist", children: [tabs.map((tab, index) => (_jsx("button", { ref: (el) => {
|
|
89
|
+
}, ...rest, children: [_jsxs("div", { className: "kpt-tabs__header", role: "tablist", onKeyDown: onKeyDown, children: [tabs.map((tab, index) => (_jsx("button", { ref: (el) => {
|
|
71
90
|
buttonsRef.current[index] = el;
|
|
72
|
-
}, type: "button", className: cn('kpt-tabs__tab', index === active && 'is-active'), role: "tab", disabled: tab.disabled, "aria-selected": index === active, onClick: () => select(index), children: tab.label }, index))), _jsx("span", { className: "kpt-tabs__ink", style: { width: ink.w, transform: `translateX(${ink.x}px)` } })] }), _jsx("div", { className: "kpt-tabs__body", role: "tabpanel", children: contents[active] !== undefined ? (_jsx("div", { className: "kpt-tabs__panel", children: contents[active] }, active)) : null })] }));
|
|
91
|
+
}, type: "button", className: cn('kpt-tabs__tab', index === active && 'is-active'), role: "tab", id: `${baseId}-tab-${index}`, disabled: tab.disabled, "aria-selected": index === active, "aria-controls": index === active ? `${baseId}-panel` : undefined, tabIndex: index === tabStop ? 0 : -1, onClick: () => select(index), children: tab.label }, index))), _jsx("span", { className: "kpt-tabs__ink", style: { width: ink.w, transform: `translateX(${ink.x}px)` } })] }), _jsx("div", { className: "kpt-tabs__body", role: "tabpanel", tabIndex: 0, id: `${baseId}-panel`, "aria-labelledby": `${baseId}-tab-${active}`, children: contents[active] !== undefined ? (_jsx("div", { className: "kpt-tabs__panel", children: contents[active] }, active)) : null })] }));
|
|
73
92
|
}
|
|
74
93
|
KptTabs.Tab = KptTab;
|
package/dist/textarea/llms.txt
CHANGED
|
@@ -31,6 +31,16 @@ height.
|
|
|
31
31
|
<KptTextarea invalid={!!err} onTouch={markTouched} />
|
|
32
32
|
</KptFormField>
|
|
33
33
|
|
|
34
|
+
## Screen reader and keyboard
|
|
35
|
+
`counter`: boolean — a "12 / 280" counter under the field (requires the native `maxLength`), read
|
|
36
|
+
together with it through `aria-describedby`.
|
|
37
|
+
|
|
38
|
+
## Label and description
|
|
39
|
+
`id` goes to the native field, not the host; without one it comes from `KptFormField` or is
|
|
40
|
+
generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
|
|
41
|
+
added to `aria-describedby` (merged with your own). Without a wrapper, name it with `aria-label`.
|
|
42
|
+
New: `id`, `ariaLabel` and `required` (→ `aria-required`); `[formField]` binds `required`.
|
|
43
|
+
|
|
34
44
|
## Tokens
|
|
35
45
|
`--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}` —
|
|
36
46
|
the same set as the text field, so both controls stay in step.
|
|
@@ -5,6 +5,8 @@ export interface KptTextareaProps extends Omit<ComponentPropsWithRef<'textarea'>
|
|
|
5
5
|
autoResize?: boolean;
|
|
6
6
|
/** Oznacza pole jako wymagane atrybutem `aria-required`, a NIE natywnym `required`. */
|
|
7
7
|
required?: boolean;
|
|
8
|
+
/** Licznik „12 / 280” pod polem (wymaga `maxLength`), czytany razem z polem przez `aria-describedby`. */
|
|
9
|
+
counter?: boolean;
|
|
8
10
|
/** Pole jest nieprawidłowe. Widoczne dopiero, gdy `touched` też jest prawdziwe. */
|
|
9
11
|
invalid?: boolean;
|
|
10
12
|
/** Czy pole było dotknięte; pominięty = stan błędu pokazuje się od razu po `invalid`. */
|
|
@@ -25,4 +27,4 @@ export interface KptTextareaProps extends Omit<ComponentPropsWithRef<'textarea'>
|
|
|
25
27
|
* @example
|
|
26
28
|
* <KptTextarea name="notes" placeholder="Notatki…" autoResize />
|
|
27
29
|
*/
|
|
28
|
-
export declare function KptTextarea({ autoResize, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, rows, onChange, onBlur, ref, 'aria-invalid': ariaInvalid, ...rest }: Readonly<KptTextareaProps>): import("react").JSX.Element;
|
|
30
|
+
export declare function KptTextarea({ autoResize, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, rows, onChange, onBlur, ref, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, counter, ...rest }: Readonly<KptTextareaProps>): import("react").JSX.Element;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useLayoutEffect, useRef } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { composeRefs } from "../internal/compose-refs.js";
|
|
5
|
-
import {
|
|
6
|
-
import { useKptFormFieldContext } from "../form-field/form-field-context.js";
|
|
5
|
+
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
7
6
|
/**
|
|
8
7
|
* Pole wieloliniowe (textarea), z opcjonalnym auto-resize.
|
|
9
8
|
*
|
|
@@ -13,11 +12,16 @@ import { useKptFormFieldContext } from "../form-field/form-field-context.js";
|
|
|
13
12
|
* @example
|
|
14
13
|
* <KptTextarea name="notes" placeholder="Notatki…" autoResize />
|
|
15
14
|
*/
|
|
16
|
-
export function KptTextarea({ autoResize = false, required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, rows = 3, onChange, onBlur, ref, 'aria-invalid': ariaInvalid, ...rest }) {
|
|
17
|
-
const field = useKptFormFieldContext();
|
|
15
|
+
export function KptTextarea({ autoResize = false, required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, rows = 3, onChange, onBlur, ref, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, counter = false, ...rest }) {
|
|
18
16
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
19
|
-
const
|
|
17
|
+
const field = useKptFieldA11y(id, 'kpt-textarea');
|
|
18
|
+
const textareaId = field.id;
|
|
20
19
|
const innerRef = useRef(null);
|
|
20
|
+
// Długość tekstu dla licznika: w trybie kontrolowanym z `value`, w niekontrolowanym — z wpisów.
|
|
21
|
+
const [typedLength, setTypedLength] = useState(() => String(rest.value ?? rest.defaultValue ?? '').length);
|
|
22
|
+
const length = rest.value !== undefined ? String(rest.value).length : typedLength;
|
|
23
|
+
const showCounter = counter && !!rest.maxLength;
|
|
24
|
+
const counterId = `${textareaId}-counter`;
|
|
21
25
|
const showInvalid = invalid && (touched ?? true);
|
|
22
26
|
const resize = useCallback(() => {
|
|
23
27
|
const el = innerRef.current;
|
|
@@ -34,11 +38,12 @@ export function KptTextarea({ autoResize = false, required = false, invalid = fa
|
|
|
34
38
|
const handleChange = useCallback((event) => {
|
|
35
39
|
onChange?.(event);
|
|
36
40
|
onValueChange?.(event.target.value);
|
|
41
|
+
setTypedLength(event.target.value.length);
|
|
37
42
|
resize();
|
|
38
43
|
}, [onChange, onValueChange, resize]);
|
|
39
44
|
const handleBlur = useCallback((event) => {
|
|
40
45
|
onBlur?.(event);
|
|
41
46
|
onTouch?.();
|
|
42
47
|
}, [onBlur, onTouch]);
|
|
43
|
-
return (
|
|
48
|
+
return (_jsxs("span", { className: cn('kpt-textarea-host', hostClassName), ...hostRest, children: [_jsx("textarea", { className: cn('kpt-textarea__field', className), id: textareaId, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy, showCounter && counterId), rows: rows, ref: composeRefs(innerRef, ref), "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }), showCounter ? (_jsxs("span", { className: "kpt-textarea__counter", id: counterId, children: [length, " / ", rest.maxLength] })) : null] }));
|
|
44
49
|
}
|