@koslibs/components 1.6.0 → 1.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 (53) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +85 -0
  3. package/dist/components/Button/button.js +20 -4
  4. package/dist/components/Button/index_module.css +1 -27
  5. package/dist/components/Collapse/collapse.js +3 -1
  6. package/dist/components/Collapse/index_module.css +2 -4
  7. package/dist/components/Input/index_module.css +9 -20
  8. package/dist/components/Input/input.d.ts +1 -1
  9. package/dist/components/Input/vars.css +7 -7
  10. package/dist/components/Modal/components/footer/index_module.css +1 -1
  11. package/dist/components/Modal/components/header/index.d.ts +1 -1
  12. package/dist/components/Modal/components/header/index_module.css +3 -3
  13. package/dist/components/Modal/index_module.css +2 -2
  14. package/dist/components/Popover/components/popover-surface/index.js +1 -1
  15. package/dist/components/Popover/{use-popover-position.d.ts → hooks/use-popover-position.d.ts} +1 -1
  16. package/dist/components/Spinner/preset_module.css +3 -1
  17. package/dist/components/Status/index.d.ts +2 -0
  18. package/dist/components/Status/index.js +1 -0
  19. package/dist/components/Status/index.module.js +25 -0
  20. package/dist/components/Status/index_module.css +109 -0
  21. package/dist/components/Status/status.d.ts +85 -0
  22. package/dist/components/Status/status.js +35 -0
  23. package/dist/components/Status/vars.css +36 -0
  24. package/dist/components/Tabs/context.d.ts +10 -0
  25. package/dist/components/Tabs/context.js +3 -0
  26. package/dist/components/Tabs/hooks/use-tab-indicator.d.ts +11 -0
  27. package/dist/components/Tabs/hooks/use-tab-indicator.js +43 -0
  28. package/dist/components/Tabs/index.d.ts +3 -0
  29. package/dist/components/Tabs/index.js +2 -0
  30. package/dist/components/Tabs/index.module.js +12 -0
  31. package/dist/components/Tabs/index_module.css +92 -0
  32. package/dist/components/Tabs/tab.d.ts +9 -0
  33. package/dist/components/Tabs/tab.js +38 -0
  34. package/dist/components/Tabs/tabs.d.ts +13 -0
  35. package/dist/components/Tabs/tabs.js +116 -0
  36. package/dist/components/Tabs/types.d.ts +61 -0
  37. package/dist/components/Tabs/types.js +0 -0
  38. package/dist/components/Tabs/vars.css +20 -0
  39. package/dist/components/Textarea/index_module.css +8 -19
  40. package/dist/components/Textarea/textarea.d.ts +1 -1
  41. package/dist/components/Typography/index.d.ts +1 -1
  42. package/dist/components/Typography/index_module.css +2 -2
  43. package/dist/components/Typography/preset.module.js +2 -0
  44. package/dist/components/Typography/preset_module.css +4 -0
  45. package/dist/components/Typography/shared.d.ts +1 -1
  46. package/dist/components/Typography/text.d.ts +1 -1
  47. package/dist/components/Typography/vars.css +1 -1
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.js +2 -0
  50. package/dist/styles/theme.css +1 -0
  51. package/dist/styles/variables.css +63 -0
  52. package/package.json +2 -1
  53. /package/dist/components/Popover/{use-popover-position.js → hooks/use-popover-position.js} +0 -0
