@konce-pt/react 0.11.2 → 0.12.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/README.md +8 -2
- package/dist/accordion/accordion.js +11 -10
- package/dist/alert/alert.js +1 -0
- package/dist/alert/llms.txt +1 -0
- package/dist/app-shell/app-shell.js +12 -5
- package/dist/app-shell/llms.txt +5 -4
- package/dist/auth/auth.js +17 -8
- package/dist/auth/llms.txt +3 -2
- package/dist/autocomplete/autocomplete.d.ts +1 -1
- package/dist/autocomplete/autocomplete.js +12 -4
- package/dist/autocomplete/llms.txt +3 -0
- package/dist/avatar/avatar.js +1 -0
- package/dist/avatar-group/avatar-group.js +1 -0
- package/dist/backdrop/backdrop.d.ts +0 -19
- package/dist/backdrop/backdrop.js +18 -13
- package/dist/bottom-sheet/bottom-sheet.d.ts +6 -1
- package/dist/bottom-sheet/bottom-sheet.js +3 -2
- package/dist/bottom-sheet/llms.txt +3 -0
- package/dist/breadcrumb/breadcrumb.d.ts +7 -1
- package/dist/breadcrumb/breadcrumb.js +10 -2
- package/dist/breadcrumb/llms.txt +8 -0
- package/dist/button/button.js +2 -1
- package/dist/button-group/button-group.d.ts +14 -1
- package/dist/button-group/button-group.js +30 -4
- package/dist/button-group/llms.txt +9 -6
- package/dist/card/card.js +4 -3
- package/dist/carousel/carousel.js +4 -1
- package/dist/charts/chart-legend.js +1 -0
- package/dist/charts/chart.d.ts +3 -2
- package/dist/charts/chart.js +12 -4
- package/dist/charts/llms.txt +6 -3
- package/dist/chat/chat.js +39 -9
- package/dist/chat/llms.txt +9 -0
- package/dist/chat-composer/chat-composer.d.ts +5 -2
- package/dist/chat-composer/chat-composer.js +10 -1
- package/dist/chat-dock/chat-dock.js +1 -0
- package/dist/chat-message/chat-message.js +2 -1
- package/dist/chat-typing/chat-typing.js +1 -0
- package/dist/checkbox/checkbox.d.ts +1 -1
- package/dist/checkbox/checkbox.js +9 -2
- package/dist/checkbox/llms.txt +3 -0
- package/dist/chip/chip.js +1 -0
- package/dist/chips-input/chips-input.d.ts +3 -1
- package/dist/chips-input/chips-input.js +14 -5
- package/dist/chips-input/llms.txt +3 -0
- package/dist/clock/clock.d.ts +9 -4
- package/dist/clock/clock.js +35 -5
- package/dist/clock/llms.txt +6 -2
- package/dist/color-picker/color-picker.d.ts +5 -1
- package/dist/color-picker/color-picker.js +36 -17
- package/dist/color-picker/llms.txt +3 -0
- package/dist/confirm/confirm-container.js +1 -0
- package/dist/confirm/confirm-store.js +6 -0
- package/dist/confirm/llms.txt +2 -1
- package/dist/context-menu/context-menu.js +35 -12
- package/dist/context-menu/llms.txt +3 -0
- package/dist/data-table/data-table.d.ts +12 -1
- package/dist/data-table/data-table.js +113 -51
- package/dist/data-table/export-csv.d.ts +2 -2
- package/dist/data-table/export-csv.js +7 -9
- package/dist/data-table/llms.txt +19 -4
- package/dist/data-view/data-view.js +1 -0
- package/dist/date-range/date-range.d.ts +16 -4
- package/dist/date-range/date-range.js +69 -46
- package/dist/date-range/llms.txt +17 -3
- package/dist/datepicker/datepicker.d.ts +17 -3
- package/dist/datepicker/datepicker.js +86 -54
- package/dist/datepicker/llms.txt +17 -3
- package/dist/dialog/dialog.js +29 -8
- package/dist/dialog/llms.txt +3 -0
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +1 -0
- package/dist/drawer/drawer.d.ts +6 -1
- package/dist/drawer/drawer.js +3 -2
- package/dist/drawer/llms.txt +3 -0
- package/dist/fieldset/fieldset.js +4 -1
- package/dist/file-upload/file-upload.d.ts +7 -1
- package/dist/file-upload/file-upload.js +18 -7
- package/dist/file-upload/llms.txt +6 -3
- package/dist/form-field/form-field.js +1 -0
- package/dist/galleria/galleria.js +4 -1
- package/dist/i18n/i18n-context.d.ts +3 -3
- package/dist/i18n/i18n-context.js +25 -3
- package/dist/icon/icon.js +4 -1
- package/dist/image/image.js +4 -4
- package/dist/input/input.js +1 -0
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +15 -41
- package/dist/input-mask/llms.txt +3 -0
- package/dist/input-number/input-number.d.ts +3 -1
- package/dist/input-number/input-number.js +15 -11
- package/dist/input-number/llms.txt +5 -2
- package/dist/input-otp/input-otp.d.ts +16 -3
- package/dist/input-otp/input-otp.js +43 -19
- package/dist/input-otp/llms.txt +11 -0
- package/dist/internal/mask.d.ts +14 -0
- package/dist/internal/mask.js +62 -0
- package/dist/internal/number-group.d.ts +22 -0
- package/dist/internal/number-group.js +53 -0
- package/dist/internal/option-key.d.ts +6 -0
- package/dist/internal/option-key.js +8 -0
- package/dist/internal/overlay.js +37 -7
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/scroll-lock.d.ts +6 -0
- package/dist/internal/scroll-lock.js +35 -0
- package/dist/internal/slot-children.d.ts +13 -0
- package/dist/internal/slot-children.js +31 -0
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.js +2 -3
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-modal-focus.d.ts +1 -0
- package/dist/internal/use-modal-focus.js +12 -1
- package/dist/knob/knob.d.ts +5 -2
- package/dist/knob/knob.js +15 -14
- package/dist/knob/llms.txt +4 -2
- package/dist/listbox/listbox.d.ts +3 -1
- package/dist/listbox/listbox.js +30 -10
- package/dist/listbox/llms.txt +3 -0
- package/dist/map/llms.txt +5 -1
- package/dist/map/map-create-dialog.js +30 -22
- package/dist/map/map-hours-field.js +1 -0
- package/dist/map/map-image-field.js +2 -1
- package/dist/map/map-legend.js +1 -0
- package/dist/map/map-poi-card.js +2 -1
- package/dist/map/map-search.js +54 -30
- package/dist/map/map.d.ts +6 -1
- package/dist/map/map.js +62 -34
- package/dist/megamenu/llms.txt +11 -0
- package/dist/megamenu/megamenu.d.ts +7 -1
- package/dist/megamenu/megamenu.js +15 -6
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.js +10 -3
- package/dist/menubar/llms.txt +11 -0
- package/dist/menubar/menubar.d.ts +7 -1
- package/dist/menubar/menubar.js +15 -3
- package/dist/meter-group/meter-group.js +1 -0
- package/dist/order-list/llms.txt +3 -0
- package/dist/order-list/order-list.js +21 -6
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/panel.js +5 -3
- package/dist/password/llms.txt +6 -2
- package/dist/password/password.js +4 -14
- package/dist/peek/llms.txt +1 -1
- package/dist/peek/peek.js +37 -15
- package/dist/pick-list/llms.txt +3 -0
- package/dist/pick-list/pick-list.js +19 -3
- package/dist/popover/llms.txt +3 -0
- package/dist/popover/popover.d.ts +3 -1
- package/dist/popover/popover.js +10 -3
- package/dist/progress/progress.js +1 -0
- package/dist/public-api.d.ts +1 -1
- package/dist/radio-group/llms.txt +3 -0
- package/dist/radio-group/radio-group.d.ts +3 -1
- package/dist/radio-group/radio-group.js +13 -2
- package/dist/rating/rating.js +1 -0
- package/dist/rich-text/llms.txt +14 -0
- package/dist/rich-text/rich-text.d.ts +3 -9
- package/dist/rich-text/rich-text.js +241 -96
- package/dist/rich-text/sanitize.d.ts +9 -0
- package/dist/rich-text/sanitize.js +77 -29
- package/dist/roadmap/llms.txt +9 -3
- package/dist/roadmap/roadmap.js +88 -11
- package/dist/scroll-top/scroll-top.js +1 -0
- package/dist/select/llms.txt +3 -0
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +46 -32
- package/dist/shared/types.d.ts +21 -0
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/sidenav.d.ts +1 -2
- package/dist/sidenav/sidenav.js +4 -3
- package/dist/slider/llms.txt +3 -0
- package/dist/slider/slider.d.ts +3 -1
- package/dist/slider/slider.js +15 -4
- package/dist/speed-dial/speed-dial.js +2 -1
- package/dist/spinner/spinner.js +1 -0
- package/dist/split-button/split-button.js +10 -3
- package/dist/splitter/splitter.js +23 -8
- package/dist/stepper/llms.txt +3 -0
- package/dist/stepper/stepper.js +9 -5
- package/dist/switch/llms.txt +3 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +9 -2
- package/dist/switch-group/llms.txt +3 -0
- package/dist/switch-group/switch-group.d.ts +3 -1
- package/dist/switch-group/switch-group.js +5 -3
- package/dist/tabs/llms.txt +3 -0
- package/dist/tabs/tabs.js +30 -14
- package/dist/textarea/textarea.js +1 -0
- package/dist/toast/llms.txt +3 -0
- package/dist/toast/toast-container.js +1 -0
- package/dist/toast/toast-store.d.ts +5 -1
- package/dist/toast/toast-store.js +1 -1
- package/dist/tooltip/llms.txt +5 -3
- package/dist/tooltip/tooltip.d.ts +3 -2
- package/dist/tooltip/tooltip.js +90 -31
- package/dist/tree/llms.txt +1 -0
- package/dist/tree/tree.d.ts +6 -1
- package/dist/tree/tree.js +14 -7
- package/package.json +15 -14
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ npm i @konce-pt/react @konce-pt/tokens @konce-pt/styles
|
|
|
52
52
|
# or: pnpm add / yarn add
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
`react` and `react-dom` (
|
|
55
|
+
`react` and `react-dom` (`^19.2`) are peers you already have. Optional extras:
|
|
56
56
|
|
|
57
57
|
- [`@konce-pt/grid`](https://www.npmjs.com/package/@konce-pt/grid) — the mobile-first layout system;
|
|
58
58
|
its React components live in `@konce-pt/react/grid`.
|
|
@@ -79,6 +79,9 @@ runtime; React has no other source for them, so this import is not optional.
|
|
|
79
79
|
|
|
80
80
|
Dark theme: set `<html data-theme="dark">` (or rely on `prefers-color-scheme`).
|
|
81
81
|
|
|
82
|
+
**Next.js App Router / Server Components:** every interactive component module starts with
|
|
83
|
+
`'use client'`, so you can import components directly into a server component — no wrapper file.
|
|
84
|
+
|
|
82
85
|
> **No CDK here.** Components with a floating panel (`KptSelect`, `KptPopover`, `KptMenu`,
|
|
83
86
|
> `KptAutocomplete`, `KptDatepicker`…) portal into `document.body` through the internal
|
|
84
87
|
> `KptOverlay`, so `@angular/cdk/overlay-prebuilt.css` is **not** needed — positioning lives in the
|
|
@@ -314,7 +317,7 @@ MIT © konce.pt
|
|
|
314
317
|
npm i @konce-pt/react @konce-pt/tokens @konce-pt/styles
|
|
315
318
|
```
|
|
316
319
|
|
|
317
|
-
`react` i `react-dom` (
|
|
320
|
+
`react` i `react-dom` (`^19.2`) to peery, które masz już w aplikacji. Opcjonalnie:
|
|
318
321
|
`@konce-pt/grid` (układ; komponenty w `@konce-pt/react/grid`), `@konce-pt/validators`
|
|
319
322
|
(walidatory PL: NIP, REGON, PESEL, IBAN, kod pocztowy) oraz `leaflet` (`^1.9`) — **opcjonalny**
|
|
320
323
|
peer, potrzebny wyłącznie dla `@konce-pt/react/map`.
|
|
@@ -338,6 +341,9 @@ nie ma dla nich innego źródła, więc ten import nie jest opcjonalny.
|
|
|
338
341
|
Motyw ciemny: `<html data-theme="dark">` (albo `prefers-color-scheme`). Nadpisania `--kpt-color-*`
|
|
339
342
|
w arkuszu importowanym **po** `@konce-pt/styles`.
|
|
340
343
|
|
|
344
|
+
**Next.js App Router / Server Components:** każdy moduł interaktywnego komponentu zaczyna się od
|
|
345
|
+
`'use client'`, więc komponenty importujesz wprost do komponentu serwerowego — bez pliku-opakowania.
|
|
346
|
+
|
|
341
347
|
> **CDK nie jest tu potrzebny.** Komponenty z panelem (`KptSelect`, `KptPopover`, `KptMenu`,
|
|
342
348
|
> `KptAutocomplete`, `KptDatepicker`…) renderują go przez wewnętrzny `KptOverlay` do portalu na
|
|
343
349
|
> `document.body`, więc `@angular/cdk/overlay-prebuilt.css` nie jest wymagany. W otwartym
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
+
import { cloneElement, createContext, isValidElement, useContext, } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { slotChildren } from "../internal/slot-children.js";
|
|
4
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
7
|
import { useKptId } from "../internal/use-id.js";
|
|
6
8
|
import { KptIcon } from "../icon/icon.js";
|
|
@@ -34,14 +36,13 @@ export function KptAccordion({ multi = false, animated = true, animationDuration
|
|
|
34
36
|
},
|
|
35
37
|
animMs: `${animated ? animationDuration : 0}ms`,
|
|
36
38
|
};
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
index
|
|
43
|
-
|
|
44
|
-
});
|
|
39
|
+
// Indeks panelu = liczba paneli przed nim; fragmenty rozwinięte, więc panel z warunku też się liczy.
|
|
40
|
+
const items = slotChildren(children);
|
|
41
|
+
const isPanel = (child) => isValidElement(child) && child.type?.kptAccordionSlot === 'panel';
|
|
42
|
+
const panelIndex = items.map((_, at) => items.slice(0, at).filter(isPanel).length);
|
|
43
|
+
const panels = items.map((child, at) => isPanel(child)
|
|
44
|
+
? cloneElement(child, { key: child.key ?? `panel-${panelIndex[at]}`, index: panelIndex[at] })
|
|
45
|
+
: isValidElement(child) ? cloneElement(child, { key: child.key ?? `child-${at}` }) : child);
|
|
45
46
|
return (_jsx(AccordionContext.Provider, { value: context, children: _jsx("div", { className: cn('kpt-accordion-host', className), ...rest, children: panels }) }));
|
|
46
47
|
}
|
|
47
48
|
/** Panel akordeonu. Umieszczaj w `<KptAccordion>` albo używaj samodzielnie. */
|
|
@@ -69,7 +70,7 @@ export function KptAccordionPanel({ title, disabled = false, expanded, defaultEx
|
|
|
69
70
|
};
|
|
70
71
|
return (_jsx("div", { className: cn('kpt-accordion-panel-host', className), style: { ['--kpt-anim-duration']: accordion?.animMs ?? '500ms', ...style }, ...rest, children: _jsxs("div", { className: cn('kpt-accordion-panel', isOpen && 'is-open'), children: [_jsxs("button", { type: "button", className: "kpt-accordion-panel__header", id: headerId, "aria-expanded": isOpen, "aria-controls": bodyId, disabled: disabled, onClick: toggle, children: [_jsx("span", { className: "kpt-accordion-panel__title", children: title }), _jsx(KptIcon, { name: "chevron-down", className: cn('kpt-accordion-panel__chevron', isOpen && 'is-open') })] }), _jsx("div", { className: cn('kpt-accordion-panel__collapse', isOpen && 'is-open'),
|
|
71
72
|
// Treść to region nazwany nagłówkiem — czytnik ogłasza nie tylko stan, ale i to, czego dotyczy.
|
|
72
|
-
role: "region", id: bodyId, "aria-labelledby": headerId, inert: !isOpen,
|
|
73
|
+
role: "region", id: bodyId, "aria-labelledby": headerId, inert: !isOpen, children: _jsx("div", { className: "kpt-accordion-panel__collapse-inner", children: _jsx("div", { className: "kpt-accordion-panel__body", children: children }) }) })] }) }));
|
|
73
74
|
}
|
|
74
75
|
KptAccordionPanel.kptAccordionSlot = 'panel';
|
|
75
76
|
KptAccordion.Panel = KptAccordionPanel;
|
package/dist/alert/alert.js
CHANGED
package/dist/alert/llms.txt
CHANGED
|
@@ -45,3 +45,4 @@ The role follows the weight of the message: `info` and `success` are `role="stat
|
|
|
45
45
|
politely, without interrupting), `warning` and `danger` are `role="alert"` (read at once). The icon
|
|
46
46
|
is decorative; the × button is labelled from `common.close`. After closing, focus falls back to the
|
|
47
47
|
page — move it yourself in the close handler when the alert was reached with the keyboard.
|
|
48
|
+
A live region announces **changes**: an alert rendered together with the page (or revealed by toggling `hidden`) is not announced reliably. To have it read, add it after the action (`@if` / conditional render) — the inserted element is announced.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -58,9 +59,15 @@ export function KptAppShell({ toolbar, sidenav, children, sidenavOpen, defaultSi
|
|
|
58
59
|
useEffect(() => {
|
|
59
60
|
if (!isOverlay || !isSidenavOpen)
|
|
60
61
|
return;
|
|
62
|
+
// Escape zamyka nakładkę nawigacji — ale nie wtedy, gdy zamyka nim coś otwartego nad shellem
|
|
63
|
+
// (okno, menu, popover): te wołają `preventDefault()` albo trzymają fokus w sobie.
|
|
61
64
|
const onKeyDown = (event) => {
|
|
62
|
-
if (event.key
|
|
63
|
-
|
|
65
|
+
if (event.key !== 'Escape' || event.defaultPrevented)
|
|
66
|
+
return;
|
|
67
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
68
|
+
if (target?.closest('dialog[open], [popover], [role="menu"], [role="listbox"], [role="dialog"]'))
|
|
69
|
+
return;
|
|
70
|
+
closeSidenav();
|
|
64
71
|
};
|
|
65
72
|
document.addEventListener('keydown', onKeyDown);
|
|
66
73
|
return () => document.removeEventListener('keydown', onKeyDown);
|
|
@@ -79,13 +86,13 @@ export function KptAppShell({ toolbar, sidenav, children, sidenavOpen, defaultSi
|
|
|
79
86
|
// nie miałby dokąd pójść. Po otwarciu przenosimy go do panelu, po zamknięciu oddajemy tam,
|
|
80
87
|
// skąd przyszedł — chyba że użytkownik zdążył go przenieść gdzie indziej. Stan `null` przed
|
|
81
88
|
// pierwszym renderem: nakładka otwarta już przy starcie nie zabiera fokusu.
|
|
82
|
-
const
|
|
89
|
+
const navRef = useRef(null);
|
|
83
90
|
const overlayWasOpen = useRef(null);
|
|
84
91
|
const returnFocusTo = useRef(null);
|
|
85
92
|
useEffect(() => {
|
|
86
93
|
const was = overlayWasOpen.current;
|
|
87
94
|
overlayWasOpen.current = contentInert;
|
|
88
|
-
const panel =
|
|
95
|
+
const panel = navRef.current;
|
|
89
96
|
if (was === null || was === contentInert || !panel)
|
|
90
97
|
return;
|
|
91
98
|
const active = document.activeElement;
|
|
@@ -99,7 +106,7 @@ export function KptAppShell({ toolbar, sidenav, children, sidenavOpen, defaultSi
|
|
|
99
106
|
returnFocusTo.current.focus();
|
|
100
107
|
returnFocusTo.current = null;
|
|
101
108
|
}, [contentInert]);
|
|
102
|
-
return (_jsx(KptAppShellContext.Provider, { value: context, children: _jsxs("div", { className: cn('kpt-app-shell', dockedOpen && 'kpt-app-shell--nav-open', compactOpen && 'kpt-app-shell--nav-compact-open', className), "data-sidenav-mode": sidenavMode, "data-sidenav-breakpoint": sidenavBreakpoint, "data-sidenav-rail": sidenavRailBreakpoint ?? undefined, ...rest, children: [toolbar, _jsxs("div", { className: "kpt-app-shell__body", children: [_jsx("
|
|
109
|
+
return (_jsx(KptAppShellContext.Provider, { value: context, children: _jsxs("div", { className: cn('kpt-app-shell', dockedOpen && 'kpt-app-shell--nav-open', compactOpen && 'kpt-app-shell--nav-compact-open', className), "data-sidenav-mode": sidenavMode, "data-sidenav-breakpoint": sidenavBreakpoint, "data-sidenav-rail": sidenavRailBreakpoint ?? undefined, ...rest, children: [toolbar, _jsxs("div", { className: "kpt-app-shell__body", children: [_jsx("nav", { ref: navRef, className: "kpt-app-shell__sidenav", tabIndex: -1, id: sidenavId, "aria-label": sidenavLabel || t('appShell.navigation'), children: sidenav }), _jsx("div", { className: "kpt-app-shell__scrim", "aria-hidden": "true", onClick: closeSidenav }), _jsx("main", { className: "kpt-app-shell__content", inert: contentInert, children: children })] })] }) }));
|
|
103
110
|
}
|
|
104
111
|
/** Dostęp do stanu shella. Poza `KptAppShell` rzuca — to zawsze błąd użycia. */
|
|
105
112
|
export function useKptAppShell() {
|
package/dist/app-shell/llms.txt
CHANGED
|
@@ -8,7 +8,7 @@ Import: `import { KptAppShell, useKptShellNavToggle } from '@konce-pt/react';`
|
|
|
8
8
|
data-sidenav-mode="auto" data-sidenav-breakpoint="lg">
|
|
9
9
|
…toolbar…
|
|
10
10
|
<div class="kpt-app-shell__body">
|
|
11
|
-
<
|
|
11
|
+
<nav class="kpt-app-shell__sidenav" tabindex="-1" id="…">…sidenav…</nav>
|
|
12
12
|
<div class="kpt-app-shell__scrim" aria-hidden="true"></div>
|
|
13
13
|
<main class="kpt-app-shell__content">…children…</main>
|
|
14
14
|
</div>
|
|
@@ -54,9 +54,10 @@ decides WHICH state the button flips. The scrim is always in the DOM; CSS contro
|
|
|
54
54
|
`--kpt-color-overlay-scrim`, `--kpt-motion-duration-base`, `--kpt-motion-easing-standard`.
|
|
55
55
|
|
|
56
56
|
## Accessibility
|
|
57
|
-
- The panel is
|
|
57
|
+
- The panel is a `<nav>` with a label and an `id` for `aria-controls`.
|
|
58
58
|
- A closed docked panel leaves the tab order through `visibility`, correct on the first frame.
|
|
59
|
-
- While the overlay is open the content gets `inert`, and Escape closes the panel
|
|
60
|
-
|
|
59
|
+
- While the overlay is open the content gets `inert`, and Escape closes the panel — unless something
|
|
60
|
+
opened above the shell (a dialog, menu or popover) already handled it.
|
|
61
|
+
- Opening the overlay moves focus into the panel (its first focusable element, or the `<nav>`
|
|
61
62
|
itself); closing it returns focus to where it came from — usually the menu button — unless the user
|
|
62
63
|
has already moved it elsewhere. An overlay that is open when the page loads does not take focus.
|
package/dist/auth/auth.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
+
import { Fragment, isValidElement, useId, useRef, useState, } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { slotChildren } from "../internal/slot-children.js";
|
|
4
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
6
8
|
import { KptAlert } from "../alert/alert.js";
|
|
@@ -59,13 +61,13 @@ export function KptAuth({ mode, defaultMode = 'signIn', onModeChange, layout = '
|
|
|
59
61
|
const headingRef = useRef(null);
|
|
60
62
|
// ── Sloty ──
|
|
61
63
|
const slots = {};
|
|
62
|
-
|
|
64
|
+
for (const child of slotChildren(children)) {
|
|
63
65
|
if (!isValidElement(child))
|
|
64
|
-
|
|
66
|
+
continue;
|
|
65
67
|
const name = child.type?.kptAuthSlot;
|
|
66
68
|
if (name)
|
|
67
69
|
slots[name] = child.props.children;
|
|
68
|
-
}
|
|
70
|
+
}
|
|
69
71
|
// ── Modele formularzy ──
|
|
70
72
|
const [signInModel, setSignInModel] = useState({
|
|
71
73
|
email: '',
|
|
@@ -125,7 +127,8 @@ export function KptAuth({ mode, defaultMode = 'signIn', onModeChange, layout = '
|
|
|
125
127
|
const anyError = (kinds) => Object.values(kinds).some((kind) => kind !== null);
|
|
126
128
|
const submitAll = (form, fields) => {
|
|
127
129
|
// Submit dotyka całego formularza — inaczej pola, których użytkownik nie ruszył, nie
|
|
128
|
-
// pokazałyby błędu
|
|
130
|
+
// pokazałyby błędu. Dlatego przycisk wysyłki nie jest wyłączany przy błędach: pokazuje,
|
|
131
|
+
// czego brakuje, zamiast stać martwy (WCAG 3.3.1).
|
|
129
132
|
setTouched((prev) => {
|
|
130
133
|
const next = new Set(prev);
|
|
131
134
|
for (const field of fields)
|
|
@@ -152,22 +155,28 @@ export function KptAuth({ mode, defaultMode = 'signIn', onModeChange, layout = '
|
|
|
152
155
|
const promoBody = promoText === null ? t(`auth.promo.${promoMode}Text`) : promoText;
|
|
153
156
|
return (_jsx("div", { className: cn('kpt-auth-host', className), "data-layout": layout, "data-mode": activeMode, style: { ['--kpt-anim-duration']: `${animated ? animationDuration : 0}ms`, ...style }, ...rest, children: _jsx("div", { className: "kpt-auth", children: _jsxs("div", { className: "kpt-auth__card", children: [_jsx("div", { className: "kpt-auth__form-side", children: _jsxs("div", { className: "kpt-auth__inner", "data-mode": activeMode, children: [_jsx("div", { className: "kpt-auth__brand", children: slots.brand }), _jsx("h2", { ref: headingRef, className: "kpt-auth__title", id: ids[activeMode], "data-mode": activeMode, tabIndex: -1, children: t(`auth.${activeMode}Title`) }), _jsx("p", { className: "kpt-auth__subtitle", children: t(`auth.${activeMode}Subtitle`) }), error ? _jsx(KptAlert, { variant: "danger", children: error }) : null, notice ? _jsx(KptAlert, { variant: "success", children: notice }) : null, activeMode === 'signIn' ? (_jsxs("form", { className: "kpt-auth__form", "aria-labelledby": ids.signIn, onSubmit: (event) => {
|
|
154
157
|
event.preventDefault();
|
|
158
|
+
if (busy)
|
|
159
|
+
return;
|
|
155
160
|
submitAll('signIn', ['email', 'password']);
|
|
156
161
|
if (!anyError(signInErrors))
|
|
157
162
|
onSignIn?.({ ...signInModel });
|
|
158
|
-
}, children: [_jsx(KptFormField, { label: t('auth.email'), error: errorFor('signIn', 'email', signInErrors), required: true, controlId: ids.email, children: _jsx(KptInput, { type: "email", autoComplete: "email", id: ids.email, placeholder: t('auth.emailPlaceholder'), value: signInModel.email, onValueChange: (email) => setSignInModel({ ...signInModel, email }), onTouch: () => touch('signIn.email') }) }), _jsx(KptFormField, { label: t('auth.password'), error: errorFor('signIn', 'password', signInErrors), required: true, controlId: ids.password, children: _jsx(KptPassword, { autoComplete: "current-password", id: ids.password, value: signInModel.password, onValueChange: (password) => setSignInModel({ ...signInModel, password }), onTouch: () => touch('signIn.password') }) }), slots.fields, _jsxs("div", { className: "kpt-auth__row", children: [showRemember ? (_jsx(KptCheckbox, { label: t('auth.remember'), checked: signInModel.remember, onCheckedChange: (remember) => setSignInModel({ ...signInModel, remember }) })) : (_jsx("span", {})), _jsx("button", { type: "button", className: "kpt-auth__link", onClick: () => changeMode('forgot'), children: t('auth.forgotLink') })] }), _jsx(KptButton, { type: "submit", fullWidth: true, loading: busy,
|
|
163
|
+
}, children: [_jsx(KptFormField, { label: t('auth.email'), error: errorFor('signIn', 'email', signInErrors), required: true, controlId: ids.email, children: _jsx(KptInput, { type: "email", autoComplete: "email", id: ids.email, placeholder: t('auth.emailPlaceholder'), value: signInModel.email, onValueChange: (email) => setSignInModel({ ...signInModel, email }), onTouch: () => touch('signIn.email') }) }), _jsx(KptFormField, { label: t('auth.password'), error: errorFor('signIn', 'password', signInErrors), required: true, controlId: ids.password, children: _jsx(KptPassword, { autoComplete: "current-password", id: ids.password, value: signInModel.password, onValueChange: (password) => setSignInModel({ ...signInModel, password }), onTouch: () => touch('signIn.password') }) }), slots.fields, _jsxs("div", { className: "kpt-auth__row", children: [showRemember ? (_jsx(KptCheckbox, { label: t('auth.remember'), checked: signInModel.remember, onCheckedChange: (remember) => setSignInModel({ ...signInModel, remember }) })) : (_jsx("span", {})), _jsx("button", { type: "button", className: "kpt-auth__link", onClick: () => changeMode('forgot'), children: t('auth.forgotLink') })] }), _jsx(KptButton, { type: "submit", fullWidth: true, loading: busy, children: t('auth.signInSubmit') })] })) : null, activeMode === 'signUp' ? (_jsxs("form", { className: "kpt-auth__form", "aria-labelledby": ids.signUp, onSubmit: (event) => {
|
|
159
164
|
event.preventDefault();
|
|
165
|
+
if (busy)
|
|
166
|
+
return;
|
|
160
167
|
submitAll('signUp', ['name', 'email', 'password', 'confirm', 'terms']);
|
|
161
168
|
if (!anyError(signUpErrors)) {
|
|
162
169
|
const { name, email, password, terms } = signUpModel;
|
|
163
170
|
onSignUp?.({ name, email, password, terms });
|
|
164
171
|
}
|
|
165
|
-
}, children: [showName ? (_jsx(KptFormField, { label: t('auth.name'), error: errorFor('signUp', 'name', signUpErrors), required: true, controlId: ids.name, children: _jsx(KptInput, { autoComplete: "name", id: ids.name, value: signUpModel.name, onValueChange: (name) => setSignUpModel({ ...signUpModel, name }), onTouch: () => touch('signUp.name') }) })) : null, _jsx(KptFormField, { label: t('auth.email'), error: errorFor('signUp', 'email', signUpErrors), required: true, controlId: ids.regEmail, children: _jsx(KptInput, { type: "email", autoComplete: "email", id: ids.regEmail, placeholder: t('auth.emailPlaceholder'), value: signUpModel.email, onValueChange: (email) => setSignUpModel({ ...signUpModel, email }), onTouch: () => touch('signUp.email') }) }), _jsx(KptFormField, { label: t('auth.password'), error: errorFor('signUp', 'password', signUpErrors), hint: t('auth.passwordHint', { min: KPT_AUTH_MIN_PASSWORD }), required: true, controlId: ids.regPassword, children: _jsx(KptPassword, { autoComplete: "new-password", feedback: true, id: ids.regPassword, value: signUpModel.password, onValueChange: (password) => setSignUpModel({ ...signUpModel, password }), onTouch: () => touch('signUp.password') }) }), _jsx(KptFormField, { label: t('auth.confirmPassword'), error: errorFor('signUp', 'confirm', signUpErrors), required: true, controlId: ids.regConfirm, children: _jsx(KptPassword, { autoComplete: "new-password", id: ids.regConfirm, value: signUpModel.confirm, onValueChange: (confirm) => setSignUpModel({ ...signUpModel, confirm }), onTouch: () => touch('signUp.confirm') }) }), slots.fields, requireTerms ? (_jsx(KptFormField, { error: errorFor('signUp', 'terms', signUpErrors), children: _jsx(KptCheckbox, { label: t('auth.terms'), checked: signUpModel.terms, onCheckedChange: (terms) => setSignUpModel({ ...signUpModel, terms }), onTouch: () => touch('signUp.terms') }) })) : null, _jsx(KptButton, { type: "submit", fullWidth: true, loading: busy,
|
|
172
|
+
}, children: [showName ? (_jsx(KptFormField, { label: t('auth.name'), error: errorFor('signUp', 'name', signUpErrors), required: true, controlId: ids.name, children: _jsx(KptInput, { autoComplete: "name", id: ids.name, value: signUpModel.name, onValueChange: (name) => setSignUpModel({ ...signUpModel, name }), onTouch: () => touch('signUp.name') }) })) : null, _jsx(KptFormField, { label: t('auth.email'), error: errorFor('signUp', 'email', signUpErrors), required: true, controlId: ids.regEmail, children: _jsx(KptInput, { type: "email", autoComplete: "email", id: ids.regEmail, placeholder: t('auth.emailPlaceholder'), value: signUpModel.email, onValueChange: (email) => setSignUpModel({ ...signUpModel, email }), onTouch: () => touch('signUp.email') }) }), _jsx(KptFormField, { label: t('auth.password'), error: errorFor('signUp', 'password', signUpErrors), hint: t('auth.passwordHint', { min: KPT_AUTH_MIN_PASSWORD }), required: true, controlId: ids.regPassword, children: _jsx(KptPassword, { autoComplete: "new-password", feedback: true, id: ids.regPassword, value: signUpModel.password, onValueChange: (password) => setSignUpModel({ ...signUpModel, password }), onTouch: () => touch('signUp.password') }) }), _jsx(KptFormField, { label: t('auth.confirmPassword'), error: errorFor('signUp', 'confirm', signUpErrors), required: true, controlId: ids.regConfirm, children: _jsx(KptPassword, { autoComplete: "new-password", id: ids.regConfirm, value: signUpModel.confirm, onValueChange: (confirm) => setSignUpModel({ ...signUpModel, confirm }), onTouch: () => touch('signUp.confirm') }) }), slots.fields, requireTerms ? (_jsx(KptFormField, { error: errorFor('signUp', 'terms', signUpErrors), children: _jsx(KptCheckbox, { label: t('auth.terms'), checked: signUpModel.terms, onCheckedChange: (terms) => setSignUpModel({ ...signUpModel, terms }), onTouch: () => touch('signUp.terms') }) })) : null, _jsx(KptButton, { type: "submit", fullWidth: true, loading: busy, children: t('auth.signUpSubmit') })] })) : null, activeMode === 'forgot' ? (_jsxs("form", { className: "kpt-auth__form", "aria-labelledby": ids.forgot, onSubmit: (event) => {
|
|
166
173
|
event.preventDefault();
|
|
174
|
+
if (busy)
|
|
175
|
+
return;
|
|
167
176
|
submitAll('forgot', ['email']);
|
|
168
177
|
if (!anyError(resetErrors))
|
|
169
178
|
onResetPassword?.({ ...resetModel });
|
|
170
|
-
}, children: [_jsx(KptFormField, { label: t('auth.email'), error: errorFor('forgot', 'email', resetErrors), required: true, controlId: ids.resetEmail, children: _jsx(KptInput, { type: "email", autoComplete: "email", id: ids.resetEmail, placeholder: t('auth.emailPlaceholder'), value: resetModel.email, onValueChange: (email) => setResetModel({ email }), onTouch: () => touch('forgot.email') }) }), _jsx(KptButton, { type: "submit", fullWidth: true, loading: busy,
|
|
179
|
+
}, children: [_jsx(KptFormField, { label: t('auth.email'), error: errorFor('forgot', 'email', resetErrors), required: true, controlId: ids.resetEmail, children: _jsx(KptInput, { type: "email", autoComplete: "email", id: ids.resetEmail, placeholder: t('auth.emailPlaceholder'), value: resetModel.email, onValueChange: (email) => setResetModel({ email }), onTouch: () => touch('forgot.email') }) }), _jsx(KptButton, { type: "submit", fullWidth: true, loading: busy, children: t('auth.resetSubmit') }), _jsx("button", { type: "button", className: "kpt-auth__link kpt-auth__link--center", onClick: () => changeMode('signIn'), children: t('auth.backToSignIn') })] })) : null, activeMode !== 'forgot' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "kpt-auth__sso", children: slots.sso }), providers.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "kpt-auth__separator", role: "separator", "aria-label": t('auth.orContinueWith'), children: _jsx("span", { className: "kpt-auth__separator-text", children: t('auth.orContinueWith') }) }), _jsx("div", { className: "kpt-auth__providers", "data-layout": providerLayout, children: providers.map((provider) => {
|
|
171
180
|
const custom = renderProvider?.(provider, () => selectProvider(provider));
|
|
172
181
|
// Fragment, nie `<span>`: lista dostawców to grid, więc dodatkowy
|
|
173
182
|
// element stałby się jej dzieckiem i własny przycisk przestałby
|
package/dist/auth/llms.txt
CHANGED
|
@@ -31,8 +31,9 @@ Three independent forms, one per mode, validated on every render: e-mail require
|
|
|
31
31
|
password required (sign up: at least 8 characters), the confirmation matching, consent required
|
|
32
32
|
when `requireTerms`. The validator returns a `kind`, and the message is composed at render time,
|
|
33
33
|
so **changing the language repaints the errors**. The "do not shout at an untouched field" gate
|
|
34
|
-
lives in the presentation
|
|
35
|
-
submitting marks the whole form as touched.
|
|
34
|
+
lives in the presentation. The submit button is **not** disabled while the form is invalid:
|
|
35
|
+
submitting marks the whole form as touched and shows what is missing (WCAG 3.3.1), and `onSignIn` /
|
|
36
|
+
`onSignUp` / `onResetPassword` fire only for a valid form.
|
|
36
37
|
|
|
37
38
|
## The heading takes focus after a mode change
|
|
38
39
|
Without it a screen reader never announces the change, and focus stays on a button that has just
|
|
@@ -20,4 +20,4 @@ export interface KptAutocompleteProps extends Omit<ComponentPropsWithRef<'input'
|
|
|
20
20
|
* @example
|
|
21
21
|
* <KptAutocomplete options={cities} value={city} onValueChange={setCity} placeholder="Miasto…" />
|
|
22
22
|
*/
|
|
23
|
-
export declare function KptAutocomplete({ options, value, defaultValue, onValueChange, invalid, touched, onTouch, disabled, className, onBlur, onFocus, onKeyDown, ref, id, 'aria-describedby': ariaDescribedBy, locale, dictionary, ...rest }: Readonly<KptAutocompleteProps>): import("react").JSX.Element;
|
|
23
|
+
export declare function KptAutocomplete({ options, value, defaultValue, onValueChange, invalid, touched, onTouch, disabled, readOnly, className, onBlur, onFocus, onKeyDown, ref, id, 'aria-describedby': ariaDescribedBy, locale, dictionary, ...rest }: Readonly<KptAutocompleteProps>): import("react").JSX.Element;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -18,10 +19,13 @@ import { useKptMessages } from "../i18n/i18n-context.js";
|
|
|
18
19
|
* @example
|
|
19
20
|
* <KptAutocomplete options={cities} value={city} onValueChange={setCity} placeholder="Miasto…" />
|
|
20
21
|
*/
|
|
21
|
-
export function KptAutocomplete({ options = [], value, defaultValue = '', onValueChange, invalid = false, touched, onTouch, disabled = false, className, onBlur, onFocus, onKeyDown, ref, id, 'aria-describedby': ariaDescribedBy, locale, dictionary, ...rest }) {
|
|
22
|
+
export function KptAutocomplete({ options = [], value, defaultValue = '', onValueChange, invalid = false, touched, onTouch, disabled = false, readOnly = false, className, onBlur, onFocus, onKeyDown, ref, id, 'aria-describedby': ariaDescribedBy, locale, dictionary, ...rest }) {
|
|
22
23
|
const { t } = useKptMessages({ locale, dictionary });
|
|
23
24
|
const field = useKptFieldA11y(id, 'kpt-autocomplete');
|
|
24
25
|
const inputRef = useRef(null);
|
|
26
|
+
/** Kotwica nakładki w stanie (callback ref), nie `inputRef.current` czytane w renderze. */
|
|
27
|
+
const [anchor, setAnchor] = useState(null);
|
|
28
|
+
const setRefs = useMemo(() => composeRefs(inputRef, ref, setAnchor), [ref]);
|
|
25
29
|
const [current, setCurrent] = useControllableState({
|
|
26
30
|
value,
|
|
27
31
|
defaultValue,
|
|
@@ -55,6 +59,9 @@ export function KptAutocomplete({ options = [], value, defaultValue = '', onValu
|
|
|
55
59
|
}, [setCurrent]);
|
|
56
60
|
const handleKeyDown = (event) => {
|
|
57
61
|
onKeyDown?.(event);
|
|
62
|
+
// Tylko do odczytu: podpowiedzi się nie otwierają, a Escape nie czyści wartości.
|
|
63
|
+
if (readOnly)
|
|
64
|
+
return;
|
|
58
65
|
if (event.key === 'ArrowDown') {
|
|
59
66
|
event.preventDefault();
|
|
60
67
|
setOpen(true);
|
|
@@ -86,20 +93,21 @@ export function KptAutocomplete({ options = [], value, defaultValue = '', onValu
|
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
};
|
|
89
|
-
return (_jsxs("span", { className: "kpt-autocomplete-host", children: [_jsx("input", { ref:
|
|
96
|
+
return (_jsxs("span", { className: "kpt-autocomplete-host", children: [_jsx("input", { ref: setRefs, className: cn('kpt-autocomplete__input', className), type: "text", id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), role: "combobox", "aria-autocomplete": "list", autoComplete: "off", "aria-expanded": panelOpen, "aria-controls": panelOpen ? listId : undefined, "aria-activedescendant": activeOptionId, "aria-invalid": showInvalid || undefined, "aria-readonly": readOnly || undefined, disabled: disabled, readOnly: readOnly, value: current, onChange: (event) => {
|
|
90
97
|
setCurrent(event.target.value);
|
|
91
98
|
setOpen(true);
|
|
92
99
|
setTyped(true);
|
|
93
100
|
setActiveIndex(-1);
|
|
94
101
|
}, onFocus: (event) => {
|
|
95
102
|
onFocus?.(event);
|
|
96
|
-
|
|
103
|
+
if (!readOnly)
|
|
104
|
+
setOpen(true);
|
|
97
105
|
}, onBlur: (event) => {
|
|
98
106
|
onBlur?.(event);
|
|
99
107
|
setOpen(false);
|
|
100
108
|
setTyped(false);
|
|
101
109
|
onTouch?.();
|
|
102
|
-
}, onKeyDown: handleKeyDown, ...rest }), _jsx(KptOverlay, { open: panelOpen, anchor:
|
|
110
|
+
}, onKeyDown: handleKeyDown, ...rest }), _jsx(KptOverlay, { open: panelOpen, anchor: anchor, onClose: () => setOpen(false), matchAnchorWidth: true, withBackdrop: false, children: _jsx("ul", { className: "kpt-autocomplete__panel", role: "listbox", id: listId, "aria-label": rest['aria-label'], children: filtered.map((option, index) => (_jsx("li", { id: optionId(index), className: cn('kpt-autocomplete__option', index === activeIndex && 'is-active'), role: "option", "aria-selected": index === activeIndex,
|
|
103
111
|
// `mousedown` z zablokowanym domyślnym zachowaniem: klik w opcję nie może
|
|
104
112
|
// zabrać fokusu polu, bo `blur` zamknąłby panel przed wyborem.
|
|
105
113
|
onMouseDown: (event) => {
|
|
@@ -54,3 +54,6 @@ added to `aria-describedby` (merged with your own). Without a wrapper, name it w
|
|
|
54
54
|
## Accessibility
|
|
55
55
|
The input is `role="combobox"` with `aria-expanded`; the panel is `role="listbox"` with
|
|
56
56
|
`role="option"` rows carrying `aria-selected`.
|
|
57
|
+
|
|
58
|
+
## Read-only and required
|
|
59
|
+
`readOnly` is the native read-only field with `aria-readonly`; the suggestions do not open and Escape does not clear the value.
|
package/dist/avatar/avatar.js
CHANGED
|
@@ -57,23 +57,4 @@ export interface KptBackdropProps extends ComponentPropsWithRef<'div'> {
|
|
|
57
57
|
/** Propsy warstwy z treścią (`.kpt-backdrop__content`). */
|
|
58
58
|
contentProps?: ComponentPropsWithRef<'div'>;
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Dekoracyjna warstwa tła dla sekcji: komponent obejmuje treść i maluje pod nią animowany canvas.
|
|
62
|
-
* Sięga się po niego tam, gdzie płaska powierzchnia wygląda na niedokończoną, a wideo albo duży
|
|
63
|
-
* obraz kosztowałyby transfer i nie poszłyby za motywem.
|
|
64
|
-
*
|
|
65
|
-
* Komponent jest opakowaniem: canvas leży pod spodem, a dzieci idą warstwę wyżej i zachowują się
|
|
66
|
-
* zwyczajnie. Rysowanie stoi, gdy nie ma czego oglądać — sekcja poza ekranem, karta w tle,
|
|
67
|
-
* `paused`, `speed` równe zeru albo ograniczony ruch w systemie; wtedy zostaje jedna statyczna
|
|
68
|
-
* klatka zamiast pustki, bo efekt jest częścią kompozycji, nie ozdobą.
|
|
69
|
-
*
|
|
70
|
-
* Rysowanie i cała matematyka siedzą w `@konce-pt/backdrop`, wspólnym z portem Angulara —
|
|
71
|
-
* ten komponent to sam cykl życia.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* <KptBackdrop effect="wave" rotation={18} colors={['#5b8af2', '#a86df2', '#f25b9e']}>
|
|
75
|
-
* <h1>Build your app from the first prompt</h1>
|
|
76
|
-
* <KptButton>Get started</KptButton>
|
|
77
|
-
* </KptBackdrop>
|
|
78
|
-
*/
|
|
79
60
|
export declare function KptBackdrop({ effect, background, speed, seed, paused, adaptive, color, anchor, cell, gap, density, falloff, colors, rotation, band, amplitude, contentProps, className, style, children, ...rest }: Readonly<KptBackdropProps>): import("react").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
4
|
import { KPT_BACKDROP_MAX_DPR, KPT_BACKDROP_MAX_STEP, KPT_SIGNAL_GRID_CELL, KPT_SIGNAL_GRID_DENSITY, KPT_SIGNAL_GRID_FALLOFF, KPT_SIGNAL_GRID_GAP, KPT_WAVE_AMPLITUDE, KPT_WAVE_BAND, KPT_WAVE_ROTATION, adaptBackdropValue, createBackdropRenderer, observeBackdropTheme, readBackdropInk, readBackdropLead, readBackdropSurface, resolveBackdropColors, } from '@konce-pt/backdrop';
|
|
4
5
|
import { cn } from "../internal/cn.js";
|
|
5
6
|
import { isDevBuild } from "../internal/dev.js";
|
|
@@ -23,6 +24,18 @@ const EMPTY = [];
|
|
|
23
24
|
* <KptButton>Get started</KptButton>
|
|
24
25
|
* </KptBackdrop>
|
|
25
26
|
*/
|
|
27
|
+
const REDUCED_MOTION = '(prefers-reduced-motion: reduce)';
|
|
28
|
+
/** `prefers-reduced-motion` jako zewnętrzny magazyn — bez `setState` w efekcie i bez mignięcia ruchu. */
|
|
29
|
+
function subscribeReducedMotion(onChange) {
|
|
30
|
+
if (typeof matchMedia === 'undefined')
|
|
31
|
+
return () => undefined;
|
|
32
|
+
const query = matchMedia(REDUCED_MOTION);
|
|
33
|
+
query.addEventListener('change', onChange);
|
|
34
|
+
return () => query.removeEventListener('change', onChange);
|
|
35
|
+
}
|
|
36
|
+
function readReducedMotion() {
|
|
37
|
+
return typeof matchMedia !== 'undefined' && matchMedia(REDUCED_MOTION).matches;
|
|
38
|
+
}
|
|
26
39
|
export function KptBackdrop({ effect = 'signal-grid', background = '', speed = 1, seed = 0, paused = false, adaptive = false, color = '', anchor = 'right', cell = KPT_SIGNAL_GRID_CELL, gap = KPT_SIGNAL_GRID_GAP, density = KPT_SIGNAL_GRID_DENSITY, falloff = KPT_SIGNAL_GRID_FALLOFF, colors = EMPTY, rotation = KPT_WAVE_ROTATION, band = KPT_WAVE_BAND, amplitude = KPT_WAVE_AMPLITUDE, contentProps, className, style, children, ...rest }) {
|
|
27
40
|
const host = useRef(null);
|
|
28
41
|
const canvas = useRef(null);
|
|
@@ -42,10 +55,8 @@ export function KptBackdrop({ effect = 'signal-grid', background = '', speed = 1
|
|
|
42
55
|
* i wtedy treść zostaje przy `--kpt-color-on-surface` ze stylu.
|
|
43
56
|
*/
|
|
44
57
|
const [ink, setInk] = useState({ tone: '', color: '' });
|
|
45
|
-
const rate = useRef(speed);
|
|
46
|
-
rate.current = speed;
|
|
47
58
|
const [onScreen, setOnScreen] = useState(true);
|
|
48
|
-
const
|
|
59
|
+
const reduced = useSyncExternalStore(subscribeReducedMotion, readReducedMotion, () => false);
|
|
49
60
|
/** Licznik zmian motywu; podbicie każe przeliczyć kolory z tokenów na nowo. */
|
|
50
61
|
const [theme, setTheme] = useState(0);
|
|
51
62
|
const paint = useCallback(() => {
|
|
@@ -146,13 +157,6 @@ export function KptBackdrop({ effect = 'signal-grid', background = '', speed = 1
|
|
|
146
157
|
document.removeEventListener('visibilitychange', onTabSwitch);
|
|
147
158
|
};
|
|
148
159
|
}, []);
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
const query = matchMedia('(prefers-reduced-motion: reduce)');
|
|
151
|
-
setReduced(query.matches);
|
|
152
|
-
const onChange = (event) => setReduced(event.matches);
|
|
153
|
-
query.addEventListener('change', onChange);
|
|
154
|
-
return () => query.removeEventListener('change', onChange);
|
|
155
|
-
}, []);
|
|
156
160
|
// Zmiana motywu przestawia tokeny, a kolory renderera są już wyliczone — trzeba je odczytać
|
|
157
161
|
// na nowo. Nasłuch trafia też w zmiany niezwiązane z barwą, więc licznik podbija się dopiero
|
|
158
162
|
// wtedy, gdy odczyt naprawdę dał inny kolor.
|
|
@@ -180,8 +184,9 @@ export function KptBackdrop({ effect = 'signal-grid', background = '', speed = 1
|
|
|
180
184
|
const tick = (now) => {
|
|
181
185
|
const step = last === 0 ? 0 : Math.min(now - last, KPT_BACKDROP_MAX_STEP);
|
|
182
186
|
last = now;
|
|
183
|
-
// Prędkość wchodzi w zegar, nie w fazę — suwak przyspiesza obraz zamiast go przeskakiwać.
|
|
184
|
-
|
|
187
|
+
// Prędkość wchodzi w zegar, nie w fazę — suwak przyspiesza obraz zamiast go przeskakiwać. Pętla
|
|
188
|
+
// restartuje się przy zmianie `speed` (zależność efektu), a zegar żyje w refie, więc nic nie skacze.
|
|
189
|
+
clock.current += (step / 1000) * speed;
|
|
185
190
|
paint();
|
|
186
191
|
frame = requestAnimationFrame(tick);
|
|
187
192
|
};
|
|
@@ -4,6 +4,11 @@ export interface KptBottomSheetProps extends ComponentPropsWithRef<'div'>, KptI1
|
|
|
4
4
|
open: boolean;
|
|
5
5
|
onOpenChange: (open: boolean) => void;
|
|
6
6
|
header?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Nazwa panelu bez `header` — okno dialogowe bez nazwy czytnik ogłasza jako samo „okno
|
|
9
|
+
* dialogowe” (WCAG 4.1.2). Z nagłówkiem nazwą jest nagłówek (`aria-labelledby`).
|
|
10
|
+
*/
|
|
11
|
+
ariaLabel?: string;
|
|
7
12
|
closeOnBackdrop?: boolean;
|
|
8
13
|
children?: ReactNode;
|
|
9
14
|
}
|
|
@@ -18,4 +23,4 @@ export interface KptBottomSheetProps extends ComponentPropsWithRef<'div'>, KptI1
|
|
|
18
23
|
* @example
|
|
19
24
|
* <KptBottomSheet open={open} onOpenChange={setOpen} header="Udostępnij">…</KptBottomSheet>
|
|
20
25
|
*/
|
|
21
|
-
export declare function KptBottomSheet({ open, onOpenChange, header, closeOnBackdrop, locale, dictionary, className, children, ...rest }: Readonly<KptBottomSheetProps>): import("react").JSX.Element;
|
|
26
|
+
export declare function KptBottomSheet({ open, onOpenChange, header, ariaLabel, closeOnBackdrop, locale, dictionary, className, children, ...rest }: Readonly<KptBottomSheetProps>): import("react").JSX.Element;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useRef } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -15,13 +16,13 @@ import { useKptMessages } from "../i18n/i18n-context.js";
|
|
|
15
16
|
* @example
|
|
16
17
|
* <KptBottomSheet open={open} onOpenChange={setOpen} header="Udostępnij">…</KptBottomSheet>
|
|
17
18
|
*/
|
|
18
|
-
export function KptBottomSheet({ open, onOpenChange, header = '', closeOnBackdrop = true, locale, dictionary, className, children, ...rest }) {
|
|
19
|
+
export function KptBottomSheet({ open, onOpenChange, header = '', ariaLabel = '', closeOnBackdrop = true, locale, dictionary, className, children, ...rest }) {
|
|
19
20
|
const { t } = useKptMessages({ locale, dictionary });
|
|
20
21
|
const close = () => onOpenChange(false);
|
|
21
22
|
const panelRef = useRef(null);
|
|
22
23
|
const titleId = useKptId(undefined, 'kpt-bottom-sheet-title');
|
|
23
24
|
useModalFocus(open, panelRef);
|
|
24
|
-
return (_jsxs("div", { className: cn('kpt-bottom-sheet-host', className), ...rest, children: [_jsx("div", { className: cn('kpt-bottom-sheet__backdrop', open && 'is-open'), onClick: () => closeOnBackdrop && close() }), _jsxs("div", { ref: panelRef, className: cn('kpt-bottom-sheet__panel', open && 'is-open'), role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, tabIndex: -1, onKeyDown: (event) => {
|
|
25
|
+
return (_jsxs("div", { className: cn('kpt-bottom-sheet-host', className), ...rest, children: [_jsx("div", { className: cn('kpt-bottom-sheet__backdrop', open && 'is-open'), onClick: () => closeOnBackdrop && close() }), _jsxs("div", { ref: panelRef, className: cn('kpt-bottom-sheet__panel', open && 'is-open'), role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, "aria-label": header ? undefined : ariaLabel || undefined, tabIndex: -1, onKeyDown: (event) => {
|
|
25
26
|
if (event.key === 'Escape')
|
|
26
27
|
close();
|
|
27
28
|
}, children: [_jsx("button", { type: "button", className: "kpt-bottom-sheet__handle", onClick: close, "aria-label": t('common.close') }), header ? (_jsx("div", { className: "kpt-bottom-sheet__header", id: titleId, children: header })) : null, _jsx("div", { className: "kpt-bottom-sheet__body", children: children })] })] }));
|
|
@@ -47,3 +47,6 @@ The panel `--kpt-color-surface-raised`, the top radius `--kpt-radius-lg`, `--kpt
|
|
|
47
47
|
## Accessibility
|
|
48
48
|
`role="dialog"`, `aria-modal` and `aria-labelledby` pointing at the header; the handle is a real button
|
|
49
49
|
labelled from `common.close`. Focus handling — see "Focus and keyboard" above.
|
|
50
|
+
|
|
51
|
+
## Name and scrolling
|
|
52
|
+
Without `header` give the sheet `ariaLabel` — a `role="dialog"` without a name is announced as a bare "dialog". An open sheet locks page scrolling underneath.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptNavigateEvent } from '../shared/types.ts';
|
|
2
3
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
4
|
export interface KptBreadcrumbItem {
|
|
4
5
|
label: string;
|
|
@@ -6,6 +7,11 @@ export interface KptBreadcrumbItem {
|
|
|
6
7
|
}
|
|
7
8
|
export interface KptBreadcrumbProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
|
|
8
9
|
items?: readonly KptBreadcrumbItem[];
|
|
10
|
+
/**
|
|
11
|
+
* Zwykły klik w odnośnik. Aplikacja z routerem woła `event.preventDefault()` i nawiguje sama —
|
|
12
|
+
* bez tego `<a href>` przeładowuje stronę.
|
|
13
|
+
*/
|
|
14
|
+
onNavigate?: (event: KptNavigateEvent<KptBreadcrumbItem>) => void;
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Ścieżka nawigacji (breadcrumbs).
|
|
@@ -16,4 +22,4 @@ export interface KptBreadcrumbProps extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
16
22
|
* @example
|
|
17
23
|
* <KptBreadcrumb items={[{ label: 'Start', url: '/' }, { label: 'Ustawienia' }]} />
|
|
18
24
|
*/
|
|
19
|
-
export declare function KptBreadcrumb({ items, locale, dictionary, className, ...rest }: Readonly<KptBreadcrumbProps>): import("react").JSX.Element;
|
|
25
|
+
export declare function KptBreadcrumb({ items, onNavigate, locale, dictionary, className, ...rest }: Readonly<KptBreadcrumbProps>): import("react").JSX.Element;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { kptIsPlainClick } from "../shared/types.js";
|
|
2
4
|
import { cn } from "../internal/cn.js";
|
|
3
5
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
4
6
|
import { KptIcon } from "../icon/icon.js";
|
|
@@ -11,10 +13,16 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
11
13
|
* @example
|
|
12
14
|
* <KptBreadcrumb items={[{ label: 'Start', url: '/' }, { label: 'Ustawienia' }]} />
|
|
13
15
|
*/
|
|
14
|
-
export function KptBreadcrumb({ items = [], locale, dictionary, className, ...rest }) {
|
|
16
|
+
export function KptBreadcrumb({ items = [], onNavigate, locale, dictionary, className, ...rest }) {
|
|
15
17
|
const { t } = useKptMessages({ locale, dictionary });
|
|
18
|
+
/** Zwykły klik w odnośnik → `onNavigate` (Ctrl/⌘/Shift, środkowy przycisk i `_blank` zostają natywne). */
|
|
19
|
+
const linkClick = (event, item) => {
|
|
20
|
+
if (item.url && onNavigate && kptIsPlainClick(event, event.currentTarget.target)) {
|
|
21
|
+
onNavigate({ item, url: item.url, event: event.nativeEvent });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
16
24
|
return (_jsx("div", { className: cn('kpt-breadcrumb-host', className), ...rest, children: _jsx("nav", { className: "kpt-breadcrumb", "aria-label": t('breadcrumb.label'), children: _jsx("ol", { className: "kpt-breadcrumb__list", children: items.map((item, index) => {
|
|
17
25
|
const last = index === items.length - 1;
|
|
18
|
-
return (_jsxs("li", { className: "kpt-breadcrumb__item", children: [item.url && !last ? (_jsx("a", { className: "kpt-breadcrumb__link", href: item.url, children: item.label })) : (_jsx("span", { className: "kpt-breadcrumb__current", "aria-current": last ? 'page' : undefined, children: item.label })), last ? null : _jsx(KptIcon, { name: "chevron-right", className: "kpt-breadcrumb__sep" })] }, index));
|
|
26
|
+
return (_jsxs("li", { className: "kpt-breadcrumb__item", children: [item.url && !last ? (_jsx("a", { className: "kpt-breadcrumb__link", href: item.url, onClick: (event) => linkClick(event, item), children: item.label })) : (_jsx("span", { className: "kpt-breadcrumb__current", "aria-current": last ? 'page' : undefined, children: item.label })), last ? null : _jsx(KptIcon, { name: "chevron-right", className: "kpt-breadcrumb__sep" })] }, item.url ?? `${index}-${item.label}`));
|
|
19
27
|
}) }) }) }));
|
|
20
28
|
}
|
package/dist/breadcrumb/llms.txt
CHANGED
|
@@ -40,3 +40,11 @@ The nav label comes from `breadcrumb.label` in the dictionary.
|
|
|
40
40
|
A `<nav>` landmark labelled from `breadcrumb.label`, holding an ordered list (`<ol>`/`<li>`), so a
|
|
41
41
|
screen reader announces how many levels the trail has. The current page carries
|
|
42
42
|
`aria-current="page"`; separators are icons hidden from assistive technology.
|
|
43
|
+
|
|
44
|
+
## Router
|
|
45
|
+
Links are plain `<a href>`, so without a router a click reloads the page. `onNavigate` fires on a plain
|
|
46
|
+
click (left button, no Ctrl/⌘/Shift, no `target="_blank"`) with `{ item, url, event }`: call
|
|
47
|
+
`event.preventDefault()` and navigate with your router. Modified clicks stay native (a new tab, a new
|
|
48
|
+
window). The library depends on no router.
|
|
49
|
+
|
|
50
|
+
<KptBreadcrumb items={items} onNavigate={({ event, url }) => { event.preventDefault(); navigate(url); }} />
|
package/dist/button/button.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { isValidElement } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -50,7 +51,7 @@ function renderInner(args) {
|
|
|
50
51
|
// kliknąć i wejść na niego z klawiatury.
|
|
51
52
|
href: isDisabled ? undefined : href, target: target,
|
|
52
53
|
// `target="_blank"` bez `rel` to wektor na `window.opener` — dokładamy domyślne.
|
|
53
|
-
rel: rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined), "aria-disabled": isDisabled || undefined, tabIndex: isDisabled ? -1 : undefined, ...buttonProps, children: content }));
|
|
54
|
+
rel: rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined), "aria-disabled": isDisabled || undefined, tabIndex: isDisabled ? -1 : undefined, ...(buttonProps), children: content }));
|
|
54
55
|
}
|
|
55
56
|
return (_jsx("button", { type: type, disabled: isDisabled, ...buttonProps, children: content }));
|
|
56
57
|
}
|