@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,184 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Children, Fragment, isValidElement, useId, useRef, useState, } 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 { KptAlert } from "../alert/alert.js";
|
|
7
|
+
import { KptButton } from "../button/button.js";
|
|
8
|
+
import { KptCheckbox } from "../checkbox/checkbox.js";
|
|
9
|
+
import { KptFormField } from "../form-field/form-field.js";
|
|
10
|
+
import { KptIcon } from "../icon/icon.js";
|
|
11
|
+
import { KptInput } from "../input/input.js";
|
|
12
|
+
import { KptPassword } from "../password/password.js";
|
|
13
|
+
import { KPT_AUTH_MIN_PASSWORD, } from "./auth-types.js";
|
|
14
|
+
function makeSlot(name) {
|
|
15
|
+
const Slot = (_props) => null;
|
|
16
|
+
Slot.kptAuthSlot = name;
|
|
17
|
+
Slot.displayName = `KptAuth.${name}`;
|
|
18
|
+
return Slot;
|
|
19
|
+
}
|
|
20
|
+
const Brand = makeSlot('brand');
|
|
21
|
+
const Promo = makeSlot('promo');
|
|
22
|
+
const Sso = makeSlot('sso');
|
|
23
|
+
const Fields = makeSlot('fields');
|
|
24
|
+
const Footer = makeSlot('footer');
|
|
25
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
26
|
+
/**
|
|
27
|
+
* Ekran uwierzytelniania: logowanie, rejestracja i odzyskiwanie hasła w jednym komponencie,
|
|
28
|
+
* z panelem promocyjnym i listą dostawców zewnętrznych.
|
|
29
|
+
*
|
|
30
|
+
* Komponent jest prezentacyjny — nie woła API ani nie nawiguje. Wywołania backendu podpina
|
|
31
|
+
* aplikacja pod `onSignIn` / `onSignUp` / `onResetPassword` / `onProviderSelect`, a stan
|
|
32
|
+
* operacji podaje z powrotem przez `busy`, `error` i `notice`.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <KptAuth providers={providers} busy={loading} error={serverError} onSignIn={login}>
|
|
36
|
+
* <KptAuth.Brand><img src="/logo.svg" alt="Konce" /></KptAuth.Brand>
|
|
37
|
+
* </KptAuth>
|
|
38
|
+
*/
|
|
39
|
+
export function KptAuth({ mode, defaultMode = 'signIn', onModeChange, layout = 'slider', providers = [], providerLayout = 'list', showName = true, requireTerms = true, showRemember = true, busy = false, error = null, notice = null, promoTitle = null, promoText = null, animated = true, animationDuration = 320, onSignIn, onSignUp, onResetPassword, onProviderSelect, renderProvider, locale, dictionary, className, style, children, ...rest }) {
|
|
40
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
41
|
+
const [activeMode, setActiveMode] = useControllableState({
|
|
42
|
+
value: mode,
|
|
43
|
+
defaultValue: defaultMode,
|
|
44
|
+
onChange: onModeChange,
|
|
45
|
+
});
|
|
46
|
+
const uid = useId();
|
|
47
|
+
const ids = {
|
|
48
|
+
signIn: `${uid}-signin`,
|
|
49
|
+
signUp: `${uid}-signup`,
|
|
50
|
+
forgot: `${uid}-forgot`,
|
|
51
|
+
email: `${uid}-email`,
|
|
52
|
+
password: `${uid}-password`,
|
|
53
|
+
name: `${uid}-name`,
|
|
54
|
+
regEmail: `${uid}-reg-email`,
|
|
55
|
+
regPassword: `${uid}-reg-password`,
|
|
56
|
+
regConfirm: `${uid}-reg-confirm`,
|
|
57
|
+
resetEmail: `${uid}-reset-email`,
|
|
58
|
+
};
|
|
59
|
+
const headingRef = useRef(null);
|
|
60
|
+
// ── Sloty ──
|
|
61
|
+
const slots = {};
|
|
62
|
+
Children.forEach(children, (child) => {
|
|
63
|
+
if (!isValidElement(child))
|
|
64
|
+
return;
|
|
65
|
+
const name = child.type?.kptAuthSlot;
|
|
66
|
+
if (name)
|
|
67
|
+
slots[name] = child.props.children;
|
|
68
|
+
});
|
|
69
|
+
// ── Modele formularzy ──
|
|
70
|
+
const [signInModel, setSignInModel] = useState({
|
|
71
|
+
email: '',
|
|
72
|
+
password: '',
|
|
73
|
+
remember: false,
|
|
74
|
+
});
|
|
75
|
+
const [signUpModel, setSignUpModel] = useState({
|
|
76
|
+
name: '',
|
|
77
|
+
email: '',
|
|
78
|
+
password: '',
|
|
79
|
+
confirm: '',
|
|
80
|
+
terms: false,
|
|
81
|
+
});
|
|
82
|
+
const [resetModel, setResetModel] = useState({ email: '' });
|
|
83
|
+
const [touched, setTouched] = useState(new Set());
|
|
84
|
+
const touch = (field) => setTouched((prev) => new Set(prev).add(field));
|
|
85
|
+
/*
|
|
86
|
+
* Walidacja jest bezwarunkowa i zwraca sam `kind` — komunikat składamy dopiero przy
|
|
87
|
+
* renderowaniu, dzięki czemu zmiana języka w runtime przerysowuje błędy. Bramka
|
|
88
|
+
* „nie strasz czerwienią świeżego pola" (`touched`) żyje w prezentacji, nie w walidatorze,
|
|
89
|
+
* więc `invalid` mówi prawdę także wtedy, gdy pola nikt jeszcze nie dotknął.
|
|
90
|
+
*/
|
|
91
|
+
const signInErrors = {
|
|
92
|
+
email: !signInModel.email ? 'required' : !EMAIL_RE.test(signInModel.email) ? 'email' : null,
|
|
93
|
+
password: !signInModel.password ? 'required' : null,
|
|
94
|
+
};
|
|
95
|
+
const signUpErrors = {
|
|
96
|
+
name: showName && !signUpModel.name ? 'required' : null,
|
|
97
|
+
email: !signUpModel.email ? 'required' : !EMAIL_RE.test(signUpModel.email) ? 'email' : null,
|
|
98
|
+
password: !signUpModel.password
|
|
99
|
+
? 'required'
|
|
100
|
+
: signUpModel.password.length < KPT_AUTH_MIN_PASSWORD
|
|
101
|
+
? 'minLength'
|
|
102
|
+
: null,
|
|
103
|
+
// Puste „powtórz hasło" nie jest jeszcze niezgodnością — dopiero wpisana, inna wartość.
|
|
104
|
+
confirm: signUpModel.confirm && signUpModel.confirm !== signUpModel.password ? 'mismatch' : null,
|
|
105
|
+
terms: requireTerms && !signUpModel.terms ? 'required' : null,
|
|
106
|
+
};
|
|
107
|
+
const resetErrors = {
|
|
108
|
+
email: !resetModel.email ? 'required' : !EMAIL_RE.test(resetModel.email) ? 'email' : null,
|
|
109
|
+
};
|
|
110
|
+
const message = (kind) => {
|
|
111
|
+
switch (kind) {
|
|
112
|
+
case 'required':
|
|
113
|
+
return t('auth.errorRequired');
|
|
114
|
+
case 'email':
|
|
115
|
+
return t('auth.errorEmail');
|
|
116
|
+
case 'minLength':
|
|
117
|
+
return t('auth.errorMinLength', { min: KPT_AUTH_MIN_PASSWORD });
|
|
118
|
+
case 'mismatch':
|
|
119
|
+
return t('auth.errorMismatch');
|
|
120
|
+
default:
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const errorFor = (form, field, kinds) => touched.has(`${form}.${field}`) ? message(kinds[field] ?? null) : null;
|
|
125
|
+
const anyError = (kinds) => Object.values(kinds).some((kind) => kind !== null);
|
|
126
|
+
const submitAll = (form, fields) => {
|
|
127
|
+
// Submit dotyka całego formularza — inaczej pola, których użytkownik nie ruszył, nie
|
|
128
|
+
// pokazałyby błędu i przycisk wyglądałby na zepsuty.
|
|
129
|
+
setTouched((prev) => {
|
|
130
|
+
const next = new Set(prev);
|
|
131
|
+
for (const field of fields)
|
|
132
|
+
next.add(`${form}.${field}`);
|
|
133
|
+
return next;
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
const changeMode = (next) => {
|
|
137
|
+
if (next === activeMode)
|
|
138
|
+
return;
|
|
139
|
+
setActiveMode(next);
|
|
140
|
+
// Fokus na nagłówku nowego trybu: bez tego czytnik nie ogłosi zmiany, a fokus zostaje
|
|
141
|
+
// na przycisku, który właśnie zjechał z ekranu.
|
|
142
|
+
requestAnimationFrame(() => headingRef.current?.focus());
|
|
143
|
+
};
|
|
144
|
+
const selectProvider = (provider) => {
|
|
145
|
+
if (busy)
|
|
146
|
+
return;
|
|
147
|
+
onProviderSelect?.(provider);
|
|
148
|
+
};
|
|
149
|
+
const signUpActive = activeMode === 'signUp';
|
|
150
|
+
const promoMode = signUpActive ? 'signIn' : 'signUp';
|
|
151
|
+
const promoHeading = promoTitle === null ? t(`auth.promo.${promoMode}Title`) : promoTitle;
|
|
152
|
+
const promoBody = promoText === null ? t(`auth.promo.${promoMode}Text`) : promoText;
|
|
153
|
+
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
|
+
event.preventDefault();
|
|
155
|
+
submitAll('signIn', ['email', 'password']);
|
|
156
|
+
if (!anyError(signInErrors))
|
|
157
|
+
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, disabled: anyError(signInErrors), children: t('auth.signInSubmit') })] })) : null, activeMode === 'signUp' ? (_jsxs("form", { className: "kpt-auth__form", "aria-labelledby": ids.signUp, onSubmit: (event) => {
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
submitAll('signUp', ['name', 'email', 'password', 'confirm', 'terms']);
|
|
161
|
+
if (!anyError(signUpErrors)) {
|
|
162
|
+
const { name, email, password, terms } = signUpModel;
|
|
163
|
+
onSignUp?.({ name, email, password, terms });
|
|
164
|
+
}
|
|
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, disabled: anyError(signUpErrors), children: t('auth.signUpSubmit') })] })) : null, activeMode === 'forgot' ? (_jsxs("form", { className: "kpt-auth__form", "aria-labelledby": ids.forgot, onSubmit: (event) => {
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
submitAll('forgot', ['email']);
|
|
168
|
+
if (!anyError(resetErrors))
|
|
169
|
+
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, disabled: anyError(resetErrors), 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
|
+
const custom = renderProvider?.(provider, () => selectProvider(provider));
|
|
172
|
+
// Fragment, nie `<span>`: lista dostawców to grid, więc dodatkowy
|
|
173
|
+
// element stałby się jej dzieckiem i własny przycisk przestałby
|
|
174
|
+
// rozciągać się na szerokość kolumny (Angular wstawia `ng-container`).
|
|
175
|
+
if (custom)
|
|
176
|
+
return _jsx(Fragment, { children: custom }, provider.id);
|
|
177
|
+
return (_jsxs("button", { type: "button", className: "kpt-auth__provider", disabled: busy, "aria-label": t('auth.continueWith', { provider: provider.label }), onClick: () => selectProvider(provider), children: [provider.iconUrl ? (_jsx("img", { className: "kpt-auth__provider-logo", src: provider.iconUrl, alt: "" })) : provider.icon ? (_jsx(KptIcon, { className: "kpt-auth__provider-logo", name: provider.icon })) : null, _jsx("span", { className: "kpt-auth__provider-label", children: provider.label }), provider.description && providerLayout === 'list' ? (_jsx("span", { className: "kpt-auth__provider-desc", children: provider.description })) : null] }, provider.id));
|
|
178
|
+
}) })] })) : null, _jsxs("p", { className: "kpt-auth__switch", children: [signUpActive ? t('auth.haveAccount') : t('auth.noAccount'), ' ', _jsx("button", { type: "button", className: "kpt-auth__link", onClick: () => changeMode(signUpActive ? 'signIn' : 'signUp'), children: signUpActive ? t('auth.signInSwitch') : t('auth.signUpSwitch') })] })] })) : null, _jsx("div", { className: "kpt-auth__footer", children: slots.footer })] }) }), _jsx("aside", { className: "kpt-auth__promo", children: _jsxs("div", { className: "kpt-auth__promo-inner", children: [promoHeading ? _jsx("p", { className: "kpt-auth__promo-title", children: promoHeading }) : null, promoBody ? _jsx("p", { className: "kpt-auth__promo-text", children: promoBody }) : null, slots.promo, activeMode !== 'forgot' ? (_jsx(KptButton, { hostProps: { className: 'kpt-auth__promo-cta' }, variant: "outline", onClick: () => changeMode(promoMode), children: promoMode === 'signUp' ? t('auth.signUpSwitch') : t('auth.signInSwitch') })) : null] }) })] }) }) }));
|
|
179
|
+
}
|
|
180
|
+
KptAuth.Brand = Brand;
|
|
181
|
+
KptAuth.Promo = Promo;
|
|
182
|
+
KptAuth.Sso = Sso;
|
|
183
|
+
KptAuth.Fields = Fields;
|
|
184
|
+
KptAuth.Footer = Footer;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
export interface KptAutocompleteProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange'> {
|
|
3
|
+
options?: string[];
|
|
4
|
+
value?: string;
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
onValueChange?: (value: string) => void;
|
|
7
|
+
invalid?: boolean;
|
|
8
|
+
touched?: boolean;
|
|
9
|
+
onTouch?: () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Pole z podpowiedziami. Wartością jest wpisany tekst, nie wybrana opcja — podpowiedzi
|
|
13
|
+
* skracają pisanie, ale nie ograniczają wpisu, więc pole nadaje się też do wartości spoza listy.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <KptAutocomplete options={cities} value={city} onValueChange={setCity} placeholder="Miasto…" />
|
|
17
|
+
*/
|
|
18
|
+
export declare function KptAutocomplete({ options, value, defaultValue, onValueChange, invalid, touched, onTouch, disabled, className, onBlur, onFocus, onKeyDown, ref, ...rest }: Readonly<KptAutocompleteProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { composeRefs } from "../internal/compose-refs.js";
|
|
5
|
+
import { KptOverlay } from "../internal/overlay.js";
|
|
6
|
+
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
7
|
+
/**
|
|
8
|
+
* Pole z podpowiedziami. Wartością jest wpisany tekst, nie wybrana opcja — podpowiedzi
|
|
9
|
+
* skracają pisanie, ale nie ograniczają wpisu, więc pole nadaje się też do wartości spoza listy.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <KptAutocomplete options={cities} value={city} onValueChange={setCity} placeholder="Miasto…" />
|
|
13
|
+
*/
|
|
14
|
+
export function KptAutocomplete({ options = [], value, defaultValue = '', onValueChange, invalid = false, touched, onTouch, disabled = false, className, onBlur, onFocus, onKeyDown, ref, ...rest }) {
|
|
15
|
+
const inputRef = useRef(null);
|
|
16
|
+
const [current, setCurrent] = useControllableState({
|
|
17
|
+
value,
|
|
18
|
+
defaultValue,
|
|
19
|
+
onChange: onValueChange,
|
|
20
|
+
});
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
23
|
+
const showInvalid = invalid && (touched ?? true);
|
|
24
|
+
const filtered = useMemo(() => {
|
|
25
|
+
const q = current.trim().toLowerCase();
|
|
26
|
+
return q ? options.filter((option) => option.toLowerCase().includes(q)) : options;
|
|
27
|
+
}, [options, current]);
|
|
28
|
+
const pick = useCallback((option) => {
|
|
29
|
+
setCurrent(option);
|
|
30
|
+
setOpen(false);
|
|
31
|
+
}, [setCurrent]);
|
|
32
|
+
const handleKeyDown = (event) => {
|
|
33
|
+
onKeyDown?.(event);
|
|
34
|
+
if (event.key === 'ArrowDown') {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
setOpen(true);
|
|
37
|
+
setActiveIndex((index) => Math.min(index + 1, filtered.length - 1));
|
|
38
|
+
}
|
|
39
|
+
else if (event.key === 'ArrowUp') {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
setActiveIndex((index) => Math.max(index - 1, 0));
|
|
42
|
+
}
|
|
43
|
+
else if (event.key === 'Enter') {
|
|
44
|
+
const option = filtered[activeIndex];
|
|
45
|
+
if (option) {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
pick(option);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (event.key === 'Escape') {
|
|
51
|
+
setOpen(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return (_jsxs("span", { className: "kpt-autocomplete-host", children: [_jsx("input", { ref: composeRefs(inputRef, ref), className: cn('kpt-autocomplete__input', className), type: "text", role: "combobox", "aria-expanded": open, "aria-invalid": showInvalid || undefined, disabled: disabled, value: current, onChange: (event) => {
|
|
55
|
+
setCurrent(event.target.value);
|
|
56
|
+
setOpen(true);
|
|
57
|
+
setActiveIndex(-1);
|
|
58
|
+
}, onFocus: (event) => {
|
|
59
|
+
onFocus?.(event);
|
|
60
|
+
setOpen(true);
|
|
61
|
+
}, onBlur: (event) => {
|
|
62
|
+
onBlur?.(event);
|
|
63
|
+
setOpen(false);
|
|
64
|
+
onTouch?.();
|
|
65
|
+
}, onKeyDown: handleKeyDown, ...rest }), _jsx(KptOverlay, { open: open && filtered.length > 0, anchor: inputRef.current, onClose: () => setOpen(false), matchAnchorWidth: true, withBackdrop: false, children: _jsx("ul", { className: "kpt-autocomplete__panel", role: "listbox", children: filtered.map((option, index) => (_jsx("li", { className: cn('kpt-autocomplete__option', index === activeIndex && 'is-active'), role: "option", "aria-selected": index === activeIndex,
|
|
66
|
+
// `mousedown` z zablokowanym domyślnym zachowaniem: klik w opcję nie może
|
|
67
|
+
// zabrać fokusu polu, bo `blur` zamknąłby panel przed wyborem.
|
|
68
|
+
onMouseDown: (event) => {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
pick(option);
|
|
71
|
+
}, onMouseEnter: () => setActiveIndex(index), children: option }, option))) }) })] }));
|
|
72
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import type { KptHostProps } from '../internal/types.ts';
|
|
3
|
+
export type KptAvatarSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
4
|
+
export type KptAvatarShape = 'circle' | 'square';
|
|
5
|
+
export interface KptAvatarProps extends Omit<ComponentPropsWithRef<'span'>, 'children'> {
|
|
6
|
+
/** Adres obrazu; ma pierwszeństwo przed inicjałami i ikoną. */
|
|
7
|
+
src?: string | null;
|
|
8
|
+
/** Imię i nazwisko — źródło inicjałów, `title` oraz etykiety dostępności. */
|
|
9
|
+
name?: string;
|
|
10
|
+
/** Nazwa ikony z rejestru, gdy nie ma obrazu ani nazwy. */
|
|
11
|
+
icon?: string | null;
|
|
12
|
+
size?: KptAvatarSize;
|
|
13
|
+
shape?: KptAvatarShape;
|
|
14
|
+
/**
|
|
15
|
+
* Propsy wrappera hosta (`.kpt-avatar-host`). To on jest elementem układu, więc klasy
|
|
16
|
+
* pozycjonujące awatar w rodzicu — jak `kpt-card__avatar` — idą właśnie tutaj.
|
|
17
|
+
*/
|
|
18
|
+
hostProps?: KptHostProps<'span'>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Awatar — obraz, inicjały (z `name`) lub ikona.
|
|
22
|
+
*
|
|
23
|
+
* `name` trafia do natywnego `title` (podpowiedź spod kursora — inicjały same w sobie nie mówią,
|
|
24
|
+
* kto to jest). Bez `src` awatar dostaje dodatkowo `role="img"` i `aria-label`, a same inicjały
|
|
25
|
+
* są `aria-hidden`, żeby czytnik nie przeczytał „AK" litera po literze. Przy `src` etykietę
|
|
26
|
+
* niesie już `alt` obrazu — nie dublujemy jej.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* <KptAvatar src="/u/anna.jpg" />
|
|
30
|
+
* <KptAvatar name="Anna Kowalska" size="lg" />
|
|
31
|
+
* <KptAvatar icon="user" shape="square" />
|
|
32
|
+
*/
|
|
33
|
+
export declare function KptAvatar({ src, name, icon, size, shape, hostProps, className, ...rest }: Readonly<KptAvatarProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { KptIcon } from "../icon/icon.js";
|
|
4
|
+
/**
|
|
5
|
+
* Awatar — obraz, inicjały (z `name`) lub ikona.
|
|
6
|
+
*
|
|
7
|
+
* `name` trafia do natywnego `title` (podpowiedź spod kursora — inicjały same w sobie nie mówią,
|
|
8
|
+
* kto to jest). Bez `src` awatar dostaje dodatkowo `role="img"` i `aria-label`, a same inicjały
|
|
9
|
+
* są `aria-hidden`, żeby czytnik nie przeczytał „AK" litera po literze. Przy `src` etykietę
|
|
10
|
+
* niesie już `alt` obrazu — nie dublujemy jej.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <KptAvatar src="/u/anna.jpg" />
|
|
14
|
+
* <KptAvatar name="Anna Kowalska" size="lg" />
|
|
15
|
+
* <KptAvatar icon="user" shape="square" />
|
|
16
|
+
*/
|
|
17
|
+
export function KptAvatar({ src = null, name = '', icon = null, size = 'md', shape = 'circle', hostProps, className, ...rest }) {
|
|
18
|
+
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
19
|
+
const labelled = !src && !!name;
|
|
20
|
+
return (_jsx("span", { className: cn('kpt-avatar-host', hostClassName), ...hostRest, children: _jsx("span", { className: cn('kpt-avatar', className), ...rest, "data-size": size, "data-shape": shape, title: name || undefined, role: labelled ? 'img' : undefined, "aria-label": labelled ? name : undefined, children: src ? (_jsx("img", { className: "kpt-avatar__img", src: src, alt: name })) : name ? (_jsx("span", { className: "kpt-avatar__initials", "aria-hidden": "true", children: initialsOf(name) })) : icon ? (_jsx(KptIcon, { name: icon })) : null }) }));
|
|
21
|
+
}
|
|
22
|
+
/** Do dwóch pierwszych liter z kolejnych członów nazwy. */
|
|
23
|
+
function initialsOf(name) {
|
|
24
|
+
return name
|
|
25
|
+
.trim()
|
|
26
|
+
.split(/\s+/)
|
|
27
|
+
.slice(0, 2)
|
|
28
|
+
.map((word) => word[0]?.toUpperCase() ?? '')
|
|
29
|
+
.join('');
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptAvatarSize } from '../avatar/avatar.tsx';
|
|
3
|
+
export interface KptAvatarItem {
|
|
4
|
+
name?: string;
|
|
5
|
+
src?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface KptAvatarGroupProps extends ComponentPropsWithRef<'div'> {
|
|
9
|
+
avatars?: KptAvatarItem[];
|
|
10
|
+
/** Ile awatarów pokazać, zanim pojawi się „+N". */
|
|
11
|
+
max?: number;
|
|
12
|
+
size?: KptAvatarSize;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Grupa nakładających się awatarów z licznikiem „+N". Buduje na `KptAvatar`, więc każdy element
|
|
16
|
+
* dziedziczy jego `title` z imieniem — inicjały same w sobie nie mówią, kto to jest.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <KptAvatarGroup avatars={team} max={4} />
|
|
20
|
+
*/
|
|
21
|
+
export declare function KptAvatarGroup({ avatars, max, size, className, ...rest }: Readonly<KptAvatarGroupProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { KptAvatar } from "../avatar/avatar.js";
|
|
4
|
+
/**
|
|
5
|
+
* Grupa nakładających się awatarów z licznikiem „+N". Buduje na `KptAvatar`, więc każdy element
|
|
6
|
+
* dziedziczy jego `title` z imieniem — inicjały same w sobie nie mówią, kto to jest.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <KptAvatarGroup avatars={team} max={4} />
|
|
10
|
+
*/
|
|
11
|
+
export function KptAvatarGroup({ avatars = [], max = 4, size = 'md', className, ...rest }) {
|
|
12
|
+
const shown = avatars.slice(0, max);
|
|
13
|
+
const overflow = Math.max(0, avatars.length - max);
|
|
14
|
+
return (_jsx("div", { className: cn('kpt-avatar-group-host', className), ...rest, children: _jsxs("div", { className: "kpt-avatar-group", "data-size": size, children: [shown.map((avatar, index) => (_jsx("span", { className: "kpt-avatar-group__item", children: _jsx(KptAvatar, { name: avatar.name || '', src: avatar.src || null, icon: avatar.icon || null, size: size }) }, index))), overflow > 0 ? (_jsxs("span", { className: "kpt-avatar-group__item kpt-avatar-group__more", "data-size": size, children: ["+", overflow] })) : null] }) }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from 'react';
|
|
2
|
+
export type KptBadgeVariant = 'neutral' | 'primary' | 'success' | 'danger' | 'warning' | 'info';
|
|
3
|
+
export type KptBadgeSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
export interface KptBadgeProps extends ComponentPropsWithRef<'span'> {
|
|
5
|
+
value?: string | number | null;
|
|
6
|
+
variant?: KptBadgeVariant;
|
|
7
|
+
/** Rozmiar — `sm` mieści się w wierszu tekstu, `md` to domyślny licznik. */
|
|
8
|
+
size?: KptBadgeSize;
|
|
9
|
+
/** Sama kropka statusu, bez liczby. */
|
|
10
|
+
dot?: boolean;
|
|
11
|
+
/** Nałożenie na treść podaną jako `children`. */
|
|
12
|
+
overlay?: boolean;
|
|
13
|
+
/** Powyżej tej wartości licznik pokazuje `99+`. */
|
|
14
|
+
max?: number;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Znacznik (badge) — licznik lub kropka statusu. Samodzielny albo nałożony na treść (`overlay`).
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <KptBadge value={3} variant="danger" />
|
|
22
|
+
* <KptBadge value={128} overlay><KptIcon name="menu" /></KptBadge>
|
|
23
|
+
* <KptBadge dot variant="success" overlay><KptAvatar name="AK" /></KptBadge>
|
|
24
|
+
*/
|
|
25
|
+
export declare function KptBadge({ value, variant, size, dot, overlay, max, className, children, ...rest }: Readonly<KptBadgeProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
/**
|
|
4
|
+
* Znacznik (badge) — licznik lub kropka statusu. Samodzielny albo nałożony na treść (`overlay`).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* <KptBadge value={3} variant="danger" />
|
|
8
|
+
* <KptBadge value={128} overlay><KptIcon name="menu" /></KptBadge>
|
|
9
|
+
* <KptBadge dot variant="success" overlay><KptAvatar name="AK" /></KptBadge>
|
|
10
|
+
*/
|
|
11
|
+
export function KptBadge({ value = null, variant = 'primary', size = 'md', dot = false, overlay = false, max = 99, className, children, ...rest }) {
|
|
12
|
+
const display = typeof value === 'number' && value > max ? `${max}+` : (value ?? '');
|
|
13
|
+
return (_jsxs("span", { className: cn('kpt-badge-host', className), ...rest, children: [children, _jsx("span", { className: cn('kpt-badge', overlay && 'kpt-badge--overlay', dot && 'kpt-badge--dot'), "data-variant": variant, "data-size": size, children: dot ? '' : display })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
+
export interface KptBottomSheetProps extends ComponentPropsWithRef<'div'>, KptI18nOverrides {
|
|
4
|
+
open: boolean;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
header?: string;
|
|
7
|
+
closeOnBackdrop?: boolean;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Arkusz wysuwany z dołu ekranu.
|
|
12
|
+
*
|
|
13
|
+
* Arkusz zostaje w DOM także zamknięty — chowa go `transform` i `visibility`, nigdy
|
|
14
|
+
* `display: none`, bo inaczej animacje wjazdu i zjazdu nie miałyby od czego wystartować.
|
|
15
|
+
* `visibility` wyjmuje go przy tym z kolejności Tab i z drzewa dostępności bez `inert`,
|
|
16
|
+
* i robi to już na pierwszej klatce.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <KptBottomSheet open={open} onOpenChange={setOpen} header="Udostępnij">…</KptBottomSheet>
|
|
20
|
+
*/
|
|
21
|
+
export declare function KptBottomSheet({ open, onOpenChange, header, closeOnBackdrop, locale, dictionary, className, children, ...rest }: Readonly<KptBottomSheetProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
4
|
+
/**
|
|
5
|
+
* Arkusz wysuwany z dołu ekranu.
|
|
6
|
+
*
|
|
7
|
+
* Arkusz zostaje w DOM także zamknięty — chowa go `transform` i `visibility`, nigdy
|
|
8
|
+
* `display: none`, bo inaczej animacje wjazdu i zjazdu nie miałyby od czego wystartować.
|
|
9
|
+
* `visibility` wyjmuje go przy tym z kolejności Tab i z drzewa dostępności bez `inert`,
|
|
10
|
+
* i robi to już na pierwszej klatce.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <KptBottomSheet open={open} onOpenChange={setOpen} header="Udostępnij">…</KptBottomSheet>
|
|
14
|
+
*/
|
|
15
|
+
export function KptBottomSheet({ open, onOpenChange, header = '', closeOnBackdrop = true, locale, dictionary, className, children, ...rest }) {
|
|
16
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
17
|
+
const close = () => onOpenChange(false);
|
|
18
|
+
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", { className: cn('kpt-bottom-sheet__panel', open && 'is-open'), role: "dialog", "aria-modal": "true", tabIndex: -1, onKeyDown: (event) => {
|
|
19
|
+
if (event.key === 'Escape')
|
|
20
|
+
close();
|
|
21
|
+
}, 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", children: header }) : null, _jsx("div", { className: "kpt-bottom-sheet__body", children: children })] })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
+
export interface KptBreadcrumbItem {
|
|
4
|
+
label: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface KptBreadcrumbProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
|
|
8
|
+
items?: KptBreadcrumbItem[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Ścieżka nawigacji (breadcrumbs).
|
|
12
|
+
*
|
|
13
|
+
* Ostatnia pozycja nigdy nie jest odnośnikiem, nawet gdy ma `url` — to bieżąca strona, a link
|
|
14
|
+
* prowadzący w to samo miejsce, w którym już jesteś, tylko myli.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <KptBreadcrumb items={[{ label: 'Start', url: '/' }, { label: 'Ustawienia' }]} />
|
|
18
|
+
*/
|
|
19
|
+
export declare function KptBreadcrumb({ items, locale, dictionary, className, ...rest }: Readonly<KptBreadcrumbProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
4
|
+
import { KptIcon } from "../icon/icon.js";
|
|
5
|
+
/**
|
|
6
|
+
* Ścieżka nawigacji (breadcrumbs).
|
|
7
|
+
*
|
|
8
|
+
* Ostatnia pozycja nigdy nie jest odnośnikiem, nawet gdy ma `url` — to bieżąca strona, a link
|
|
9
|
+
* prowadzący w to samo miejsce, w którym już jesteś, tylko myli.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <KptBreadcrumb items={[{ label: 'Start', url: '/' }, { label: 'Ustawienia' }]} />
|
|
13
|
+
*/
|
|
14
|
+
export function KptBreadcrumb({ items = [], locale, dictionary, className, ...rest }) {
|
|
15
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
16
|
+
return (_jsx("div", { className: cn('kpt-breadcrumb-host', className), ...rest, children: _jsx("nav", { className: "kpt-breadcrumb", "aria-label": t('breadcrumb.label'), children: items.map((item, index) => {
|
|
17
|
+
const last = index === items.length - 1;
|
|
18
|
+
return (_jsxs("span", { style: { display: 'contents' }, 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));
|
|
19
|
+
}) }) }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
import type { AsChildProps, KptHostProps } from '../internal/types.ts';
|
|
3
|
+
export type KptButtonVariant = 'filled' | 'tonal' | 'outline' | 'text' | 'danger';
|
|
4
|
+
export type KptButtonSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
export interface KptButtonProps extends ComponentPropsWithRef<'button'>, AsChildProps {
|
|
6
|
+
/** Wariant wizualny. */
|
|
7
|
+
variant?: KptButtonVariant;
|
|
8
|
+
/** Rozmiar. */
|
|
9
|
+
size?: KptButtonSize;
|
|
10
|
+
/** Spinner + zablokowana interakcja; ustawia `aria-busy`. */
|
|
11
|
+
loading?: boolean;
|
|
12
|
+
/** Pełna szerokość kontenera. */
|
|
13
|
+
fullWidth?: boolean;
|
|
14
|
+
/** Propsy wrappera hosta (`.kpt-button-host`). `className` i `ref` idą na `<button>`. */
|
|
15
|
+
hostProps?: KptHostProps<'span'>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Przycisk Koncept UI. Renderuje natywny `<button>` (semantyka + submit formularza)
|
|
19
|
+
* w hoście `.kpt-button-host` — dwa poziomy DOM, dokładnie jak w porcie Angulara,
|
|
20
|
+
* bo host niesie własne style (`display: inline-flex`, wariant pełnej szerokości).
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <KptButton onClick={save}>Zapisz</KptButton>
|
|
24
|
+
* <KptButton variant="outline" size="sm">Anuluj</KptButton>
|
|
25
|
+
* <KptButton asChild><a href="/docs">Dokumentacja</a></KptButton>
|
|
26
|
+
*/
|
|
27
|
+
export declare function KptButton({ variant, size, type, loading, fullWidth, disabled, asChild, hostProps, className, children, ...rest }: Readonly<KptButtonProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { isValidElement } from 'react';
|
|
3
|
+
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { renderAsChild } from "../internal/slot.js";
|
|
5
|
+
/**
|
|
6
|
+
* Przycisk Koncept UI. Renderuje natywny `<button>` (semantyka + submit formularza)
|
|
7
|
+
* w hoście `.kpt-button-host` — dwa poziomy DOM, dokładnie jak w porcie Angulara,
|
|
8
|
+
* bo host niesie własne style (`display: inline-flex`, wariant pełnej szerokości).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <KptButton onClick={save}>Zapisz</KptButton>
|
|
12
|
+
* <KptButton variant="outline" size="sm">Anuluj</KptButton>
|
|
13
|
+
* <KptButton asChild><a href="/docs">Dokumentacja</a></KptButton>
|
|
14
|
+
*/
|
|
15
|
+
export function KptButton({ variant = 'filled', size = 'md', type = 'button', loading = false, fullWidth = false, disabled = false, asChild = false, hostProps, className, children, ...rest }) {
|
|
16
|
+
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
17
|
+
const isDisabled = disabled || loading;
|
|
18
|
+
// Zawartość jest identyczna w obu trybach — w `asChild` podmieniamy tylko element
|
|
19
|
+
// zewnętrzny, nie strukturę środka, żeby SCSS (`__spinner`, `__content`) dalej działał.
|
|
20
|
+
const content = (_jsxs(_Fragment, { children: [loading ? _jsx("span", { className: "kpt-button__spinner", "aria-hidden": "true" }) : null, _jsx("span", { className: "kpt-button__content", children: asChildContent(asChild, children) })] }));
|
|
21
|
+
const buttonProps = {
|
|
22
|
+
className: cn('kpt-button', loading && 'kpt-button--loading', className),
|
|
23
|
+
'data-variant': variant,
|
|
24
|
+
'data-size': size,
|
|
25
|
+
// React serializuje atrybuty aria-* z booleana na "true"/"false" — wynikowy DOM
|
|
26
|
+
// ma `aria-busy="true"`, tak jak port Angulara.
|
|
27
|
+
'aria-busy': loading || undefined,
|
|
28
|
+
...rest,
|
|
29
|
+
};
|
|
30
|
+
return (_jsx("span", { className: cn('kpt-button-host', fullWidth && 'kpt-button-host--full', hostClassName), ...hostRest, children: asChild && isValidElement(children) ? (renderAsChild(children, { ...buttonProps, ...disabledProps(children, isDisabled) }, content)) : (_jsx("button", { type: type, disabled: isDisabled, ...buttonProps, children: content })) }));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Stan wyłączenia dla podstawionego elementu. Natywny `<button>` dostaje `disabled`
|
|
34
|
+
* (SCSS stylizuje `:disabled`); każdy inny element — `aria-disabled`, bo `disabled`
|
|
35
|
+
* nie ma tam znaczenia. Element bez `:disabled` nie dostanie wyglądu wyłączonego,
|
|
36
|
+
* więc `asChild` + `disabled` to świadomie ograniczone połączenie.
|
|
37
|
+
*/
|
|
38
|
+
function disabledProps(child, isDisabled) {
|
|
39
|
+
if (!isDisabled)
|
|
40
|
+
return {};
|
|
41
|
+
const isNativeButton = isValidElement(child) && child.type === 'button';
|
|
42
|
+
return isNativeButton ? { disabled: true } : { 'aria-disabled': true };
|
|
43
|
+
}
|
|
44
|
+
/** W trybie `asChild` treścią są dzieci podstawionego elementu, nie on sam. */
|
|
45
|
+
function asChildContent(asChild, children) {
|
|
46
|
+
if (!asChild || !isValidElement(children))
|
|
47
|
+
return children;
|
|
48
|
+
return children.props.children;
|
|
49
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import type { KptHostProps } from '../internal/types.ts';
|
|
3
|
+
import type { KptOption } from '../shared/types.ts';
|
|
4
|
+
export interface KptButtonGroupProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'onChange' | 'defaultValue'> {
|
|
5
|
+
options?: KptOption<T>[];
|
|
6
|
+
value?: T;
|
|
7
|
+
defaultValue?: T;
|
|
8
|
+
onValueChange?: (value: T) => void;
|
|
9
|
+
size?: 'sm' | 'md' | 'lg';
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
/** Wywoływane po wyborze — moment, w którym kontrolka staje się dotknięta. */
|
|
12
|
+
onTouch?: () => void;
|
|
13
|
+
/** Propsy wrappera hosta (`.kpt-button-group-host`). */
|
|
14
|
+
hostProps?: KptHostProps<'span'>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Segmentowana grupa przycisków (toggle) — pojedynczy wybór z opcji.
|
|
18
|
+
*
|
|
19
|
+
* Działa kontrolowana (`value` + `onValueChange`) i niekontrolowana (`defaultValue`).
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* <KptButtonGroup value={view} onValueChange={setView}
|
|
23
|
+
* options={[{ label: 'Lista', value: 'list' }, { label: 'Siatka', value: 'grid' }]} />
|
|
24
|
+
*/
|
|
25
|
+
export declare function KptButtonGroup<T>({ options, value, defaultValue, onValueChange, size, disabled, onTouch, hostProps, className, ...rest }: Readonly<KptButtonGroupProps<T>>): import("react").JSX.Element;
|