@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,6 @@
|
|
|
1
|
+
export { KptGrid } from '../grid/grid.tsx';
|
|
2
|
+
export type { KptGridProps } from '../grid/grid.tsx';
|
|
3
|
+
export { KptCol } from '../grid/col.tsx';
|
|
4
|
+
export type { KptColProps, KptColSpan } from '../grid/col.tsx';
|
|
5
|
+
export { KptFlex } from '../grid/flex.tsx';
|
|
6
|
+
export type { KptFlexProps, KptFlexDirection, KptFlexWrap, KptAlignment } from '../grid/flex.tsx';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @konce-pt/react/grid — ergonomia Reacta nad arkuszem `@konce-pt/grid`.
|
|
3
|
+
* Komponenty ustawiają wyłącznie custom properties inline; układ liczy natywny CSS.
|
|
4
|
+
* Osobny subpath, bo arkusz układu jest opcjonalny i nie każda aplikacja go ładuje.
|
|
5
|
+
*/
|
|
6
|
+
export { KptGrid } from "../grid/grid.js";
|
|
7
|
+
export { KptCol } from "../grid/col.js";
|
|
8
|
+
export { KptFlex } from "../grid/flex.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type DeepPartial, type KptLocale, type KptMessages } from '@konce-pt/i18n';
|
|
3
|
+
/** Kontrakt zwracany przez `useKptI18n()`. */
|
|
4
|
+
export interface KptI18nContextValue {
|
|
5
|
+
/** Bieżący język. */
|
|
6
|
+
locale: KptLocale;
|
|
7
|
+
/** Zmiana języka w runtime — działa tylko w obrębie `KptI18nProvider`. */
|
|
8
|
+
setLocale: (locale: KptLocale) => void;
|
|
9
|
+
/** Aktywny słownik. */
|
|
10
|
+
messages: KptMessages;
|
|
11
|
+
/** Słownik zarejestrowany dla danego języka; nieznany język spada na EN. */
|
|
12
|
+
messagesFor: (locale: KptLocale) => KptMessages;
|
|
13
|
+
/** Tłumaczy klucz w notacji kropkowej (`'paginator.range'`) z interpolacją `{name}`. */
|
|
14
|
+
t: (key: string, params?: Record<string, string | number>) => string;
|
|
15
|
+
}
|
|
16
|
+
export interface KptI18nProviderProps {
|
|
17
|
+
/** Język początkowy (domyślnie 'en'). */
|
|
18
|
+
locale?: KptLocale;
|
|
19
|
+
/**
|
|
20
|
+
* Słowniki do zarejestrowania lub nadpisania, kluczowane kodem języka.
|
|
21
|
+
* Może dodać nowy język (`{ de }`) lub nadpisać istniejący
|
|
22
|
+
* (`{ en: { paginator: { rowsPerPage: 'Rows:' } } }`). Deep-merge na bazę EN/PL.
|
|
23
|
+
*/
|
|
24
|
+
messages?: Record<string, DeepPartial<KptMessages>>;
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Dostarcza tłumaczenia poddrzewu. Bez providera komponenty działają na EN —
|
|
29
|
+
* brak konfiguracji nigdy nie jest błędem.
|
|
30
|
+
*/
|
|
31
|
+
export declare function KptI18nProvider({ locale, messages, children }: Readonly<KptI18nProviderProps>): import("react").JSX.Element;
|
|
32
|
+
/** Czyta tłumaczenia z kontekstu. Poza providerem zwraca słownik EN. */
|
|
33
|
+
export declare function useKptI18n(): KptI18nContextValue;
|
|
34
|
+
/** Nadpisania i18n dostępne w propsach każdego komponentu z etykietami. */
|
|
35
|
+
export interface KptI18nOverrides {
|
|
36
|
+
/** Wymusza język tylko dla tego komponentu, niezależnie od `KptI18nProvider`. */
|
|
37
|
+
locale?: KptLocale;
|
|
38
|
+
/** Nadpisuje pojedyncze etykiety tego komponentu (deep-merge na aktywny słownik). */
|
|
39
|
+
dictionary?: DeepPartial<KptMessages>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Tłumaczenia dla pojedynczego komponentu. `locale` i `dictionary` z propsów mają
|
|
43
|
+
* priorytet nad kontekstem — tak samo jak inputy etykiet biją słownik w Angularze.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useKptMessages(overrides?: KptI18nOverrides): KptI18nContextValue;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
3
|
+
import { KPT_MESSAGES_EN, KPT_MESSAGES_PL, deepMerge, translate, } from '@konce-pt/i18n';
|
|
4
|
+
const FALLBACK = {
|
|
5
|
+
locale: 'en',
|
|
6
|
+
setLocale: () => { },
|
|
7
|
+
messages: KPT_MESSAGES_EN,
|
|
8
|
+
messagesFor: () => KPT_MESSAGES_EN,
|
|
9
|
+
t: (key, params) => translate(KPT_MESSAGES_EN, key, params),
|
|
10
|
+
};
|
|
11
|
+
const KptI18nContext = createContext(null);
|
|
12
|
+
/**
|
|
13
|
+
* Dostarcza tłumaczenia poddrzewu. Bez providera komponenty działają na EN —
|
|
14
|
+
* brak konfiguracji nigdy nie jest błędem.
|
|
15
|
+
*/
|
|
16
|
+
export function KptI18nProvider({ locale = 'en', messages, children }) {
|
|
17
|
+
const [current, setCurrent] = useState(locale);
|
|
18
|
+
const registry = useMemo(() => {
|
|
19
|
+
const base = new Map([
|
|
20
|
+
['en', KPT_MESSAGES_EN],
|
|
21
|
+
['pl', KPT_MESSAGES_PL],
|
|
22
|
+
]);
|
|
23
|
+
for (const [code, patch] of Object.entries(messages ?? {})) {
|
|
24
|
+
base.set(code, deepMerge(base.get(code) ?? KPT_MESSAGES_EN, patch));
|
|
25
|
+
}
|
|
26
|
+
return base;
|
|
27
|
+
}, [messages]);
|
|
28
|
+
const messagesFor = useCallback((code) => registry.get(code) ?? KPT_MESSAGES_EN, [registry]);
|
|
29
|
+
const value = useMemo(() => {
|
|
30
|
+
const active = messagesFor(current);
|
|
31
|
+
return {
|
|
32
|
+
locale: current,
|
|
33
|
+
setLocale: setCurrent,
|
|
34
|
+
messages: active,
|
|
35
|
+
messagesFor,
|
|
36
|
+
t: (key, params) => translate(active, key, params),
|
|
37
|
+
};
|
|
38
|
+
}, [current, messagesFor]);
|
|
39
|
+
return _jsx(KptI18nContext.Provider, { value: value, children: children });
|
|
40
|
+
}
|
|
41
|
+
/** Czyta tłumaczenia z kontekstu. Poza providerem zwraca słownik EN. */
|
|
42
|
+
export function useKptI18n() {
|
|
43
|
+
return useContext(KptI18nContext) ?? FALLBACK;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Tłumaczenia dla pojedynczego komponentu. `locale` i `dictionary` z propsów mają
|
|
47
|
+
* priorytet nad kontekstem — tak samo jak inputy etykiet biją słownik w Angularze.
|
|
48
|
+
*/
|
|
49
|
+
export function useKptMessages(overrides) {
|
|
50
|
+
const context = useKptI18n();
|
|
51
|
+
const { locale, dictionary } = overrides ?? {};
|
|
52
|
+
return useMemo(() => {
|
|
53
|
+
if (!locale && !dictionary)
|
|
54
|
+
return context;
|
|
55
|
+
const effectiveLocale = locale ?? context.locale;
|
|
56
|
+
const base = locale ? context.messagesFor(locale) : context.messages;
|
|
57
|
+
const merged = dictionary ? deepMerge(base, dictionary) : base;
|
|
58
|
+
return {
|
|
59
|
+
...context,
|
|
60
|
+
locale: effectiveLocale,
|
|
61
|
+
messages: merged,
|
|
62
|
+
t: (key, params) => translate(merged, key, params),
|
|
63
|
+
};
|
|
64
|
+
}, [context, locale, dictionary]);
|
|
65
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rejestruje (lub nadpisuje) ikony: `{ nazwa: 'inner svg' }`.
|
|
3
|
+
*
|
|
4
|
+
* Treść jest sanityzowana do allowlisty geometrii SVG — mimo to `registerKptIcons()` wolno
|
|
5
|
+
* wołać wyłącznie z treścią kontrolowaną przez developera (build-time), nigdy bezpośrednio
|
|
6
|
+
* z danych runtime/CMS/uploadu bez własnej walidacji domenowej wyżej w stosie.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerKptIcons(next: Record<string, string>): void;
|
|
9
|
+
/** Zwraca wewnętrzny SVG ikony lub `undefined`. */
|
|
10
|
+
export declare function getKptIcon(name: string): string | undefined;
|
|
11
|
+
/** Czy ikona o tej nazwie jest w rejestrze. */
|
|
12
|
+
export declare function hasKptIcon(name: string): boolean;
|
|
13
|
+
/** Subskrypcja zmian rejestru — używana przez `KptIcon` przez `useSyncExternalStore`. */
|
|
14
|
+
export declare function subscribeKptIcons(listener: () => void): () => void;
|
|
15
|
+
/** Licznik rejestracji; rośnie po każdym `registerKptIcons`. */
|
|
16
|
+
export declare function getKptIconsVersion(): number;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { KPT_ICONS, sanitizeSvgFragment } from '@konce-pt/icons';
|
|
2
|
+
/*
|
|
3
|
+
* Rejestr ikon dla `KptIcon`. Odpowiednik serwisu `KptIconRegistry` z portu Angulara,
|
|
4
|
+
* ale jako moduł-singleton: ikony są globalne dla aplikacji, więc nie ma czego wstrzykiwać.
|
|
5
|
+
* Zmiany są ogłaszane subskrybentom, żeby `KptIcon` przerysował się po późnej rejestracji.
|
|
6
|
+
*/
|
|
7
|
+
const icons = new Map(Object.entries(KPT_ICONS));
|
|
8
|
+
const listeners = new Set();
|
|
9
|
+
let version = 0;
|
|
10
|
+
/**
|
|
11
|
+
* Rejestruje (lub nadpisuje) ikony: `{ nazwa: 'inner svg' }`.
|
|
12
|
+
*
|
|
13
|
+
* Treść jest sanityzowana do allowlisty geometrii SVG — mimo to `registerKptIcons()` wolno
|
|
14
|
+
* wołać wyłącznie z treścią kontrolowaną przez developera (build-time), nigdy bezpośrednio
|
|
15
|
+
* z danych runtime/CMS/uploadu bez własnej walidacji domenowej wyżej w stosie.
|
|
16
|
+
*/
|
|
17
|
+
export function registerKptIcons(next) {
|
|
18
|
+
for (const [name, svg] of Object.entries(next)) {
|
|
19
|
+
icons.set(name, sanitizeSvgFragment(svg));
|
|
20
|
+
}
|
|
21
|
+
version += 1;
|
|
22
|
+
for (const listener of listeners)
|
|
23
|
+
listener();
|
|
24
|
+
}
|
|
25
|
+
/** Zwraca wewnętrzny SVG ikony lub `undefined`. */
|
|
26
|
+
export function getKptIcon(name) {
|
|
27
|
+
return icons.get(name);
|
|
28
|
+
}
|
|
29
|
+
/** Czy ikona o tej nazwie jest w rejestrze. */
|
|
30
|
+
export function hasKptIcon(name) {
|
|
31
|
+
return icons.has(name);
|
|
32
|
+
}
|
|
33
|
+
/** Subskrypcja zmian rejestru — używana przez `KptIcon` przez `useSyncExternalStore`. */
|
|
34
|
+
export function subscribeKptIcons(listener) {
|
|
35
|
+
listeners.add(listener);
|
|
36
|
+
return () => {
|
|
37
|
+
listeners.delete(listener);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Licznik rejestracji; rośnie po każdym `registerKptIcons`. */
|
|
41
|
+
export function getKptIconsVersion() {
|
|
42
|
+
return version;
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
export interface KptIconProps extends ComponentPropsWithRef<'span'> {
|
|
3
|
+
/** Nazwa ikony z rejestru (wbudowana lub zarejestrowana). */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Rozmiar jako długość CSS (np. '1.25rem'); domyślnie dziedziczy z `font-size`. */
|
|
6
|
+
size?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Ikona Koncept UI (inline SVG, zestaw Tabler Icons — MIT). Rozmiar dziedziczy z `font-size`
|
|
10
|
+
* (1em) lub ustaw `size`; kolor z `currentColor`. Ikona musi być w rejestrze — wbudowany
|
|
11
|
+
* zestaw działa od razu, pełny dodaje `registerKptTablerIcons()` z `@konce-pt/react/icons`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <KptIcon name="menu" />
|
|
15
|
+
* <KptIcon name="check" size="1.25rem" />
|
|
16
|
+
*/
|
|
17
|
+
export declare function KptIcon({ name, size, className, style, ...rest }: Readonly<KptIconProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { renderKptIconSvg } from '@konce-pt/icons';
|
|
3
|
+
import { useSyncExternalStore } from 'react';
|
|
4
|
+
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { getKptIcon, getKptIconsVersion, subscribeKptIcons } from "./icon-registry.js";
|
|
6
|
+
/**
|
|
7
|
+
* Ikona Koncept UI (inline SVG, zestaw Tabler Icons — MIT). Rozmiar dziedziczy z `font-size`
|
|
8
|
+
* (1em) lub ustaw `size`; kolor z `currentColor`. Ikona musi być w rejestrze — wbudowany
|
|
9
|
+
* zestaw działa od razu, pełny dodaje `registerKptTablerIcons()` z `@konce-pt/react/icons`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <KptIcon name="menu" />
|
|
13
|
+
* <KptIcon name="check" size="1.25rem" />
|
|
14
|
+
*/
|
|
15
|
+
export function KptIcon({ name, size = '', className, style, ...rest }) {
|
|
16
|
+
// Rejestr bywa uzupełniany po pierwszym renderze (leniwy import zestawu Tabler),
|
|
17
|
+
// więc komponent słucha jego wersji zamiast czytać go raz.
|
|
18
|
+
useSyncExternalStore(subscribeKptIcons, getKptIconsVersion, getKptIconsVersion);
|
|
19
|
+
// Treść pochodzi z zaufanego rejestru i przeszła sanitizację przy rejestracji —
|
|
20
|
+
// to ten sam kontrakt, co `innerHTML` w porcie Angulara.
|
|
21
|
+
const html = renderKptIconSvg(getKptIcon(name));
|
|
22
|
+
return (_jsx("span", { className: cn('kpt-icon-host', className), "aria-hidden": "true", style: size ? { fontSize: size, ...style } : style, dangerouslySetInnerHTML: { __html: html }, ...rest }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import type { KptButtonSize, KptButtonVariant } from '../button/button.tsx';
|
|
3
|
+
import type { KptHostProps } from '../internal/types.ts';
|
|
4
|
+
export interface KptIconButtonProps extends ComponentPropsWithRef<'button'> {
|
|
5
|
+
variant?: KptButtonVariant;
|
|
6
|
+
size?: KptButtonSize;
|
|
7
|
+
shape?: 'round' | 'square';
|
|
8
|
+
/** Spinner zamiast ikony + zablokowana interakcja; ustawia `aria-busy`. */
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
/** Etykieta dostępności — wymagana, bo przycisk nie ma tekstu. */
|
|
11
|
+
'aria-label': string;
|
|
12
|
+
/** Propsy wrappera hosta (`.kpt-icon-button-host`). */
|
|
13
|
+
hostProps?: KptHostProps<'span'>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Przycisk ikonowy (tylko ikona). `aria-label` jest wymagany typem — bez tekstu przycisk nie ma
|
|
17
|
+
* dostępnej nazwy, a to najczęstszy błąd dostępności w tej rodzinie komponentów.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <KptIconButton aria-label="Ustawienia"><KptIcon name="menu" /></KptIconButton>
|
|
21
|
+
* <KptIconButton variant="filled" shape="square" aria-label="Usuń"><KptIcon name="x" /></KptIconButton>
|
|
22
|
+
*/
|
|
23
|
+
export declare function KptIconButton({ variant, size, shape, type, loading, disabled, hostProps, className, children, ...rest }: Readonly<KptIconButtonProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
/**
|
|
4
|
+
* Przycisk ikonowy (tylko ikona). `aria-label` jest wymagany typem — bez tekstu przycisk nie ma
|
|
5
|
+
* dostępnej nazwy, a to najczęstszy błąd dostępności w tej rodzinie komponentów.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <KptIconButton aria-label="Ustawienia"><KptIcon name="menu" /></KptIconButton>
|
|
9
|
+
* <KptIconButton variant="filled" shape="square" aria-label="Usuń"><KptIcon name="x" /></KptIconButton>
|
|
10
|
+
*/
|
|
11
|
+
export function KptIconButton({ variant = 'text', size = 'md', shape = 'round', type = 'button', loading = false, disabled = false, hostProps, className, children, ...rest }) {
|
|
12
|
+
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
13
|
+
return (_jsx("span", { className: cn('kpt-icon-button-host', hostClassName), ...hostRest, children: _jsx("button", { className: cn('kpt-icon-button', className), type: type, "data-variant": variant, "data-size": size, "data-shape": shape, disabled: disabled || loading, "aria-busy": loading || undefined, ...rest, children: loading ? _jsx("span", { className: "kpt-icon-button__spinner", "aria-hidden": "true" }) : children }) }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { KPT_TABLER_ICONS, KPT_TABLER_ICON_NAMES } from '@konce-pt/icons/tabler';
|
|
2
|
+
/**
|
|
3
|
+
* Rejestruje pełny zestaw Tabler Icons (5130), dzięki czemu `KptIcon` może użyć dowolnej
|
|
4
|
+
* ikony po nazwie. Wywołaj raz, w wejściu aplikacji — rejestr jest globalny, więc nie ma
|
|
5
|
+
* potrzeby owijać drzewa żadnym providerem.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // main.tsx
|
|
9
|
+
* import { registerKptTablerIcons } from '@konce-pt/react/icons';
|
|
10
|
+
* registerKptTablerIcons();
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerKptTablerIcons(): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @konce-pt/react/icons — pełny zestaw Tabler Icons (MIT) dla `KptIcon`.
|
|
3
|
+
* Opcjonalny (pobierany tylko przy imporcie), by nie powiększać rdzenia biblioteki.
|
|
4
|
+
*/
|
|
5
|
+
import { KPT_TABLER_ICONS } from '@konce-pt/icons/tabler';
|
|
6
|
+
import { registerKptIcons } from "../icon/icon-registry.js";
|
|
7
|
+
export { KPT_TABLER_ICONS, KPT_TABLER_ICON_NAMES } from '@konce-pt/icons/tabler';
|
|
8
|
+
/**
|
|
9
|
+
* Rejestruje pełny zestaw Tabler Icons (5130), dzięki czemu `KptIcon` może użyć dowolnej
|
|
10
|
+
* ikony po nazwie. Wywołaj raz, w wejściu aplikacji — rejestr jest globalny, więc nie ma
|
|
11
|
+
* potrzeby owijać drzewa żadnym providerem.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // main.tsx
|
|
15
|
+
* import { registerKptTablerIcons } from '@konce-pt/react/icons';
|
|
16
|
+
* registerKptTablerIcons();
|
|
17
|
+
*/
|
|
18
|
+
export function registerKptTablerIcons() {
|
|
19
|
+
registerKptIcons(KPT_TABLER_ICONS);
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
+
export interface KptImageProps extends Omit<ComponentPropsWithRef<'div'>, 'onClick'>, KptI18nOverrides {
|
|
4
|
+
src: string;
|
|
5
|
+
alt?: string;
|
|
6
|
+
/** Szerokość jako długość CSS, np. '240px'. */
|
|
7
|
+
width?: string | null;
|
|
8
|
+
/** Kliknięcie otwiera pełnoekranowy podgląd. */
|
|
9
|
+
preview?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Obraz z opcjonalnym podglądem pełnoekranowym.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <KptImage src="/foto.jpg" alt="Zdjęcie" preview width="240px" />
|
|
16
|
+
*/
|
|
17
|
+
export declare function KptImage({ src, alt, width, preview, locale, dictionary, className, ...rest }: Readonly<KptImageProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
|
+
import { KptIcon } from "../icon/icon.js";
|
|
6
|
+
/**
|
|
7
|
+
* Obraz z opcjonalnym podglądem pełnoekranowym.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <KptImage src="/foto.jpg" alt="Zdjęcie" preview width="240px" />
|
|
11
|
+
*/
|
|
12
|
+
export function KptImage({ src, alt = '', width = null, preview = false, locale, dictionary, className, ...rest }) {
|
|
13
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
14
|
+
const [open, setOpen] = useState(false);
|
|
15
|
+
return (_jsxs("div", { className: cn('kpt-image-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-image', preview && 'kpt-image--preview'), style: width ? { width } : undefined, onClick: () => preview && setOpen(true), children: [_jsx("img", { className: "kpt-image__img", src: src, alt: alt }), preview ? (_jsx("div", { className: "kpt-image__mask", "aria-hidden": "true", children: _jsx(KptIcon, { name: "zoom-in" }) })) : null] }), open ? (_jsxs("div", { className: "kpt-image__overlay", onClick: () => setOpen(false), children: [_jsx("button", { type: "button", className: "kpt-image__close", onClick: () => setOpen(false), "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) }), _jsx("img", { className: "kpt-image__full", src: src, alt: alt, onClick: (event) => event.stopPropagation() })] })) : null] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
import type { KptHostProps } from '../internal/types.ts';
|
|
3
|
+
export type KptInputType = 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url' | 'time';
|
|
4
|
+
export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'type' | 'required'> {
|
|
5
|
+
/** Typ pola. */
|
|
6
|
+
type?: KptInputType;
|
|
7
|
+
/**
|
|
8
|
+
* Oznacza pole jako wymagane atrybutem `aria-required`, a NIE natywnym `required`.
|
|
9
|
+
* Natywny blokowałby submit i uruchamiał walidację przeglądarki obok Twojej własnej.
|
|
10
|
+
*/
|
|
11
|
+
required?: boolean;
|
|
12
|
+
/** Pole jest nieprawidłowe. Widoczne dopiero, gdy `touched` też jest prawdziwe. */
|
|
13
|
+
invalid?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Czy pole było dotknięte. Pominięty = stan błędu pokazuje się od razu po `invalid`,
|
|
16
|
+
* bo w Reakcie to zwykle biblioteka formularza decyduje, kiedy błąd jest gotowy.
|
|
17
|
+
* Podaj go, gdy chcesz bramkę „dopiero po dotknięciu" znaną z portu Angulara.
|
|
18
|
+
*/
|
|
19
|
+
touched?: boolean;
|
|
20
|
+
/** Wygodniejszy odpowiednik `onChange` — dostaje samą wartość. */
|
|
21
|
+
onValueChange?: (value: string) => void;
|
|
22
|
+
/** Wywoływane na `blur` — moment, w którym pole staje się dotknięte. */
|
|
23
|
+
onTouch?: () => void;
|
|
24
|
+
/** Propsy wrappera hosta (`.kpt-input-host`). `className` i `ref` idą na `<input>`. */
|
|
25
|
+
hostProps?: KptHostProps<'span'>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pole tekstowe Koncept UI. Renderuje natywny `<input>` w hoście `.kpt-input-host`.
|
|
29
|
+
*
|
|
30
|
+
* Działa kontrolowane (`value` + `onValueChange`) i niekontrolowane (`defaultValue`) —
|
|
31
|
+
* w drugim trybie wartość należy do DOM, więc natywny `<form>`, `<form action>`, reset
|
|
32
|
+
* formularza i React Hook Form (`register`) działają bez adaptera.
|
|
33
|
+
*
|
|
34
|
+
* Wewnątrz `<KptFormField>` pole samo przejmuje `id` z wrappera, więc `label[for]` jest
|
|
35
|
+
* spięte bez konfiguracji.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* <KptInput name="email" type="email" defaultValue="" />
|
|
39
|
+
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
40
|
+
*/
|
|
41
|
+
export declare function KptInput({ type, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, 'aria-invalid': ariaInvalid, ...rest }: Readonly<KptInputProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { useKptId } from "../internal/use-id.js";
|
|
5
|
+
import { useKptFormFieldContext } from "../form-field/form-field-context.js";
|
|
6
|
+
/**
|
|
7
|
+
* Pole tekstowe Koncept UI. Renderuje natywny `<input>` w hoście `.kpt-input-host`.
|
|
8
|
+
*
|
|
9
|
+
* Działa kontrolowane (`value` + `onValueChange`) i niekontrolowane (`defaultValue`) —
|
|
10
|
+
* w drugim trybie wartość należy do DOM, więc natywny `<form>`, `<form action>`, reset
|
|
11
|
+
* formularza i React Hook Form (`register`) działają bez adaptera.
|
|
12
|
+
*
|
|
13
|
+
* Wewnątrz `<KptFormField>` pole samo przejmuje `id` z wrappera, więc `label[for]` jest
|
|
14
|
+
* spięte bez konfiguracji.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <KptInput name="email" type="email" defaultValue="" />
|
|
18
|
+
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
19
|
+
*/
|
|
20
|
+
export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, 'aria-invalid': ariaInvalid, ...rest }) {
|
|
21
|
+
const field = useKptFormFieldContext();
|
|
22
|
+
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
23
|
+
const inputId = useKptId(id ?? field?.controlId, 'kpt-input');
|
|
24
|
+
// Stan nieprawidłowy pokazujemy dopiero po dotknięciu pola — jak natywne `:user-invalid`.
|
|
25
|
+
// Bez `touched` nie ma czego bramkować, więc decyduje samo `invalid`.
|
|
26
|
+
const showInvalid = invalid && (touched ?? true);
|
|
27
|
+
const handleChange = useCallback((event) => {
|
|
28
|
+
onChange?.(event);
|
|
29
|
+
onValueChange?.(event.target.value);
|
|
30
|
+
}, [onChange, onValueChange]);
|
|
31
|
+
const handleBlur = useCallback((event) => {
|
|
32
|
+
onBlur?.(event);
|
|
33
|
+
onTouch?.();
|
|
34
|
+
}, [onBlur, onTouch]);
|
|
35
|
+
return (_jsx("span", { className: cn('kpt-input-host', hostClassName), ...hostRest, children: _jsx("input", { className: cn('kpt-input__field', className), id: inputId, type: type, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }) }));
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange'> {
|
|
3
|
+
value?: string;
|
|
4
|
+
defaultValue?: string;
|
|
5
|
+
onValueChange?: (value: string) => void;
|
|
6
|
+
/** Wzorzec maski; pozostałe znaki to literały (spacje, nawiasy, myślniki). */
|
|
7
|
+
mask?: string;
|
|
8
|
+
invalid?: boolean;
|
|
9
|
+
touched?: boolean;
|
|
10
|
+
onTouch?: () => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Pole z maską znakową. Tokeny: `9` = cyfra, `a` = litera, `*` = alfanumeryczny; reszta wzorca
|
|
14
|
+
* to literały wstawiane automatycznie.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
|
|
18
|
+
*/
|
|
19
|
+
export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, invalid, touched, onTouch, disabled, className, id, onBlur, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
import { useKptId } from "../internal/use-id.js";
|
|
5
|
+
import { useKptFormFieldContext } from "../form-field/form-field-context.js";
|
|
6
|
+
/** Tokeny maski: `9` = cyfra, `a` = litera, `*` = znak alfanumeryczny. */
|
|
7
|
+
const TOKENS = {
|
|
8
|
+
'9': /\d/,
|
|
9
|
+
a: /[a-zA-Z]/,
|
|
10
|
+
'*': /[a-zA-Z0-9]/,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Pole z maską znakową. Tokeny: `9` = cyfra, `a` = litera, `*` = alfanumeryczny; reszta wzorca
|
|
14
|
+
* to literały wstawiane automatycznie.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
|
|
18
|
+
*/
|
|
19
|
+
export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, ...rest }) {
|
|
20
|
+
const field = useKptFormFieldContext();
|
|
21
|
+
const inputId = useKptId(id ?? field?.controlId, 'kpt-input-mask');
|
|
22
|
+
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
23
|
+
const showInvalid = invalid && (touched ?? true);
|
|
24
|
+
/*
|
|
25
|
+
* Maska przepisuje wejście od zera: z tekstu bierzemy wyłącznie znaki pasujące do kolejnych
|
|
26
|
+
* tokenów, a literały wstawiamy same. Dzięki temu wklejenie „48123456789" i wpisanie
|
|
27
|
+
* „(48) 123…" dają ten sam wynik, a usunięcie literału nie zostawia dziury we wzorcu.
|
|
28
|
+
*/
|
|
29
|
+
const applyMask = (raw) => {
|
|
30
|
+
if (!mask)
|
|
31
|
+
return raw;
|
|
32
|
+
const chars = raw.split('');
|
|
33
|
+
let charIndex = 0;
|
|
34
|
+
let out = '';
|
|
35
|
+
for (let maskIndex = 0; maskIndex < mask.length && charIndex < chars.length;) {
|
|
36
|
+
const token = mask[maskIndex];
|
|
37
|
+
const rule = TOKENS[token];
|
|
38
|
+
if (rule) {
|
|
39
|
+
while (charIndex < chars.length && !rule.test(chars[charIndex]))
|
|
40
|
+
charIndex++;
|
|
41
|
+
if (charIndex >= chars.length)
|
|
42
|
+
break;
|
|
43
|
+
out += chars[charIndex];
|
|
44
|
+
charIndex++;
|
|
45
|
+
maskIndex++;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
out += token;
|
|
49
|
+
maskIndex++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
};
|
|
54
|
+
const onInput = (event) => {
|
|
55
|
+
const formatted = applyMask(event.target.value);
|
|
56
|
+
setCurrent(formatted);
|
|
57
|
+
// Gdy maska odrzuci znak, wartość się nie zmienia i React nie przerysuje pola —
|
|
58
|
+
// wpisany znak zostałby na ekranie, więc DOM zrównujemy ręcznie.
|
|
59
|
+
event.target.value = formatted;
|
|
60
|
+
};
|
|
61
|
+
return (_jsx("span", { className: "kpt-input-mask-host", children: _jsx("input", { className: cn('kpt-input-mask__input', showInvalid && 'kpt-input-mask--invalid', className), id: inputId, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
|
|
62
|
+
onBlur?.(event);
|
|
63
|
+
onTouch?.();
|
|
64
|
+
}, ...rest }) }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
+
export interface KptInputNumberProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
|
|
4
|
+
value?: number | null;
|
|
5
|
+
defaultValue?: number | null;
|
|
6
|
+
onValueChange?: (value: number | null) => void;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
/** Dopisek pokazywany za liczbą, np. ' szt.' albo 'ms'. */
|
|
12
|
+
suffix?: string;
|
|
13
|
+
/** Przyciski krokowania. `false` daje samo pole — wariant do zakresów „od–do". */
|
|
14
|
+
showButtons?: boolean;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
invalid?: boolean;
|
|
17
|
+
touched?: boolean;
|
|
18
|
+
onTouch?: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` — puste pole ma znaczenie,
|
|
22
|
+
* bo „brak wartości" to nie to samo co zero.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
|
|
26
|
+
* <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
|
|
27
|
+
*/
|
|
28
|
+
export declare function KptInputNumber({ value, defaultValue, onValueChange, min, max, step, placeholder, suffix, showButtons, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputNumberProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
6
|
+
import { KptIcon } from "../icon/icon.js";
|
|
7
|
+
/**
|
|
8
|
+
* Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` — puste pole ma znaczenie,
|
|
9
|
+
* bo „brak wartości" to nie to samo co zero.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
|
|
13
|
+
* <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
|
|
14
|
+
*/
|
|
15
|
+
export function KptInputNumber({ value, defaultValue = null, onValueChange, min, max, step = 1, placeholder = '', suffix = '', showButtons = true, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
16
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
17
|
+
const [current, setCurrent] = useControllableState({
|
|
18
|
+
value,
|
|
19
|
+
defaultValue,
|
|
20
|
+
onChange: onValueChange,
|
|
21
|
+
});
|
|
22
|
+
const showInvalid = invalid && (touched ?? true);
|
|
23
|
+
const clamp = useCallback((n) => {
|
|
24
|
+
if (min !== undefined && n < min)
|
|
25
|
+
return min;
|
|
26
|
+
if (max !== undefined && n > max)
|
|
27
|
+
return max;
|
|
28
|
+
return n;
|
|
29
|
+
}, [min, max]);
|
|
30
|
+
const display = current === null || current === undefined || Number.isNaN(current) ? '' : `${current}${suffix}`;
|
|
31
|
+
const canIncrement = max === undefined || (current ?? -Infinity) < max;
|
|
32
|
+
const canDecrement = min === undefined || (current ?? Infinity) > min;
|
|
33
|
+
const onInput = (event) => {
|
|
34
|
+
// Przecinek jako separator dziesiętny i dopisek trafiają do pola razem z liczbą —
|
|
35
|
+
// czyścimy je przed parsowaniem, żeby wpisywanie „12,5 zł" działało naturalnie.
|
|
36
|
+
const raw = event.target.value.replace(suffix, '').replace(',', '.').trim();
|
|
37
|
+
if (raw === '') {
|
|
38
|
+
setCurrent(null);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const parsed = Number(raw);
|
|
42
|
+
if (!Number.isNaN(parsed))
|
|
43
|
+
setCurrent(clamp(parsed));
|
|
44
|
+
};
|
|
45
|
+
const stepBy = (direction) => {
|
|
46
|
+
const base = current ?? min ?? 0;
|
|
47
|
+
setCurrent(clamp(base + direction * step));
|
|
48
|
+
onTouch?.();
|
|
49
|
+
};
|
|
50
|
+
const onBlur = (_event) => onTouch?.();
|
|
51
|
+
return (_jsx("div", { className: cn('kpt-input-number-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-input-number', disabled && 'kpt-input-number--disabled', showInvalid && 'kpt-input-number--invalid', !showButtons && 'kpt-input-number--plain'), children: [showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", disabled: disabled || !canDecrement, onClick: () => stepBy(-1), "aria-label": t('inputNumber.decrement'), children: _jsx(KptIcon, { name: "minus" }) })) : null, _jsx("input", { className: "kpt-input-number__input", type: "text", inputMode: "decimal", value: display, disabled: disabled, placeholder: placeholder, onChange: onInput, onBlur: onBlur }), showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", disabled: disabled || !canIncrement, onClick: () => stepBy(1), "aria-label": t('inputNumber.increment'), children: _jsx(KptIcon, { name: "plus" }) })) : null] }) }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
export interface KptInputOtpProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'> {
|
|
3
|
+
value?: string;
|
|
4
|
+
defaultValue?: string;
|
|
5
|
+
onValueChange?: (value: string) => void;
|
|
6
|
+
length?: number;
|
|
7
|
+
/** Dopuszcza wyłącznie cyfry i przełącza klawiaturę mobilną na numeryczną. */
|
|
8
|
+
integerOnly?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
invalid?: boolean;
|
|
11
|
+
touched?: boolean;
|
|
12
|
+
onTouch?: () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Pole kodu jednorazowego (OTP/PIN) — N osobnych pól, automatyczne przejście do następnego
|
|
16
|
+
* i wklejanie całego kodu naraz. Wartością jest sklejony string.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <KptInputOtp value={code} onValueChange={setCode} length={6} integerOnly />
|
|
20
|
+
*/
|
|
21
|
+
export declare function KptInputOtp({ value, defaultValue, onValueChange, length, integerOnly, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptInputOtpProps>): import("react").JSX.Element;
|