@antadesign/anta 0.3.1 → 0.3.3

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 (98) hide show
  1. package/dist/anta_helpers.d.ts +111 -0
  2. package/dist/anta_helpers.js +90 -1
  3. package/dist/calendar-core.d.ts +126 -0
  4. package/dist/calendar-core.js +289 -0
  5. package/dist/components/Button.d.ts +9 -1
  6. package/dist/components/Button.js +12 -18
  7. package/dist/components/Calendar.d.ts +80 -0
  8. package/dist/components/Calendar.js +220 -0
  9. package/dist/components/Checkbox.d.ts +21 -15
  10. package/dist/components/Checkbox.js +17 -7
  11. package/dist/components/Expander.d.ts +7 -1
  12. package/dist/components/Expander.js +4 -2
  13. package/dist/components/Input.d.ts +16 -8
  14. package/dist/components/Input.js +19 -11
  15. package/dist/components/InputDate.d.ts +87 -0
  16. package/dist/components/InputDate.js +278 -0
  17. package/dist/components/InputDate.module.css +1 -0
  18. package/dist/components/Menu.d.ts +5 -1
  19. package/dist/components/Menu.js +6 -2
  20. package/dist/components/MenuItem.d.ts +48 -7
  21. package/dist/components/MenuItem.js +71 -7
  22. package/dist/components/MenuSeparator.d.ts +13 -2
  23. package/dist/components/MenuSeparator.js +12 -2
  24. package/dist/components/Progress.d.ts +4 -1
  25. package/dist/components/Progress.js +4 -3
  26. package/dist/components/RadioGroup.d.ts +17 -15
  27. package/dist/components/RadioGroup.js +8 -8
  28. package/dist/components/Select.d.ts +255 -0
  29. package/dist/components/Select.js +290 -0
  30. package/dist/components/Select.module.css +1 -0
  31. package/dist/components/Tab.d.ts +37 -0
  32. package/dist/components/Tab.js +5 -0
  33. package/dist/components/TabPanel.d.ts +22 -0
  34. package/dist/components/TabPanel.js +5 -0
  35. package/dist/components/Tabs.d.ts +129 -0
  36. package/dist/components/Tabs.js +176 -0
  37. package/dist/components/Tabs.module.css +1 -0
  38. package/dist/components/Tag.d.ts +10 -9
  39. package/dist/components/Tag.js +2 -2
  40. package/dist/components/Text.d.ts +6 -5
  41. package/dist/components/Title.d.ts +4 -2
  42. package/dist/components/Tooltip.d.ts +13 -1
  43. package/dist/components/Tooltip.js +9 -0
  44. package/dist/elements/a-button.css +1 -1
  45. package/dist/elements/a-calendar.css +1 -0
  46. package/dist/elements/a-calendar.d.ts +76 -0
  47. package/dist/elements/a-calendar.js +190 -0
  48. package/dist/elements/a-checkbox.css +1 -1
  49. package/dist/elements/a-checkbox.d.ts +1 -2
  50. package/dist/elements/a-checkbox.js +5 -5
  51. package/dist/elements/a-expander.css +1 -1
  52. package/dist/elements/a-expander.d.ts +16 -5
  53. package/dist/elements/a-expander.js +65 -9
  54. package/dist/elements/a-icon.shapes.css +1 -1
  55. package/dist/elements/a-icon.shapes.d.ts +6 -1
  56. package/dist/elements/a-icon.shapes.js +9 -0
  57. package/dist/elements/a-input.css +1 -1
  58. package/dist/elements/a-input.d.ts +6 -0
  59. package/dist/elements/a-input.js +28 -9
  60. package/dist/elements/a-menu-group.css +1 -1
  61. package/dist/elements/a-menu-item.css +1 -1
  62. package/dist/elements/a-menu-item.d.ts +14 -4
  63. package/dist/elements/a-menu-item.js +17 -0
  64. package/dist/elements/a-menu-separator.css +1 -1
  65. package/dist/elements/a-menu.css +1 -1
  66. package/dist/elements/a-menu.d.ts +51 -8
  67. package/dist/elements/a-menu.js +285 -42
  68. package/dist/elements/a-progress.css +1 -1
  69. package/dist/elements/a-radio-group.d.ts +1 -3
  70. package/dist/elements/a-radio-group.js +13 -13
  71. package/dist/elements/a-radio.css +1 -1
  72. package/dist/elements/a-radio.d.ts +3 -11
  73. package/dist/elements/a-radio.js +3 -34
  74. package/dist/elements/a-tab.css +1 -0
  75. package/dist/elements/a-tab.d.ts +6 -0
  76. package/dist/elements/a-tab.js +15 -0
  77. package/dist/elements/a-tabpanel.css +1 -0
  78. package/dist/elements/a-tabs.css +1 -0
  79. package/dist/elements/a-tabs.d.ts +28 -0
  80. package/dist/elements/a-tabs.js +157 -0
  81. package/dist/elements/a-tag.css +1 -1
  82. package/dist/elements/a-text.css +1 -1
  83. package/dist/elements/a-text.d.ts +14 -13
  84. package/dist/elements/a-text.js +53 -23
  85. package/dist/elements/a-title.css +1 -1
  86. package/dist/elements/a-tooltip.css +1 -1
  87. package/dist/elements/a-tooltip.d.ts +16 -8
  88. package/dist/elements/a-tooltip.js +62 -16
  89. package/dist/elements/index.d.ts +4 -0
  90. package/dist/elements/index.js +10 -0
  91. package/dist/general_types.d.ts +252 -30
  92. package/dist/index.d.ts +15 -1
  93. package/dist/index.js +43 -1
  94. package/dist/jsx-runtime.d.ts +20 -7
  95. package/dist/jsx-runtime.js +6 -0
  96. package/dist/reset.css +1 -1
  97. package/dist/tokens.css +1 -1
  98. package/package.json +4 -2
