@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,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Children, isValidElement, useCallback, useLayoutEffect, useRef, useState, } from 'react';
3
+ import { cn } from "../internal/cn.js";
4
+ import { useControllableState } from "../internal/use-controllable-state.js";
5
+ /**
6
+ * Pojedyncza zakładka. Umieszczaj w `<KptTabs>`. Treść renderuje się leniwie — dopiero gdy
7
+ * zakładka jest aktywna, tak samo jak szablon `kpt-tab` w porcie Angulara.
8
+ */
9
+ export function KptTab(_props) {
10
+ // Zakładka nigdy nie renderuje się sama: `KptTabs` czyta jej propsy i sam decyduje,
11
+ // co pokazać. Ten komponent jest wyłącznie nośnikiem konfiguracji.
12
+ return null;
13
+ }
14
+ KptTab.kptTabsSlot = 'tab';
15
+ /**
16
+ * Zakładki — przełączanie widoków, treść renderowana leniwie.
17
+ *
18
+ * Podkreślenie aktywnej zakładki (`__ink`) jest mierzone z DOM przycisku, nie liczone z indeksu:
19
+ * zakładki mają różne szerokości, a etykiety zmieniają się z językiem.
20
+ *
21
+ * @example
22
+ * <KptTabs>
23
+ * <KptTabs.Tab label="Ogólne">…</KptTabs.Tab>
24
+ * <KptTabs.Tab label="Zaawansowane">…</KptTabs.Tab>
25
+ * </KptTabs>
26
+ */
27
+ export function KptTabs({ activeIndex, defaultActiveIndex = 0, onActiveIndexChange, animated = true, animationDuration = 500, className, style, children, ...rest }) {
28
+ const [active, setActive] = useControllableState({
29
+ value: activeIndex,
30
+ defaultValue: defaultActiveIndex,
31
+ onChange: onActiveIndexChange,
32
+ });
33
+ const [direction, setDirection] = useState(1);
34
+ const [ink, setInk] = useState({ x: 0, w: 0 });
35
+ const buttonsRef = useRef([]);
36
+ const tabs = [];
37
+ const contents = [];
38
+ Children.forEach(children, (child) => {
39
+ if (!isValidElement(child) || child.type?.kptTabsSlot !== 'tab')
40
+ return;
41
+ const props = child.props;
42
+ tabs.push(props);
43
+ contents.push(props.children);
44
+ });
45
+ const measureInk = useCallback(() => {
46
+ const button = buttonsRef.current[active];
47
+ if (!button)
48
+ return;
49
+ const next = { x: button.offsetLeft, w: button.offsetWidth };
50
+ // Pomiar biegnie po KAŻDYM renderze (etykiety zmieniają się z językiem, a przycisków nie da
51
+ // się zmierzyć przed montażem). Bez porównania `setInk` wywoływałby kolejny render w kółko —
52
+ // React przerywa aktualizację dopiero wtedy, gdy dostanie ten sam obiekt stanu.
53
+ setInk((prev) => (prev.x === next.x && prev.w === next.w ? prev : next));
54
+ }, [active]);
55
+ useLayoutEffect(measureInk);
56
+ useLayoutEffect(() => {
57
+ window.addEventListener('resize', measureInk);
58
+ return () => window.removeEventListener('resize', measureInk);
59
+ }, [measureInk]);
60
+ const select = (index) => {
61
+ if (tabs[index]?.disabled)
62
+ return;
63
+ setDirection(index >= active ? 1 : -1);
64
+ setActive(index);
65
+ };
66
+ return (_jsxs("div", { className: cn('kpt-tabs-host', className), style: {
67
+ ['--kpt-anim-duration']: `${animated ? animationDuration : 0}ms`,
68
+ ['--kpt-anim-dir']: direction,
69
+ ...style,
70
+ }, ...rest, children: [_jsxs("div", { className: "kpt-tabs__header", role: "tablist", children: [tabs.map((tab, index) => (_jsx("button", { ref: (el) => {
71
+ buttonsRef.current[index] = el;
72
+ }, type: "button", className: cn('kpt-tabs__tab', index === active && 'is-active'), role: "tab", disabled: tab.disabled, "aria-selected": index === active, onClick: () => select(index), children: tab.label }, index))), _jsx("span", { className: "kpt-tabs__ink", style: { width: ink.w, transform: `translateX(${ink.x}px)` } })] }), _jsx("div", { className: "kpt-tabs__body", role: "tabpanel", children: contents[active] !== undefined ? (_jsx("div", { className: "kpt-tabs__panel", children: contents[active] }, active)) : null })] }));
73
+ }
74
+ KptTabs.Tab = KptTab;
@@ -0,0 +1,28 @@
1
+ import { type ComponentPropsWithRef } from 'react';
2
+ import type { KptHostProps } from '../internal/types.ts';
3
+ export interface KptTextareaProps extends Omit<ComponentPropsWithRef<'textarea'>, 'required'> {
4
+ /** Dopasowuje wysokość pola do treści przy każdej zmianie. */
5
+ autoResize?: boolean;
6
+ /** Oznacza pole jako wymagane atrybutem `aria-required`, a NIE natywnym `required`. */
7
+ required?: boolean;
8
+ /** Pole jest nieprawidłowe. Widoczne dopiero, gdy `touched` też jest prawdziwe. */
9
+ invalid?: boolean;
10
+ /** Czy pole było dotknięte; pominięty = stan błędu pokazuje się od razu po `invalid`. */
11
+ touched?: boolean;
12
+ /** Wygodniejszy odpowiednik `onChange` — dostaje samą wartość. */
13
+ onValueChange?: (value: string) => void;
14
+ /** Wywoływane na `blur` — moment, w którym pole staje się dotknięte. */
15
+ onTouch?: () => void;
16
+ /** Propsy wrappera hosta (`.kpt-textarea-host`). `className` i `ref` idą na `<textarea>`. */
17
+ hostProps?: KptHostProps<'span'>;
18
+ }
19
+ /**
20
+ * Pole wieloliniowe (textarea), z opcjonalnym auto-resize.
21
+ *
22
+ * Działa kontrolowane (`value` + `onValueChange`) i niekontrolowane (`defaultValue`) — auto-resize
23
+ * czyta wysokość wprost z DOM, więc jest poprawny w obu trybach, także dla wartości początkowej.
24
+ *
25
+ * @example
26
+ * <KptTextarea name="notes" placeholder="Notatki…" autoResize />
27
+ */
28
+ export declare function KptTextarea({ autoResize, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, rows, onChange, onBlur, ref, 'aria-invalid': ariaInvalid, ...rest }: Readonly<KptTextareaProps>): import("react").JSX.Element;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useLayoutEffect, useRef } from 'react';
3
+ import { cn } from "../internal/cn.js";
4
+ import { composeRefs } from "../internal/compose-refs.js";
5
+ import { useKptId } from "../internal/use-id.js";
6
+ import { useKptFormFieldContext } from "../form-field/form-field-context.js";
7
+ /**
8
+ * Pole wieloliniowe (textarea), z opcjonalnym auto-resize.
9
+ *
10
+ * Działa kontrolowane (`value` + `onValueChange`) i niekontrolowane (`defaultValue`) — auto-resize
11
+ * czyta wysokość wprost z DOM, więc jest poprawny w obu trybach, także dla wartości początkowej.
12
+ *
13
+ * @example
14
+ * <KptTextarea name="notes" placeholder="Notatki…" autoResize />
15
+ */
16
+ export function KptTextarea({ autoResize = false, required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, rows = 3, onChange, onBlur, ref, 'aria-invalid': ariaInvalid, ...rest }) {
17
+ const field = useKptFormFieldContext();
18
+ const { className: hostClassName, ...hostRest } = hostProps ?? {};
19
+ const textareaId = useKptId(id ?? field?.controlId, 'kpt-textarea');
20
+ const innerRef = useRef(null);
21
+ const showInvalid = invalid && (touched ?? true);
22
+ const resize = useCallback(() => {
23
+ const el = innerRef.current;
24
+ if (!el || !autoResize)
25
+ return;
26
+ // Zerowanie przed odczytem `scrollHeight` jest konieczne — bez niego pole potrafi tylko
27
+ // rosnąć, bo `scrollHeight` nigdy nie spada poniżej bieżącej wysokości.
28
+ el.style.height = 'auto';
29
+ el.style.height = `${el.scrollHeight}px`;
30
+ }, [autoResize]);
31
+ // Wartość początkowa i każda zmiana z zewnątrz (tryb kontrolowany) — przed malowaniem,
32
+ // żeby pole nie mrugnęło niedopasowaną wysokością.
33
+ useLayoutEffect(resize);
34
+ const handleChange = useCallback((event) => {
35
+ onChange?.(event);
36
+ onValueChange?.(event.target.value);
37
+ resize();
38
+ }, [onChange, onValueChange, resize]);
39
+ const handleBlur = useCallback((event) => {
40
+ onBlur?.(event);
41
+ onTouch?.();
42
+ }, [onBlur, onTouch]);
43
+ return (_jsx("span", { className: cn('kpt-textarea-host', hostClassName), ...hostRest, children: _jsx("textarea", { className: cn('kpt-textarea__field', className), id: textareaId, rows: rows, ref: composeRefs(innerRef, ref), "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }) }));
44
+ }
@@ -0,0 +1,19 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ export interface KptTimelineItem {
3
+ title: string;
4
+ description?: string;
5
+ time?: string;
6
+ /** Nazwa ikony z rejestru; bez niej marker zostaje pustą kropką. */
7
+ icon?: string;
8
+ variant?: 'neutral' | 'primary' | 'success' | 'danger' | 'warning' | 'info';
9
+ }
10
+ export interface KptTimelineProps extends ComponentPropsWithRef<'div'> {
11
+ items?: KptTimelineItem[];
12
+ }
13
+ /**
14
+ * Oś czasu — pionowa lista zdarzeń z markerami.
15
+ *
16
+ * @example
17
+ * <KptTimeline items={[{ title: 'Utworzono', time: '10:00', variant: 'success', icon: 'check' }]} />
18
+ */
19
+ export declare function KptTimeline({ items, className, ...rest }: Readonly<KptTimelineProps>): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../internal/cn.js";
3
+ import { KptIcon } from "../icon/icon.js";
4
+ /**
5
+ * Oś czasu — pionowa lista zdarzeń z markerami.
6
+ *
7
+ * @example
8
+ * <KptTimeline items={[{ title: 'Utworzono', time: '10:00', variant: 'success', icon: 'check' }]} />
9
+ */
10
+ export function KptTimeline({ items = [], className, ...rest }) {
11
+ return (_jsx("div", { className: cn('kpt-timeline-host', className), ...rest, children: _jsx("ol", { className: "kpt-timeline", children: items.map((item, index) => (_jsxs("li", { className: "kpt-timeline__item", children: [_jsx("div", { className: "kpt-timeline__marker", "data-variant": item.variant ?? 'primary', children: item.icon ? _jsx(KptIcon, { name: item.icon }) : null }), _jsxs("div", { className: "kpt-timeline__content", children: [_jsxs("div", { className: "kpt-timeline__header", children: [_jsx("span", { className: "kpt-timeline__title", children: item.title }), item.time ? _jsx("span", { className: "kpt-timeline__time", children: item.time }) : null] }), item.description ? _jsx("p", { className: "kpt-timeline__desc", children: item.description }) : null] })] }, index))) }) }));
12
+ }
@@ -0,0 +1,12 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
+ export interface KptToastContainerProps extends ComponentPropsWithRef<'div'>, KptI18nOverrides {
4
+ }
5
+ /**
6
+ * Kontener renderujący toasty z `kptToast`. Umieść raz w aplikacji, np. w komponencie głównym.
7
+ *
8
+ * @example
9
+ * <KptToastContainer />
10
+ * // gdziekolwiek: kptToast.success('Zapisano');
11
+ */
12
+ export declare function KptToastContainer({ locale, dictionary, className, ...rest }: Readonly<KptToastContainerProps>): import("react").JSX.Element;
@@ -0,0 +1,23 @@
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
+ import { kptToast, useKptToasts } from "./toast-store.js";
6
+ const ICONS = {
7
+ info: 'info',
8
+ success: 'circle-check',
9
+ warning: 'alert-triangle',
10
+ danger: 'circle-x',
11
+ };
12
+ /**
13
+ * Kontener renderujący toasty z `kptToast`. Umieść raz w aplikacji, np. w komponencie głównym.
14
+ *
15
+ * @example
16
+ * <KptToastContainer />
17
+ * // gdziekolwiek: kptToast.success('Zapisano');
18
+ */
19
+ export function KptToastContainer({ locale, dictionary, className, ...rest }) {
20
+ const { t } = useKptMessages({ locale, dictionary });
21
+ const toasts = useKptToasts();
22
+ return (_jsx("div", { className: cn('kpt-toast-container-host', className), ...rest, children: _jsx("div", { className: "kpt-toast-container", "aria-live": "polite", "aria-atomic": "false", children: toasts.map((toast) => (_jsxs("div", { className: "kpt-toast", "data-variant": toast.variant, role: "status", children: [_jsx(KptIcon, { className: "kpt-toast__icon", name: ICONS[toast.variant] }), _jsxs("div", { className: "kpt-toast__content", children: [toast.title ? _jsx("div", { className: "kpt-toast__title", children: toast.title }) : null, _jsx("div", { className: "kpt-toast__message", children: toast.message })] }), _jsx("button", { className: "kpt-toast__close", type: "button", onClick: () => kptToast.dismiss(toast.id), "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) })] }, toast.id))) }) }));
23
+ }
@@ -0,0 +1,26 @@
1
+ import type { KptAlertVariant } from '../shared/types.ts';
2
+ export interface KptToast {
3
+ id: number;
4
+ message: string;
5
+ title?: string;
6
+ variant: KptAlertVariant;
7
+ /** Czas życia w ms; 0 = bez auto-zamykania. */
8
+ duration: number;
9
+ }
10
+ export type KptToastOptions = Partial<Omit<KptToast, 'id' | 'message'>>;
11
+ declare function show(message: string, options?: KptToastOptions): number;
12
+ declare function dismiss(id: number): void;
13
+ declare function clear(): void;
14
+ /** Powiadomienia (toasty). Renderuje je `<KptToastContainer />`, umieszczony raz w aplikacji. */
15
+ export declare const kptToast: {
16
+ show: typeof show;
17
+ info: (message: string, options?: KptToastOptions) => number;
18
+ success: (message: string, options?: KptToastOptions) => number;
19
+ warning: (message: string, options?: KptToastOptions) => number;
20
+ error: (message: string, options?: KptToastOptions) => number;
21
+ dismiss: typeof dismiss;
22
+ clear: typeof clear;
23
+ };
24
+ /** Bieżąca lista toastów. Używane przez kontener; przydaje się też do własnego renderowania. */
25
+ export declare function useKptToasts(): KptToast[];
26
+ export {};
@@ -0,0 +1,51 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ /*
3
+ * Magazyn toastów jako moduł-singleton — odpowiednik serwisu `providedIn: 'root'` z Angulara.
4
+ * Dzięki temu `kptToast.success(...)` woła się z dowolnego miejsca, także spoza drzewa Reacta
5
+ * (obsługa błędu w warstwie danych, interceptor), bez owijania aplikacji providerem.
6
+ */
7
+ let toasts = [];
8
+ let seq = 0;
9
+ const listeners = new Set();
10
+ function emit() {
11
+ for (const listener of listeners)
12
+ listener();
13
+ }
14
+ function show(message, options = {}) {
15
+ const id = ++seq;
16
+ const toast = { id, message, variant: 'info', duration: 4000, ...options };
17
+ toasts = [...toasts, toast];
18
+ emit();
19
+ if (toast.duration > 0)
20
+ setTimeout(() => dismiss(id), toast.duration);
21
+ return id;
22
+ }
23
+ function dismiss(id) {
24
+ toasts = toasts.filter((toast) => toast.id !== id);
25
+ emit();
26
+ }
27
+ function clear() {
28
+ toasts = [];
29
+ emit();
30
+ }
31
+ /** Powiadomienia (toasty). Renderuje je `<KptToastContainer />`, umieszczony raz w aplikacji. */
32
+ export const kptToast = {
33
+ show,
34
+ info: (message, options) => show(message, { ...options, variant: 'info' }),
35
+ success: (message, options) => show(message, { ...options, variant: 'success' }),
36
+ warning: (message, options) => show(message, { ...options, variant: 'warning' }),
37
+ error: (message, options) => show(message, { ...options, variant: 'danger' }),
38
+ dismiss,
39
+ clear,
40
+ };
41
+ const EMPTY = [];
42
+ function subscribe(listener) {
43
+ listeners.add(listener);
44
+ return () => {
45
+ listeners.delete(listener);
46
+ };
47
+ }
48
+ /** Bieżąca lista toastów. Używane przez kontener; przydaje się też do własnego renderowania. */
49
+ export function useKptToasts() {
50
+ return useSyncExternalStore(subscribe, () => toasts, () => EMPTY);
51
+ }
@@ -0,0 +1,29 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ export interface KptToolbarProps extends ComponentPropsWithRef<'div'> {
3
+ /** Dodaje cień (wyniesienie) pod paskiem. */
4
+ elevated?: boolean;
5
+ /**
6
+ * Pozwala zawartości paska zawinąć się do kolejnych wierszy, gdy zabraknie miejsca.
7
+ * Opcjonalne, bo zawijanie zmienia zachowanie `<KptToolbarSpacer>` — w jednowierszowym
8
+ * pasku rozpycha on treść, a w zawijanym potrafi zepchnąć resztę do nowego wiersza.
9
+ */
10
+ wrap?: boolean;
11
+ }
12
+ /**
13
+ * Górny pasek (toolbar) — pozioma belka na treść aplikacji (logo, akcje).
14
+ * Poniżej breakpointa `sm` pasek sam zbiega się do niższej wysokości i węższych marginesów.
15
+ *
16
+ * @example
17
+ * <KptToolbar elevated>
18
+ * <strong>Koncept UI</strong>
19
+ * <KptToolbarSpacer />
20
+ * <KptButton variant="text">Wyloguj</KptButton>
21
+ * </KptToolbar>
22
+ */
23
+ export declare function KptToolbar({ elevated, wrap, className, ...rest }: Readonly<KptToolbarProps>): import("react").JSX.Element;
24
+ /**
25
+ * Elastyczny odstęp rozpychający zawartość paska. Nosi atrybut `kpttoolbarspacer` —
26
+ * to po nim celuje wspólny SCSS (nazwy atrybutów w selektorach CSS są w HTML
27
+ * case-insensitive, więc jedna reguła obsługuje oba porty).
28
+ */
29
+ export declare function KptToolbarSpacer({ className, ...rest }: Readonly<ComponentPropsWithRef<'span'>>): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../internal/cn.js";
3
+ /**
4
+ * Górny pasek (toolbar) — pozioma belka na treść aplikacji (logo, akcje).
5
+ * Poniżej breakpointa `sm` pasek sam zbiega się do niższej wysokości i węższych marginesów.
6
+ *
7
+ * @example
8
+ * <KptToolbar elevated>
9
+ * <strong>Koncept UI</strong>
10
+ * <KptToolbarSpacer />
11
+ * <KptButton variant="text">Wyloguj</KptButton>
12
+ * </KptToolbar>
13
+ */
14
+ export function KptToolbar({ elevated = false, wrap = false, className, ...rest }) {
15
+ return (_jsx("div", { className: cn('kpt-toolbar', elevated && 'kpt-toolbar--elevated', className), "data-wrap": wrap ? '' : undefined, ...rest }));
16
+ }
17
+ /**
18
+ * Elastyczny odstęp rozpychający zawartość paska. Nosi atrybut `kpttoolbarspacer` —
19
+ * to po nim celuje wspólny SCSS (nazwy atrybutów w selektorach CSS są w HTML
20
+ * case-insensitive, więc jedna reguła obsługuje oba porty).
21
+ */
22
+ export function KptToolbarSpacer({ className, ...rest }) {
23
+ return _jsx("span", { kpttoolbarspacer: '', className: className, ...rest });
24
+ }
@@ -0,0 +1,26 @@
1
+ import { type ReactNode } from 'react';
2
+ export type KptTooltipPosition = 'top' | 'bottom' | 'left' | 'right';
3
+ export interface KptTooltipProps {
4
+ /** Treść dymka. Pusty tekst wyłącza podpowiedź. */
5
+ text: string;
6
+ position?: KptTooltipPosition;
7
+ /**
8
+ * Pokazuj dymek tylko wtedy, gdy treść nie mieści się w elemencie (jest ucięta wielokropkiem).
9
+ * Dzięki temu podpowiedź nie powtarza tekstu, który i tak widać.
10
+ */
11
+ onlyWhenTruncated?: boolean;
12
+ /** Dokładnie jeden element — to on dostaje obsługę hovera i fokusu. */
13
+ children: ReactNode;
14
+ }
15
+ /**
16
+ * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body`
17
+ * i pozycjonowany względem dziecka.
18
+ *
19
+ * Port dyrektywy `[kptTooltip]`: nie renderuje własnego opakowania, tylko dokleja handlery
20
+ * i ref do przekazanego elementu, więc drzewo DOM zostaje takie samo jak bez tooltipa.
21
+ *
22
+ * @example
23
+ * <KptTooltip text="Zapisz zmiany" position="bottom"><KptButton>Zapisz</KptButton></KptTooltip>
24
+ * <KptTooltip text={name} onlyWhenTruncated><span className="ellipsis">{name}</span></KptTooltip>
25
+ */
26
+ export declare function KptTooltip({ text, position, onlyWhenTruncated, children }: Readonly<KptTooltipProps>): import("react").JSX.Element;
@@ -0,0 +1,113 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { cloneElement, isValidElement, useCallback, useRef } from 'react';
3
+ import { composeRefs } from "../internal/compose-refs.js";
4
+ /**
5
+ * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body`
6
+ * i pozycjonowany względem dziecka.
7
+ *
8
+ * Port dyrektywy `[kptTooltip]`: nie renderuje własnego opakowania, tylko dokleja handlery
9
+ * i ref do przekazanego elementu, więc drzewo DOM zostaje takie samo jak bez tooltipa.
10
+ *
11
+ * @example
12
+ * <KptTooltip text="Zapisz zmiany" position="bottom"><KptButton>Zapisz</KptButton></KptTooltip>
13
+ * <KptTooltip text={name} onlyWhenTruncated><span className="ellipsis">{name}</span></KptTooltip>
14
+ */
15
+ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false, children }) {
16
+ const hostRef = useRef(null);
17
+ const tipRef = useRef(null);
18
+ const reposition = useCallback(() => {
19
+ const tip = tipRef.current;
20
+ const host = hostRef.current;
21
+ if (!tip || !host)
22
+ return;
23
+ const r = host.getBoundingClientRect();
24
+ const t = tip.getBoundingClientRect();
25
+ const gap = 8;
26
+ let top;
27
+ let left;
28
+ switch (position) {
29
+ case 'bottom':
30
+ top = r.bottom + gap;
31
+ left = r.left + r.width / 2 - t.width / 2;
32
+ break;
33
+ case 'left':
34
+ top = r.top + r.height / 2 - t.height / 2;
35
+ left = r.left - t.width - gap;
36
+ break;
37
+ case 'right':
38
+ top = r.top + r.height / 2 - t.height / 2;
39
+ left = r.right + gap;
40
+ break;
41
+ default:
42
+ top = r.top - t.height - gap;
43
+ left = r.left + r.width / 2 - t.width / 2;
44
+ }
45
+ tip.style.top = `${Math.max(4, top)}px`;
46
+ tip.style.left = `${Math.max(4, left)}px`;
47
+ }, [position]);
48
+ const hide = useCallback(() => {
49
+ window.removeEventListener('scroll', reposition, true);
50
+ window.removeEventListener('resize', reposition);
51
+ tipRef.current?.remove();
52
+ tipRef.current = null;
53
+ }, [reposition]);
54
+ const show = useCallback(() => {
55
+ if (tipRef.current || !text)
56
+ return;
57
+ // Sprawdzamy dopiero teraz, a nie przy renderze: dzięki temu wynik jest zawsze aktualny
58
+ // po zmianie szerokości czy powiększenia, bez żadnych obserwatorów.
59
+ if (onlyWhenTruncated && !isTruncated(hostRef.current))
60
+ return;
61
+ const tip = document.createElement('div');
62
+ tip.className = 'kpt-tooltip';
63
+ tip.setAttribute('role', 'tooltip');
64
+ tip.textContent = text;
65
+ tip.style.cssText =
66
+ 'position:fixed;z-index:1400;max-width:16rem;padding:0.35rem 0.6rem;' +
67
+ 'border-radius:var(--kpt-radius-sm,0.25rem);font-size:var(--kpt-font-size-xs,0.75rem);' +
68
+ 'line-height:1.4;background:var(--kpt-color-on-surface);color:var(--kpt-color-surface);' +
69
+ 'box-shadow:var(--kpt-elevation-2);pointer-events:none;opacity:0;transition:opacity 120ms;';
70
+ document.body.appendChild(tip);
71
+ tipRef.current = tip;
72
+ reposition();
73
+ // Faza przechwytywania łapie także przewijanie zagnieżdżonych kontenerów, bo zdarzenie
74
+ // `scroll` elementu się nie propaguje.
75
+ window.addEventListener('scroll', reposition, true);
76
+ window.addEventListener('resize', reposition);
77
+ requestAnimationFrame(() => {
78
+ if (tipRef.current)
79
+ tipRef.current.style.opacity = '1';
80
+ });
81
+ }, [text, onlyWhenTruncated, reposition]);
82
+ if (!isValidElement(children))
83
+ return _jsx(_Fragment, { children: children });
84
+ const child = children;
85
+ const props = child.props;
86
+ return cloneElement(child, {
87
+ ref: composeRefs(hostRef, props['ref']),
88
+ onMouseEnter: chain(props['onMouseEnter'], show),
89
+ onMouseLeave: chain(props['onMouseLeave'], hide),
90
+ onFocus: chain(props['onFocus'], show),
91
+ onBlur: chain(props['onBlur'], hide),
92
+ });
93
+ }
94
+ /** Handler dziecka najpierw, nasz po nim — jak w scalaniu propsów slotu. */
95
+ function chain(theirs, ours) {
96
+ return (...args) => {
97
+ if (typeof theirs === 'function')
98
+ theirs(...args);
99
+ ours();
100
+ };
101
+ }
102
+ /**
103
+ * Czy treść jest przycięta? Sprawdzamy hosta oraz jego bezpośrednie dzieci, bo
104
+ * `text-overflow: ellipsis` siedzi zwykle na wewnętrznym elemencie — a taki element kurczy się
105
+ * do dostępnej szerokości i nie rozpycha rodzica, więc pomiar samego hosta nie wykryłby niczego.
106
+ */
107
+ function isTruncated(root) {
108
+ if (!root)
109
+ return false;
110
+ // Tolerancja 1 px pochłania zaokrąglenia subpikselowe przy skalowaniu.
111
+ const overflows = (el) => el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1;
112
+ return overflows(root) || Array.from(root.children).some(overflows);
113
+ }
@@ -0,0 +1,31 @@
1
+ import { type ComponentPropsWithRef } from 'react';
2
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
+ export interface KptTreeNode<T = unknown> {
4
+ label: string;
5
+ value?: T;
6
+ icon?: string;
7
+ children?: KptTreeNode<T>[];
8
+ /** Stan początkowy gałęzi; dalej rozwijaniem steruje już komponent. */
9
+ expanded?: boolean;
10
+ disabled?: boolean;
11
+ }
12
+ export interface KptTreeProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'onSelect' | 'children'>, KptI18nOverrides {
13
+ nodes?: KptTreeNode<T>[];
14
+ /** Klik w wiersz zaznacza gałąź zamiast ją rozwijać. */
15
+ selectable?: boolean;
16
+ selected?: KptTreeNode<T> | null;
17
+ defaultSelected?: KptTreeNode<T> | null;
18
+ onSelectedChange?: (node: KptTreeNode<T> | null) => void;
19
+ onNodeClick?: (node: KptTreeNode<T>) => void;
20
+ }
21
+ /**
22
+ * Drzewo hierarchiczne: rozwijanie, zaznaczanie, ikony.
23
+ *
24
+ * Stan rozwinięcia trzyma komponent w zbiorze węzłów, a nie pole `expanded` w danych: mutowanie
25
+ * cudzej tablicy przy każdym kliknięciu byłoby w Reakcie efektem ubocznym renderu, a `expanded`
26
+ * z danych zostaje tym, czym powinno być — stanem początkowym.
27
+ *
28
+ * @example
29
+ * <KptTree nodes={tree} selectable selected={node} onSelectedChange={setNode} />
30
+ */
31
+ export declare function KptTree<T>({ nodes, selectable, selected, defaultSelected, onSelectedChange, onNodeClick, locale, dictionary, className, ...rest }: Readonly<KptTreeProps<T>>): import("react").JSX.Element;
@@ -0,0 +1,62 @@
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 { useControllableState } from "../internal/use-controllable-state.js";
5
+ import { useKptMessages } from "../i18n/i18n-context.js";
6
+ import { KptIcon } from "../icon/icon.js";
7
+ /**
8
+ * Drzewo hierarchiczne: rozwijanie, zaznaczanie, ikony.
9
+ *
10
+ * Stan rozwinięcia trzyma komponent w zbiorze węzłów, a nie pole `expanded` w danych: mutowanie
11
+ * cudzej tablicy przy każdym kliknięciu byłoby w Reakcie efektem ubocznym renderu, a `expanded`
12
+ * z danych zostaje tym, czym powinno być — stanem początkowym.
13
+ *
14
+ * @example
15
+ * <KptTree nodes={tree} selectable selected={node} onSelectedChange={setNode} />
16
+ */
17
+ export function KptTree({ nodes = [], selectable = false, selected, defaultSelected = null, onSelectedChange, onNodeClick, locale, dictionary, className, ...rest }) {
18
+ const { t } = useKptMessages({ locale, dictionary });
19
+ const [active, setActive] = useControllableState({
20
+ value: selected,
21
+ defaultValue: defaultSelected,
22
+ onChange: onSelectedChange,
23
+ });
24
+ // Węzły identyfikujemy referencją — te same obiekty, które przyszły w `nodes`.
25
+ const [open, setOpen] = useState(() => {
26
+ const initial = new Set();
27
+ const walk = (list) => {
28
+ for (const node of list) {
29
+ if (node.expanded)
30
+ initial.add(node);
31
+ if (node.children?.length)
32
+ walk(node.children);
33
+ }
34
+ };
35
+ walk(nodes);
36
+ return initial;
37
+ });
38
+ const hasChildren = (node) => !!node.children?.length;
39
+ const isOpen = (node) => open.has(node);
40
+ const toggle = (node) => {
41
+ const next = new Set(open);
42
+ if (next.has(node))
43
+ next.delete(node);
44
+ else
45
+ next.add(node);
46
+ setOpen(next);
47
+ };
48
+ const onRow = (node) => {
49
+ if (node.disabled)
50
+ return;
51
+ if (selectable)
52
+ setActive(node);
53
+ else if (hasChildren(node))
54
+ toggle(node);
55
+ onNodeClick?.(node);
56
+ };
57
+ const renderList = (list, level) => list.map((node, index) => (_jsxs("li", { className: "kpt-tree__node", role: "treeitem", "aria-expanded": hasChildren(node) ? isOpen(node) : undefined, children: [_jsxs("div", { className: cn('kpt-tree__row', active === node && 'is-selected', node.disabled && 'is-disabled'), style: { paddingInlineStart: `${0.5 + level * 1.1}rem` }, onClick: () => onRow(node), children: [hasChildren(node) ? (_jsx("button", { type: "button", className: "kpt-tree__toggle", "aria-label": isOpen(node) ? t('tree.collapse') : t('tree.expand'), onClick: (event) => {
58
+ event.stopPropagation();
59
+ toggle(node);
60
+ }, children: _jsx(KptIcon, { name: "chevron-right", className: cn('kpt-tree__chevron', isOpen(node) && 'is-open') }) })) : (_jsx("span", { className: "kpt-tree__spacer" })), node.icon ? _jsx(KptIcon, { name: node.icon, className: "kpt-tree__icon" }) : null, _jsx("span", { className: "kpt-tree__label", children: node.label })] }), hasChildren(node) && isOpen(node) ? (_jsx("ul", { role: "group", children: renderList(node.children, level + 1) })) : null] }, index)));
61
+ return (_jsx("div", { className: cn('kpt-tree-host', className), ...rest, children: _jsx("ul", { className: "kpt-tree", role: "tree", children: renderList(nodes, 0) }) }));
62
+ }