@konce-pt/react 0.9.1 → 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
|
@@ -4,7 +4,7 @@ import { cn } from "../internal/cn.js";
|
|
|
4
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
5
|
import { useKptId } from "../internal/use-id.js";
|
|
6
6
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
7
|
-
import {
|
|
7
|
+
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
8
8
|
import { KptIcon } from "../icon/icon.js";
|
|
9
9
|
/** Cztery niezależne kryteria; każde spełnione podnosi wynik o jeden. */
|
|
10
10
|
function scoreOf(password) {
|
|
@@ -25,13 +25,14 @@ function scoreOf(password) {
|
|
|
25
25
|
* @example
|
|
26
26
|
* <KptPassword value={password} onValueChange={setPassword} feedback autoComplete="new-password" />
|
|
27
27
|
*/
|
|
28
|
-
export function KptPassword({ value, defaultValue = '', onValueChange, placeholder = '', id, autoComplete = 'current-password', feedback = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
28
|
+
export function KptPassword({ value, defaultValue = '', onValueChange, placeholder = '', id, autoComplete = 'current-password', feedback = false, ariaLabel = null, required = false, readOnly = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
29
29
|
const { t, messages } = useKptMessages({ locale, dictionary });
|
|
30
|
-
const field =
|
|
31
|
-
const inputId =
|
|
30
|
+
const field = useKptFieldA11y(id, 'kpt-password');
|
|
31
|
+
const inputId = field.id;
|
|
32
|
+
const meterId = useKptId(undefined, 'kpt-password-strength');
|
|
32
33
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
33
34
|
const [visible, setVisible] = useState(false);
|
|
34
35
|
const showInvalid = invalid && (touched ?? true);
|
|
35
36
|
const score = scoreOf(current);
|
|
36
|
-
return (_jsxs("div", { className: cn('kpt-password-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-password', disabled && 'kpt-password--disabled', showInvalid && 'kpt-password--invalid'), children: [_jsx("input", { className: "kpt-password__input", id: inputId, type: visible ? 'text' : 'password', value: current, disabled: disabled, placeholder: placeholder, autoComplete: autoComplete ?? undefined, onChange: (event) => setCurrent(event.target.value), onBlur: () => onTouch?.() }), _jsx("button", { type: "button", className: "kpt-password__toggle", disabled: disabled, onClick: () => setVisible(!visible), "aria-label": visible ? t('password.hide') : t('password.show'), children: _jsx(KptIcon, { name: visible ? 'eye-off' : 'eye' }) })] }), feedback && current ? (_jsxs("div", { className: "kpt-password__meter", "data-score": score, children: [_jsx("div", { className: "kpt-password__bars", children: [0, 1, 2, 3].map((index) => (_jsx("span", { className: cn('kpt-password__bar', index < score && 'is-on') }, index))) }), _jsx("span", { className: "kpt-password__hint", children: messages.password.strength[score] })] })) : null] }));
|
|
37
|
+
return (_jsxs("div", { className: cn('kpt-password-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-password', disabled && 'kpt-password--disabled', showInvalid && 'kpt-password--invalid'), children: [_jsx("input", { className: "kpt-password__input", id: inputId, type: visible ? 'text' : 'password', value: current, disabled: disabled, readOnly: readOnly, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-describedby": joinIds(field.describedBy, feedback && current && meterId), placeholder: placeholder, autoComplete: autoComplete ?? undefined, onChange: (event) => setCurrent(event.target.value), onBlur: () => onTouch?.() }), _jsx("button", { type: "button", className: "kpt-password__toggle", disabled: disabled, onClick: () => setVisible(!visible), "aria-label": visible ? t('password.hide') : t('password.show'), children: _jsx(KptIcon, { name: visible ? 'eye-off' : 'eye' }) })] }), feedback && current ? (_jsxs("div", { className: "kpt-password__meter", id: meterId, "data-score": score, children: [_jsx("div", { className: "kpt-password__bars", "aria-hidden": "true", children: [0, 1, 2, 3].map((index) => (_jsx("span", { className: cn('kpt-password__bar', index < score && 'is-on') }, index))) }), _jsx("span", { className: "kpt-password__hint", children: messages.password.strength[score] })] })) : null] }));
|
|
37
38
|
}
|
package/dist/pick-list/llms.txt
CHANGED
|
@@ -60,7 +60,16 @@ double-click carry no visible label, so they add no dictionary keys.
|
|
|
60
60
|
## Tokens
|
|
61
61
|
The selection `--kpt-color-primary`, the headers `--kpt-color-surface-variant`.
|
|
62
62
|
|
|
63
|
+
## Keyboard
|
|
64
|
+
Each list is a `listbox` with `aria-multiselectable` and a single Tab stop (roving tabindex):
|
|
65
|
+
- `↓` / `↑` move between items (wrapping), `Home` / `End` jump to the ends;
|
|
66
|
+
- `Space` toggles the focused item's selection (`aria-selected`), the same as a click;
|
|
67
|
+
- `Enter` moves the focused item to the other list, the same as a double-click — focus stays at
|
|
68
|
+
that position in the list, on the next item.
|
|
69
|
+
The move buttons act on the same selection, so the whole control works without a mouse.
|
|
70
|
+
|
|
63
71
|
## Accessibility
|
|
64
|
-
|
|
65
|
-
"move right"
|
|
66
|
-
|
|
72
|
+
Each list is named after its header (`aria-label`). The four move buttons are labelled from the
|
|
73
|
+
dictionary, so "move all right" is distinguishable from "move right" without seeing the double
|
|
74
|
+
chevron. Native HTML5 drag and drop has no keyboard equivalent — `Enter` and the buttons cover
|
|
75
|
+
everything it does, and `dragdrop` adds to them, it never replaces them.
|
|
@@ -22,6 +22,10 @@ export interface KptPickListProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
22
22
|
* Z `dragdrop` dochodzi przeciąganie — porządkowanie wewnątrz panelu i przenoszenie na
|
|
23
23
|
* konkretną pozycję drugiego panelu.
|
|
24
24
|
*
|
|
25
|
+
* Każda lista to `listbox` z wielokrotnym wyborem i jednym przystankiem Tab (roving tabindex):
|
|
26
|
+
* strzałki / `Home` / `End` przechodzą po pozycjach, spacja zaznacza, `Enter` przenosi pozycję
|
|
27
|
+
* na drugą stronę (jak dwuklik).
|
|
28
|
+
*
|
|
25
29
|
* @example
|
|
26
30
|
* <KptPickList
|
|
27
31
|
* source={available} onSourceChange={setAvailable}
|
|
@@ -30,4 +34,4 @@ export interface KptPickListProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
30
34
|
* renderItem={(item) => item.name}
|
|
31
35
|
* />
|
|
32
36
|
*/
|
|
33
|
-
export declare function KptPickList<T>({ source, defaultSource, onSourceChange, target, defaultTarget, onTargetChange, sourceHeader, targetHeader, dragdrop, renderItem, locale, dictionary, className, ...rest }: Readonly<KptPickListProps<T>>): import("react").JSX.Element;
|
|
37
|
+
export declare function KptPickList<T>({ source, defaultSource, onSourceChange, target, defaultTarget, onTargetChange, sourceHeader, targetHeader, dragdrop, renderItem, locale, dictionary, className, ref, ...rest }: Readonly<KptPickListProps<T>>): import("react").JSX.Element;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { composeRefs } from "../internal/compose-refs.js";
|
|
4
5
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
6
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
6
7
|
import { KptIcon } from "../icon/icon.js";
|
|
8
|
+
import { nextMenuIndex } from "../menu/menu-keys.js";
|
|
7
9
|
const EMPTY = [];
|
|
8
10
|
/**
|
|
9
11
|
* PickList — transfer elementów między dwiema listami. Klik zaznacza, przyciski pośrodku
|
|
@@ -11,6 +13,10 @@ const EMPTY = [];
|
|
|
11
13
|
* Z `dragdrop` dochodzi przeciąganie — porządkowanie wewnątrz panelu i przenoszenie na
|
|
12
14
|
* konkretną pozycję drugiego panelu.
|
|
13
15
|
*
|
|
16
|
+
* Każda lista to `listbox` z wielokrotnym wyborem i jednym przystankiem Tab (roving tabindex):
|
|
17
|
+
* strzałki / `Home` / `End` przechodzą po pozycjach, spacja zaznacza, `Enter` przenosi pozycję
|
|
18
|
+
* na drugą stronę (jak dwuklik).
|
|
19
|
+
*
|
|
14
20
|
* @example
|
|
15
21
|
* <KptPickList
|
|
16
22
|
* source={available} onSourceChange={setAvailable}
|
|
@@ -19,7 +25,7 @@ const EMPTY = [];
|
|
|
19
25
|
* renderItem={(item) => item.name}
|
|
20
26
|
* />
|
|
21
27
|
*/
|
|
22
|
-
export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, target, defaultTarget = EMPTY, onTargetChange, sourceHeader, targetHeader, dragdrop = false, renderItem, locale, dictionary, className, ...rest }) {
|
|
28
|
+
export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, target, defaultTarget = EMPTY, onTargetChange, sourceHeader, targetHeader, dragdrop = false, renderItem, locale, dictionary, className, ref, ...rest }) {
|
|
23
29
|
const { t } = useKptMessages({ locale, dictionary });
|
|
24
30
|
const [left, setLeft] = useControllableState({
|
|
25
31
|
value: source,
|
|
@@ -41,11 +47,33 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
41
47
|
const [over, setOver] = useState(null);
|
|
42
48
|
/** Panel, nad którego pustym obszarem jest kursor — upuszczenie tam dopina na koniec. */
|
|
43
49
|
const [listOver, setListOver] = useState(null);
|
|
50
|
+
/** Pozycja, która ostatnio miała fokus w każdej liście — ona jest przystankiem Tab. */
|
|
51
|
+
const [focusLeft, setFocusLeft] = useState(0);
|
|
52
|
+
const [focusRight, setFocusRight] = useState(0);
|
|
53
|
+
/** Pozycja, na którą po renderze trzeba przenieść fokus DOM (klawiatura). */
|
|
54
|
+
const [focusRequest, setFocusRequest] = useState(null);
|
|
55
|
+
const rootRef = useRef(null);
|
|
44
56
|
const listOf = (side) => (side === 'source' ? left : right);
|
|
45
57
|
const setListOf = (side) => (side === 'source' ? setLeft : setRight);
|
|
46
58
|
const selectionOf = (side) => (side === 'source' ? selLeft : selRight);
|
|
47
59
|
const setSelectionOf = (side) => side === 'source' ? setSelLeft : setSelRight;
|
|
48
60
|
const other = (side) => (side === 'source' ? 'target' : 'source');
|
|
61
|
+
const focusOf = (side) => (side === 'source' ? focusLeft : focusRight);
|
|
62
|
+
const setFocusOf = (side) => (side === 'source' ? setFocusLeft : setFocusRight);
|
|
63
|
+
/** Przystanek Tab listy — ostatnio skupiona pozycja, przycięta do długości listy. */
|
|
64
|
+
const tabIndexOf = (side) => Math.min(focusOf(side), listOf(side).length - 1);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!focusRequest)
|
|
67
|
+
return;
|
|
68
|
+
const list = rootRef.current?.querySelectorAll('.kpt-pick-list__items')[focusRequest.side === 'source' ? 0 : 1];
|
|
69
|
+
const options = list?.querySelectorAll('[role="option"]') ?? [];
|
|
70
|
+
options[Math.min(focusRequest.index, options.length - 1)]?.focus();
|
|
71
|
+
setFocusRequest(null);
|
|
72
|
+
}, [focusRequest]);
|
|
73
|
+
const focusItem = (side, index) => {
|
|
74
|
+
setFocusOf(side)(index);
|
|
75
|
+
setFocusRequest({ side, index });
|
|
76
|
+
};
|
|
49
77
|
const toggle = (side, item) => {
|
|
50
78
|
const next = new Set(selectionOf(side));
|
|
51
79
|
if (next.has(item))
|
|
@@ -91,6 +119,25 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
91
119
|
const to = other(from);
|
|
92
120
|
transfer(from, index, to, listOf(to).length);
|
|
93
121
|
};
|
|
122
|
+
const onItemKeyDown = (side, index, item, event) => {
|
|
123
|
+
if (event.key === ' ') {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
toggle(side, item);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (event.key === 'Enter') {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
moveOne(side, item);
|
|
131
|
+
// Pozycja zniknęła z listy — fokus zostaje w tym samym miejscu listy, na następnej.
|
|
132
|
+
focusItem(side, index);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const next = nextMenuIndex(listOf(side).length, index, event.key);
|
|
136
|
+
if (next === null)
|
|
137
|
+
return;
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
focusItem(side, next);
|
|
140
|
+
};
|
|
94
141
|
const clearDrag = () => {
|
|
95
142
|
setDrag(null);
|
|
96
143
|
setOver(null);
|
|
@@ -162,10 +209,10 @@ export function KptPickList({ source, defaultSource = EMPTY, onSourceChange, tar
|
|
|
162
209
|
const items = listOf(side);
|
|
163
210
|
const selection = selectionOf(side);
|
|
164
211
|
const header = side === 'source' ? (sourceHeader ?? t('pickList.available')) : (targetHeader ?? t('pickList.selected'));
|
|
165
|
-
return (_jsxs("div", { className: "kpt-pick-list__col", children: [_jsx("div", { className: "kpt-pick-list__header", children: header }), _jsx("ul", { className: cn('kpt-pick-list__items', listOver === side && 'is-dragover'), onDragOver: (event) => onListDragOver(side, event), onDragLeave: () => setListOver(null), onDrop: (event) => onListDrop(side, event), children: items.map((item, index) => {
|
|
212
|
+
return (_jsxs("div", { className: "kpt-pick-list__col", children: [_jsx("div", { className: "kpt-pick-list__header", children: header }), _jsx("ul", { className: cn('kpt-pick-list__items', listOver === side && 'is-dragover'), role: "listbox", "aria-multiselectable": "true", "aria-label": header, onDragOver: (event) => onListDragOver(side, event), onDragLeave: () => setListOver(null), onDrop: (event) => onListDrop(side, event), children: items.map((item, index) => {
|
|
166
213
|
const dragging = drag?.side === side && drag.index === index;
|
|
167
|
-
return (_jsx("li", { className: cn('kpt-pick-list__item', selection.has(item) && 'is-selected', dragging && 'is-dragging', dragdrop && 'kpt-pick-list__item--draggable', over?.side === side && over.index === index && !dragging && 'kpt-pick-list__item--dragover'), draggable: dragdrop || undefined, onClick: () => toggle(side, item), onDoubleClick: () => moveOne(side, item), onDragStart: (event) => onDragStart(side, index, event), onDragOver: (event) => onDragOver(side, index, event), onDragLeave: () => setOver(null), onDrop: (event) => onDrop(side, index, event), onDragEnd: clearDrag, children: renderItem(item, index) }, index));
|
|
214
|
+
return (_jsx("li", { className: cn('kpt-pick-list__item', selection.has(item) && 'is-selected', dragging && 'is-dragging', dragdrop && 'kpt-pick-list__item--draggable', over?.side === side && over.index === index && !dragging && 'kpt-pick-list__item--dragover'), role: "option", "aria-selected": selection.has(item), tabIndex: index === tabIndexOf(side) ? 0 : -1, onFocus: () => setFocusOf(side)(index), onKeyDown: (event) => onItemKeyDown(side, index, item, event), draggable: dragdrop || undefined, onClick: () => toggle(side, item), onDoubleClick: () => moveOne(side, item), onDragStart: (event) => onDragStart(side, index, event), onDragOver: (event) => onDragOver(side, index, event), onDragLeave: () => setOver(null), onDrop: (event) => onDrop(side, index, event), onDragEnd: clearDrag, children: renderItem(item, index) }, index));
|
|
168
215
|
}) })] }));
|
|
169
216
|
};
|
|
170
|
-
return (_jsx("div", { className: cn('kpt-pick-list-host', className), ...rest, children: _jsxs("div", { className: "kpt-pick-list", children: [column('source'), _jsxs("div", { className: "kpt-pick-list__controls", children: [_jsxs("button", { type: "button", "aria-label": t('pickList.moveAllRight'), onClick: () => move('source', true), children: [_jsx(KptIcon, { name: "chevron-right" }), _jsx(KptIcon, { name: "chevron-right" })] }), _jsx("button", { type: "button", "aria-label": t('pickList.moveRight'), onClick: () => move('source', false), children: _jsx(KptIcon, { name: "chevron-right" }) }), _jsx("button", { type: "button", "aria-label": t('pickList.moveLeft'), onClick: () => move('target', false), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsxs("button", { type: "button", "aria-label": t('pickList.moveAllLeft'), onClick: () => move('target', true), children: [_jsx(KptIcon, { name: "chevron-left" }), _jsx(KptIcon, { name: "chevron-left" })] })] }), column('target')] }) }));
|
|
217
|
+
return (_jsx("div", { ref: composeRefs(rootRef, ref), className: cn('kpt-pick-list-host', className), ...rest, children: _jsxs("div", { className: "kpt-pick-list", children: [column('source'), _jsxs("div", { className: "kpt-pick-list__controls", children: [_jsxs("button", { type: "button", "aria-label": t('pickList.moveAllRight'), onClick: () => move('source', true), children: [_jsx(KptIcon, { name: "chevron-right" }), _jsx(KptIcon, { name: "chevron-right" })] }), _jsx("button", { type: "button", "aria-label": t('pickList.moveRight'), onClick: () => move('source', false), children: _jsx(KptIcon, { name: "chevron-right" }) }), _jsx("button", { type: "button", "aria-label": t('pickList.moveLeft'), onClick: () => move('target', false), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsxs("button", { type: "button", "aria-label": t('pickList.moveAllLeft'), onClick: () => move('target', true), children: [_jsx(KptIcon, { name: "chevron-left" }), _jsx(KptIcon, { name: "chevron-left" })] })] }), column('target')] }) }));
|
|
171
218
|
}
|
package/dist/popover/llms.txt
CHANGED
|
@@ -14,6 +14,7 @@ Import: `import { KptPopover } from '@konce-pt/react';`
|
|
|
14
14
|
- `trigger`: ReactNode — the element that opens the bubble, rendered in place
|
|
15
15
|
- `children`: ReactNode — the panel content
|
|
16
16
|
- `open` / `defaultOpen` / `onOpenChange` — controlled or uncontrolled
|
|
17
|
+
- `placement`: 'bottom' (default) | 'top' — the preferred side; it flips when there is no room
|
|
17
18
|
- `className`, `ref` and every other `<span>` prop go to the host
|
|
18
19
|
|
|
19
20
|
## The panel is a portal
|
|
@@ -38,5 +39,11 @@ backdrop takes that click, exactly as the CDK backdrop does.
|
|
|
38
39
|
`--kpt-z-overlay`.
|
|
39
40
|
|
|
40
41
|
## Accessibility
|
|
41
|
-
The panel is `role="dialog"`
|
|
42
|
-
|
|
42
|
+
The panel is `role="dialog"` with an `id` the trigger points to.
|
|
43
|
+
|
|
44
|
+
## Focus and keyboard
|
|
45
|
+
The control inside the trigger slot gets `aria-haspopup="dialog"`, a live `aria-expanded` and, while
|
|
46
|
+
open, `aria-controls` — on the real `<button>`, not on the wrapper. Opening moves focus to the first
|
|
47
|
+
interactive element of the bubble; `Escape` or a click outside closes it, and focus returns to the
|
|
48
|
+
trigger when it was inside the bubble. Focus is not trapped — the popover is supporting content, not
|
|
49
|
+
a modal.
|
|
@@ -7,17 +7,23 @@ export interface KptPopoverProps extends Omit<ComponentPropsWithRef<'span'>, 'co
|
|
|
7
7
|
open?: boolean;
|
|
8
8
|
defaultOpen?: boolean;
|
|
9
9
|
onOpenChange?: (open: boolean) => void;
|
|
10
|
+
/** Preferowana strona dymka; gdy brakuje miejsca, przechodzi na drugą. */
|
|
11
|
+
placement?: 'bottom' | 'top';
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Popover — dowolna treść w dymku przypiętym do wyzwalacza (bogatszy niż tooltip).
|
|
13
15
|
*
|
|
14
16
|
* Port Angulara stoi na CDK Overlay; tutaj panel renderuje się w portalu do `document.body`,
|
|
15
|
-
* z tym samym zachowaniem: pozycja pod wyzwalaczem
|
|
16
|
-
* zamykanie kliknięciem poza panelem i klawiszem Escape.
|
|
17
|
+
* z tym samym zachowaniem: pozycja pod wyzwalaczem (albo nad nim przy `placement="top"`)
|
|
18
|
+
* z odbiciem przy braku miejsca, zamykanie kliknięciem poza panelem i klawiszem Escape.
|
|
19
|
+
*
|
|
20
|
+
* Dostępność (jak w Angularze): kontrolka w wyzwalaczu dostaje `aria-haspopup="dialog"`,
|
|
21
|
+
* `aria-expanded` i `aria-controls`; po otwarciu fokus przechodzi do pierwszego interaktywnego
|
|
22
|
+
* elementu dymka, a po zamknięciu wraca na wyzwalacz, jeśli był w dymku.
|
|
17
23
|
*
|
|
18
24
|
* @example
|
|
19
25
|
* <KptPopover trigger={<KptButton variant="outline">Szczegóły</KptButton>}>
|
|
20
26
|
* <div>Bogata treść popovera…</div>
|
|
21
27
|
* </KptPopover>
|
|
22
28
|
*/
|
|
23
|
-
export declare function KptPopover({ trigger, children, open, defaultOpen, onOpenChange, className, ...rest }: Readonly<KptPopoverProps>): import("react").JSX.Element;
|
|
29
|
+
export declare function KptPopover({ trigger, children, open, defaultOpen, onOpenChange, placement, className, ...rest }: Readonly<KptPopoverProps>): import("react").JSX.Element;
|
package/dist/popover/popover.js
CHANGED
|
@@ -1,27 +1,62 @@
|
|
|
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";
|
|
7
|
+
/** Elementy, które same przyjmują fokus — pierwszy z nich dostaje fokus po otwarciu dymka. */
|
|
8
|
+
const FOCUSABLE = 'a[href], button:not(:disabled), input:not(:disabled), select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
6
9
|
/**
|
|
7
10
|
* Popover — dowolna treść w dymku przypiętym do wyzwalacza (bogatszy niż tooltip).
|
|
8
11
|
*
|
|
9
12
|
* Port Angulara stoi na CDK Overlay; tutaj panel renderuje się w portalu do `document.body`,
|
|
10
|
-
* z tym samym zachowaniem: pozycja pod wyzwalaczem
|
|
11
|
-
* zamykanie kliknięciem poza panelem i klawiszem Escape.
|
|
13
|
+
* z tym samym zachowaniem: pozycja pod wyzwalaczem (albo nad nim przy `placement="top"`)
|
|
14
|
+
* z odbiciem przy braku miejsca, zamykanie kliknięciem poza panelem i klawiszem Escape.
|
|
15
|
+
*
|
|
16
|
+
* Dostępność (jak w Angularze): kontrolka w wyzwalaczu dostaje `aria-haspopup="dialog"`,
|
|
17
|
+
* `aria-expanded` i `aria-controls`; po otwarciu fokus przechodzi do pierwszego interaktywnego
|
|
18
|
+
* elementu dymka, a po zamknięciu wraca na wyzwalacz, jeśli był w dymku.
|
|
12
19
|
*
|
|
13
20
|
* @example
|
|
14
21
|
* <KptPopover trigger={<KptButton variant="outline">Szczegóły</KptButton>}>
|
|
15
22
|
* <div>Bogata treść popovera…</div>
|
|
16
23
|
* </KptPopover>
|
|
17
24
|
*/
|
|
18
|
-
export function KptPopover({ trigger, children, open, defaultOpen = false, onOpenChange, className, ...rest }) {
|
|
25
|
+
export function KptPopover({ trigger, children, open, defaultOpen = false, onOpenChange, placement = 'bottom', className, ...rest }) {
|
|
19
26
|
const triggerRef = useRef(null);
|
|
27
|
+
const panelRef = useRef(null);
|
|
28
|
+
const panelId = useKptId(undefined, 'kpt-popover');
|
|
20
29
|
const [isOpen, setOpen] = useControllableState({
|
|
21
30
|
value: open,
|
|
22
31
|
defaultValue: defaultOpen,
|
|
23
32
|
onChange: onOpenChange,
|
|
24
33
|
});
|
|
25
|
-
const
|
|
26
|
-
|
|
34
|
+
const findControl = () => triggerRef.current?.querySelector('button, a, [role="button"]') ?? null;
|
|
35
|
+
// ARIA na prawdziwej kontrolce w slocie, nie na opakowaniu `<span>` (patrz `KptMenu`).
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const control = findControl();
|
|
38
|
+
if (!control)
|
|
39
|
+
return;
|
|
40
|
+
control.setAttribute('aria-haspopup', 'dialog');
|
|
41
|
+
control.setAttribute('aria-expanded', String(isOpen));
|
|
42
|
+
if (isOpen)
|
|
43
|
+
control.setAttribute('aria-controls', panelId);
|
|
44
|
+
else
|
|
45
|
+
control.removeAttribute('aria-controls');
|
|
46
|
+
}, [isOpen, trigger, panelId]);
|
|
47
|
+
// Fokus do dymka klatkę później — `KptOverlay` trzyma panel niewidoczny, dopóki go nie zmierzy.
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!isOpen)
|
|
50
|
+
return;
|
|
51
|
+
const frame = requestAnimationFrame(() => panelRef.current?.querySelector(FOCUSABLE)?.focus());
|
|
52
|
+
return () => cancelAnimationFrame(frame);
|
|
53
|
+
}, [isOpen]);
|
|
54
|
+
const close = useCallback(() => {
|
|
55
|
+
const active = document.activeElement;
|
|
56
|
+
// Fokus wraca na wyzwalacz tylko wtedy, gdy był w dymku (albo przepadł na `<body>`).
|
|
57
|
+
if (!active || active === document.body || panelRef.current?.contains(active))
|
|
58
|
+
findControl()?.focus();
|
|
59
|
+
setOpen(false);
|
|
60
|
+
}, [setOpen]);
|
|
61
|
+
return (_jsxs("span", { className: cn('kpt-popover-host', className), ...rest, children: [_jsx("span", { className: "kpt-popover__trigger", ref: triggerRef, onClick: () => setOpen(!isOpen), children: trigger }), _jsx(KptOverlay, { open: isOpen, anchor: triggerRef.current, onClose: close, offset: 6, preferAbove: placement === 'top', children: _jsx("div", { ref: panelRef, id: panelId, className: "kpt-popover__panel", role: "dialog", children: children }) })] }));
|
|
27
62
|
}
|
package/dist/public-api.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export type { KptConfirmOptions, KptConfirmRequest } from './confirm/confirm-sto
|
|
|
90
90
|
export { KptToastContainer } from './toast/toast-container.tsx';
|
|
91
91
|
export type { KptToastContainerProps } from './toast/toast-container.tsx';
|
|
92
92
|
export { kptToast, useKptToasts } from './toast/toast-store.ts';
|
|
93
|
-
export type { KptToast, KptToastOptions } from './toast/toast-store.ts';
|
|
93
|
+
export type { KptToast, KptToastAction, KptToastOptions } from './toast/toast-store.ts';
|
|
94
94
|
export { KptMenu } from './menu/menu.tsx';
|
|
95
95
|
export { KptMenubar } from './menubar/menubar.tsx';
|
|
96
96
|
export type { KptMenubarProps } from './menubar/menubar.tsx';
|
|
@@ -123,8 +123,8 @@ export type { KptSelectProps } from './select/select.tsx';
|
|
|
123
123
|
export type { KptAlertVariant, KptOption } from './shared/types.ts';
|
|
124
124
|
export { KptFormField } from './form-field/form-field.tsx';
|
|
125
125
|
export type { KptFormFieldLabelType, KptFormFieldProps } from './form-field/form-field.tsx';
|
|
126
|
-
export { useKptFormFieldContext, useKptFormFieldFilled } from './form-field/form-field-context.ts';
|
|
127
|
-
export type { KptFormFieldContextValue } from './form-field/form-field-context.ts';
|
|
126
|
+
export { useKptFieldA11y, useKptFormFieldContext, useKptFormFieldFilled } from './form-field/form-field-context.ts';
|
|
127
|
+
export type { KptFieldA11y, KptFormFieldContextValue } from './form-field/form-field-context.ts';
|
|
128
128
|
export { KptCheckbox } from './checkbox/checkbox.tsx';
|
|
129
129
|
export type { KptCheckboxProps } from './checkbox/checkbox.tsx';
|
|
130
130
|
export { KptAutocomplete } from './autocomplete/autocomplete.tsx';
|
|
@@ -156,7 +156,7 @@ export type { KptDateRangeMode, KptDateRangeProps, KptDateRangeValue } from './d
|
|
|
156
156
|
export { KptClock } from './clock/clock.tsx';
|
|
157
157
|
export type { KptClockProps } from './clock/clock.tsx';
|
|
158
158
|
export { KptColorPicker } from './color-picker/color-picker.tsx';
|
|
159
|
-
export type { KptColorPickerProps } from './color-picker/color-picker.tsx';
|
|
159
|
+
export type { KptColorPickerProps, KptColorPreset } from './color-picker/color-picker.tsx';
|
|
160
160
|
export { KptFileUpload } from './file-upload/file-upload.tsx';
|
|
161
161
|
export type { KptFileUploadProps } from './file-upload/file-upload.tsx';
|
|
162
162
|
export { KptSwitchGroup } from './switch-group/switch-group.tsx';
|
|
@@ -175,10 +175,10 @@ export type { KptIconButtonProps } from './icon-button/icon-button.tsx';
|
|
|
175
175
|
export { KptFab } from './fab/fab.tsx';
|
|
176
176
|
export type { KptFabProps } from './fab/fab.tsx';
|
|
177
177
|
export { KptSplitButton } from './split-button/split-button.tsx';
|
|
178
|
-
export type { KptSplitButtonProps } from './split-button/split-button.tsx';
|
|
178
|
+
export type { KptSplitButtonProps, KptSplitButtonVariant } from './split-button/split-button.tsx';
|
|
179
179
|
export type { KptButtonProps, KptButtonSize, KptButtonVariant } from './button/button.tsx';
|
|
180
180
|
export { KptCard, KptCardActions, KptCardFooter, KptCardHeader, KptCardLead, KptCardMedia, KptCardMeta, KptCardSection, KptCardSubtitle, KptCardTags, KptCardTitle, } from './card/card.tsx';
|
|
181
|
-
export type { KptCardProps, KptCardVariant } from './card/card.tsx';
|
|
181
|
+
export type { KptCardHeadingLevel, KptCardProps, KptCardVariant } from './card/card.tsx';
|
|
182
182
|
export { KptI18nProvider, useKptI18n, useKptMessages } from './i18n/i18n-context.tsx';
|
|
183
183
|
export type { KptI18nContextValue, KptI18nOverrides, KptI18nProviderProps } from './i18n/i18n-context.tsx';
|
|
184
184
|
export { KPT_MESSAGES_EN, KPT_MESSAGES_PL } from '@konce-pt/i18n';
|
package/dist/public-api.js
CHANGED
|
@@ -71,7 +71,7 @@ export { KptOverlay } from "./internal/overlay.js";
|
|
|
71
71
|
export { KptButtonGroup } from "./button-group/button-group.js";
|
|
72
72
|
export { KptSelect } from "./select/select.js";
|
|
73
73
|
export { KptFormField } from "./form-field/form-field.js";
|
|
74
|
-
export { useKptFormFieldContext, useKptFormFieldFilled } from "./form-field/form-field-context.js";
|
|
74
|
+
export { useKptFieldA11y, useKptFormFieldContext, useKptFormFieldFilled } from "./form-field/form-field-context.js";
|
|
75
75
|
export { KptCheckbox } from "./checkbox/checkbox.js";
|
|
76
76
|
export { KptAutocomplete } from "./autocomplete/autocomplete.js";
|
|
77
77
|
export { KptListbox } from "./listbox/listbox.js";
|
|
@@ -31,6 +31,17 @@ interfere.
|
|
|
31
31
|
<KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
|
|
32
32
|
<KptRadioGroup name="plan" options={plans} defaultValue="pro" />
|
|
33
33
|
|
|
34
|
+
## Screen reader and keyboard
|
|
35
|
+
`orientation`: 'vertical' (default) | 'horizontal' — options in a column or in a wrapping row; also
|
|
36
|
+
sets `aria-orientation`.
|
|
37
|
+
|
|
38
|
+
## Label and description
|
|
39
|
+
`id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
|
|
40
|
+
generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
|
|
41
|
+
`aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
|
|
42
|
+
is added to `aria-describedby`. New: `id`, `ariaLabel` and `required` (→ `aria-required`) on the
|
|
43
|
+
`radiogroup`.
|
|
44
|
+
|
|
34
45
|
## Tokens
|
|
35
46
|
`--kpt-color-primary` (the checked dot), `--kpt-form-field-border`,
|
|
36
47
|
`--kpt-form-field-border-error`.
|
|
@@ -7,6 +7,14 @@ export interface KptRadioGroupProps<T> extends Omit<ComponentPropsWithRef<'div'>
|
|
|
7
7
|
onValueChange?: (value: T) => void;
|
|
8
8
|
/** Nazwa grupy w formularzu; pominięta = generowana. */
|
|
9
9
|
name?: string;
|
|
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 grupę jako wymaganą atrybutem `aria-required`. */
|
|
15
|
+
required?: boolean;
|
|
16
|
+
/** Układ opcji: pod sobą (domyślnie) albo w rzędzie, z zawijaniem. */
|
|
17
|
+
orientation?: 'vertical' | 'horizontal';
|
|
10
18
|
disabled?: boolean;
|
|
11
19
|
invalid?: boolean;
|
|
12
20
|
touched?: boolean;
|
|
@@ -21,4 +29,4 @@ export interface KptRadioGroupProps<T> extends Omit<ComponentPropsWithRef<'div'>
|
|
|
21
29
|
* @example
|
|
22
30
|
* <KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
|
|
23
31
|
*/
|
|
24
|
-
export declare function KptRadioGroup<T>({ options, value, defaultValue, onValueChange, name, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptRadioGroupProps<T>>): import("react").JSX.Element;
|
|
32
|
+
export declare function KptRadioGroup<T>({ options, value, defaultValue, onValueChange, name, id, ariaLabel, required, orientation, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptRadioGroupProps<T>>): import("react").JSX.Element;
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } 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 { useKptId } from "../internal/use-id.js";
|
|
5
|
+
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
5
6
|
/**
|
|
6
7
|
* Grupa przycisków opcji (radio). Opcje przez `options`.
|
|
7
8
|
*
|
|
@@ -11,7 +12,9 @@ import { useKptId } from "../internal/use-id.js";
|
|
|
11
12
|
* @example
|
|
12
13
|
* <KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
|
|
13
14
|
*/
|
|
14
|
-
export function KptRadioGroup({ options = [], value, defaultValue, onValueChange, name, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
15
|
+
export function KptRadioGroup({ options = [], value, defaultValue, onValueChange, name, id, ariaLabel = null, required = false, orientation = 'vertical', disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
16
|
+
/** Grupa nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
|
|
17
|
+
const field = useKptFieldA11y(id, 'kpt-radio-group');
|
|
15
18
|
const groupName = useKptId(name, 'kpt-radio');
|
|
16
19
|
const [current, setCurrent] = useControllableState({
|
|
17
20
|
value,
|
|
@@ -19,5 +22,5 @@ export function KptRadioGroup({ options = [], value, defaultValue, onValueChange
|
|
|
19
22
|
onChange: onValueChange,
|
|
20
23
|
});
|
|
21
24
|
const showInvalid = invalid && (touched ?? true);
|
|
22
|
-
return (_jsx("div", { className: cn('kpt-radio-group-host', className), ...rest, children: _jsx("div", { className:
|
|
25
|
+
return (_jsx("div", { className: cn('kpt-radio-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-radio-group', orientation === 'horizontal' && 'kpt-radio-group--horizontal'), role: "radiogroup", "aria-orientation": orientation, "aria-invalid": showInvalid || undefined, id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "aria-required": required || undefined, children: options.map((option) => (_jsxs("label", { className: cn('kpt-radio', (disabled || option.disabled) && 'kpt-radio--disabled'), children: [_jsx("input", { type: "radio", className: "kpt-radio__input", name: groupName, checked: option.value === current, disabled: disabled || option.disabled, onChange: () => setCurrent(option.value), onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-radio__dot", "aria-hidden": "true" }), _jsx("span", { className: "kpt-radio__label", children: option.label })] }, String(option.value)))) }) }));
|
|
23
26
|
}
|
package/dist/rating/llms.txt
CHANGED
|
@@ -5,8 +5,8 @@ Import: `import { KptRating } from '@konce-pt/react';`
|
|
|
5
5
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-rating-host">
|
|
8
|
-
<div class="kpt-rating" role="
|
|
9
|
-
<button class="kpt-rating__star is-filled" aria-label="1 of 5">…</button>
|
|
8
|
+
<div class="kpt-rating" role="radiogroup" aria-label="…">
|
|
9
|
+
<button class="kpt-rating__star is-filled" role="radio" aria-checked="false" tabindex="-1" aria-label="1 of 5">…</button>
|
|
10
10
|
…count buttons…
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
@@ -14,9 +14,11 @@ Import: `import { KptRating } from '@konce-pt/react';`
|
|
|
14
14
|
## Props
|
|
15
15
|
- `value` / `defaultValue` / `onValueChange` — the score as a number, `0` means unrated
|
|
16
16
|
- `count`: number (default 5)
|
|
17
|
+
- `allowHalf`: boolean — half stars, the value goes in steps of 0.5
|
|
17
18
|
- `readOnly`: boolean — display only, no clicking and no hover preview
|
|
18
19
|
- `disabled`: boolean
|
|
19
|
-
- `onTouch()` —
|
|
20
|
+
- `onTouch()` — when focus leaves the group
|
|
21
|
+
- `id`, `ariaLabel`, `required`, `invalid`, `touched`
|
|
20
22
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
21
23
|
|
|
22
24
|
## Clicking a lit star clears the score
|
|
@@ -35,9 +37,20 @@ per instance with the `locale` / `dictionary` props.
|
|
|
35
37
|
<KptRating value={score} onValueChange={setScore} count={5} />
|
|
36
38
|
<KptRating value={4} readOnly />
|
|
37
39
|
|
|
40
|
+
## Half stars
|
|
41
|
+
`allowHalf` splits every star into two halves, each its own `role="radio"` ("3.5 of 5" — the number
|
|
42
|
+
follows the page language, PL "3,5 z 5"), so the group keeps the radio pattern: clicking the left half
|
|
43
|
+
gives x.5, and the arrows step by 0.5. Read-only stars show the value rounded to a half (4.3 → four
|
|
44
|
+
and a half stars) while the name carries the exact score ("4.3 of 5"), so an average can be shown
|
|
45
|
+
as it is.
|
|
46
|
+
|
|
38
47
|
## Tokens
|
|
39
48
|
Lit stars `--kpt-color-warning`, empty ones `--kpt-color-border-strong`.
|
|
40
49
|
|
|
41
50
|
## Accessibility
|
|
42
|
-
`role="
|
|
43
|
-
|
|
51
|
+
`role="radiogroup"` named by `ariaLabel` or the `KptFormField` label; each star is
|
|
52
|
+
`role="radio"` with `aria-checked`, labelled from `rating.star` (`{value}`, `{max}`) — EN `1 of 5`,
|
|
53
|
+
PL `1 z 5`. One Tab stop: `→`/`↑` raise the score by one, `←`/`↓` lower it (mirrored in RTL),
|
|
54
|
+
`Home`/`End` set the extremes, `Delete`/`Backspace` clear it; clicking the chosen star also clears it.
|
|
55
|
+
`readOnly` renders `role="img"` named "4 of 5" with no buttons, so a screen reader does not announce
|
|
56
|
+
a row of disabled controls. `touch` fires when focus leaves the group.
|
package/dist/rating/rating.d.ts
CHANGED
|
@@ -6,17 +6,35 @@ export interface KptRatingProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
|
|
|
6
6
|
onValueChange?: (value: number) => void;
|
|
7
7
|
/** Liczba gwiazdek. */
|
|
8
8
|
count?: number;
|
|
9
|
+
/** Połówki gwiazdek: wartość co 0,5, każda połówka to osobne `radio`. */
|
|
10
|
+
allowHalf?: boolean;
|
|
9
11
|
/** Ocena tylko do odczytu — bez klikania i bez podglądu pod kursorem. */
|
|
10
12
|
readOnly?: boolean;
|
|
13
|
+
/** Id grupy (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** Nazwa dostępna grupy, gdy nie opisuje jej etykieta (`KptFormField`). */
|
|
16
|
+
ariaLabel?: string | null;
|
|
17
|
+
/** Oznacza ocenę jako wymaganą atrybutem `aria-required`. */
|
|
18
|
+
required?: boolean;
|
|
11
19
|
disabled?: boolean;
|
|
20
|
+
invalid?: boolean;
|
|
21
|
+
touched?: boolean;
|
|
12
22
|
onTouch?: () => void;
|
|
13
23
|
}
|
|
14
24
|
/**
|
|
15
25
|
* Ocena gwiazdkami. Kliknięcie zapalonej gwiazdki zeruje ocenę, więc wystawioną można cofnąć
|
|
16
26
|
* bez osobnego przycisku „wyczyść".
|
|
17
27
|
*
|
|
28
|
+
* Do edycji to grupa radiowa (`role="radiogroup"`, gwiazdki jako `role="radio"`): jeden przystanek
|
|
29
|
+
* Tab, strzałki zmieniają ocenę o krok (w RTL odwrócone w poziomie), `Home`/`End` ustawiają
|
|
30
|
+
* skrajne, `Delete`/`Backspace` zeruje. Z `allowHalf` każda gwiazdka ma dwie połówki — dwa osobne
|
|
31
|
+
* `radio` („3,5 z 5”), a krok strzałek to 0,5. Tylko do odczytu (`readOnly`) to obrazek z nazwą
|
|
32
|
+
* „4,3 z 5” — bez przycisków, które czytnik ogłaszałby jako wyłączone; gwiazdki pokazują wartość
|
|
33
|
+
* zaokrągloną do połówki.
|
|
34
|
+
*
|
|
18
35
|
* @example
|
|
19
|
-
* <KptRating value={score} onValueChange={setScore} count={5} />
|
|
20
|
-
* <KptRating value={
|
|
36
|
+
* <KptRating value={score} onValueChange={setScore} count={5} ariaLabel="Ocena" />
|
|
37
|
+
* <KptRating value={score} onValueChange={setScore} allowHalf ariaLabel="Ocena" />
|
|
38
|
+
* <KptRating value={4.3} readOnly />
|
|
21
39
|
*/
|
|
22
|
-
export declare function KptRating({ value, defaultValue, onValueChange, count, readOnly, disabled, onTouch, locale, dictionary, className, ...rest }: Readonly<KptRatingProps>): import("react").JSX.Element;
|
|
40
|
+
export declare function KptRating({ value, defaultValue, onValueChange, count, allowHalf, readOnly, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptRatingProps>): import("react").JSX.Element;
|