@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.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/accordion/accordion.d.ts +45 -0
  4. package/dist/accordion/accordion.js +69 -0
  5. package/dist/alert/alert.d.ts +25 -0
  6. package/dist/alert/alert.js +31 -0
  7. package/dist/app-shell/app-shell.d.ts +94 -0
  8. package/dist/app-shell/app-shell.js +105 -0
  9. package/dist/auth/auth-types.d.ts +44 -0
  10. package/dist/auth/auth-types.js +2 -0
  11. package/dist/auth/auth.d.ts +76 -0
  12. package/dist/auth/auth.js +184 -0
  13. package/dist/autocomplete/autocomplete.d.ts +18 -0
  14. package/dist/autocomplete/autocomplete.js +72 -0
  15. package/dist/avatar/avatar.d.ts +33 -0
  16. package/dist/avatar/avatar.js +30 -0
  17. package/dist/avatar-group/avatar-group.d.ts +21 -0
  18. package/dist/avatar-group/avatar-group.js +15 -0
  19. package/dist/badge/badge.d.ts +25 -0
  20. package/dist/badge/badge.js +14 -0
  21. package/dist/bottom-sheet/bottom-sheet.d.ts +21 -0
  22. package/dist/bottom-sheet/bottom-sheet.js +22 -0
  23. package/dist/breadcrumb/breadcrumb.d.ts +19 -0
  24. package/dist/breadcrumb/breadcrumb.js +20 -0
  25. package/dist/button/button.d.ts +27 -0
  26. package/dist/button/button.js +49 -0
  27. package/dist/button-group/button-group.d.ts +25 -0
  28. package/dist/button-group/button-group.js +30 -0
  29. package/dist/card/card.d.ts +66 -0
  30. package/dist/card/card.js +96 -0
  31. package/dist/carousel/carousel.d.ts +20 -0
  32. package/dist/carousel/carousel.js +24 -0
  33. package/dist/charts/chart-legend.d.ts +23 -0
  34. package/dist/charts/chart-legend.js +9 -0
  35. package/dist/charts/chart-tooltip.d.ts +21 -0
  36. package/dist/charts/chart-tooltip.js +13 -0
  37. package/dist/charts/chart.d.ts +18 -0
  38. package/dist/charts/chart.js +231 -0
  39. package/dist/charts-entry/public-api.d.ts +4 -0
  40. package/dist/charts-entry/public-api.js +7 -0
  41. package/dist/checkbox/checkbox.d.ts +33 -0
  42. package/dist/checkbox/checkbox.js +29 -0
  43. package/dist/chip/chip.d.ts +22 -0
  44. package/dist/chip/chip.js +16 -0
  45. package/dist/chips-input/chips-input.d.ts +25 -0
  46. package/dist/chips-input/chips-input.js +54 -0
  47. package/dist/clock/clock.d.ts +17 -0
  48. package/dist/clock/clock.js +57 -0
  49. package/dist/color-picker/color-picker.d.ts +23 -0
  50. package/dist/color-picker/color-picker.js +28 -0
  51. package/dist/confirm/confirm-container.d.ts +13 -0
  52. package/dist/confirm/confirm-container.js +26 -0
  53. package/dist/confirm/confirm-store.d.ts +29 -0
  54. package/dist/confirm/confirm-store.js +52 -0
  55. package/dist/context-menu/context-menu.d.ts +21 -0
  56. package/dist/context-menu/context-menu.js +36 -0
  57. package/dist/data-table/data-table-types.d.ts +67 -0
  58. package/dist/data-table/data-table-types.js +1 -0
  59. package/dist/data-table/data-table.d.ts +62 -0
  60. package/dist/data-table/data-table.js +453 -0
  61. package/dist/data-table/export-csv.d.ts +7 -0
  62. package/dist/data-table/export-csv.js +19 -0
  63. package/dist/data-view/data-view.d.ts +21 -0
  64. package/dist/data-view/data-view.js +25 -0
  65. package/dist/date-range/date-range.d.ts +30 -0
  66. package/dist/date-range/date-range.js +155 -0
  67. package/dist/datepicker/datepicker.d.ts +39 -0
  68. package/dist/datepicker/datepicker.js +202 -0
  69. package/dist/dialog/dialog.d.ts +35 -0
  70. package/dist/dialog/dialog.js +59 -0
  71. package/dist/divider/divider.d.ts +16 -0
  72. package/dist/divider/divider.js +13 -0
  73. package/dist/drawer/drawer.d.ts +25 -0
  74. package/dist/drawer/drawer.js +25 -0
  75. package/dist/empty/empty.d.ts +18 -0
  76. package/dist/empty/empty.js +14 -0
  77. package/dist/fab/fab.d.ts +22 -0
  78. package/dist/fab/fab.js +17 -0
  79. package/dist/fieldset/fieldset.d.ts +22 -0
  80. package/dist/fieldset/fieldset.js +24 -0
  81. package/dist/file-upload/file-upload.d.ts +22 -0
  82. package/dist/file-upload/file-upload.js +50 -0
  83. package/dist/form-field/form-field-context.d.ts +23 -0
  84. package/dist/form-field/form-field-context.js +17 -0
  85. package/dist/form-field/form-field.d.ts +39 -0
  86. package/dist/form-field/form-field.js +41 -0
  87. package/dist/galleria/galleria.d.ts +22 -0
  88. package/dist/galleria/galleria.js +21 -0
  89. package/dist/grid/col.d.ts +39 -0
  90. package/dist/grid/col.js +32 -0
  91. package/dist/grid/flex.d.ts +45 -0
  92. package/dist/grid/flex.js +35 -0
  93. package/dist/grid/grid.d.ts +47 -0
  94. package/dist/grid/grid.js +38 -0
  95. package/dist/grid/layout.d.ts +18 -0
  96. package/dist/grid/layout.js +32 -0
  97. package/dist/grid-entry/public-api.d.ts +6 -0
  98. package/dist/grid-entry/public-api.js +8 -0
  99. package/dist/i18n/i18n-context.d.ts +45 -0
  100. package/dist/i18n/i18n-context.js +65 -0
  101. package/dist/icon/icon-registry.d.ts +16 -0
  102. package/dist/icon/icon-registry.js +43 -0
  103. package/dist/icon/icon.d.ts +17 -0
  104. package/dist/icon/icon.js +23 -0
  105. package/dist/icon-button/icon-button.d.ts +23 -0
  106. package/dist/icon-button/icon-button.js +14 -0
  107. package/dist/icons-entry/public-api.d.ts +12 -0
  108. package/dist/icons-entry/public-api.js +20 -0
  109. package/dist/image/image.d.ts +17 -0
  110. package/dist/image/image.js +16 -0
  111. package/dist/input/input.d.ts +41 -0
  112. package/dist/input/input.js +36 -0
  113. package/dist/input-mask/input-mask.d.ts +19 -0
  114. package/dist/input-mask/input-mask.js +65 -0
  115. package/dist/input-number/input-number.d.ts +28 -0
  116. package/dist/input-number/input-number.js +52 -0
  117. package/dist/input-otp/input-otp.d.ts +21 -0
  118. package/dist/input-otp/input-otp.js +59 -0
  119. package/dist/internal/cn.d.ts +9 -0
  120. package/dist/internal/cn.js +19 -0
  121. package/dist/internal/compose-refs.d.ts +6 -0
  122. package/dist/internal/compose-refs.js +16 -0
  123. package/dist/internal/overlay-container.d.ts +3 -0
  124. package/dist/internal/overlay-container.js +19 -0
  125. package/dist/internal/overlay.d.ts +26 -0
  126. package/dist/internal/overlay.js +64 -0
  127. package/dist/internal/slot.d.ts +23 -0
  128. package/dist/internal/slot.js +49 -0
  129. package/dist/internal/types.d.ts +25 -0
  130. package/dist/internal/types.js +1 -0
  131. package/dist/internal/use-breakpoint.d.ts +18 -0
  132. package/dist/internal/use-breakpoint.js +52 -0
  133. package/dist/internal/use-controllable-state.d.ts +11 -0
  134. package/dist/internal/use-controllable-state.js +18 -0
  135. package/dist/internal/use-id.d.ts +9 -0
  136. package/dist/internal/use-id.js +13 -0
  137. package/dist/knob/knob.d.ts +27 -0
  138. package/dist/knob/knob.js +86 -0
  139. package/dist/listbox/listbox.d.ts +28 -0
  140. package/dist/listbox/listbox.js +43 -0
  141. package/dist/map/map-create-dialog.d.ts +21 -0
  142. package/dist/map/map-create-dialog.js +196 -0
  143. package/dist/map/map-hours-field.d.ts +22 -0
  144. package/dist/map/map-hours-field.js +90 -0
  145. package/dist/map/map-image-field.d.ts +24 -0
  146. package/dist/map/map-image-field.js +61 -0
  147. package/dist/map/map-legend.d.ts +29 -0
  148. package/dist/map/map-legend.js +41 -0
  149. package/dist/map/map-poi-card.d.ts +42 -0
  150. package/dist/map/map-poi-card.js +152 -0
  151. package/dist/map/map-search.d.ts +37 -0
  152. package/dist/map/map-search.js +201 -0
  153. package/dist/map/map.d.ts +113 -0
  154. package/dist/map/map.js +425 -0
  155. package/dist/map/ports.d.ts +76 -0
  156. package/dist/map/ports.js +174 -0
  157. package/dist/map-entry/public-api.d.ts +20 -0
  158. package/dist/map-entry/public-api.js +19 -0
  159. package/dist/megamenu/megamenu.d.ts +17 -0
  160. package/dist/megamenu/megamenu.js +28 -0
  161. package/dist/menu/menu-item.d.ts +33 -0
  162. package/dist/menu/menu-item.js +1 -0
  163. package/dist/menu/menu-rows.d.ts +9 -0
  164. package/dist/menu/menu-rows.js +15 -0
  165. package/dist/menu/menu.d.ts +22 -0
  166. package/dist/menu/menu.js +31 -0
  167. package/dist/menubar/menubar.d.ts +16 -0
  168. package/dist/menubar/menubar.js +39 -0
  169. package/dist/meter-group/meter-group.d.ts +20 -0
  170. package/dist/meter-group/meter-group.js +24 -0
  171. package/dist/order-list/order-list.d.ts +18 -0
  172. package/dist/order-list/order-list.js +37 -0
  173. package/dist/paginator/paginator.d.ts +29 -0
  174. package/dist/paginator/paginator.js +45 -0
  175. package/dist/panel/panel.d.ts +31 -0
  176. package/dist/panel/panel.js +36 -0
  177. package/dist/password/password.d.ts +28 -0
  178. package/dist/password/password.js +37 -0
  179. package/dist/pick-list/pick-list.d.ts +28 -0
  180. package/dist/pick-list/pick-list.js +65 -0
  181. package/dist/popover/popover.d.ts +23 -0
  182. package/dist/popover/popover.js +27 -0
  183. package/dist/progress/progress.d.ts +19 -0
  184. package/dist/progress/progress.js +21 -0
  185. package/dist/public-api.d.ts +181 -0
  186. package/dist/public-api.js +105 -0
  187. package/dist/radio-group/radio-group.d.ts +24 -0
  188. package/dist/radio-group/radio-group.js +23 -0
  189. package/dist/rating/rating.d.ts +22 -0
  190. package/dist/rating/rating.js +30 -0
  191. package/dist/rich-text/lists.d.ts +17 -0
  192. package/dist/rich-text/lists.js +144 -0
  193. package/dist/rich-text/rich-text.d.ts +28 -0
  194. package/dist/rich-text/rich-text.js +654 -0
  195. package/dist/rich-text/sanitize.d.ts +10 -0
  196. package/dist/rich-text/sanitize.js +85 -0
  197. package/dist/rich-text/tables.d.ts +44 -0
  198. package/dist/rich-text/tables.js +268 -0
  199. package/dist/roadmap/roadmap.d.ts +37 -0
  200. package/dist/roadmap/roadmap.js +271 -0
  201. package/dist/roadmap-entry/public-api.d.ts +4 -0
  202. package/dist/roadmap-entry/public-api.js +7 -0
  203. package/dist/scroll-top/scroll-top.d.ts +16 -0
  204. package/dist/scroll-top/scroll-top.js +38 -0
  205. package/dist/select/select.d.ts +37 -0
  206. package/dist/select/select.js +117 -0
  207. package/dist/shared/types.d.ts +8 -0
  208. package/dist/shared/types.js +1 -0
  209. package/dist/sidenav/sidenav.d.ts +26 -0
  210. package/dist/sidenav/sidenav.js +41 -0
  211. package/dist/skeleton/skeleton.d.ts +20 -0
  212. package/dist/skeleton/skeleton.js +15 -0
  213. package/dist/slider/slider.d.ts +28 -0
  214. package/dist/slider/slider.js +30 -0
  215. package/dist/speed-dial/speed-dial.d.ts +20 -0
  216. package/dist/speed-dial/speed-dial.js +23 -0
  217. package/dist/spinner/spinner.d.ts +15 -0
  218. package/dist/spinner/spinner.js +14 -0
  219. package/dist/split-button/split-button.d.ts +28 -0
  220. package/dist/split-button/split-button.js +34 -0
  221. package/dist/splitter/splitter.d.ts +39 -0
  222. package/dist/splitter/splitter.js +80 -0
  223. package/dist/stepper/stepper.d.ts +51 -0
  224. package/dist/stepper/stepper.js +71 -0
  225. package/dist/switch/switch.d.ts +39 -0
  226. package/dist/switch/switch.js +30 -0
  227. package/dist/switch-group/switch-group.d.ts +29 -0
  228. package/dist/switch-group/switch-group.js +47 -0
  229. package/dist/tabs/tabs.d.ts +38 -0
  230. package/dist/tabs/tabs.js +74 -0
  231. package/dist/textarea/textarea.d.ts +28 -0
  232. package/dist/textarea/textarea.js +44 -0
  233. package/dist/timeline/timeline.d.ts +19 -0
  234. package/dist/timeline/timeline.js +12 -0
  235. package/dist/toast/toast-container.d.ts +12 -0
  236. package/dist/toast/toast-container.js +23 -0
  237. package/dist/toast/toast-store.d.ts +26 -0
  238. package/dist/toast/toast-store.js +51 -0
  239. package/dist/toolbar/toolbar.d.ts +29 -0
  240. package/dist/toolbar/toolbar.js +24 -0
  241. package/dist/tooltip/tooltip.d.ts +26 -0
  242. package/dist/tooltip/tooltip.js +113 -0
  243. package/dist/tree/tree.d.ts +31 -0
  244. package/dist/tree/tree.js +62 -0
  245. package/package.json +91 -0
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, 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 { KptIcon } from "../icon/icon.js";
7
+ /**
8
+ * Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
9
+ * opcjonalny filtr.
10
+ *
11
+ * W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją — lista bywa filtrem,
12
+ * a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
13
+ *
14
+ * @example
15
+ * <KptListbox options={roles} value={role} onValueChange={setRole} filter />
16
+ * <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
17
+ */
18
+ export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
19
+ const { t } = useKptMessages({ locale, dictionary });
20
+ const [current, setCurrent] = useControllableState({
21
+ value,
22
+ defaultValue,
23
+ onChange: onValueChange,
24
+ });
25
+ const [query, setQuery] = useState('');
26
+ const showInvalid = invalid && (touched ?? true);
27
+ const visible = useMemo(() => {
28
+ const q = query.trim().toLowerCase();
29
+ return q ? options.filter((option) => option.label.toLowerCase().includes(q)) : options;
30
+ }, [options, query]);
31
+ const isSelected = (candidate) => multiple ? Array.isArray(current) && current.includes(candidate) : current === candidate;
32
+ const toggle = (candidate) => {
33
+ if (multiple) {
34
+ const list = Array.isArray(current) ? current : [];
35
+ setCurrent(list.includes(candidate) ? list.filter((item) => item !== candidate) : [...list, candidate]);
36
+ }
37
+ else {
38
+ setCurrent(current === candidate ? null : candidate);
39
+ }
40
+ onTouch?.();
41
+ };
42
+ return (_jsx("div", { className: cn('kpt-listbox-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-listbox', showInvalid && 'kpt-listbox--invalid'), role: "listbox", "aria-multiselectable": multiple, children: [filter ? (_jsxs("div", { className: "kpt-listbox__filter", children: [_jsx(KptIcon, { name: "search" }), _jsx("input", { type: "text", placeholder: t('listbox.search'), value: query, onChange: (event) => setQuery(event.target.value) })] })) : null, _jsx("div", { className: "kpt-listbox__items", children: visible.length ? (visible.map((option) => (_jsxs("button", { type: "button", role: "option", className: cn('kpt-listbox__item', isSelected(option.value) && 'is-selected'), "aria-selected": isSelected(option.value), disabled: disabled || option.disabled, onClick: () => toggle(option.value), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, String(option.value))))) : (_jsx("div", { className: "kpt-listbox__empty", children: t('listbox.empty') })) })] }) }));
43
+ }
@@ -0,0 +1,21 @@
1
+ import type { KptLatLng, KptMapCategory } from '@konce-pt/map';
2
+ import { type ComponentPropsWithRef } from 'react';
3
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
+ import type { KptMapGeocoderPort } from './ports.ts';
5
+ export interface KptMapCreateDialogProps extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'onSubmit'>, KptI18nOverrides {
6
+ category: KptMapCategory;
7
+ at: KptLatLng;
8
+ /** Geokoder tego formularza — źródło podpowiedzi adresowych dla pól z `autofill`. */
9
+ geocoder?: KptMapGeocoderPort | null;
10
+ onSave: (value: Record<string, unknown>) => void;
11
+ onCancel: () => void;
12
+ }
13
+ /**
14
+ * Treść dialogu „dodaj lokalizację" budowana ze schemy kategorii (`category.fields`).
15
+ *
16
+ * Port Angulara stoi tu na Signal Forms; w Reakcie formularza-frameworka nie ma, więc model to
17
+ * zwykły stan, a reguły walidacji są mapą funkcji obok niego. Kontrakt zostaje ten sam:
18
+ * walidacja deklarowana raz przy schemie, a bramka „nie strasz czerwienią" siedzi w prezentacji
19
+ * — błąd pokazuje się po dotknięciu pola albo po próbie zapisu.
20
+ */
21
+ export declare function KptMapCreateDialog({ category, at, geocoder, onSave, onCancel, locale: localeOverride, dictionary, className, ...rest }: Readonly<KptMapCreateDialogProps>): import("react").JSX.Element;
@@ -0,0 +1,196 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { emptyWeek, parseTime, weekdayName } from '@konce-pt/map';
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import { cn } from "../internal/cn.js";
5
+ import { useKptMessages } from "../i18n/i18n-context.js";
6
+ import { KptButton } from "../button/button.js";
7
+ import { KptCheckbox } from "../checkbox/checkbox.js";
8
+ import { KptDatepicker } from "../datepicker/datepicker.js";
9
+ import { KptFormField } from "../form-field/form-field.js";
10
+ import { KptInput } from "../input/input.js";
11
+ import { KptInputNumber } from "../input-number/input-number.js";
12
+ import { KptSelect } from "../select/select.js";
13
+ import { KptTextarea } from "../textarea/textarea.js";
14
+ import { KptMapHoursField } from "./map-hours-field.js";
15
+ import { KptMapImageField } from "./map-image-field.js";
16
+ /** Typy pól, które przyjmą fragment adresu. Data czy liczba nie mają z nim nic wspólnego. */
17
+ const TEXTUAL = new Set(['text', 'textarea']);
18
+ /** Pusta wartość właściwa dla typu pola — formularz startuje bez `undefined`. */
19
+ function initialValue(def) {
20
+ if (def.initial !== undefined)
21
+ return def.initial;
22
+ switch (def.type) {
23
+ case 'number':
24
+ return null;
25
+ case 'checkbox':
26
+ return false;
27
+ case 'multiselect':
28
+ return [];
29
+ case 'select':
30
+ return null;
31
+ case 'hours':
32
+ return emptyWeek();
33
+ default:
34
+ return '';
35
+ }
36
+ }
37
+ function initialModel(fields) {
38
+ const out = {};
39
+ for (const def of fields)
40
+ out[def.key] = initialValue(def);
41
+ return out;
42
+ }
43
+ /**
44
+ * Treść dialogu „dodaj lokalizację" budowana ze schemy kategorii (`category.fields`).
45
+ *
46
+ * Port Angulara stoi tu na Signal Forms; w Reakcie formularza-frameworka nie ma, więc model to
47
+ * zwykły stan, a reguły walidacji są mapą funkcji obok niego. Kontrakt zostaje ten sam:
48
+ * walidacja deklarowana raz przy schemie, a bramka „nie strasz czerwienią" siedzi w prezentacji
49
+ * — błąd pokazuje się po dotknięciu pola albo po próbie zapisu.
50
+ */
51
+ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCancel, locale: localeOverride, dictionary, className, ...rest }) {
52
+ const { t, locale } = useKptMessages({ locale: localeOverride, dictionary });
53
+ const fields = useMemo(() => category.fields ?? [], [category]);
54
+ const [model, setModel] = useState(() => initialModel(fields));
55
+ const [touched, setTouched] = useState({});
56
+ /** Po nieudanej próbie zapisu pokazujemy błędy wszystkich pól, nie tylko dotkniętych. */
57
+ const [submitted, setSubmitted] = useState(false);
58
+ /** Trwa zapytanie do geokodera — pokazujemy notkę zamiast ciszy. */
59
+ const [geocoding, setGeocoding] = useState(false);
60
+ /** Ostatnio ustalony adres; `address` bywa czytelniejszy niż suma pól. */
61
+ const [address, setAddress] = useState(null);
62
+ // Inna kategoria to inny formularz — model wraca do wartości początkowych.
63
+ const schemaRef = useMemo(() => fields, [fields]);
64
+ useEffect(() => {
65
+ setModel(initialModel(schemaRef));
66
+ setTouched({});
67
+ setSubmitted(false);
68
+ }, [schemaRef]);
69
+ /** Czy kategoria ma pola do podpowiedzenia i jest czym je podpowiedzieć. */
70
+ const autofillFields = useMemo(() => fields.filter((def) => def.autofill && TEXTUAL.has(def.type)), [fields]);
71
+ const hasAutofill = !!geocoder && autofillFields.length > 0;
72
+ /**
73
+ * Podpowiada adres z geokodowania wstecznego współrzędnych kliknięcia. Wypełnia wyłącznie
74
+ * pola z `autofill` i wyłącznie te, które są jeszcze puste — podpowiedź nigdy nie kasuje
75
+ * tego, co użytkownik zdążył wpisać.
76
+ */
77
+ useEffect(() => {
78
+ if (!geocoder || autofillFields.length === 0)
79
+ return;
80
+ const controller = new AbortController();
81
+ setGeocoding(true);
82
+ geocoder
83
+ .reverse(at, { language: locale, signal: controller.signal })
84
+ .then((found) => {
85
+ if (controller.signal.aborted)
86
+ return;
87
+ setGeocoding(false);
88
+ setAddress(found);
89
+ if (!found)
90
+ return;
91
+ setModel((current) => {
92
+ const next = { ...current };
93
+ for (const def of autofillFields) {
94
+ const value = found[def.autofill];
95
+ // Puste pole = to, czego użytkownik nie tknął.
96
+ if (typeof value === 'string' && value && !next[def.key])
97
+ next[def.key] = value;
98
+ }
99
+ return next;
100
+ });
101
+ })
102
+ .catch(() => {
103
+ if (!controller.signal.aborted)
104
+ setGeocoding(false);
105
+ });
106
+ return () => {
107
+ controller.abort();
108
+ setGeocoding(false);
109
+ };
110
+ }, [geocoder, at, locale, autofillFields]);
111
+ /**
112
+ * Komunikat błędu pola albo `null`. Dzień otwarty musi mieć obie godziny i różne od siebie;
113
+ * dzień oznaczony jako zamknięty przechodzi bez zastrzeżeń — to prawidłowa odpowiedź,
114
+ * a nie brak danych.
115
+ */
116
+ const errorFor = (def) => {
117
+ const value = model[def.key];
118
+ if (def.required && (value === null || value === undefined || value === '' || (Array.isArray(value) && !value.length))) {
119
+ return t('map.fieldRequired', { label: def.label });
120
+ }
121
+ if (def.type === 'email' && typeof value === 'string' && value && !/^[^@\s]+@[^@\s.]+\.[^@\s]+$/.test(value)) {
122
+ return t('map.field.invalidEmail');
123
+ }
124
+ if (def.type === 'url' && typeof value === 'string' && value && !/^https?:\/\/\S+$/i.test(value)) {
125
+ return t('map.field.invalidUrl');
126
+ }
127
+ if (def.type === 'number' && typeof value === 'number') {
128
+ if (typeof def.min === 'number' && value < def.min)
129
+ return t('map.fieldRequired', { label: def.label });
130
+ if (typeof def.max === 'number' && value > def.max)
131
+ return t('map.fieldRequired', { label: def.label });
132
+ }
133
+ if (def.type === 'hours')
134
+ return hoursError(value);
135
+ return null;
136
+ };
137
+ const hoursError = (week) => {
138
+ if (!week)
139
+ return null;
140
+ for (const day of week) {
141
+ if (day.closed)
142
+ continue;
143
+ if (day.ranges.length === 0) {
144
+ return t('map.field.hoursIncomplete', { day: weekdayName(day.day, locale) });
145
+ }
146
+ for (const range of day.ranges) {
147
+ const from = parseTime(range.from);
148
+ const to = parseTime(range.to);
149
+ if (from === null || to === null || from === to) {
150
+ return t('map.field.hoursIncomplete', { day: weekdayName(day.day, locale) });
151
+ }
152
+ }
153
+ }
154
+ return null;
155
+ };
156
+ /** Komunikat pokazujemy dopiero po dotknięciu pola albo po próbie zapisu. */
157
+ const visibleError = (def) => touched[def.key] || submitted ? errorFor(def) : null;
158
+ const invalid = fields.some((def) => errorFor(def) !== null);
159
+ const set = (key, value) => setModel((current) => ({ ...current, [key]: value }));
160
+ const touch = (key) => () => setTouched((current) => ({ ...current, [key]: true }));
161
+ const onSubmit = (event) => {
162
+ event.preventDefault();
163
+ setSubmitted(true);
164
+ if (invalid)
165
+ return;
166
+ onSave({ ...model });
167
+ };
168
+ /** Czy pole renderuje jeden `<input>`, na który może wskazać `label[for]`. */
169
+ const hasOwnInput = (def) => def.type === 'text' || def.type === 'tel' || def.type === 'email' || def.type === 'url';
170
+ const controlId = (def) => `kpt-map-field-${def.key}`;
171
+ return (_jsx("div", { className: cn('kpt-map-create', className), ...rest, children: _jsxs("form", { className: "kpt-map-create__form", onSubmit: onSubmit, noValidate: true, children: [_jsxs("p", { className: "kpt-map-create__coords", children: [_jsxs("span", { children: [t('map.latitude'), ": ", _jsx("strong", { children: at.lat.toFixed(5) })] }), _jsxs("span", { children: [t('map.longitude'), ": ", _jsx("strong", { children: at.lng.toFixed(5) })] })] }), geocoding ? (_jsx("p", { className: "kpt-map-create__geo", "aria-live": "polite", children: t('map.lookingUpAddress') })) : address?.address ? (_jsx("p", { className: "kpt-map-create__geo", "aria-live": "polite", children: [address.address, address.city].filter(Boolean).join(', ') })) : hasAutofill ? (_jsx("p", { className: "kpt-map-create__geo", "aria-live": "polite", children: t('map.addressNotFound') })) : null, fields.map((def) => (_jsx(KptFormField, { label: def.label, hint: def.hint ?? '', error: visibleError(def), required: !!def.required, controlId: hasOwnInput(def) ? controlId(def) : '', children: renderControl(def) }, def.key))), _jsxs("div", { className: "kpt-map-create__actions", children: [_jsx(KptButton, { type: "button", variant: "text", onClick: onCancel, children: t('map.cancel') }), _jsx(KptButton, { type: "submit", disabled: submitted && invalid, children: t('map.save') })] })] }) }));
172
+ function renderControl(def) {
173
+ const value = model[def.key];
174
+ const common = { onTouch: touch(def.key), invalid: !!visibleError(def), touched: !!touched[def.key] };
175
+ switch (def.type) {
176
+ case 'textarea':
177
+ return (_jsx(KptTextarea, { value: String(value ?? ''), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? '', ...common }));
178
+ case 'number':
179
+ return (_jsx(KptInputNumber, { value: value, onValueChange: (next) => set(def.key, next), min: typeof def.min === 'number' ? def.min : undefined, max: typeof def.max === 'number' ? def.max : undefined, placeholder: def.placeholder ?? '', ...common }));
180
+ case 'date':
181
+ return (_jsx(KptDatepicker, { value: value, onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? '', minDate: def.min ?? null, maxDate: def.max ?? null, ...common }));
182
+ case 'select':
183
+ return (_jsx(KptSelect, { value: value, onValueChange: (next) => set(def.key, next), options: (def.options ?? []).map((option) => ({ value: option.value, label: option.label })), placeholder: def.placeholder ?? '', ...common }));
184
+ case 'multiselect':
185
+ return (_jsx(KptSelect, { multiple: true, value: value, onValueChange: (next) => set(def.key, next), options: (def.options ?? []).map((option) => ({ value: option.value, label: option.label })), placeholder: def.placeholder ?? '', ...common }));
186
+ case 'checkbox':
187
+ return (_jsx(KptCheckbox, { checked: !!value, onCheckedChange: (next) => set(def.key, next), label: def.placeholder ?? '', onTouch: touch(def.key) }));
188
+ case 'image':
189
+ return (_jsx(KptMapImageField, { value: String(value ?? ''), onValueChange: (next) => set(def.key, next), invalid: !!visibleError(def), touched: !!touched[def.key], onTouch: touch(def.key) }));
190
+ case 'hours':
191
+ return (_jsx(KptMapHoursField, { value: value ?? emptyWeek(), onValueChange: (next) => set(def.key, next), onTouch: touch(def.key) }));
192
+ default:
193
+ return (_jsx(KptInput, { id: controlId(def), type: def.type === 'tel' || def.type === 'email' || def.type === 'url' ? def.type : 'text', value: String(value ?? ''), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? (def.type === 'url' ? 'https://' : ''), ...common }));
194
+ }
195
+ }
196
+ }
@@ -0,0 +1,22 @@
1
+ import type { KptMapOpeningHours } from '@konce-pt/map';
2
+ import { type ComponentPropsWithRef } from 'react';
3
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
+ export interface KptMapHoursFieldProps extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'onChange'>, KptI18nOverrides {
5
+ value: KptMapOpeningHours;
6
+ onValueChange: (value: KptMapOpeningHours) => void;
7
+ disabled?: boolean;
8
+ readOnly?: boolean;
9
+ /** Wywoływane po każdej zmianie — moment, w którym pole staje się dotknięte. */
10
+ onTouch?: () => void;
11
+ }
12
+ /**
13
+ * Pole „godziny otwarcia" formularza dodawania lokalizacji (`type: 'hours'` w schemie).
14
+ *
15
+ * Siedem wierszy od poniedziałku, każdy z parą godzin i znacznikiem „zamknięte". Skrót
16
+ * „tak samo przez cały tydzień" kopiuje poniedziałek na pozostałe dni — bez niego wypełnienie
17
+ * formularza to czternaście pól.
18
+ *
19
+ * Nazwy dni idą z `Intl`, nie ze słownika: rozkład tygodnia jest uniwersalny, a tłumaczenie
20
+ * nazw dni przeglądarka ma już zrobione.
21
+ */
22
+ export declare function KptMapHoursField({ value, onValueChange, disabled, readOnly, onTouch, locale: localeOverride, dictionary, className, ...rest }: Readonly<KptMapHoursFieldProps>): import("react").JSX.Element;
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { emptyWeek, weekdayName } from '@konce-pt/map';
3
+ import { useState } from 'react';
4
+ import { cn } from "../internal/cn.js";
5
+ import { useKptMessages } from "../i18n/i18n-context.js";
6
+ import { KptCheckbox } from "../checkbox/checkbox.js";
7
+ import { KptDatepicker } from "../datepicker/datepicker.js";
8
+ import { KptIcon } from "../icon/icon.js";
9
+ import { KptIconButton } from "../icon-button/icon-button.js";
10
+ /** Ile przedziałów na dobę dopuszczamy. Dwa pokrywają przerwę i dobę przez północ. */
11
+ const MAX_RANGES = 2;
12
+ /**
13
+ * Snap minut na tarczy zegara. Godziny otwarcia chodzą co kwadrans, a nie co minutę —
14
+ * krok 5 daje trafienie w 07:30 czy 22:45 bez celowania w pojedynczą minutę.
15
+ */
16
+ const MINUTE_STEP = 5;
17
+ /**
18
+ * Pole „godziny otwarcia" formularza dodawania lokalizacji (`type: 'hours'` w schemie).
19
+ *
20
+ * Siedem wierszy od poniedziałku, każdy z parą godzin i znacznikiem „zamknięte". Skrót
21
+ * „tak samo przez cały tydzień" kopiuje poniedziałek na pozostałe dni — bez niego wypełnienie
22
+ * formularza to czternaście pól.
23
+ *
24
+ * Nazwy dni idą z `Intl`, nie ze słownika: rozkład tygodnia jest uniwersalny, a tłumaczenie
25
+ * nazw dni przeglądarka ma już zrobione.
26
+ */
27
+ export function KptMapHoursField({ value, onValueChange, disabled = false, readOnly = false, onTouch, locale: localeOverride, dictionary, className, ...rest }) {
28
+ const { t, locale } = useKptMessages({ locale: localeOverride, dictionary });
29
+ /** Skrót „tak samo przez cały tydzień" — stan pola, nie część wartości. */
30
+ const [sameAllWeek, setSameAllWeek] = useState(false);
31
+ const rows = Array.from({ length: 7 }, (_, index) => {
32
+ const day = (index + 1);
33
+ const entry = value.find((candidate) => candidate.day === day);
34
+ return {
35
+ day,
36
+ name: weekdayName(day, locale),
37
+ closed: entry?.closed ?? !entry,
38
+ ranges: (entry?.ranges ?? []).map((range) => ({ from: range.from, to: range.to })),
39
+ };
40
+ });
41
+ /** Zapis zawsze przez pełny, uporządkowany tydzień — model nie ma dziur. */
42
+ const write = (change) => {
43
+ const base = emptyWeek().map((day) => value.find((entry) => entry.day === day.day) ?? day);
44
+ onValueChange(change(base));
45
+ onTouch?.();
46
+ };
47
+ /** Zmiana jednego dnia; przy jednakowym tygodniu poniedziałek rozlewa się na resztę. */
48
+ const patch = (day, change) => {
49
+ write((week) => {
50
+ const next = week.map((entry) => (entry.day === day ? change(entry) : entry));
51
+ if (!sameAllWeek || day !== 1)
52
+ return next;
53
+ const monday = next[0];
54
+ return next.map((entry) => entry.day === 1
55
+ ? entry
56
+ : { day: entry.day, closed: monday.closed, ranges: monday.ranges.map((range) => ({ ...range })) });
57
+ });
58
+ };
59
+ const locked = (row) => disabled || readOnly || (sameAllWeek && row.day !== 1);
60
+ const onSameAllWeek = (same) => {
61
+ setSameAllWeek(same);
62
+ if (!same)
63
+ return;
64
+ const monday = rows[0];
65
+ write((week) => week.map((day) => day.day === 1
66
+ ? day
67
+ : { day: day.day, closed: monday.closed, ranges: monday.ranges.map((range) => ({ ...range })) }));
68
+ };
69
+ const setClosed = (row, closed) => patch(row.day, (day) => ({
70
+ ...day,
71
+ closed,
72
+ // Przy odznaczeniu dajemy jeden pusty przedział, żeby było co wypełnić.
73
+ ranges: closed ? [] : day.ranges.length ? day.ranges : [{ from: '', to: '' }],
74
+ }));
75
+ const setTime = (row, index, edge, time) => patch(row.day, (day) => {
76
+ const ranges = day.ranges.map((range) => ({ ...range }));
77
+ while (ranges.length <= index)
78
+ ranges.push({ from: '', to: '' });
79
+ ranges[index][edge] = time;
80
+ return { ...day, closed: false, ranges };
81
+ });
82
+ return (_jsxs("div", { className: cn('kpt-map-hours-field', className), ...rest, children: [_jsx(KptCheckbox, { className: "kpt-map-hours-field__same", checked: sameAllWeek, onCheckedChange: onSameAllWeek, disabled: disabled || readOnly, label: t('map.field.hoursSameAllWeek') }), _jsx("ul", { className: "kpt-map-hours-field__week", children: rows.map((row) => (_jsxs("li", { className: "kpt-map-hours-field__day", "data-locked": locked(row) ? '' : undefined, children: [_jsx("span", { className: "kpt-map-hours-field__name", children: row.name }), _jsx("div", { className: "kpt-map-hours-field__ranges", children: !row.closed ? (_jsxs(_Fragment, { children: [row.ranges.map((range, index) => (_jsxs("div", { className: "kpt-map-hours-field__range", children: [_jsx(KptDatepicker, { selectionMode: "time", minuteStep: MINUTE_STEP, value: range.from, onValueChange: (time) => setTime(row, index, 'from', String(time ?? '')), disabled: locked(row), "aria-label": `${t('map.field.hoursFrom')} — ${row.name}`, onTouch: onTouch }), _jsx("span", { className: "kpt-map-hours-field__dash", "aria-hidden": "true", children: "\u2013" }), _jsx(KptDatepicker, { selectionMode: "time", minuteStep: MINUTE_STEP, value: range.to, onValueChange: (time) => setTime(row, index, 'to', String(time ?? '')), disabled: locked(row), "aria-label": `${t('map.field.hoursTo')} — ${row.name}`, onTouch: onTouch }), row.ranges.length > 1 ? (_jsx(KptIconButton, { variant: "text", size: "sm", disabled: locked(row), "aria-label": t('map.field.hoursRemoveRange'), onClick: () => patch(row.day, (day) => ({
83
+ ...day,
84
+ ranges: day.ranges.filter((_, i) => i !== index).map((r) => ({ ...r })),
85
+ })), children: _jsx(KptIcon, { name: "minus" }) })) : null] }, index))), row.ranges.length < MAX_RANGES ? (_jsxs("button", { className: "kpt-map-hours-field__add", type: "button", disabled: locked(row), onClick: () => patch(row.day, (day) => ({
86
+ ...day,
87
+ closed: false,
88
+ ranges: [...day.ranges.map((r) => ({ ...r })), { from: '', to: '' }],
89
+ })), children: [_jsx(KptIcon, { name: "plus" }), t('map.field.hoursAddRange')] })) : null] })) : null }), _jsx(KptCheckbox, { className: "kpt-map-hours-field__closed", checked: row.closed, onCheckedChange: (closed) => setClosed(row, closed), disabled: locked(row), label: t('map.field.hoursClosed') })] }, row.day))) })] }));
90
+ }
@@ -0,0 +1,24 @@
1
+ import { type ComponentPropsWithRef } from 'react';
2
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
+ export interface KptMapImageFieldProps extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'onChange'>, KptI18nOverrides {
4
+ value: string;
5
+ onValueChange: (value: string) => void;
6
+ disabled?: boolean;
7
+ readOnly?: boolean;
8
+ invalid?: boolean;
9
+ touched?: boolean;
10
+ /** Górny limit pliku w bajtach. */
11
+ maxBytes?: number;
12
+ onTouch?: () => void;
13
+ }
14
+ /**
15
+ * Pole „zdjęcie" formularza dodawania lokalizacji (`type: 'image'` w schemie kategorii).
16
+ *
17
+ * Wartością jest zawsze **string**: albo `data:` z wybranego pliku, albo adres wklejony przez
18
+ * użytkownika. Karta miejsca nie rozróżnia jednego od drugiego, więc formularz nie musi
19
+ * decydować za aplikację, gdzie zdjęcia mieszkają.
20
+ *
21
+ * Plik czytamy w przeglądarce i nigdzie nie wysyłamy — biblioteka nie ma i nie chce mieć
22
+ * wiedzy o storage'u aplikacji.
23
+ */
24
+ export declare function KptMapImageField({ value, onValueChange, disabled, readOnly, invalid, touched, maxBytes, onTouch, locale, dictionary, className, ...rest }: Readonly<KptMapImageFieldProps>): import("react").JSX.Element;
@@ -0,0 +1,61 @@
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 { KptFileUpload } from "../file-upload/file-upload.js";
6
+ import { KptIcon } from "../icon/icon.js";
7
+ import { KptInput } from "../input/input.js";
8
+ /** Domyślny limit pliku. Data URL puchnie o ~⅓, a wartość leci w `onLocationCreate`. */
9
+ const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;
10
+ /**
11
+ * Pole „zdjęcie" formularza dodawania lokalizacji (`type: 'image'` w schemie kategorii).
12
+ *
13
+ * Wartością jest zawsze **string**: albo `data:` z wybranego pliku, albo adres wklejony przez
14
+ * użytkownika. Karta miejsca nie rozróżnia jednego od drugiego, więc formularz nie musi
15
+ * decydować za aplikację, gdzie zdjęcia mieszkają.
16
+ *
17
+ * Plik czytamy w przeglądarce i nigdzie nie wysyłamy — biblioteka nie ma i nie chce mieć
18
+ * wiedzy o storage'u aplikacji.
19
+ */
20
+ export function KptMapImageField({ value, onValueChange, disabled = false, readOnly = false, invalid = false, touched = false, maxBytes = DEFAULT_MAX_BYTES, onTouch, locale, dictionary, className, ...rest }) {
21
+ const { t } = useKptMessages({ locale, dictionary });
22
+ /** Pliki trzymane wyłącznie po to, żeby `KptFileUpload` miał co pokazać. */
23
+ const [files, setFiles] = useState([]);
24
+ /** Komunikat o zbyt dużym pliku; walidacja rozmiaru nie ma sensu w schemie. */
25
+ const [sizeError, setSizeError] = useState(null);
26
+ const maxLabel = `${Math.round(maxBytes / (1024 * 1024))} MB`;
27
+ const urlValue = value.startsWith('data:') ? '' : value;
28
+ const onFiles = (next) => {
29
+ const file = next[0];
30
+ setFiles(file ? [file] : []);
31
+ setSizeError(null);
32
+ onTouch?.();
33
+ if (!file) {
34
+ // Usunięcie pliku czyści wartość tylko wtedy, gdy to plik ją ustawił.
35
+ if (value.startsWith('data:'))
36
+ onValueChange('');
37
+ return;
38
+ }
39
+ if (file.size > maxBytes) {
40
+ setSizeError(t('map.field.imageTooLarge', { max: maxLabel }));
41
+ setFiles([]);
42
+ return;
43
+ }
44
+ const reader = new FileReader();
45
+ reader.onload = () => onValueChange(String(reader.result ?? ''));
46
+ reader.onerror = () => setSizeError(t('map.field.imageTooLarge', { max: maxLabel }));
47
+ reader.readAsDataURL(file);
48
+ };
49
+ return (_jsxs("div", { className: cn('kpt-map-image-field', className), ...rest, children: [_jsx(KptFileUpload, { accept: "image/*", value: files, onValueChange: onFiles, disabled: disabled || readOnly, invalid: invalid, touched: touched }), _jsxs("label", { className: "kpt-map-image-field__url", children: [_jsx("span", { className: "kpt-map-image-field__url-label", children: t('map.field.orPasteUrl') }), _jsx(KptInput, { type: "url", value: urlValue, onValueChange: (url) => {
50
+ // Adres i plik wykluczają się — dwa źródła jednej wartości myliłyby użytkownika.
51
+ if (url)
52
+ setFiles([]);
53
+ setSizeError(null);
54
+ onValueChange(url);
55
+ }, disabled: disabled, readOnly: readOnly, onTouch: onTouch, placeholder: "https://" })] }), sizeError ? (_jsx("p", { className: "kpt-map-image-field__error", "aria-live": "polite", children: sizeError })) : null, value ? (_jsxs("figure", { className: "kpt-map-image-field__preview", children: [_jsx("img", { src: value, alt: t('map.field.imagePreview') }), _jsx("button", { className: "kpt-map-image-field__clear", type: "button", "aria-label": t('map.field.removeImage'), title: t('map.field.removeImage'), disabled: disabled || readOnly, onClick: () => {
56
+ setFiles([]);
57
+ setSizeError(null);
58
+ onValueChange('');
59
+ onTouch?.();
60
+ }, children: _jsx(KptIcon, { name: "x" }) })] })) : null] }));
61
+ }
@@ -0,0 +1,29 @@
1
+ import type { KptMapCategory } from '@konce-pt/map';
2
+ import type { ComponentPropsWithRef } from 'react';
3
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
+ export interface KptMapLegendProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
5
+ categories: readonly KptMapCategory[];
6
+ /**
7
+ * Identyfikatory ukrytych kategorii — kontrolowane. Nazwa dłuższa niż `hidden` z portu
8
+ * Angulara, bo `hidden` na elemencie DOM jest booleanem i kolidowałoby z propsem `<div>`.
9
+ */
10
+ hiddenCategories?: string[];
11
+ defaultHiddenCategories?: string[];
12
+ onHiddenCategoriesChange?: (hidden: string[]) => void;
13
+ /** Opcjonalne liczniki `{ categoryId: liczba }` — pokazywane przy etykiecie. */
14
+ counts?: Record<string, number> | null;
15
+ orientation?: 'horizontal' | 'vertical';
16
+ /** Wyłącza chowanie kategorii — legenda staje się czysto informacyjna. */
17
+ interactive?: boolean;
18
+ }
19
+ /**
20
+ * Legenda mapy — lista kategorii ze znacznikiem koloru i ikoną. Pozycje są przyciskami:
21
+ * kliknięcie chowa i pokazuje kategorię.
22
+ *
23
+ * `KptMap` renderuje ją sam pod mapą przy `legend`; kto chce ją gdzie indziej, wyłącza tamto
24
+ * i wstawia ten komponent ręcznie.
25
+ *
26
+ * @example
27
+ * <KptMapLegend categories={categories} hiddenCategories={hidden} onHiddenCategoriesChange={setHidden} />
28
+ */
29
+ export declare function KptMapLegend({ categories, hiddenCategories, defaultHiddenCategories, onHiddenCategoriesChange, counts, orientation, interactive, locale, dictionary, className, ...rest }: Readonly<KptMapLegendProps>): import("react").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { colorVarOf, contrastVarOf } from '@konce-pt/map';
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
+ * Legenda mapy — lista kategorii ze znacznikiem koloru i ikoną. Pozycje są przyciskami:
9
+ * kliknięcie chowa i pokazuje kategorię.
10
+ *
11
+ * `KptMap` renderuje ją sam pod mapą przy `legend`; kto chce ją gdzie indziej, wyłącza tamto
12
+ * i wstawia ten komponent ręcznie.
13
+ *
14
+ * @example
15
+ * <KptMapLegend categories={categories} hiddenCategories={hidden} onHiddenCategoriesChange={setHidden} />
16
+ */
17
+ export function KptMapLegend({ categories, hiddenCategories, defaultHiddenCategories = [], onHiddenCategoriesChange, counts = null, orientation = 'horizontal', interactive = true, locale, dictionary, className, ...rest }) {
18
+ const { t } = useKptMessages({ locale, dictionary });
19
+ const [hiddenIds, setHidden] = useControllableState({
20
+ value: hiddenCategories,
21
+ defaultValue: defaultHiddenCategories,
22
+ onChange: onHiddenCategoriesChange,
23
+ });
24
+ const hiddenSet = new Set(hiddenIds);
25
+ const toggle = (id) => {
26
+ if (!interactive)
27
+ return;
28
+ setHidden(hiddenSet.has(id) ? hiddenIds.filter((current) => current !== id) : [...hiddenIds, id]);
29
+ };
30
+ return (_jsx("div", { className: cn('kpt-map-legend', className), "data-orientation": orientation, role: "group", "aria-label": t('map.legend'), ...rest, children: categories
31
+ .filter((category) => category.legend !== false)
32
+ .map((category) => {
33
+ const isHidden = hiddenSet.has(category.id);
34
+ const label = t(isHidden ? 'map.showCategory' : 'map.hideCategory', { category: category.label });
35
+ return (_jsxs("button", { type: "button", className: cn('kpt-map-legend__item', isHidden && 'kpt-map-legend__item--off'), style: {
36
+ // Glif dostaje kolor kontrastowy — zaszyta biel gubi się na jasnych rolach.
37
+ '--_kpt-legend-color': colorVarOf(category.color),
38
+ '--_kpt-legend-fg': contrastVarOf(category.color),
39
+ }, "aria-pressed": interactive ? !isHidden : undefined, "aria-label": interactive ? label : undefined, disabled: !interactive, onClick: () => toggle(category.id), children: [_jsx("span", { className: "kpt-map-legend__swatch", "aria-hidden": "true", children: category.icon ? _jsx(KptIcon, { name: category.icon }) : null }), _jsx("span", { className: "kpt-map-legend__label", children: category.label }), counts ? _jsx("span", { className: "kpt-map-legend__count", children: counts[category.id] ?? 0 }) : null] }, category.id));
40
+ }) }));
41
+ }
@@ -0,0 +1,42 @@
1
+ import type { KptMapCategory, KptMapPoi } from '@konce-pt/map';
2
+ import { type ComponentPropsWithRef } from 'react';
3
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
+ export interface KptMapPoiCardProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
5
+ poi: KptMapPoi;
6
+ /** Odległość w metrach; `null` chowa wiersz dystansu. */
7
+ distance?: number | null;
8
+ /** Źródło ikony, koloru i etykiety wiersza kategorii; `null` chowa ten wiersz. */
9
+ category?: KptMapCategory | null;
10
+ /** Etykieta przycisku głównego; pusta chowa przycisk. */
11
+ primaryLabel?: string;
12
+ showShare?: boolean;
13
+ showFavourite?: boolean;
14
+ favourite?: boolean;
15
+ defaultFavourite?: boolean;
16
+ onFavouriteChange?: (favourite: boolean) => void;
17
+ closable?: boolean;
18
+ /** Rozwinąć godziny od razu. Domyślnie zwinięte, jak na karcie miejsca w mapach. */
19
+ hoursExpanded?: boolean;
20
+ defaultHoursExpanded?: boolean;
21
+ onHoursExpandedChange?: (expanded: boolean) => void;
22
+ onPrimaryAction?: (poi: KptMapPoi) => void;
23
+ onClose?: () => void;
24
+ onShare?: (poi: KptMapPoi) => void;
25
+ }
26
+ /**
27
+ * Karta miejsca do popupu mapy — dystans, zdjęcie, akcje, kategoria, rozwijane godziny
28
+ * otwarcia i wiersze kontaktowe z kopiowaniem.
29
+ *
30
+ * Komponent jest sterowany danymi: bierze `KptMapPoi`, a nie model domenowy aplikacji.
31
+ * Mapowanie `KptMapLocation.data` → `KptMapPoi` robi aplikacja, dzięki czemu karta nie wie,
32
+ * czy pokazuje ładowarkę, restaurację, czy punkt serwisowy.
33
+ *
34
+ * Sam popup trzeba przełączyć na `popupLayout="card"` — inaczej karta dostanie szerokość
35
+ * i padding przeznaczone dla treści tekstowej.
36
+ *
37
+ * @example
38
+ * <KptMap popupLayout="card" renderPopup={({ location, data, distance, close }) => (
39
+ * <KptMapPoiCard poi={poiOf(location, data)} distance={distance} onClose={close} />
40
+ * )} />
41
+ */
42
+ export declare function KptMapPoiCard({ poi, distance, category, primaryLabel, showShare, showFavourite, favourite, defaultFavourite, onFavouriteChange, closable, hoursExpanded, defaultHoursExpanded, onHoursExpandedChange, onPrimaryAction, onClose, onShare, locale: localeOverride, dictionary, className, ...rest }: Readonly<KptMapPoiCardProps>): import("react").JSX.Element;