@konce-pt/react 0.8.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/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/accordion/accordion.d.ts +45 -0
- package/dist/accordion/accordion.js +69 -0
- package/dist/alert/alert.d.ts +25 -0
- package/dist/alert/alert.js +31 -0
- package/dist/app-shell/app-shell.d.ts +94 -0
- package/dist/app-shell/app-shell.js +105 -0
- package/dist/auth/auth-types.d.ts +44 -0
- package/dist/auth/auth-types.js +2 -0
- package/dist/auth/auth.d.ts +76 -0
- package/dist/auth/auth.js +184 -0
- package/dist/autocomplete/autocomplete.d.ts +18 -0
- package/dist/autocomplete/autocomplete.js +72 -0
- package/dist/avatar/avatar.d.ts +33 -0
- package/dist/avatar/avatar.js +30 -0
- package/dist/avatar-group/avatar-group.d.ts +21 -0
- package/dist/avatar-group/avatar-group.js +15 -0
- package/dist/badge/badge.d.ts +25 -0
- package/dist/badge/badge.js +14 -0
- package/dist/bottom-sheet/bottom-sheet.d.ts +21 -0
- package/dist/bottom-sheet/bottom-sheet.js +22 -0
- package/dist/breadcrumb/breadcrumb.d.ts +19 -0
- package/dist/breadcrumb/breadcrumb.js +20 -0
- package/dist/button/button.d.ts +27 -0
- package/dist/button/button.js +49 -0
- package/dist/button-group/button-group.d.ts +25 -0
- package/dist/button-group/button-group.js +30 -0
- package/dist/card/card.d.ts +66 -0
- package/dist/card/card.js +96 -0
- package/dist/carousel/carousel.d.ts +20 -0
- package/dist/carousel/carousel.js +24 -0
- package/dist/charts/chart-legend.d.ts +23 -0
- package/dist/charts/chart-legend.js +9 -0
- package/dist/charts/chart-tooltip.d.ts +21 -0
- package/dist/charts/chart-tooltip.js +13 -0
- package/dist/charts/chart.d.ts +18 -0
- package/dist/charts/chart.js +231 -0
- package/dist/charts-entry/public-api.d.ts +4 -0
- package/dist/charts-entry/public-api.js +7 -0
- package/dist/checkbox/checkbox.d.ts +33 -0
- package/dist/checkbox/checkbox.js +29 -0
- package/dist/chip/chip.d.ts +22 -0
- package/dist/chip/chip.js +16 -0
- package/dist/chips-input/chips-input.d.ts +25 -0
- package/dist/chips-input/chips-input.js +54 -0
- package/dist/clock/clock.d.ts +17 -0
- package/dist/clock/clock.js +57 -0
- package/dist/color-picker/color-picker.d.ts +23 -0
- package/dist/color-picker/color-picker.js +28 -0
- package/dist/confirm/confirm-container.d.ts +13 -0
- package/dist/confirm/confirm-container.js +26 -0
- package/dist/confirm/confirm-store.d.ts +29 -0
- package/dist/confirm/confirm-store.js +52 -0
- package/dist/context-menu/context-menu.d.ts +21 -0
- package/dist/context-menu/context-menu.js +36 -0
- package/dist/data-table/data-table-types.d.ts +67 -0
- package/dist/data-table/data-table-types.js +1 -0
- package/dist/data-table/data-table.d.ts +62 -0
- package/dist/data-table/data-table.js +453 -0
- package/dist/data-table/export-csv.d.ts +7 -0
- package/dist/data-table/export-csv.js +19 -0
- package/dist/data-view/data-view.d.ts +21 -0
- package/dist/data-view/data-view.js +25 -0
- package/dist/date-range/date-range.d.ts +30 -0
- package/dist/date-range/date-range.js +155 -0
- package/dist/datepicker/datepicker.d.ts +39 -0
- package/dist/datepicker/datepicker.js +202 -0
- package/dist/dialog/dialog.d.ts +35 -0
- package/dist/dialog/dialog.js +59 -0
- package/dist/divider/divider.d.ts +16 -0
- package/dist/divider/divider.js +13 -0
- package/dist/drawer/drawer.d.ts +25 -0
- package/dist/drawer/drawer.js +25 -0
- package/dist/empty/empty.d.ts +18 -0
- package/dist/empty/empty.js +14 -0
- package/dist/fab/fab.d.ts +22 -0
- package/dist/fab/fab.js +17 -0
- package/dist/fieldset/fieldset.d.ts +22 -0
- package/dist/fieldset/fieldset.js +24 -0
- package/dist/file-upload/file-upload.d.ts +22 -0
- package/dist/file-upload/file-upload.js +50 -0
- package/dist/form-field/form-field-context.d.ts +23 -0
- package/dist/form-field/form-field-context.js +17 -0
- package/dist/form-field/form-field.d.ts +39 -0
- package/dist/form-field/form-field.js +41 -0
- package/dist/galleria/galleria.d.ts +22 -0
- package/dist/galleria/galleria.js +21 -0
- package/dist/grid/col.d.ts +39 -0
- package/dist/grid/col.js +32 -0
- package/dist/grid/flex.d.ts +45 -0
- package/dist/grid/flex.js +35 -0
- package/dist/grid/grid.d.ts +47 -0
- package/dist/grid/grid.js +38 -0
- package/dist/grid/layout.d.ts +18 -0
- package/dist/grid/layout.js +32 -0
- package/dist/grid-entry/public-api.d.ts +6 -0
- package/dist/grid-entry/public-api.js +8 -0
- package/dist/i18n/i18n-context.d.ts +45 -0
- package/dist/i18n/i18n-context.js +65 -0
- package/dist/icon/icon-registry.d.ts +16 -0
- package/dist/icon/icon-registry.js +43 -0
- package/dist/icon/icon.d.ts +17 -0
- package/dist/icon/icon.js +23 -0
- package/dist/icon-button/icon-button.d.ts +23 -0
- package/dist/icon-button/icon-button.js +14 -0
- package/dist/icons-entry/public-api.d.ts +12 -0
- package/dist/icons-entry/public-api.js +20 -0
- package/dist/image/image.d.ts +17 -0
- package/dist/image/image.js +16 -0
- package/dist/input/input.d.ts +41 -0
- package/dist/input/input.js +36 -0
- package/dist/input-mask/input-mask.d.ts +19 -0
- package/dist/input-mask/input-mask.js +65 -0
- package/dist/input-number/input-number.d.ts +28 -0
- package/dist/input-number/input-number.js +52 -0
- package/dist/input-otp/input-otp.d.ts +21 -0
- package/dist/input-otp/input-otp.js +59 -0
- package/dist/internal/cn.d.ts +9 -0
- package/dist/internal/cn.js +19 -0
- package/dist/internal/compose-refs.d.ts +6 -0
- package/dist/internal/compose-refs.js +16 -0
- package/dist/internal/overlay-container.d.ts +3 -0
- package/dist/internal/overlay-container.js +19 -0
- package/dist/internal/overlay.d.ts +26 -0
- package/dist/internal/overlay.js +64 -0
- package/dist/internal/slot.d.ts +23 -0
- package/dist/internal/slot.js +49 -0
- package/dist/internal/types.d.ts +25 -0
- package/dist/internal/types.js +1 -0
- package/dist/internal/use-breakpoint.d.ts +18 -0
- package/dist/internal/use-breakpoint.js +52 -0
- package/dist/internal/use-controllable-state.d.ts +11 -0
- package/dist/internal/use-controllable-state.js +18 -0
- package/dist/internal/use-id.d.ts +9 -0
- package/dist/internal/use-id.js +13 -0
- package/dist/knob/knob.d.ts +27 -0
- package/dist/knob/knob.js +86 -0
- package/dist/listbox/listbox.d.ts +28 -0
- package/dist/listbox/listbox.js +43 -0
- package/dist/map/map-create-dialog.d.ts +21 -0
- package/dist/map/map-create-dialog.js +196 -0
- package/dist/map/map-hours-field.d.ts +22 -0
- package/dist/map/map-hours-field.js +90 -0
- package/dist/map/map-image-field.d.ts +24 -0
- package/dist/map/map-image-field.js +61 -0
- package/dist/map/map-legend.d.ts +29 -0
- package/dist/map/map-legend.js +41 -0
- package/dist/map/map-poi-card.d.ts +42 -0
- package/dist/map/map-poi-card.js +152 -0
- package/dist/map/map-search.d.ts +37 -0
- package/dist/map/map-search.js +201 -0
- package/dist/map/map.d.ts +113 -0
- package/dist/map/map.js +425 -0
- package/dist/map/ports.d.ts +76 -0
- package/dist/map/ports.js +174 -0
- package/dist/map-entry/public-api.d.ts +20 -0
- package/dist/map-entry/public-api.js +19 -0
- package/dist/megamenu/megamenu.d.ts +17 -0
- package/dist/megamenu/megamenu.js +28 -0
- package/dist/menu/menu-item.d.ts +33 -0
- package/dist/menu/menu-item.js +1 -0
- package/dist/menu/menu-rows.d.ts +9 -0
- package/dist/menu/menu-rows.js +15 -0
- package/dist/menu/menu.d.ts +22 -0
- package/dist/menu/menu.js +31 -0
- package/dist/menubar/menubar.d.ts +16 -0
- package/dist/menubar/menubar.js +39 -0
- package/dist/meter-group/meter-group.d.ts +20 -0
- package/dist/meter-group/meter-group.js +24 -0
- package/dist/order-list/order-list.d.ts +18 -0
- package/dist/order-list/order-list.js +37 -0
- package/dist/paginator/paginator.d.ts +29 -0
- package/dist/paginator/paginator.js +45 -0
- package/dist/panel/panel.d.ts +31 -0
- package/dist/panel/panel.js +36 -0
- package/dist/password/password.d.ts +28 -0
- package/dist/password/password.js +37 -0
- package/dist/pick-list/pick-list.d.ts +28 -0
- package/dist/pick-list/pick-list.js +65 -0
- package/dist/popover/popover.d.ts +23 -0
- package/dist/popover/popover.js +27 -0
- package/dist/progress/progress.d.ts +19 -0
- package/dist/progress/progress.js +21 -0
- package/dist/public-api.d.ts +181 -0
- package/dist/public-api.js +105 -0
- package/dist/radio-group/radio-group.d.ts +24 -0
- package/dist/radio-group/radio-group.js +23 -0
- package/dist/rating/rating.d.ts +22 -0
- package/dist/rating/rating.js +30 -0
- package/dist/rich-text/lists.d.ts +17 -0
- package/dist/rich-text/lists.js +144 -0
- package/dist/rich-text/rich-text.d.ts +28 -0
- package/dist/rich-text/rich-text.js +654 -0
- package/dist/rich-text/sanitize.d.ts +10 -0
- package/dist/rich-text/sanitize.js +85 -0
- package/dist/rich-text/tables.d.ts +44 -0
- package/dist/rich-text/tables.js +268 -0
- package/dist/roadmap/roadmap.d.ts +37 -0
- package/dist/roadmap/roadmap.js +271 -0
- package/dist/roadmap-entry/public-api.d.ts +4 -0
- package/dist/roadmap-entry/public-api.js +7 -0
- package/dist/scroll-top/scroll-top.d.ts +16 -0
- package/dist/scroll-top/scroll-top.js +38 -0
- package/dist/select/select.d.ts +37 -0
- package/dist/select/select.js +117 -0
- package/dist/shared/types.d.ts +8 -0
- package/dist/shared/types.js +1 -0
- package/dist/sidenav/sidenav.d.ts +26 -0
- package/dist/sidenav/sidenav.js +41 -0
- package/dist/skeleton/skeleton.d.ts +20 -0
- package/dist/skeleton/skeleton.js +15 -0
- package/dist/slider/slider.d.ts +28 -0
- package/dist/slider/slider.js +30 -0
- package/dist/speed-dial/speed-dial.d.ts +20 -0
- package/dist/speed-dial/speed-dial.js +23 -0
- package/dist/spinner/spinner.d.ts +15 -0
- package/dist/spinner/spinner.js +14 -0
- package/dist/split-button/split-button.d.ts +28 -0
- package/dist/split-button/split-button.js +34 -0
- package/dist/splitter/splitter.d.ts +39 -0
- package/dist/splitter/splitter.js +80 -0
- package/dist/stepper/stepper.d.ts +51 -0
- package/dist/stepper/stepper.js +71 -0
- package/dist/switch/switch.d.ts +39 -0
- package/dist/switch/switch.js +30 -0
- package/dist/switch-group/switch-group.d.ts +29 -0
- package/dist/switch-group/switch-group.js +47 -0
- package/dist/tabs/tabs.d.ts +38 -0
- package/dist/tabs/tabs.js +74 -0
- package/dist/textarea/textarea.d.ts +28 -0
- package/dist/textarea/textarea.js +44 -0
- package/dist/timeline/timeline.d.ts +19 -0
- package/dist/timeline/timeline.js +12 -0
- package/dist/toast/toast-container.d.ts +12 -0
- package/dist/toast/toast-container.js +23 -0
- package/dist/toast/toast-store.d.ts +26 -0
- package/dist/toast/toast-store.js +51 -0
- package/dist/toolbar/toolbar.d.ts +29 -0
- package/dist/toolbar/toolbar.js +24 -0
- package/dist/tooltip/tooltip.d.ts +26 -0
- package/dist/tooltip/tooltip.js +113 -0
- package/dist/tree/tree.d.ts +31 -0
- package/dist/tree/tree.js +62 -0
- package/package.json +91 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
|
+
/**
|
|
5
|
+
* Pole kodu jednorazowego (OTP/PIN) — N osobnych pól, automatyczne przejście do następnego
|
|
6
|
+
* i wklejanie całego kodu naraz. Wartością jest sklejony string.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <KptInputOtp value={code} onValueChange={setCode} length={6} integerOnly />
|
|
10
|
+
*/
|
|
11
|
+
export function KptInputOtp({ value, defaultValue = '', onValueChange, length = 6, integerOnly = false, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
12
|
+
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
13
|
+
const showInvalid = invalid && (touched ?? true);
|
|
14
|
+
const charAt = (index) => current[index] ?? '';
|
|
15
|
+
/*
|
|
16
|
+
* Wartość jest jednym stringiem, a pól jest N — pozycje pośrodku muszą więc dać się zostawić
|
|
17
|
+
* puste. Dopełniamy spacjami, podmieniamy znak i obcinamy końcowe spacje, żeby „a__c" dało się
|
|
18
|
+
* zapisać, a „a" nie zamieniło się w „a ".
|
|
19
|
+
*/
|
|
20
|
+
const setChar = (index, char) => {
|
|
21
|
+
const chars = current.padEnd(length, ' ').split('');
|
|
22
|
+
chars[index] = char || ' ';
|
|
23
|
+
setCurrent(chars.join('').replace(/ +$/, ''));
|
|
24
|
+
};
|
|
25
|
+
const focusSibling = (el, direction) => {
|
|
26
|
+
const target = direction === 1 ? el.nextElementSibling : el.previousElementSibling;
|
|
27
|
+
target?.focus();
|
|
28
|
+
};
|
|
29
|
+
const onInput = (index, event) => {
|
|
30
|
+
const el = event.target;
|
|
31
|
+
let char = el.value.slice(-1);
|
|
32
|
+
if (integerOnly && !/\d/.test(char))
|
|
33
|
+
char = '';
|
|
34
|
+
setChar(index, char);
|
|
35
|
+
// DOM trzeba zsynchronizować ręcznie: odrzucony znak nie zmienia wartości, więc React
|
|
36
|
+
// nie przerysuje pola, a wpisany znak zostałby na ekranie.
|
|
37
|
+
el.value = char;
|
|
38
|
+
if (char)
|
|
39
|
+
focusSibling(el, 1);
|
|
40
|
+
};
|
|
41
|
+
const onKeyDown = (index, event) => {
|
|
42
|
+
const el = event.currentTarget;
|
|
43
|
+
if (event.key === 'Backspace' && !charAt(index))
|
|
44
|
+
focusSibling(el, -1);
|
|
45
|
+
else if (event.key === 'ArrowLeft')
|
|
46
|
+
focusSibling(el, -1);
|
|
47
|
+
else if (event.key === 'ArrowRight')
|
|
48
|
+
focusSibling(el, 1);
|
|
49
|
+
};
|
|
50
|
+
const onPaste = (event) => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
let text = event.clipboardData.getData('text');
|
|
53
|
+
if (integerOnly)
|
|
54
|
+
text = text.replace(/\D/g, '');
|
|
55
|
+
setCurrent(text.slice(0, length));
|
|
56
|
+
onTouch?.();
|
|
57
|
+
};
|
|
58
|
+
return (_jsx("div", { className: cn('kpt-input-otp-host', className), ...rest, children: _jsx("div", { className: cn('kpt-input-otp', showInvalid && 'kpt-input-otp--invalid'), children: Array.from({ length }, (_, index) => (_jsx("input", { className: "kpt-input-otp__cell", type: "text", inputMode: integerOnly ? 'numeric' : 'text', maxLength: 1, value: charAt(index), disabled: disabled, onChange: (event) => onInput(index, event), onKeyDown: (event) => onKeyDown(index, event), onPaste: onPaste, onFocus: (event) => event.target.select() }, index))) }) }));
|
|
59
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sklejanie klas CSS. Odpowiednik `clsx` w kilkunastu linijkach — biblioteka
|
|
3
|
+
* zostaje bez zależności runtime, a konsument nie ryzykuje konfliktu wersji.
|
|
4
|
+
*
|
|
5
|
+
* Wartości fałszywe są pomijane, tablice spłaszczane, duplikaty usuwane
|
|
6
|
+
* (ostatnie wystąpienie wygrywa kolejnością, co ma znaczenie tylko wizualnie).
|
|
7
|
+
*/
|
|
8
|
+
export type ClassValue = string | number | false | null | undefined | ClassValue[];
|
|
9
|
+
export declare function cn(...values: ClassValue[]): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function cn(...values) {
|
|
2
|
+
const out = [];
|
|
3
|
+
const push = (value) => {
|
|
4
|
+
if (!value && value !== 0)
|
|
5
|
+
return;
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
for (const item of value)
|
|
8
|
+
push(item);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
for (const token of String(value).split(/\s+/)) {
|
|
12
|
+
if (token && !out.includes(token))
|
|
13
|
+
out.push(token);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
for (const value of values)
|
|
17
|
+
push(value);
|
|
18
|
+
return out.join(' ');
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Ref } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Łączy kilka refów w jeden callback. Potrzebne wszędzie, gdzie komponent trzyma
|
|
4
|
+
* własny ref do elementu, a jednocześnie oddaje go na zewnątrz przez prop `ref`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function composeRefs<T>(...refs: (Ref<T> | undefined)[]): (node: T | null) => void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Łączy kilka refów w jeden callback. Potrzebne wszędzie, gdzie komponent trzyma
|
|
3
|
+
* własny ref do elementu, a jednocześnie oddaje go na zewnątrz przez prop `ref`.
|
|
4
|
+
*/
|
|
5
|
+
export function composeRefs(...refs) {
|
|
6
|
+
return (node) => {
|
|
7
|
+
for (const ref of refs) {
|
|
8
|
+
if (typeof ref === 'function') {
|
|
9
|
+
ref(node);
|
|
10
|
+
}
|
|
11
|
+
else if (ref && typeof ref === 'object') {
|
|
12
|
+
ref.current = node;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
/*
|
|
3
|
+
* Miejsce, do którego portalują się panele nakładek.
|
|
4
|
+
*
|
|
5
|
+
* `showModal()` wynosi `<dialog>` do warstwy szczytowej przeglądarki, a ta stoi ponad całym
|
|
6
|
+
* drzewem dokumentu — żaden `z-index` tego nie przebije. Panel selecta czy menu otwarty
|
|
7
|
+
* z wnętrza modala portalowałby się na `document.body` i byłby NIEWIDOCZNY, schowany za tłem
|
|
8
|
+
* okna. `KptDialog` podstawia więc siebie jako kontener na czas, gdy jest otwarty.
|
|
9
|
+
*
|
|
10
|
+
* Port Angulara rozwiązuje to samo, przenosząc kontener CDK Overlay do wnętrza `<dialog>`.
|
|
11
|
+
*/
|
|
12
|
+
export const KptOverlayContainerContext = createContext(null);
|
|
13
|
+
/** Kontener dla portalu nakładki; poza modalem to `document.body`. */
|
|
14
|
+
export function useKptOverlayContainer() {
|
|
15
|
+
const container = useContext(KptOverlayContainerContext);
|
|
16
|
+
if (container)
|
|
17
|
+
return container;
|
|
18
|
+
return typeof document === 'undefined' ? null : document.body;
|
|
19
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface KptOverlayProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
/** Element, względem którego pozycjonujemy panel. */
|
|
5
|
+
anchor: HTMLElement | null;
|
|
6
|
+
/** Kliknięcie w backdrop albo Escape. */
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
/** Panel dostaje szerokość wyzwalacza (tak działa lista selecta). */
|
|
9
|
+
matchAnchorWidth?: boolean;
|
|
10
|
+
/** Odstęp od wyzwalacza w pikselach. */
|
|
11
|
+
offset?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Krawędź, do której wyrównujemy panel: `start` (lewa, domyślnie) albo `end` (prawa).
|
|
14
|
+
* `end` przydaje się, gdy wyzwalacz siedzi przy prawej krawędzi kontrolki — panel otwiera się
|
|
15
|
+
* wtedy w lewo i nie wychodzi poza nią.
|
|
16
|
+
*/
|
|
17
|
+
align?: 'start' | 'end';
|
|
18
|
+
/**
|
|
19
|
+
* Przezroczysty backdrop przechwytujący kliknięcie poza panelem. Wyłącz go tam, gdzie panel
|
|
20
|
+
* ma zostawić wyzwalacz klikalnym — autocomplete zamyka się na `blur` pola, a backdrop
|
|
21
|
+
* zasłoniłby to pole i zabrałby mu każde kliknięcie.
|
|
22
|
+
*/
|
|
23
|
+
withBackdrop?: boolean;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
export declare function KptOverlay({ open, anchor, onClose, matchAnchorWidth, offset, align, withBackdrop, children, }: Readonly<KptOverlayProps>): import("react").ReactPortal | null;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { useKptOverlayContainer } from "./overlay-container.js";
|
|
5
|
+
/** Ta sama warstwa co `--kpt-z-overlay` w tokenach — panel ma być nad treścią aplikacji. */
|
|
6
|
+
const Z_INDEX = 'var(--kpt-z-overlay, 1100)';
|
|
7
|
+
export function KptOverlay({ open, anchor, onClose, matchAnchorWidth = false, offset = 4, align = 'start', withBackdrop = true, children, }) {
|
|
8
|
+
const container = useKptOverlayContainer();
|
|
9
|
+
const panelRef = useRef(null);
|
|
10
|
+
const [position, setPosition] = useState(null);
|
|
11
|
+
const update = useCallback(() => {
|
|
12
|
+
if (!anchor)
|
|
13
|
+
return;
|
|
14
|
+
const rect = anchor.getBoundingClientRect();
|
|
15
|
+
const panelHeight = panelRef.current?.offsetHeight ?? 0;
|
|
16
|
+
const panelWidth = panelRef.current?.offsetWidth ?? 0;
|
|
17
|
+
const below = window.innerHeight - rect.bottom;
|
|
18
|
+
// Odbicie w górę tylko wtedy, gdy pod spodem naprawdę brakuje miejsca, a nad wyzwalaczem
|
|
19
|
+
// jest go więcej — inaczej panel skakałby przy każdym przewinięciu o kilka pikseli.
|
|
20
|
+
const flip = panelHeight > 0 && below < panelHeight + offset && rect.top > below;
|
|
21
|
+
setPosition({
|
|
22
|
+
top: flip ? rect.top - panelHeight - offset : rect.bottom + offset,
|
|
23
|
+
left: align === 'end' ? rect.right - panelWidth : rect.left,
|
|
24
|
+
...(matchAnchorWidth ? { width: rect.width } : {}),
|
|
25
|
+
});
|
|
26
|
+
}, [anchor, matchAnchorWidth, offset, align]);
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
if (!open) {
|
|
29
|
+
setPosition(null);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
update();
|
|
33
|
+
}, [open, update]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!open)
|
|
36
|
+
return;
|
|
37
|
+
const onScrollOrResize = () => update();
|
|
38
|
+
// `capture` łapie też przewijanie kontenerów w środku strony, nie tylko okna.
|
|
39
|
+
window.addEventListener('scroll', onScrollOrResize, true);
|
|
40
|
+
window.addEventListener('resize', onScrollOrResize);
|
|
41
|
+
const onKeyDown = (event) => {
|
|
42
|
+
if (event.key === 'Escape')
|
|
43
|
+
onClose();
|
|
44
|
+
};
|
|
45
|
+
document.addEventListener('keydown', onKeyDown);
|
|
46
|
+
return () => {
|
|
47
|
+
window.removeEventListener('scroll', onScrollOrResize, true);
|
|
48
|
+
window.removeEventListener('resize', onScrollOrResize);
|
|
49
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
50
|
+
};
|
|
51
|
+
}, [open, update, onClose]);
|
|
52
|
+
if (!open || !container)
|
|
53
|
+
return null;
|
|
54
|
+
return createPortal(_jsxs(_Fragment, { children: [withBackdrop ? (_jsx("div", { style: { position: 'fixed', inset: 0, zIndex: Z_INDEX }, onMouseDown: onClose })) : null, _jsx("div", { ref: panelRef, style: {
|
|
55
|
+
position: 'fixed',
|
|
56
|
+
top: position?.top ?? 0,
|
|
57
|
+
left: position?.left ?? 0,
|
|
58
|
+
width: position?.width,
|
|
59
|
+
zIndex: Z_INDEX,
|
|
60
|
+
// Panel bez pozycji jeszcze nie został zmierzony — chowamy go na pierwszej klatce,
|
|
61
|
+
// żeby nie mrugnął w lewym górnym rogu.
|
|
62
|
+
visibility: position ? 'visible' : 'hidden',
|
|
63
|
+
}, children: children })] }), container);
|
|
64
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
type AnyProps = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Scala propsy slotu z propsami dziecka. Zasady, w kolejności ważności:
|
|
5
|
+
* handlery są komponowane (najpierw dziecka, potem slotu), `className` sklejane,
|
|
6
|
+
* `style` nakładane (dziecko wygrywa), `ref` łączony, reszta — dziecko nadpisuje slot.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mergeSlotProps(slotProps: AnyProps, childProps: AnyProps): AnyProps;
|
|
9
|
+
/**
|
|
10
|
+
* Renderuje `child` z propsami slotu i podmienioną treścią. Używane przez komponenty,
|
|
11
|
+
* które w trybie `asChild` muszą zachować własne opakowanie treści — na przykład
|
|
12
|
+
* przycisk owija zawartość w `.kpt-button__content` niezależnie od tego, jakim
|
|
13
|
+
* elementem ostatecznie jest.
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderAsChild(child: ReactElement, slotProps: AnyProps, content: ReactNode): ReactElement;
|
|
16
|
+
export interface SlotProps {
|
|
17
|
+
/** Dokładnie jeden element — to on przejmie propsy slotu. */
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
[prop: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** Renderuje swoje dziecko z doklejonymi propsami. Bez dziecka-elementu zwraca `null`. */
|
|
22
|
+
export declare function Slot({ children, ...slotProps }: Readonly<SlotProps>): ReactElement | null;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { cloneElement, isValidElement } from 'react';
|
|
2
|
+
import { cn } from "./cn.js";
|
|
3
|
+
import { composeRefs } from "./compose-refs.js";
|
|
4
|
+
/**
|
|
5
|
+
* Scala propsy slotu z propsami dziecka. Zasady, w kolejności ważności:
|
|
6
|
+
* handlery są komponowane (najpierw dziecka, potem slotu), `className` sklejane,
|
|
7
|
+
* `style` nakładane (dziecko wygrywa), `ref` łączony, reszta — dziecko nadpisuje slot.
|
|
8
|
+
*/
|
|
9
|
+
export function mergeSlotProps(slotProps, childProps) {
|
|
10
|
+
const merged = { ...slotProps };
|
|
11
|
+
for (const [key, childValue] of Object.entries(childProps)) {
|
|
12
|
+
const slotValue = slotProps[key];
|
|
13
|
+
if (/^on[A-Z]/.test(key) && typeof childValue === 'function' && typeof slotValue === 'function') {
|
|
14
|
+
merged[key] = (...args) => {
|
|
15
|
+
childValue(...args);
|
|
16
|
+
slotValue(...args);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
else if (key === 'className') {
|
|
20
|
+
merged[key] = cn(slotValue, childValue);
|
|
21
|
+
}
|
|
22
|
+
else if (key === 'style') {
|
|
23
|
+
merged[key] = { ...slotValue, ...childValue };
|
|
24
|
+
}
|
|
25
|
+
else if (key === 'ref') {
|
|
26
|
+
merged[key] = composeRefs(slotValue, childValue);
|
|
27
|
+
}
|
|
28
|
+
else if (childValue !== undefined) {
|
|
29
|
+
merged[key] = childValue;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return merged;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Renderuje `child` z propsami slotu i podmienioną treścią. Używane przez komponenty,
|
|
36
|
+
* które w trybie `asChild` muszą zachować własne opakowanie treści — na przykład
|
|
37
|
+
* przycisk owija zawartość w `.kpt-button__content` niezależnie od tego, jakim
|
|
38
|
+
* elementem ostatecznie jest.
|
|
39
|
+
*/
|
|
40
|
+
export function renderAsChild(child, slotProps, content) {
|
|
41
|
+
return cloneElement(child, mergeSlotProps(slotProps, child.props), content);
|
|
42
|
+
}
|
|
43
|
+
/** Renderuje swoje dziecko z doklejonymi propsami. Bez dziecka-elementu zwraca `null`. */
|
|
44
|
+
export function Slot({ children, ...slotProps }) {
|
|
45
|
+
if (!isValidElement(children))
|
|
46
|
+
return null;
|
|
47
|
+
const child = children;
|
|
48
|
+
return cloneElement(child, mergeSlotProps(slotProps, child.props));
|
|
49
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Propsy wrappera hosta. Port odtwarza dwupoziomowy DOM Angulara
|
|
4
|
+
* (`<kpt-button class="kpt-button-host">` wokół `<button class="kpt-button">`),
|
|
5
|
+
* bo host niesie własne style. `className` i `ref` komponentu trafiają na element
|
|
6
|
+
* wewnętrzny — wrapper konfiguruje się właśnie tym propem.
|
|
7
|
+
*/
|
|
8
|
+
export type KptHostProps<E extends ElementType = 'span'> = Omit<ComponentPropsWithoutRef<E>, 'children'> & {
|
|
9
|
+
/**
|
|
10
|
+
* Atrybuty `data-*` hosta. JSX przepuszcza je na elementach wbudowanych sam z siebie,
|
|
11
|
+
* ale ten typ opisuje zwykły obiekt — bez tego wpisu stan wariantu, który w Angularze
|
|
12
|
+
* siedzi na hoście (`[attr.data-on]`), nie miałby jak tam trafić.
|
|
13
|
+
*/
|
|
14
|
+
[key: `data-${string}`]: string | number | boolean | undefined;
|
|
15
|
+
};
|
|
16
|
+
/** Wspólny fragment propsów dla komponentów wspierających wzorzec `asChild`. */
|
|
17
|
+
export interface AsChildProps {
|
|
18
|
+
/**
|
|
19
|
+
* Zamiast renderować własny element, oddaje propsy jedynemu dziecku.
|
|
20
|
+
* Pozwala podstawić `<Link>`, `<a>` albo dowolny inny komponent
|
|
21
|
+
* bez utraty klas i atrybutów `data-*`.
|
|
22
|
+
*/
|
|
23
|
+
asChild?: boolean;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Nazwa breakpointa z tokenów `--kpt-breakpoint-*` (mobile-first). */
|
|
2
|
+
export type KptBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
3
|
+
/**
|
|
4
|
+
* Progi breakpointów w pikselach — czytane wprost z `@konce-pt/tokens`, więc nie da się
|
|
5
|
+
* ich rozjechać z `@media` biblioteki ani z portem Angulara (tam ta sama tabela powstaje
|
|
6
|
+
* w kroku generowania).
|
|
7
|
+
*/
|
|
8
|
+
export declare const KPT_BREAKPOINTS: Record<KptBreakpoint, number>;
|
|
9
|
+
/** Dowolne media query jako wartość reaktywna. Poza przeglądarką zawsze `false`. */
|
|
10
|
+
export declare function useKptMediaQuery(query: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Czy viewport ma szerokość co najmniej danego breakpointa (mobile-first).
|
|
13
|
+
*
|
|
14
|
+
* O UKŁADZIE komponentów decyduje wyłącznie CSS — ten hook odpowiada tylko za zachowania,
|
|
15
|
+
* których CSS nie wyrazi (np. w którą stronę ma zadziałać przycisk). Dzięki temu nic nie
|
|
16
|
+
* migocze przed hydracją.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useKptBreakpointUp(breakpoint: KptBreakpoint): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { KPT_BREAKPOINT_2XL, KPT_BREAKPOINT_LG, KPT_BREAKPOINT_MD, KPT_BREAKPOINT_SM, KPT_BREAKPOINT_XL, } from '@konce-pt/tokens';
|
|
3
|
+
const toPx = (value) => Number.parseFloat(value);
|
|
4
|
+
/**
|
|
5
|
+
* Progi breakpointów w pikselach — czytane wprost z `@konce-pt/tokens`, więc nie da się
|
|
6
|
+
* ich rozjechać z `@media` biblioteki ani z portem Angulara (tam ta sama tabela powstaje
|
|
7
|
+
* w kroku generowania).
|
|
8
|
+
*/
|
|
9
|
+
export const KPT_BREAKPOINTS = {
|
|
10
|
+
sm: toPx(KPT_BREAKPOINT_SM),
|
|
11
|
+
md: toPx(KPT_BREAKPOINT_MD),
|
|
12
|
+
lg: toPx(KPT_BREAKPOINT_LG),
|
|
13
|
+
xl: toPx(KPT_BREAKPOINT_XL),
|
|
14
|
+
'2xl': toPx(KPT_BREAKPOINT_2XL),
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Jeden `MediaQueryList` i jeden nasłuch na zapytanie, współdzielone przez wszystkie
|
|
18
|
+
* komponenty — tak samo jak memoizacja w `KptBreakpointObserver` po stronie Angulara.
|
|
19
|
+
*/
|
|
20
|
+
const stores = new Map();
|
|
21
|
+
function storeFor(query) {
|
|
22
|
+
const cached = stores.get(query);
|
|
23
|
+
if (cached)
|
|
24
|
+
return cached;
|
|
25
|
+
const list = typeof window === 'undefined' ? null : window.matchMedia(query);
|
|
26
|
+
const store = list
|
|
27
|
+
? {
|
|
28
|
+
subscribe: (onChange) => {
|
|
29
|
+
list.addEventListener('change', onChange);
|
|
30
|
+
return () => list.removeEventListener('change', onChange);
|
|
31
|
+
},
|
|
32
|
+
getSnapshot: () => list.matches,
|
|
33
|
+
}
|
|
34
|
+
: { subscribe: () => () => { }, getSnapshot: () => false };
|
|
35
|
+
stores.set(query, store);
|
|
36
|
+
return store;
|
|
37
|
+
}
|
|
38
|
+
/** Dowolne media query jako wartość reaktywna. Poza przeglądarką zawsze `false`. */
|
|
39
|
+
export function useKptMediaQuery(query) {
|
|
40
|
+
const store = storeFor(query);
|
|
41
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, () => false);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Czy viewport ma szerokość co najmniej danego breakpointa (mobile-first).
|
|
45
|
+
*
|
|
46
|
+
* O UKŁADZIE komponentów decyduje wyłącznie CSS — ten hook odpowiada tylko za zachowania,
|
|
47
|
+
* których CSS nie wyrazi (np. w którą stronę ma zadziałać przycisk). Dzięki temu nic nie
|
|
48
|
+
* migocze przed hydracją.
|
|
49
|
+
*/
|
|
50
|
+
export function useKptBreakpointUp(breakpoint) {
|
|
51
|
+
return useKptMediaQuery(`(min-width: ${KPT_BREAKPOINTS[breakpoint]}px)`);
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stan działający w obu trybach naraz: kontrolowanym (`value` podane z zewnątrz)
|
|
3
|
+
* i niekontrolowanym (`defaultValue` + stan wewnętrzny). Kontrakt jest ten sam
|
|
4
|
+
* co w natywnych polach formularza, więc komponenty współpracują z natywnym
|
|
5
|
+
* `<form>`, React 19 Actions i React Hook Form bez adapterów.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useControllableState<T>(options: {
|
|
8
|
+
value: T | undefined;
|
|
9
|
+
defaultValue: T;
|
|
10
|
+
onChange?: (next: T) => void;
|
|
11
|
+
}): [T, (next: T) => void];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Stan działający w obu trybach naraz: kontrolowanym (`value` podane z zewnątrz)
|
|
4
|
+
* i niekontrolowanym (`defaultValue` + stan wewnętrzny). Kontrakt jest ten sam
|
|
5
|
+
* co w natywnych polach formularza, więc komponenty współpracują z natywnym
|
|
6
|
+
* `<form>`, React 19 Actions i React Hook Form bez adapterów.
|
|
7
|
+
*/
|
|
8
|
+
export function useControllableState(options) {
|
|
9
|
+
const { value, defaultValue, onChange } = options;
|
|
10
|
+
const isControlled = value !== undefined;
|
|
11
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
12
|
+
const setValue = useCallback((next) => {
|
|
13
|
+
if (!isControlled)
|
|
14
|
+
setUncontrolled(next);
|
|
15
|
+
onChange?.(next);
|
|
16
|
+
}, [isControlled, onChange]);
|
|
17
|
+
return [isControlled ? value : uncontrolled, setValue];
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stabilny identyfikator elementu w konwencji Angulara (`kpt-input-…`), z tą samą
|
|
3
|
+
* zasadą pierwszeństwa: własne `id` przekazane przez propsy wygrywa.
|
|
4
|
+
*
|
|
5
|
+
* `useId()` z Reacta zwraca wartość ze znakami spoza [A-Za-z0-9] (React 19 używa
|
|
6
|
+
* `«r0»`), która jest poprawna w atrybucie `id`, ale nie w selektorze CSS —
|
|
7
|
+
* dlatego zostawiamy z niej tylko część alfanumeryczną.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useKptId(provided: string | undefined, prefix: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Stabilny identyfikator elementu w konwencji Angulara (`kpt-input-…`), z tą samą
|
|
4
|
+
* zasadą pierwszeństwa: własne `id` przekazane przez propsy wygrywa.
|
|
5
|
+
*
|
|
6
|
+
* `useId()` z Reacta zwraca wartość ze znakami spoza [A-Za-z0-9] (React 19 używa
|
|
7
|
+
* `«r0»`), która jest poprawna w atrybucie `id`, ale nie w selektorze CSS —
|
|
8
|
+
* dlatego zostawiamy z niej tylko część alfanumeryczną.
|
|
9
|
+
*/
|
|
10
|
+
export function useKptId(provided, prefix) {
|
|
11
|
+
const generated = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
12
|
+
return provided ?? `${prefix}-${generated}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
export interface KptKnobProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'> {
|
|
3
|
+
value?: number;
|
|
4
|
+
defaultValue?: number;
|
|
5
|
+
onValueChange?: (value: number) => void;
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
step?: number;
|
|
9
|
+
/** Bok kwadratu SVG w pikselach. */
|
|
10
|
+
size?: number;
|
|
11
|
+
/** Grubość łuku w pikselach. */
|
|
12
|
+
stroke?: number;
|
|
13
|
+
/** Dopisek za wartością w środku (np. '%'). */
|
|
14
|
+
suffix?: string;
|
|
15
|
+
showValue?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
invalid?: boolean;
|
|
18
|
+
touched?: boolean;
|
|
19
|
+
onTouch?: () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pokrętło — wybór wartości po łuku, przeciąganiem albo strzałkami.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
|
|
26
|
+
*/
|
|
27
|
+
export declare function KptKnob({ value, defaultValue, onValueChange, min, max, step, size, stroke, suffix, showValue, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptKnobProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
|
+
const TAU = Math.PI * 2;
|
|
6
|
+
/** Przerwa u dołu — kąt „otwarcia" łuku, żeby początek i koniec były rozróżnialne. */
|
|
7
|
+
const GAP = Math.PI / 4;
|
|
8
|
+
const START_ANGLE = Math.PI / 2 + GAP;
|
|
9
|
+
const SWEEP = TAU - GAP * 2;
|
|
10
|
+
/**
|
|
11
|
+
* Pokrętło — wybór wartości po łuku, przeciąganiem albo strzałkami.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
|
|
15
|
+
*/
|
|
16
|
+
export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
17
|
+
const svgRef = useRef(null);
|
|
18
|
+
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
19
|
+
const showInvalid = invalid && (touched ?? true);
|
|
20
|
+
const range = max - min;
|
|
21
|
+
const ratio = range <= 0 ? 0 : (current - min) / range;
|
|
22
|
+
/** Wycinek łuku jako ścieżka SVG; `to <= from` daje samo `M`, czyli nic widocznego. */
|
|
23
|
+
const arc = (from, to) => {
|
|
24
|
+
const r = (size - stroke) / 2;
|
|
25
|
+
const c = size / 2;
|
|
26
|
+
const a0 = START_ANGLE + SWEEP * from;
|
|
27
|
+
const a1 = START_ANGLE + SWEEP * Math.max(from, to);
|
|
28
|
+
const x0 = c + r * Math.cos(a0);
|
|
29
|
+
const y0 = c + r * Math.sin(a0);
|
|
30
|
+
const x1 = c + r * Math.cos(a1);
|
|
31
|
+
const y1 = c + r * Math.sin(a1);
|
|
32
|
+
if (Math.abs(a1 - a0) < 1e-4)
|
|
33
|
+
return `M ${x0} ${y0}`;
|
|
34
|
+
return `M ${x0} ${y0} A ${r} ${r} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${x1} ${y1}`;
|
|
35
|
+
};
|
|
36
|
+
const clamp = (raw) => Math.min(max, Math.max(min, Math.round(raw / step) * step));
|
|
37
|
+
const setFromPoint = (clientX, clientY) => {
|
|
38
|
+
const svg = svgRef.current;
|
|
39
|
+
if (!svg)
|
|
40
|
+
return;
|
|
41
|
+
const rect = svg.getBoundingClientRect();
|
|
42
|
+
const cx = rect.left + rect.width / 2;
|
|
43
|
+
const cy = rect.top + rect.height / 2;
|
|
44
|
+
let angle = Math.atan2(clientY - cy, clientX - cx) - START_ANGLE;
|
|
45
|
+
angle = ((angle % TAU) + TAU) % TAU;
|
|
46
|
+
// Kursor w przerwie u dołu nie ma swojej wartości — przyciągamy go do bliższego końca łuku,
|
|
47
|
+
// inaczej przeciągnięcie przez dół przeskakiwałoby z maksimum na minimum.
|
|
48
|
+
if (angle > SWEEP + GAP)
|
|
49
|
+
angle = angle > SWEEP + GAP / 2 ? 0 : SWEEP;
|
|
50
|
+
setCurrent(clamp(min + Math.min(1, Math.max(0, angle / SWEEP)) * range));
|
|
51
|
+
};
|
|
52
|
+
const onPointerDown = (event) => {
|
|
53
|
+
if (disabled)
|
|
54
|
+
return;
|
|
55
|
+
setFromPoint(event.clientX, event.clientY);
|
|
56
|
+
// Nasłuch wisi na oknie, a nie na pokrętle: przeciąganie ma nie gubić wartości,
|
|
57
|
+
// gdy kursor wyjedzie poza koło — a wyjeżdża praktycznie zawsze.
|
|
58
|
+
const move = (e) => setFromPoint(e.clientX, e.clientY);
|
|
59
|
+
const up = () => {
|
|
60
|
+
window.removeEventListener('pointermove', move);
|
|
61
|
+
window.removeEventListener('pointerup', up);
|
|
62
|
+
onTouch?.();
|
|
63
|
+
};
|
|
64
|
+
window.addEventListener('pointermove', move);
|
|
65
|
+
window.addEventListener('pointerup', up);
|
|
66
|
+
};
|
|
67
|
+
const onKeyDown = (event) => {
|
|
68
|
+
if (disabled)
|
|
69
|
+
return;
|
|
70
|
+
let next = current;
|
|
71
|
+
if (event.key === 'ArrowUp' || event.key === 'ArrowRight')
|
|
72
|
+
next += step;
|
|
73
|
+
else if (event.key === 'ArrowDown' || event.key === 'ArrowLeft')
|
|
74
|
+
next -= step;
|
|
75
|
+
else if (event.key === 'Home')
|
|
76
|
+
next = min;
|
|
77
|
+
else if (event.key === 'End')
|
|
78
|
+
next = max;
|
|
79
|
+
else
|
|
80
|
+
return;
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
setCurrent(Math.min(max, Math.max(min, next)));
|
|
83
|
+
onTouch?.();
|
|
84
|
+
};
|
|
85
|
+
return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid'), role: "slider", tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-disabled": disabled || undefined, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [current, suffix] })) : null] }) }) }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
+
import type { KptOption } from '../shared/types.ts';
|
|
4
|
+
export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
|
|
5
|
+
options?: KptOption<T>[];
|
|
6
|
+
value?: T | T[] | null;
|
|
7
|
+
defaultValue?: T | T[] | null;
|
|
8
|
+
onValueChange?: (value: T | T[] | null) => void;
|
|
9
|
+
multiple?: boolean;
|
|
10
|
+
/** Pole filtrowania nad listą. */
|
|
11
|
+
filter?: boolean;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
invalid?: boolean;
|
|
14
|
+
touched?: boolean;
|
|
15
|
+
onTouch?: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
|
|
19
|
+
* opcjonalny filtr.
|
|
20
|
+
*
|
|
21
|
+
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją — lista bywa filtrem,
|
|
22
|
+
* a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <KptListbox options={roles} value={role} onValueChange={setRole} filter />
|
|
26
|
+
* <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
|
|
27
|
+
*/
|
|
28
|
+
export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
|