@@ -0,0 +1,109 @@
1
+ @import "../../styles/theme.css";
2
+ @import "./vars.css";
3
+
4
+ .component-p4fC6J {
5
+ box-sizing: border-box;
6
+ vertical-align: middle;
7
+ width: fit-content;
8
+ min-width: 0;
9
+ max-width: 100%;
10
+ height: var(--status-height);
11
+ padding: 0 var(--status-padding);
12
+ justify-content: center;
13
+ align-items: center;
14
+ gap: var(--status-geometry-gap);
15
+ border-radius: var(--status-radius);
16
+ display: inline-flex;
17
+ }
18
+
19
+ .size-20-RGtFYL {
20
+ --status-height: var(--status-geometry-height-20);
21
+ --status-padding: var(--status-geometry-padding-20);
22
+ --status-rectangular-radius: var(--status-geometry-radius-rectangular-20);
23
+ --status-rounded-radius: var(--status-geometry-radius-rounded-20);
24
+ }
25
+
26
+ .size-24-80Sg54 {
27
+ --status-height: var(--status-geometry-height-24);
28
+ --status-padding: var(--status-geometry-padding-24);
29
+ --status-rectangular-radius: var(--status-geometry-radius-rectangular-24);
30
+ --status-rounded-radius: var(--status-geometry-radius-rounded-24);
31
+ }
32
+
33
+ .size-32-SU7Krr {
34
+ --status-height: var(--status-geometry-height-32);
35
+ --status-padding: var(--status-geometry-padding-32);
36
+ --status-rectangular-radius: var(--status-geometry-radius-rectangular-32);
37
+ --status-rounded-radius: var(--status-geometry-radius-rounded-32);
38
+ }
39
+
40
+ .size-40-TbyUxw {
41
+ --status-height: var(--status-geometry-height-40);
42
+ --status-padding: var(--status-geometry-padding-40);
43
+ --status-rectangular-radius: var(--status-geometry-radius-rectangular-40);
44
+ --status-rounded-radius: var(--status-geometry-radius-rounded-40);
45
+ }
46
+
47
+ .rectangular-ykPeeK {
48
+ --status-radius: var(--status-rectangular-radius);
49
+ }
50
+
51
+ .rounded-yFfN8M {
52
+ --status-radius: var(--status-rounded-radius);
53
+ }
54
+
55
+ .green-Jd9YVz {
56
+ background-color: var(--status-color-green-background);
57
+ color: var(--status-color-green-foreground);
58
+ }
59
+
60
+ .orange-tRGq_X {
61
+ background-color: var(--status-color-orange-background);
62
+ color: var(--status-color-orange-foreground);
63
+ }
64
+
65
+ .red-QhwnhH {
66
+ background-color: var(--status-color-red-background);
67
+ color: var(--status-color-red-foreground);
68
+ }
69
+
70
+ .blue-AisEDx {
71
+ background-color: var(--status-color-blue-background);
72
+ color: var(--status-color-blue-foreground);
73
+ }
74
+
75
+ .grey-T_mrQo {
76
+ background-color: var(--status-color-grey-background);
77
+ color: var(--status-color-grey-foreground);
78
+ }
79
+
80
+ .teal-E4zwls {
81
+ background-color: var(--status-color-teal-background);
82
+ color: var(--status-color-teal-foreground);
83
+ }
84
+
85
+ .purple-UK0BCp {
86
+ background-color: var(--status-color-purple-background);
87
+ color: var(--status-color-purple-foreground);
88
+ }
89
+
90
+ .sand-Bcwhyf {
91
+ background-color: var(--status-color-sand-background);
92
+ color: var(--status-color-sand-foreground);
93
+ }
94
+
95
+ .component-p4fC6J .label-Oml2a7 {
96
+ text-overflow: ellipsis;
97
+ white-space: nowrap;
98
+ min-width: 0;
99
+ line-height: normal;
100
+ overflow: hidden;
101
+ }
102
+
103
+ .addon-MKQxtF {
104
+ flex-shrink: 0;
105
+ justify-content: center;
106
+ align-items: center;
107
+ display: flex;
108
+ }
109
+
@@ -0,0 +1,85 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type StatusProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'color'> & {
3
+ /**
4
+ * Высота плашки в пикселях.
5
+ * @default 24
6
+ */
7
+ size?: 20 | 24 | 32 | 40;
8
+ /**
9
+ * Форма плашки.
10
+ * @default 'rectangular'
11
+ */
12
+ shape?: 'rounded' | 'rectangular';
13
+ /**
14
+ * Цвет фона, подписи и аддонов.
15
+ * @default 'grey'
16
+ */
17
+ color?: 'green' | 'orange' | 'red' | 'blue' | 'grey' | 'teal' | 'purple' | 'sand';
18
+ /**
19
+ * Короткая подпись статуса. При ограничении ширины сокращается многоточием.
20
+ */
21
+ message: string;
22
+ /**
23
+ * Отображать подпись заглавными буквами.
24
+ * @default false
25
+ */
26
+ uppercase?: boolean;
27
+ /**
28
+ * Декоративный элемент слева от подписи, скрытый от скринридера.
29
+ */
30
+ leftAddon?: ReactNode;
31
+ /**
32
+ * Декоративный элемент справа от подписи, скрытый от скринридера.
33
+ */
34
+ rightAddon?: ReactNode;
35
+ /**
36
+ * Дополнительный CSS-класс подписи.
37
+ */
38
+ labelClassName?: string;
39
+ /**
40
+ * Идентификатор корневого элемента для тестов.
41
+ */
42
+ dataTestId?: string;
43
+ };
44
+ export declare const Status: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLSpanElement>, "color" | "children"> & {
45
+ /**
46
+ * Высота плашки в пикселях.
47
+ * @default 24
48
+ */
49
+ size?: 20 | 24 | 32 | 40;
50
+ /**
51
+ * Форма плашки.
52
+ * @default 'rectangular'
53
+ */
54
+ shape?: "rounded" | "rectangular";
55
+ /**
56
+ * Цвет фона, подписи и аддонов.
57
+ * @default 'grey'
58
+ */
59
+ color?: "green" | "orange" | "red" | "blue" | "grey" | "teal" | "purple" | "sand";
60
+ /**
61
+ * Короткая подпись статуса. При ограничении ширины сокращается многоточием.
62
+ */
63
+ message: string;
64
+ /**
65
+ * Отображать подпись заглавными буквами.
66
+ * @default false
67
+ */
68
+ uppercase?: boolean;
69
+ /**
70
+ * Декоративный элемент слева от подписи, скрытый от скринридера.
71
+ */
72
+ leftAddon?: ReactNode;
73
+ /**
74
+ * Декоративный элемент справа от подписи, скрытый от скринридера.
75
+ */
76
+ rightAddon?: ReactNode;
77
+ /**
78
+ * Дополнительный CSS-класс подписи.
79
+ */
80
+ labelClassName?: string;
81
+ /**
82
+ * Идентификатор корневого элемента для тестов.
83
+ */
84
+ dataTestId?: string;
85
+ } & import("react").RefAttributes<HTMLSpanElement>>;
@@ -0,0 +1,35 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import { forwardRef } from "react";
4
+ import { TypographyText } from "../Typography/text.js";
5
+ import index_module from "./index.module.js";
6
+ function hasAddon(addon) {
7
+ return null != addon && 'boolean' != typeof addon;
8
+ }
9
+ const Status = /*#__PURE__*/ forwardRef(({ size = 24, shape = 'rectangular', color = 'grey', leftAddon, rightAddon, message, uppercase = false, className, labelClassName, dataTestId, ...restProps }, ref)=>/*#__PURE__*/ jsxs("span", {
10
+ ...restProps,
11
+ ref: ref,
12
+ className: classnames(index_module.component, index_module[`size-${size}`], index_module[shape], index_module[color], className),
13
+ "data-test-id": dataTestId,
14
+ children: [
15
+ hasAddon(leftAddon) && /*#__PURE__*/ jsx("span", {
16
+ className: index_module.addon,
17
+ "aria-hidden": "true",
18
+ children: leftAddon
19
+ }),
20
+ /*#__PURE__*/ jsx(TypographyText, {
21
+ size: size <= 24 ? 12 : 14,
22
+ weight: "medium",
23
+ caps: uppercase,
24
+ className: classnames(index_module.label, labelClassName),
25
+ children: message
26
+ }),
27
+ hasAddon(rightAddon) && /*#__PURE__*/ jsx("span", {
28
+ className: index_module.addon,
29
+ "aria-hidden": "true",
30
+ children: rightAddon
31
+ })
32
+ ]
33
+ }));
34
+ Status.displayName = 'Status';
35
+ export { Status };
@@ -0,0 +1,36 @@
1
+ :root {
2
+ --status-color-green-background: #d1f1d7;
3
+ --status-color-green-foreground: #08752a;
4
+ --status-color-orange-background: #ffe6cc;
5
+ --status-color-orange-foreground: #9a4600;
6
+ --status-color-red-background: #fde0df;
7
+ --status-color-red-foreground: #b42318;
8
+ --status-color-blue-background: #ddebff;
9
+ --status-color-blue-foreground: #175cd3;
10
+ --status-color-grey-background: #e9eaec;
11
+ --status-color-grey-foreground: #535862;
12
+ --status-color-teal-background: #d4f1ed;
13
+ --status-color-teal-foreground: #0b6b60;
14
+ --status-color-purple-background: #e9e2ff;
15
+ --status-color-purple-foreground: #6941c6;
16
+ --status-color-sand-background: #ede9de;
17
+ --status-color-sand-foreground: #736554;
18
+ --status-geometry-height-20: var(--size-20);
19
+ --status-geometry-height-24: var(--size-24);
20
+ --status-geometry-height-32: var(--size-32);
21
+ --status-geometry-height-40: var(--size-40);
22
+ --status-geometry-padding-20: var(--size-8);
23
+ --status-geometry-padding-24: var(--size-12);
24
+ --status-geometry-padding-32: var(--size-16);
25
+ --status-geometry-padding-40: var(--size-20);
26
+ --status-geometry-radius-rectangular-20: var(--border-radius-6);
27
+ --status-geometry-radius-rectangular-24: var(--border-radius-6);
28
+ --status-geometry-radius-rectangular-32: var(--border-radius-10);
29
+ --status-geometry-radius-rectangular-40: var(--border-radius-12);
30
+ --status-geometry-radius-rounded-20: var(--border-radius-10);
31
+ --status-geometry-radius-rounded-24: var(--border-radius-12);
32
+ --status-geometry-radius-rounded-32: var(--border-radius-16);
33
+ --status-geometry-radius-rounded-40: var(--border-radius-20);
34
+ --status-geometry-gap: var(--size-6);
35
+ }
36
+
@@ -0,0 +1,10 @@
1
+ import type { TabsProps } from './types.js';
2
+ type TabsContextValue = {
3
+ selectedId?: string;
4
+ size: NonNullable<TabsProps['size']>;
5
+ dataTestId?: string;
6
+ registerTab: (id: string, button: HTMLButtonElement | null) => void;
7
+ selectTab: (id: string, event: Parameters<NonNullable<TabsProps['onChange']>>[0]) => void;
8
+ };
9
+ export declare const TabsContext: import("react").Context<TabsContextValue | null>;
10
+ export {};
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+ const TabsContext = createContext(null);
3
+ export { TabsContext };
@@ -0,0 +1,11 @@
1
+ import { type MutableRefObject, type RefObject } from 'react';
2
+ import type { TabProps, TabsProps } from '../types.js';
3
+ type TabIndicatorOptions = {
4
+ selectedId?: string;
5
+ items: readonly TabProps[];
6
+ size: NonNullable<TabsProps['size']>;
7
+ listRef: RefObject<HTMLDivElement>;
8
+ buttons: MutableRefObject<Map<string, HTMLButtonElement>>;
9
+ };
10
+ export declare function useTabIndicator({ selectedId, items, size, listRef, buttons, }: TabIndicatorOptions): RefObject<HTMLDivElement>;
11
+ export {};
@@ -0,0 +1,43 @@
1
+ import { useLayoutEffect, useRef } from "react";
2
+ function useTabIndicator({ selectedId, items, size, listRef, buttons }) {
3
+ const indicatorRef = useRef(null);
4
+ const indicatorPosition = useRef();
5
+ useLayoutEffect(()=>{
6
+ const list = listRef.current;
7
+ const indicator = indicatorRef.current;
8
+ const button = void 0 !== selectedId ? buttons.current.get(selectedId) : void 0;
9
+ if (!list || !indicator || !button || void 0 === selectedId) {
10
+ indicatorPosition.current = void 0;
11
+ return;
12
+ }
13
+ const updateIndicator = ()=>{
14
+ const left = button.offsetLeft;
15
+ const width = button.offsetWidth;
16
+ const previous = indicatorPosition.current;
17
+ if (previous?.element === indicator && previous.id === selectedId && previous.left === left && previous.width === width) return;
18
+ indicator.style.transition = previous?.element === indicator && previous.id !== selectedId ? '' : 'none';
19
+ indicator.style.width = `${width}px`;
20
+ indicator.style.transform = `translateX(${left}px)`;
21
+ indicatorPosition.current = {
22
+ id: selectedId,
23
+ left,
24
+ width,
25
+ element: indicator
26
+ };
27
+ };
28
+ updateIndicator();
29
+ if ("u" < typeof ResizeObserver) return;
30
+ const observer = new ResizeObserver(updateIndicator);
31
+ observer.observe(list);
32
+ for (const tab of buttons.current.values())observer.observe(tab);
33
+ return ()=>observer.disconnect();
34
+ }, [
35
+ items,
36
+ selectedId,
37
+ size,
38
+ listRef,
39
+ buttons
40
+ ]);
41
+ return indicatorRef;
42
+ }
43
+ export { useTabIndicator };
@@ -0,0 +1,3 @@
1
+ export { Tabs } from './tabs.js';
2
+ export { Tab } from './tab.js';
3
+ export type { TabProps, TabsProps } from './types.js';
@@ -0,0 +1,2 @@
1
+ export { Tabs } from "./tabs.js";
2
+ export { Tab } from "./tab.js";
@@ -0,0 +1,12 @@
1
+ import "./index_module.css";
2
+ const index_module = {
3
+ component: "component-mEZo4o",
4
+ list: "list-pbTqPB",
5
+ tab: "tab-yKhKqB",
6
+ size40: "size40-F0WNwL",
7
+ size48: "size48-cLRBaS",
8
+ selected: "selected-Lm0vXW",
9
+ counter: "counter-Tokn9i",
10
+ indicator: "indicator-LDebsI"
11
+ };
12
+ export default index_module;
@@ -0,0 +1,92 @@
1
+ @import "../../styles/theme.css";
2
+ @import "./vars.css";
3
+
4
+ .component-mEZo4o {
5
+ width: 100%;
6
+ min-width: 0;
7
+ font-family: var(--font-primary);
8
+ }
9
+
10
+ .list-pbTqPB {
11
+ gap: var(--tabs-gap);
12
+ box-shadow: inset 0 -1px var(--tabs-divider-color);
13
+ display: flex;
14
+ position: relative;
15
+ }
16
+
17
+ .tab-yKhKqB {
18
+ justify-content: center;
19
+ align-items: center;
20
+ gap: var(--tabs-gap);
21
+ box-sizing: border-box;
22
+ margin: var(--size-0);
23
+ padding: var(--size-0) var(--tabs-padding);
24
+ border-radius: var(--tabs-border-radius);
25
+ color: var(--tabs-muted-color);
26
+ white-space: nowrap;
27
+ cursor: pointer;
28
+ -webkit-tap-highlight-color: transparent;
29
+ background: none;
30
+ border: 0;
31
+ outline: none;
32
+ flex: none;
33
+ display: inline-flex;
34
+ position: relative;
35
+ }
36
+
37
+ .size40-F0WNwL {
38
+ height: var(--tabs-height-40);
39
+ }
40
+
41
+ .size48-cLRBaS {
42
+ height: var(--tabs-height-48);
43
+ }
44
+
45
+ .tab-yKhKqB:after {
46
+ height: var(--tabs-indicator-height);
47
+ content: "";
48
+ background: none;
49
+ position: absolute;
50
+ bottom: 0;
51
+ left: 0;
52
+ right: 0;
53
+ }
54
+
55
+ .tab-yKhKqB:hover:not(:disabled) {
56
+ color: var(--tabs-color);
57
+ }
58
+
59
+ .tab-yKhKqB:hover:not(:disabled):not(.selected-Lm0vXW):after {
60
+ background: var(--tabs-hover-indicator-color);
61
+ }
62
+
63
+ .tab-yKhKqB.selected-Lm0vXW, .tab-yKhKqB .counter-Tokn9i {
64
+ color: var(--tabs-color);
65
+ }
66
+
67
+ .indicator-LDebsI {
68
+ height: var(--tabs-indicator-height);
69
+ background: var(--tabs-indicator-color);
70
+ pointer-events: none;
71
+ transition: transform var(--tabs-indicator-transition-duration) ease,
72
+ width var(--tabs-indicator-transition-duration) ease;
73
+ position: absolute;
74
+ bottom: 0;
75
+ left: 0;
76
+ }
77
+
78
+ .tab-yKhKqB .counter-Tokn9i {
79
+ padding: var(--size-0) var(--tabs-counter-padding);
80
+ border-radius: var(--tabs-counter-border-radius);
81
+ background: var(--tabs-counter-background-color);
82
+ }
83
+
84
+ .tab-yKhKqB:disabled, .tab-yKhKqB:disabled .counter-Tokn9i {
85
+ color: var(--tabs-disabled-color);
86
+ cursor: default;
87
+ }
88
+
89
+ .tab-yKhKqB:disabled .counter-Tokn9i {
90
+ background: var(--tabs-disabled-counter-background-color);
91
+ }
92
+
@@ -0,0 +1,9 @@
1
+ import type { TabProps } from './types.js';
2
+ /**
3
+ * Кнопка вкладки с заголовком, счётчиком и состоянием выбора из Tabs.
4
+ * Содержимое выбранной вкладки отображается отдельно в приложении.
5
+ */
6
+ export declare const Tab: {
7
+ ({ id, title, count, disabled }: TabProps): import("react").JSX.Element;
8
+ displayName: string;
9
+ };
@@ -0,0 +1,38 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import { useContext } from "react";
4
+ import { useDataTestId } from "../../hooks/use-data-test-id.js";
5
+ import { TypographyText } from "../Typography/text.js";
6
+ import { TabsContext } from "./context.js";
7
+ import index_module from "./index.module.js";
8
+ const Tab = ({ id, title, count, disabled = false })=>{
9
+ const context = useContext(TabsContext);
10
+ const getDataTestId = useDataTestId(context?.dataTestId);
11
+ if (!context) throw new Error('Tab must be rendered inside Tabs.');
12
+ const { selectedId, size, registerTab, selectTab } = context;
13
+ const selected = id === selectedId;
14
+ return /*#__PURE__*/ jsxs("button", {
15
+ ref: (button)=>registerTab(id, button),
16
+ type: "button",
17
+ disabled: disabled,
18
+ className: classnames(index_module.tab, index_module[`size${size}`], {
19
+ [index_module.selected]: selected
20
+ }),
21
+ onClick: (event)=>selectTab(id, event),
22
+ "data-test-id": getDataTestId(`tab-${id}`),
23
+ children: [
24
+ /*#__PURE__*/ jsx(TypographyText, {
25
+ size: 14,
26
+ weight: "medium",
27
+ children: title
28
+ }),
29
+ void 0 !== count && Number.isInteger(count) && count >= 0 && /*#__PURE__*/ jsx(TypographyText, {
30
+ size: 11,
31
+ className: index_module.counter,
32
+ children: count
33
+ })
34
+ ]
35
+ });
36
+ };
37
+ Tab.displayName = 'Tab';
38
+ export { Tab };
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare const Tabs: import("react").ForwardRefExoticComponent<Omit<import("react").HTMLAttributes<HTMLDivElement>, "children" | "onChange"> & {
3
+ children?: ReactNode;
4
+ selectedId?: string;
5
+ defaultSelectedId?: string;
6
+ onChange?: (event: import("react").MouseEvent<HTMLButtonElement>, payload: {
7
+ selectedId: string;
8
+ }) => void;
9
+ size?: 40 | 48;
10
+ emptyState?: ReactNode;
11
+ listClassName?: string;
12
+ dataTestId?: string;
13
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,116 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import { Children, Fragment, forwardRef, isValidElement, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { useDataTestId } from "../../hooks/use-data-test-id.js";
5
+ import { TabsContext } from "./context.js";
6
+ import { useTabIndicator } from "./hooks/use-tab-indicator.js";
7
+ import { Tab } from "./tab.js";
8
+ import index_module from "./index.module.js";
9
+ function collectTabs(children) {
10
+ const items = [];
11
+ const ids = new Set();
12
+ const collect = (nodes)=>{
13
+ Children.forEach(nodes, (child)=>{
14
+ if (null == child || 'boolean' == typeof child) return;
15
+ if (/*#__PURE__*/ isValidElement(child) && child.type === Fragment) return void collect(child.props.children);
16
+ if (!/*#__PURE__*/ isValidElement(child) || child.type !== Tab) throw new Error('Tabs accepts only Tab elements and Fragments.');
17
+ if (ids.has(child.props.id)) throw new Error(`Tabs requires unique Tab ids: "${child.props.id}" is duplicated.`);
18
+ ids.add(child.props.id);
19
+ items.push(child.props);
20
+ });
21
+ };
22
+ collect(children);
23
+ return items;
24
+ }
25
+ function resolveValue(items, value, previous) {
26
+ const index = items.findIndex((item)=>item.id === value);
27
+ if (index >= 0 && !items[index].disabled) return value;
28
+ if (index < 0) {
29
+ const previousIndex = previous.items.findIndex((item)=>item.id === value);
30
+ if (previousIndex >= 0) {
31
+ const neighbors = [
32
+ ...previous.items.slice(previousIndex + 1),
33
+ ...previous.items.slice(0, previousIndex).reverse()
34
+ ];
35
+ const neighbor = neighbors.find((candidate)=>items.some((item)=>item.id === candidate.id && !item.disabled));
36
+ if (neighbor) return neighbor.id;
37
+ }
38
+ if (items.some((item)=>item.id === previous.value && !item.disabled)) return previous.value;
39
+ }
40
+ const start = Math.max(0, index);
41
+ return items.slice(start).find((item)=>!item.disabled)?.id ?? items.slice(0, start).reverse().find((item)=>!item.disabled)?.id;
42
+ }
43
+ const Tabs = /*#__PURE__*/ forwardRef(({ children, selectedId, defaultSelectedId, onChange, size = 40, emptyState, listClassName, dataTestId, className, ...restProps }, ref)=>{
44
+ const getDataTestId = useDataTestId(dataTestId);
45
+ const items = useMemo(()=>collectTabs(children), [
46
+ children
47
+ ]);
48
+ const [internalValue, setInternalValue] = useState(defaultSelectedId);
49
+ const previousSelection = useRef({
50
+ items
51
+ });
52
+ const selectedValue = resolveValue(items, selectedId ?? internalValue, previousSelection.current);
53
+ const listRef = useRef(null);
54
+ const buttons = useRef(new Map());
55
+ useLayoutEffect(()=>{
56
+ previousSelection.current = {
57
+ items,
58
+ value: selectedValue
59
+ };
60
+ if (void 0 === selectedId && internalValue !== selectedValue) setInternalValue(selectedValue);
61
+ }, [
62
+ items,
63
+ selectedValue,
64
+ selectedId,
65
+ internalValue
66
+ ]);
67
+ const indicatorRef = useTabIndicator({
68
+ selectedId: selectedValue,
69
+ items,
70
+ size,
71
+ listRef,
72
+ buttons
73
+ });
74
+ const select = (id, event)=>{
75
+ if (id === selectedValue) return;
76
+ if (void 0 === selectedId) setInternalValue(id);
77
+ onChange?.(event, {
78
+ selectedId: id
79
+ });
80
+ };
81
+ return /*#__PURE__*/ jsx("div", {
82
+ ...restProps,
83
+ ref: ref,
84
+ className: classnames(index_module.component, className),
85
+ "data-test-id": dataTestId,
86
+ children: void 0 === selectedValue ? emptyState : /*#__PURE__*/ jsx(TabsContext.Provider, {
87
+ value: {
88
+ selectedId: selectedValue,
89
+ size,
90
+ dataTestId,
91
+ registerTab: (id, button)=>{
92
+ if (button) buttons.current.set(id, button);
93
+ else buttons.current.delete(id);
94
+ },
95
+ selectTab: select
96
+ },
97
+ children: /*#__PURE__*/ jsxs("div", {
98
+ ref: listRef,
99
+ className: classnames(index_module.list, listClassName),
100
+ "data-test-id": getDataTestId('list'),
101
+ children: [
102
+ items.map((item)=>/*#__PURE__*/ jsx(Tab, {
103
+ ...item
104
+ }, item.id)),
105
+ /*#__PURE__*/ jsx("div", {
106
+ ref: indicatorRef,
107
+ className: index_module.indicator,
108
+ "data-test-id": getDataTestId('indicator')
109
+ })
110
+ ]
111
+ })
112
+ })
113
+ });
114
+ });
115
+ Tabs.displayName = 'Tabs';
116
+ export { Tabs };