@@ -0,0 +1,290 @@
1
+ import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
+ import { useState, useId } from "../jsx-runtime";
3
+ import { Input } from "./Input";
4
+ import { Icon } from "./Icon";
5
+ import { Menu } from "./Menu";
6
+ import { MenuItem } from "./MenuItem";
7
+ import { MenuGroup } from "./MenuGroup";
8
+ import { MenuSeparator } from "./MenuSeparator";
9
+ import styles from "./Select.module.css";
10
+ const normalize = (o) => typeof o === "string" ? { value: o, label: o } : o;
11
+ const Select = (props) => {
12
+ const {
13
+ options,
14
+ selection,
15
+ indicator,
16
+ value,
17
+ defaultValue,
18
+ onValueChange,
19
+ placeholder,
20
+ icon,
21
+ label,
22
+ hint,
23
+ size,
24
+ status,
25
+ statusIcon,
26
+ round,
27
+ disabled,
28
+ toneSelected,
29
+ filter,
30
+ selectAll,
31
+ selectAllLabel = "Select all",
32
+ renderOption,
33
+ renderIndicator,
34
+ renderTrigger,
35
+ renderEmpty,
36
+ className,
37
+ style,
38
+ ...rest
39
+ } = props;
40
+ const multiple = selection === "multiple";
41
+ const statusColor = status && status !== "neutral" ? `var(--text-2-${status})` : void 0;
42
+ const mark = multiple ? "checkbox" : indicator ?? "none";
43
+ const menuItemIndicator = mark === "none" ? void 0 : mark;
44
+ const emit = onValueChange;
45
+ const controlled = value !== void 0;
46
+ const [internal, setInternal] = useState(defaultValue);
47
+ const currentRaw = controlled ? value : internal;
48
+ const [open, setOpen] = useState(false);
49
+ const [query, setQuery] = useState("");
50
+ const uid = useId();
51
+ const isSubmenu = (it) => typeof it === "object" && Array.isArray(it.submenu);
52
+ const isGroup = (it) => typeof it === "object" && Array.isArray(it.options);
53
+ const collectLeaves = (items, group, disabled2, out) => {
54
+ for (const raw of items) {
55
+ if (typeof raw !== "string" && isSubmenu(raw)) collectLeaves(raw.submenu, raw.label, disabled2 || !!raw.disabled, out);
56
+ else if (typeof raw !== "string" && isGroup(raw)) collectLeaves(raw.options, raw.label, disabled2 || !!raw.disabled, out);
57
+ else {
58
+ const o = normalize(raw);
59
+ out.push({ opt: o, disabled: disabled2 || !!o.disabled, group });
60
+ }
61
+ }
62
+ };
63
+ const allLeaves = [];
64
+ collectLeaves(options, void 0, false, allLeaves);
65
+ const byValue = new Map(allLeaves.map((l) => [l.opt.value, l.opt]));
66
+ const filtering = filter !== void 0 && filter !== false;
67
+ const q = query.trim();
68
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
69
+ const queryRe = filtering && typeof filter !== "function" && q ? new RegExp(q.split(/\s+/).map(escapeRe).join("\\s+"), "i") : null;
70
+ const matches = (o) => typeof filter === "function" ? filter(o, q) : !queryRe || [o.value, o.label ?? "", o.hint ?? ""].some((s) => queryRe.test(s));
71
+ const prune = typeof filter === "function" || filtering && !!q;
72
+ const visibleLeaves = prune ? allLeaves.filter((l) => matches(l.opt)) : allLeaves;
73
+ const flattening = filtering && !!q;
74
+ const highlight = (text) => {
75
+ if (!queryRe) return text;
76
+ const re = new RegExp(queryRe.source, "gi");
77
+ const out = [];
78
+ let last = 0;
79
+ let m;
80
+ while ((m = re.exec(text)) !== null) {
81
+ if (m.index > last) out.push(text.slice(last, m.index));
82
+ out.push(/* @__PURE__ */ jsx("b", { children: m[0] }, out.length));
83
+ last = m.index + m[0].length;
84
+ if (m[0].length === 0) re.lastIndex++;
85
+ }
86
+ if (out.length === 0) return text;
87
+ if (last < text.length) out.push(text.slice(last));
88
+ return out;
89
+ };
90
+ const selectedValues = Array.isArray(currentRaw) ? currentRaw : currentRaw != null ? [currentRaw] : [];
91
+ const isSelected = (v) => selectedValues.includes(v);
92
+ const selectedOptions = selectedValues.map((v) => byValue.get(v)).filter(Boolean);
93
+ let display = "";
94
+ if (multiple) {
95
+ if (selectedOptions.length === 1) display = selectedOptions[0].label ?? selectedOptions[0].value;
96
+ else if (selectedOptions.length > 1) display = `${selectedOptions.length} selected`;
97
+ } else if (currentRaw != null) {
98
+ const o = byValue.get(currentRaw);
99
+ display = o ? o.label ?? o.value : "";
100
+ }
101
+ const choose = (o) => {
102
+ if (multiple) {
103
+ const has = selectedValues.includes(o.value);
104
+ const next = has ? selectedValues.filter((v) => v !== o.value) : [...selectedValues, o.value];
105
+ if (!controlled) setInternal(next);
106
+ emit?.(next, { value: o.value, option: o, selected: !has });
107
+ } else {
108
+ if (!controlled) setInternal(o.value);
109
+ emit?.(o.value, { value: o.value, option: o });
110
+ }
111
+ };
112
+ const enabledValues = multiple ? visibleLeaves.filter((l) => !l.disabled).map((l) => l.opt.value) : [];
113
+ const enabledSelected = enabledValues.filter((v) => selectedValues.includes(v));
114
+ const allSelected = enabledValues.length > 0 && enabledSelected.length === enabledValues.length;
115
+ const someSelected = enabledSelected.length > 0 && !allSelected;
116
+ const toggleAll = () => {
117
+ const keep = selectedValues.filter((v) => !enabledValues.includes(v));
118
+ const next = allSelected ? keep : [...keep, ...enabledValues];
119
+ if (!controlled) setInternal(next);
120
+ emit?.(next, { all: true, selected: !allSelected });
121
+ };
122
+ const renderOptionRow = (o, disabled2) => {
123
+ const optState = { value: o.value, selected: isSelected(o.value), disabled: disabled2 };
124
+ const custom = renderOption?.(o, optState);
125
+ const customMark = renderIndicator?.(optState);
126
+ const ariaSelectable = !multiple && mark === "none" ? { role: "menuitemradio", "aria-checked": isSelected(o.value) ? "true" : "false" } : void 0;
127
+ return /* @__PURE__ */ jsx(
128
+ MenuItem,
129
+ {
130
+ id: `${uid}-opt-${o.value}`,
131
+ selectionIndicator: menuItemIndicator,
132
+ ...ariaSelectable,
133
+ indicator: customMark ?? void 0,
134
+ label: custom ? void 0 : queryRe ? highlight(o.label ?? o.value) : o.label ?? o.value,
135
+ hint: custom ? void 0 : queryRe && o.hint ? highlight(o.hint) : o.hint,
136
+ icon: custom ? void 0 : o.icon,
137
+ tone: o.tone,
138
+ toneSelected,
139
+ selected: isSelected(o.value),
140
+ disabled: disabled2 || void 0,
141
+ "data-menu-open": multiple ? "" : void 0,
142
+ onSelect: () => choose(o),
143
+ children: custom
144
+ },
145
+ o.value
146
+ );
147
+ };
148
+ const renderTree = (items, disabled2) => {
149
+ const out = [];
150
+ items.forEach((raw, i) => {
151
+ if (typeof raw !== "string" && isSubmenu(raw)) {
152
+ const dis = disabled2 || !!raw.disabled;
153
+ const inner = renderTree(raw.submenu, dis);
154
+ if (inner.length)
155
+ out.push(
156
+ /* @__PURE__ */ jsx(
157
+ MenuItem,
158
+ {
159
+ submenu: true,
160
+ label: raw.label,
161
+ icon: raw.icon,
162
+ disabled: dis || void 0,
163
+ "data-menu-open": multiple ? "" : void 0,
164
+ children: /* @__PURE__ */ jsx(Menu, { children: inner })
165
+ },
166
+ `sub-${i}-${raw.label}`
167
+ )
168
+ );
169
+ return;
170
+ }
171
+ if (typeof raw !== "string" && isGroup(raw)) {
172
+ const dis = disabled2 || !!raw.disabled;
173
+ const inner = renderTree(raw.options, dis);
174
+ if (inner.length) out.push(/* @__PURE__ */ jsx(MenuGroup, { label: raw.label, children: inner }, `grp-${i}-${raw.label}`));
175
+ return;
176
+ }
177
+ const o = normalize(raw);
178
+ if (typeof filter === "function" && !matches(o)) return;
179
+ out.push(renderOptionRow(o, disabled2 || !!o.disabled));
180
+ });
181
+ return out;
182
+ };
183
+ const renderFlat = () => {
184
+ const shown = allLeaves.filter((l) => matches(l.opt));
185
+ const out = [];
186
+ for (let i = 0; i < shown.length; ) {
187
+ const label2 = shown[i].group;
188
+ if (label2 === void 0) {
189
+ out.push(renderOptionRow(shown[i].opt, shown[i].disabled));
190
+ i++;
191
+ continue;
192
+ }
193
+ const rows = [];
194
+ const start = i;
195
+ while (i < shown.length && shown[i].group === label2) {
196
+ rows.push(renderOptionRow(shown[i].opt, shown[i].disabled));
197
+ i++;
198
+ }
199
+ out.push(/* @__PURE__ */ jsx(MenuGroup, { label: label2, children: rows }, `flat-${start}-${label2}`));
200
+ }
201
+ return out;
202
+ };
203
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
204
+ renderTrigger ? renderTrigger({ open, value: currentRaw, selected: selectedOptions, disabled: !!disabled, icon }) : /* @__PURE__ */ jsx(
205
+ Input,
206
+ {
207
+ label,
208
+ hint,
209
+ placeholder,
210
+ value: display,
211
+ readOnly: true,
212
+ dimActions: true,
213
+ disabled,
214
+ leading: icon ? /* @__PURE__ */ jsx(Icon, { shape: icon }) : void 0,
215
+ size,
216
+ status,
217
+ statusIcon,
218
+ round,
219
+ "aria-haspopup": "menu",
220
+ "aria-expanded": open ? "true" : "false",
221
+ onKeyDown: (e) => {
222
+ if (!open && (e.key === "Enter" || e.key === " " || e.key === "ArrowDown")) {
223
+ e.preventDefault();
224
+ e.currentTarget.click();
225
+ }
226
+ },
227
+ trailing: (
228
+ // Chevron rotates down → up when open (spacing + rotation in the CSS
229
+ // module, matching the Input "Select dropdown" example).
230
+ /* @__PURE__ */ jsx(
231
+ Icon,
232
+ {
233
+ shape: "chevron-down",
234
+ className: open ? `${styles.chevron} ${styles.open}` : styles.chevron,
235
+ style: statusColor ? { color: statusColor } : void 0
236
+ }
237
+ )
238
+ ),
239
+ className,
240
+ style,
241
+ ...rest
242
+ }
243
+ ),
244
+ /* @__PURE__ */ jsxs(
245
+ Menu,
246
+ {
247
+ onStateChange: (_e, { next }) => {
248
+ setOpen(next);
249
+ if (!next) setQuery("");
250
+ },
251
+ children: [
252
+ filtering && // `slot="header"` pins the field in the Menu's fixed header region (above
253
+ // the scrolling options); `data-menu-search` puts the menu in combobox
254
+ // mode — it focuses this field on open and drives an active-option cursor.
255
+ /* @__PURE__ */ jsx("div", { className: styles.filter, slot: "header", "data-menu-open": "", children: /* @__PURE__ */ jsx(
256
+ Input,
257
+ {
258
+ "data-menu-search": "",
259
+ size: "small",
260
+ value: query,
261
+ placeholder: "Filter\u2026",
262
+ "aria-label": "Filter options",
263
+ "aria-autocomplete": "list",
264
+ onInput: (e) => setQuery(e.currentTarget.value)
265
+ }
266
+ ) }),
267
+ multiple && selectAll && visibleLeaves.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
268
+ /* @__PURE__ */ jsx(
269
+ MenuItem,
270
+ {
271
+ selectionIndicator: "checkbox",
272
+ selected: allSelected,
273
+ indeterminate: someSelected,
274
+ label: selectAllLabel,
275
+ "data-menu-open": "",
276
+ "data-menu-skip-active": "",
277
+ onSelect: toggleAll
278
+ }
279
+ ),
280
+ /* @__PURE__ */ jsx(MenuSeparator, {})
281
+ ] }),
282
+ visibleLeaves.length === 0 ? renderEmpty?.({ query: q }) : flattening ? renderFlat() : renderTree(options, false)
283
+ ]
284
+ }
285
+ )
286
+ ] });
287
+ };
288
+ export {
289
+ Select
290
+ };
@@ -0,0 +1 @@
1
+ .chevron{margin-inline-end:7px;transition:transform .15s ease}.chevron.open{transform:rotate(180deg)}.filter{padding:4px 4px 0}
@@ -0,0 +1,37 @@
1
+ import type { IconShape } from "../elements/a-icon.shapes";
2
+ /** One tab in a `<Tabs>` strip. A **config component**: `Tabs` reads these props to
3
+ * render the underlying `<a-tab>` (`tabindex`, `role`, `aria-controls`, and
4
+ * selection are all `Tabs`' job), so `<Tab>` renders nothing on its own. */
5
+ export interface TabProps {
6
+ /** This tab's identity — pairs it with the `<TabPanel value="…">` of the same
7
+ * value, and the value reported by `onStateChange` / `onChange`. Unique per strip. */
8
+ value: string;
9
+ /** Visible label. Alternatively pass `children`. */
10
+ label?: React.ReactNode;
11
+ /** Tab content when you need more than a string — used if `label` is omitted. */
12
+ children?: React.ReactNode;
13
+ /** Leading icon shape, rendered before the label. */
14
+ icon?: IconShape;
15
+ /** Trailing icon shape, rendered after the label. */
16
+ iconTrailing?: IconShape;
17
+ /** Fully-round just this tab's box. `<Tabs round>` rounds the whole strip
18
+ * (tabs + sliding indicator) instead. */
19
+ round?: boolean;
20
+ /** Per-tab tone override, same vocabulary as `<Tabs tone>` — colours this one tab's
21
+ * label + icons (all priorities/modes, named or custom colour) and, when it's the
22
+ * active tab, its indicator. For a **custom literal colour** the sliding indicator can't
23
+ * adopt it (the shared moving element can't read a descendant's colour), so a custom tone
24
+ * colours the label everywhere and the indicator only in `noslide`; the six **named**
25
+ * tones colour both in every mode. Overrides the strip's `tone` for this tab.
26
+ * @defaultValue inherits the strip's `tone` */
27
+ tone?: "neutral" | "brand" | "info" | "success" | "warning" | "critical" | (string & {});
28
+ /** Disable just this tab — skipped by keyboard nav and dropped from the tab order
29
+ * (a disabled-but-selected tab stays reachable, per the ARIA pattern). */
30
+ disabled?: boolean;
31
+ }
32
+ /**
33
+ * A tab inside `<Tabs>`. Configuration only — `Tabs` reads its props and renders the
34
+ * real `<a-tab>`, so this component itself produces no DOM. Use it as a direct child
35
+ * of `<Tabs>`; rendering it elsewhere does nothing.
36
+ */
37
+ export declare const Tab: (_props: TabProps) => null;
@@ -0,0 +1,5 @@
1
+ import { markTabsKind } from "../anta_helpers";
2
+ const Tab = markTabsKind((_props) => null, "tab");
3
+ export {
4
+ Tab
5
+ };
@@ -0,0 +1,22 @@
1
+ import type { TabsMounting } from "./Tabs";
2
+ /** A panel paired to the `<Tab>` of the same `value`. A **config component**: `Tabs`
3
+ * reads these props and renders the underlying `<a-tabpanel>` (id wiring, show/hide,
4
+ * `inert`, and the mounting strategy are all `Tabs`' job). */
5
+ export interface TabPanelProps {
6
+ /** Pairs this panel with the `<Tab value="…">` of the same value. */
7
+ value: string;
8
+ /** Panel content — arbitrary React/Preact. */
9
+ children?: React.ReactNode;
10
+ /** Override the `<Tabs mounting>` strategy for just this panel. See `TabsMounting`. */
11
+ mounting?: TabsMounting;
12
+ /** CSS class on the rendered `<a-tabpanel>`. */
13
+ className?: string;
14
+ /** Inline style on the rendered `<a-tabpanel>`. */
15
+ style?: React.CSSProperties;
16
+ }
17
+ /**
18
+ * A tab panel inside `<Tabs>`. Configuration only — `Tabs` reads its props and renders
19
+ * the real `<a-tabpanel>`, so this component itself produces no DOM. Use it as a direct
20
+ * child of `<Tabs>`; rendering it elsewhere does nothing.
21
+ */
22
+ export declare const TabPanel: (_props: TabPanelProps) => null;
@@ -0,0 +1,5 @@
1
+ import { markTabsKind } from "../anta_helpers";
2
+ const TabPanel = markTabsKind((_props) => null, "panel");
3
+ export {
4
+ TabPanel
5
+ };
@@ -0,0 +1,129 @@
1
+ import type { BaseProps } from "../general_types";
2
+ import type { TabProps } from "./Tab";
3
+ /** The element's `statechange` payload — `next`/`prev` are tab values (`null` = none). */
4
+ type StateDetail = {
5
+ next: string | null;
6
+ prev: string | null;
7
+ };
8
+ type StateChangeEvent = CustomEvent<StateDetail>;
9
+ /** Snapshot passed as the 2nd argument to `onValueChange`. */
10
+ export interface TabsChangeAttrs {
11
+ value: string | null;
12
+ }
13
+ /** One tab as a plain data object for the `options` prop — the array-driven
14
+ * alternative to `<Tab>` children (mirrors `RadioGroup`'s `options`). Same
15
+ * fields as `<Tab>` minus `children` (use `label`); `icon` / `iconTrailing`
16
+ * are icon-shape strings. */
17
+ export type TabOption = Omit<TabProps, "children">;
18
+ /**
19
+ * How `<TabPanel>`s that aren't the active one are handled.
20
+ * - `'display'` *(default)* — all panels mounted; inactive ones hidden via
21
+ * `display:none`. Preserves panel DOM/form state; correct a11y for free.
22
+ * - `'visibility'` — all mounted; inactive hidden via `visibility:hidden`, so each
23
+ * panel keeps its layout box (useful to avoid reflow / for measuring).
24
+ * - `'active'` — only the active panel is rendered; React unmounts the rest (state
25
+ * resets on switch; defers expensive subtrees).
26
+ * - `'lazy'` — a panel mounts on first activation, then stays mounted+hidden.
27
+ */
28
+ export type TabsMounting = "display" | "visibility" | "active" | "lazy";
29
+ /** Public props for `<Tabs>`. */
30
+ export interface TabsProps extends Omit<BaseProps, "onChange"> {
31
+ /** The strip's contents, as config-only elements `Tabs` reads (they render
32
+ * nothing themselves): one `<Tab value="…" label="…">` per tab, and optionally a
33
+ * `<TabPanel value="…">` whose `value` matches a tab — `Tabs` then shows that
34
+ * panel's body when its tab is active. Omit the panels to use `Tabs` as a bare
35
+ * selectable strip. Order is free; tabs and panels can interleave. */
36
+ children?: React.ReactNode;
37
+ /** Tabs as a data array instead of `<Tab>` children (like `RadioGroup`'s
38
+ * `options`). When set, the strip renders from these and `<Tab>` children are
39
+ * ignored; `<TabPanel>` children still supply panel bodies (matched by `value`).
40
+ * Each entry is a `TabOption` (`value`, `label`, `icon`, `iconTrailing`,
41
+ * `tone`, `disabled`, `round`). */
42
+ options?: TabOption[];
43
+ /** Controlled active value — the `value` of the `<Tab>` to mark selected (and, when a
44
+ * `<TabPanel value="…">` shares it, the panel to reveal). When set, you own selection:
45
+ * the strip renders exactly what this says, and a user pick only *requests* a change
46
+ * via `onStateChange` — apply it by updating this prop, reject it by leaving it
47
+ * unchanged. Leave undefined (and use `defaultValue`) for uncontrolled. */
48
+ value?: string;
49
+ /** Initial active value for the uncontrolled case — the `value` of the `<Tab>` selected
50
+ * on first render. After that `Tabs` owns selection itself. */
51
+ defaultValue?: string;
52
+ /** Fired whenever the active tab changes — event-first. `detail` is
53
+ * `{ next, prev }` (values; `null` = none). Cancelable: `event.preventDefault()`
54
+ * vetoes it (uncontrolled), or in controlled mode answer by updating `value`. */
55
+ onStateChange?: (event: StateChangeEvent, detail: StateDetail) => void;
56
+ /** Fired *after* the active tab changes — a native `change` event. */
57
+ onChange?: (event: Event) => void;
58
+ /** Like `onChange`, but with a `{ value }` snapshot as the 2nd argument. */
59
+ onValueChange?: (event: Event, attrs: TabsChangeAttrs) => void;
60
+ /** Focus entered the strip (any tab) — wired to `focusin` (focus lands on a tab,
61
+ * not the tablist). */
62
+ onFocus?: (event: FocusEvent) => void;
63
+ /** Focus left the strip entirely — wired to `focusout`. */
64
+ onBlur?: (event: FocusEvent) => void;
65
+ /** Accessible name for the tablist (`aria-label`). */
66
+ label?: string;
67
+ /** Visual priority. `primary` is the raised pill on a recessed track (the
68
+ * segmented-control look); `secondary` keeps that sizing but drops the track, marking
69
+ * the selected tab with a subtle active background fill; `tertiary` is a bottom-underline
70
+ * indicator under the selected tab (no track, no rest line). `tone` colours `secondary` +
71
+ * `tertiary`; `primary` stays neutral.
72
+ * @defaultValue 'primary' */
73
+ priority?: "primary" | "secondary" | "tertiary";
74
+ /** Tone applied to the selected indicator/label, or any literal CSS color for a
75
+ * one-off custom tone (derived in oklch). Named tones track light/dark.
76
+ * @defaultValue 'neutral' */
77
+ tone?: "neutral" | "brand" | "info" | "success" | "warning" | "critical" | (string & {});
78
+ /** Size — small 24px · medium 28px · large 32px tall, matching Button's scale (the tab's
79
+ * label leading runs a touch tighter, offset by 1px more block padding per side).
80
+ * @defaultValue 'medium' */
81
+ size?: "small" | "medium" | "large";
82
+ /** Layout + arrow-key axis. Horizontal ellipsizes labels when tabs overflow (scroll
83
+ * is opt-in via CSS); vertical stacks them.
84
+ * @defaultValue 'horizontal' */
85
+ orientation?: "horizontal" | "vertical";
86
+ /** How inactive panels are mounted/hidden. Per-panel `<TabPanel mounting>` overrides.
87
+ * @defaultValue 'display' */
88
+ mounting?: TabsMounting;
89
+ /** Disable the sliding indicator. By default the selected-tab indicator animates
90
+ * between tabs (a single rectangle, via CSS anchor positioning); `noslide` paints it
91
+ * per tab so it snaps with no movement. (Browsers without anchor positioning get that
92
+ * per-tab paint automatically — `noslide` is the explicit opt-out.) */
93
+ noslide?: boolean;
94
+ /** Fully-round the tabs and the sliding indicator (and the primary track
95
+ * well). Applies strip-wide; a single `<Tab round>` rounds just that tab. A
96
+ * `number` (px) or CSS length string sets a custom radius on the top-level
97
+ * track well only — the tab pills + indicator stay fully round. */
98
+ round?: boolean | number | string;
99
+ /** Disable the whole strip. */
100
+ disabled?: boolean;
101
+ }
102
+ /**
103
+ * `<Tabs>` — a tablist with optional panels, rendered from `<Tab>` / `<TabPanel>`
104
+ * children.
105
+ *
106
+ * Like `RadioGroup`, this wrapper owns the **declarative** DOM concerns the elements
107
+ * deliberately don't touch — each tab's **`tabindex`** (every enabled tab is its own
108
+ * tab stop), `role`, the `aria-controls`/`aria-labelledby` wiring, and which panel
109
+ * shows (per `mounting`). Selection itself lives in `<a-tabs>` off-DOM (it sets each
110
+ * tab's `selected` property), so the elements never mutate the DOM; this wrapper
111
+ * reflects the current value into panel visibility on re-render.
112
+ *
113
+ * Controlled (`value` + `onStateChange`) or uncontrolled (`defaultValue`). Drop the
114
+ * `<TabPanel>`s and it's just a selectable strip emitting `statechange` / `change`.
115
+ *
116
+ * Requires `@antadesign/anta/elements` (client-side only).
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * <Tabs defaultValue="account" label="Settings">
121
+ * <Tab value="account" label="Account" icon="user" />
122
+ * <Tab value="security" label="Security" />
123
+ * <TabPanel value="account"><AccountForm /></TabPanel>
124
+ * <TabPanel value="security"><SecurityForm /></TabPanel>
125
+ * </Tabs>
126
+ * ```
127
+ */
128
+ export declare const Tabs: ({ children, options, value, defaultValue, onStateChange, onChange, onValueChange, onFocus, onBlur, label, priority, tone, size, orientation, mounting, noslide, round, disabled, className, style, id, ...rest }: TabsProps) => any;
129
+ export {};
@@ -0,0 +1,176 @@
1
+ import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
+ import { useId, useState, Fragment } from "../jsx-runtime";
3
+ import { nativeStateChange, toneStyle, roundStyle, wrapLabel, TABS_KIND } from "../anta_helpers";
4
+ import styles from "./Tabs.module.css";
5
+ const flattenChildren = (nodes) => {
6
+ const out = [];
7
+ const visit = (n) => {
8
+ if (n == null || typeof n === "boolean") return;
9
+ if (Array.isArray(n)) {
10
+ n.forEach(visit);
11
+ return;
12
+ }
13
+ if (n && n.type === Fragment) {
14
+ visit(n.props?.children);
15
+ return;
16
+ }
17
+ out.push(n);
18
+ };
19
+ visit(nodes);
20
+ return out;
21
+ };
22
+ const Tabs = ({
23
+ children,
24
+ options,
25
+ value,
26
+ defaultValue,
27
+ onStateChange,
28
+ onChange,
29
+ onValueChange,
30
+ onFocus,
31
+ onBlur,
32
+ label,
33
+ priority,
34
+ tone,
35
+ size,
36
+ orientation,
37
+ mounting = "display",
38
+ noslide,
39
+ round,
40
+ disabled,
41
+ className,
42
+ style,
43
+ id,
44
+ ...rest
45
+ }) => {
46
+ const controlled = value !== void 0;
47
+ const [internalValue, setInternalValue] = useState(defaultValue);
48
+ const currentValue = controlled ? value : internalValue;
49
+ const [mounted, setMounted] = useState(
50
+ () => new Set(currentValue != null ? [currentValue] : [])
51
+ );
52
+ if (currentValue != null && !mounted.has(currentValue)) {
53
+ setMounted((m) => m.has(currentValue) ? m : new Set(m).add(currentValue));
54
+ }
55
+ const baseId = useId();
56
+ const tabId = (v) => `${baseId}-tab-${v}`;
57
+ const panelId = (v) => `${baseId}-panel-${v}`;
58
+ const items = flattenChildren(children);
59
+ const tabs = options ? options.map((o) => ({ props: o })) : items.filter((c) => c?.type?.[TABS_KIND] === "tab");
60
+ const panels = items.filter((c) => c?.type?.[TABS_KIND] === "panel");
61
+ const panelValues = new Set(panels.map((pan) => pan.props.value));
62
+ const seen = /* @__PURE__ */ new Set();
63
+ for (const t of tabs) {
64
+ if (seen.has(t.props.value))
65
+ console.warn(`[anta] <Tabs> duplicate <Tab value=${JSON.stringify(t.props.value)}> \u2014 values must be unique.`);
66
+ seen.add(t.props.value);
67
+ }
68
+ const onstatechange = (e) => {
69
+ const { event, detail } = nativeStateChange(e);
70
+ if (!detail) return;
71
+ onStateChange?.(event, detail);
72
+ if (controlled) return;
73
+ if (event.defaultPrevented) return;
74
+ setInternalValue(detail.next ?? void 0);
75
+ };
76
+ const onchange = onChange || onValueChange ? (e) => {
77
+ onChange?.(e);
78
+ onValueChange?.(e, { value: e.currentTarget?.value ?? null });
79
+ } : void 0;
80
+ const vertical = orientation === "vertical";
81
+ const needsContainer = panels.length > 0 || vertical;
82
+ const strip = /* @__PURE__ */ jsx(
83
+ "a-tabs",
84
+ {
85
+ role: "tablist",
86
+ "aria-label": label,
87
+ "aria-orientation": vertical ? "vertical" : void 0,
88
+ "aria-disabled": disabled ? "true" : void 0,
89
+ state: controlled ? value : void 0,
90
+ "default-state": !controlled ? defaultValue : void 0,
91
+ priority: priority && priority !== "primary" ? priority : void 0,
92
+ tone: tone && tone !== "neutral" ? tone : void 0,
93
+ size: size && size !== "medium" ? size : void 0,
94
+ orientation: vertical ? "vertical" : void 0,
95
+ noslide: noslide ? "" : void 0,
96
+ round: round ? "" : void 0,
97
+ disabled: disabled ? "" : void 0,
98
+ onstatechange,
99
+ onchange,
100
+ onfocusin: onFocus,
101
+ onfocusout: onBlur,
102
+ class: needsContainer ? void 0 : className,
103
+ id: needsContainer ? void 0 : id,
104
+ style: roundStyle(round, "--tabs-round", toneStyle(tone, "--tabs-tone-source", style)),
105
+ ...needsContainer ? {} : rest,
106
+ children: tabs.map((t) => {
107
+ const p = t.props;
108
+ const tabDisabled = disabled || p.disabled;
109
+ const isSelected = p.value === currentValue;
110
+ const hasPanel = panelValues.has(p.value);
111
+ return /* @__PURE__ */ jsxs(
112
+ "a-tab",
113
+ {
114
+ role: "tab",
115
+ value: p.value,
116
+ id: tabId(p.value),
117
+ tone: p.tone && p.tone !== "neutral" ? p.tone : void 0,
118
+ style: toneStyle(p.tone, "--tabs-tone-source", void 0),
119
+ "aria-controls": hasPanel ? panelId(p.value) : void 0,
120
+ "aria-disabled": tabDisabled ? "true" : void 0,
121
+ tabIndex: tabDisabled && !isSelected ? -1 : 0,
122
+ disabled: tabDisabled ? "" : void 0,
123
+ round: round || p.round ? "" : void 0,
124
+ children: [
125
+ p.icon && /* @__PURE__ */ jsx("a-icon", { shape: p.icon, "aria-hidden": "true" }),
126
+ wrapLabel(p.label != null ? p.label : p.children, "a-tab-label"),
127
+ p.iconTrailing && /* @__PURE__ */ jsx("a-icon", { shape: p.iconTrailing, "aria-hidden": "true" })
128
+ ]
129
+ },
130
+ p.value
131
+ );
132
+ })
133
+ }
134
+ );
135
+ if (!needsContainer) return strip;
136
+ return /* @__PURE__ */ jsxs(
137
+ "div",
138
+ {
139
+ className: className ? `${styles.container} ${className}` : styles.container,
140
+ "data-orientation": vertical ? "vertical" : void 0,
141
+ id,
142
+ ...rest,
143
+ children: [
144
+ strip,
145
+ panels.map((pan) => {
146
+ const p = pan.props;
147
+ const active = p.value === currentValue;
148
+ const mode = p.mounting ?? mounting;
149
+ if (mode === "active" && !active) return null;
150
+ if (mode === "lazy" && !active && !mounted.has(p.value)) return null;
151
+ const hidden = !active;
152
+ const hideByVisibility = hidden && mode === "visibility";
153
+ return /* @__PURE__ */ jsx(
154
+ "a-tabpanel",
155
+ {
156
+ role: "tabpanel",
157
+ id: panelId(p.value),
158
+ "aria-labelledby": tabId(p.value),
159
+ tabIndex: active ? 0 : void 0,
160
+ hidden: hidden && !hideByVisibility ? true : void 0,
161
+ "data-hide": hideByVisibility ? "visibility" : void 0,
162
+ inert: hidden ? true : void 0,
163
+ class: p.className,
164
+ style: p.style,
165
+ children: p.children
166
+ },
167
+ p.value
168
+ );
169
+ })
170
+ ]
171
+ }
172
+ );
173
+ };
174
+ export {
175
+ Tabs
176
+ };
@@ -0,0 +1 @@
1
+ .container{display:flex;flex-direction:column;gap:12px;min-width:0}.container[data-orientation=vertical]{flex-direction:row;align-items:flex-start;gap:16px}.container[data-orientation=vertical]>a-tabpanel{flex:1 1 auto;min-width:0}