@eifi1/ui-kit 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -1
- package/dist/components/amount-input.d.ts +7 -0
- package/dist/components/autocomplete.d.ts +101 -0
- package/dist/components/autocomplete.js +260 -0
- package/dist/components/autocomplete.js.map +1 -0
- package/dist/components/calculator.d.ts +7 -0
- package/dist/components/chip.d.ts +3 -2
- package/dist/components/chip.js +14 -1
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +100 -0
- package/dist/components/choice-card.js +170 -0
- package/dist/components/choice-card.js.map +1 -0
- package/dist/components/combobox-core.d.ts +76 -6
- package/dist/components/combobox-core.js +119 -49
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +12 -2
- package/dist/components/combobox.js +42 -17
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +91 -0
- package/dist/components/danger-confirm.js +181 -0
- package/dist/components/danger-confirm.js.map +1 -0
- package/dist/components/dialog-frame.d.ts +84 -0
- package/dist/components/dialog-frame.js +86 -0
- package/dist/components/dialog-frame.js.map +1 -0
- package/dist/components/disclosure.d.ts +108 -0
- package/dist/components/disclosure.js +127 -0
- package/dist/components/disclosure.js.map +1 -0
- package/dist/components/entity-combobox.d.ts +17 -3
- package/dist/components/entity-combobox.js +25 -5
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/file-button.d.ts +161 -0
- package/dist/components/file-button.js +225 -0
- package/dist/components/file-button.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +72 -23
- package/dist/components/file-dropzone.js +219 -94
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/icon-picker.d.ts +72 -0
- package/dist/components/icon-picker.js +104 -0
- package/dist/components/icon-picker.js.map +1 -0
- package/dist/components/mini-calendar.d.ts +3 -0
- package/dist/components/mini-calendar.js +4 -3
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +8 -1
- package/dist/components/modal.js +4 -2
- package/dist/components/modal.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +16 -3
- package/dist/components/multi-entity-combobox.js +25 -5
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +41 -1
- package/dist/components/number-field.js +42 -10
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +35 -2
- package/dist/components/number-input.js +35 -4
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +7 -0
- package/dist/components/search-field.d.ts +16 -0
- package/dist/components/search-field.js +29 -7
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/signature-pad.d.ts +43 -1
- package/dist/components/signature-pad.js +74 -2
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/swatch-picker.d.ts +69 -0
- package/dist/components/swatch-picker.js +75 -0
- package/dist/components/swatch-picker.js.map +1 -0
- package/dist/components/tile-radio.d.ts +50 -0
- package/dist/components/tile-radio.js +140 -0
- package/dist/components/tile-radio.js.map +1 -0
- package/dist/components/toggle-group.d.ts +27 -5
- package/dist/components/toggle-group.js +22 -15
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/ui.d.ts +150 -12
- package/dist/components/ui.js +196 -22
- package/dist/components/ui.js.map +1 -1
- package/dist/i18n/defaults.d.ts +7 -0
- package/dist/i18n/defaults.js +13 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +38 -5
- package/dist/i18n/kit-labels.js +12 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/index.d.ts +16 -7
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/table-text.d.ts +127 -0
- package/dist/lib/table-text.js +82 -0
- package/dist/lib/table-text.js.map +1 -0
- package/dist/rhf/form.d.ts +79 -0
- package/dist/rhf/form.js +143 -0
- package/dist/rhf/form.js.map +1 -0
- package/dist/rhf.d.ts +4 -0
- package/dist/rhf.js +3 -0
- package/dist/rhf.js.map +1 -0
- package/dist/shell/app-shell.js +3 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/table-text.d.ts +1 -0
- package/dist/table-text.js +3 -0
- package/dist/table-text.js.map +1 -0
- package/package.json +14 -1
- package/src/components/autocomplete.tsx +425 -0
- package/src/components/chip.tsx +24 -2
- package/src/components/choice-card.tsx +305 -0
- package/src/components/combobox-core.tsx +228 -58
- package/src/components/combobox.tsx +58 -21
- package/src/components/danger-confirm.tsx +286 -0
- package/src/components/dialog-frame.tsx +179 -0
- package/src/components/disclosure.tsx +259 -0
- package/src/components/entity-combobox.tsx +41 -6
- package/src/components/file-button.tsx +431 -0
- package/src/components/file-dropzone.tsx +323 -117
- package/src/components/icon-picker.tsx +181 -0
- package/src/components/mini-calendar.tsx +7 -3
- package/src/components/modal.tsx +10 -2
- package/src/components/multi-entity-combobox.tsx +40 -6
- package/src/components/number-field.tsx +86 -10
- package/src/components/number-input.tsx +79 -2
- package/src/components/search-field.tsx +49 -6
- package/src/components/signature-pad.tsx +112 -0
- package/src/components/swatch-picker.tsx +141 -0
- package/src/components/tile-radio.tsx +228 -0
- package/src/components/toggle-group.tsx +54 -18
- package/src/components/ui.tsx +400 -24
- package/src/i18n/defaults.ts +12 -1
- package/src/i18n/kit-labels.tsx +45 -5
- package/src/index.ts +19 -0
- package/src/lib/table-text.ts +265 -0
- package/src/rhf/form.tsx +300 -0
- package/src/rhf.ts +9 -0
- package/src/shell/app-shell.tsx +3 -1
- package/src/table-text.ts +8 -0
package/dist/components/ui.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { forwardRef, useId, useState } from "react";
|
|
4
|
-
import { ChevronDown, Eye, EyeOff, HelpCircle } from "lucide-react";
|
|
3
|
+
import { forwardRef, useId, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { ChevronDown, Eye, EyeOff, HelpCircle, Plus, X } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
6
|
import { useMediaQuery } from "../hooks/use-media-query.js";
|
|
7
7
|
import { Tooltip } from "./tooltip.js";
|
|
@@ -34,17 +34,37 @@ function Button({ variant = "primary", stretch, className, ...rest }) {
|
|
|
34
34
|
}
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
|
-
const ICON_BUTTON_BASE = "inline-flex items-center justify-center rounded-md transition-colors focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed
|
|
38
|
-
const
|
|
37
|
+
const ICON_BUTTON_BASE = "inline-flex items-center justify-center rounded-md transition-colors focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
38
|
+
const ICON_BUTTON_SIZES = {
|
|
39
|
+
md: "size-9 [&_svg]:size-5",
|
|
40
|
+
sm: "size-8 [&_svg]:size-5",
|
|
41
|
+
xs: "size-7 rounded [&_svg]:size-4",
|
|
42
|
+
"2xs": "size-6 rounded [&_svg]:size-3.5"
|
|
43
|
+
};
|
|
44
|
+
const ICON_BUTTON_TONES = {
|
|
45
|
+
default: "",
|
|
46
|
+
muted: "text-[var(--text-placeholder)] hover:bg-[var(--bg-surface-2)] hover:text-[var(--text-primary)]",
|
|
47
|
+
danger: "text-[var(--text-placeholder)] hover:bg-[var(--danger-bg)] hover:text-[var(--danger)] focus:ring-[var(--danger-border)]"
|
|
48
|
+
};
|
|
49
|
+
const IconButton = forwardRef(function IconButton2({ variant = "ghost", size = "md", tone = "default", stopPropagation, className, onClick, onKeyDown, ...rest }, ref) {
|
|
39
50
|
return /* @__PURE__ */ jsx(
|
|
40
51
|
"button",
|
|
41
52
|
{
|
|
42
53
|
ref,
|
|
43
54
|
...rest,
|
|
55
|
+
onClick: (e) => {
|
|
56
|
+
if (stopPropagation) e.stopPropagation();
|
|
57
|
+
onClick?.(e);
|
|
58
|
+
},
|
|
59
|
+
onKeyDown: (e) => {
|
|
60
|
+
if (stopPropagation && (e.key === "Enter" || e.key === " ")) e.stopPropagation();
|
|
61
|
+
onKeyDown?.(e);
|
|
62
|
+
},
|
|
44
63
|
className: cn(
|
|
45
64
|
ICON_BUTTON_BASE,
|
|
46
|
-
size
|
|
65
|
+
ICON_BUTTON_SIZES[size],
|
|
47
66
|
buttonVariantClasses[variant],
|
|
67
|
+
ICON_BUTTON_TONES[tone],
|
|
48
68
|
className
|
|
49
69
|
)
|
|
50
70
|
}
|
|
@@ -167,13 +187,31 @@ function FieldHint({
|
|
|
167
187
|
function FieldLabel({ children, className, ...rest }) {
|
|
168
188
|
return /* @__PURE__ */ jsx("span", { ...rest, className: cn(FLOATING_LABEL_STATIC, "z-10", className), children });
|
|
169
189
|
}
|
|
190
|
+
function Label({ required, size = "md", disabled, className, children, ...rest }) {
|
|
191
|
+
return /* @__PURE__ */ jsxs(
|
|
192
|
+
"label",
|
|
193
|
+
{
|
|
194
|
+
...rest,
|
|
195
|
+
className: cn(
|
|
196
|
+
"inline-flex items-center gap-0.5 font-medium leading-none text-[var(--text-primary)]",
|
|
197
|
+
size === "sm" ? "text-xs" : "text-sm",
|
|
198
|
+
disabled && "cursor-default opacity-50",
|
|
199
|
+
className
|
|
200
|
+
),
|
|
201
|
+
children: [
|
|
202
|
+
children,
|
|
203
|
+
required && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-[var(--danger)]", children: "*" })
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
}
|
|
170
208
|
const FIELD_ERROR_CLASS = "mt-1 text-[11px] leading-tight text-[var(--danger)]";
|
|
171
|
-
function useFieldError(error, invalid, describedBy) {
|
|
209
|
+
function useFieldError(error, invalid, describedBy, ariaInvalid) {
|
|
172
210
|
const errorId = useId();
|
|
173
211
|
const hasError = error !== void 0 && error !== null && error !== false && error !== "";
|
|
174
212
|
return {
|
|
175
213
|
/** A field carrying a message that says what is wrong with it IS wrong. */
|
|
176
|
-
isInvalid: Boolean(invalid) || hasError,
|
|
214
|
+
isInvalid: Boolean(invalid) || hasError || ariaInvalid === true || ariaInvalid === "true",
|
|
177
215
|
describedBy: hasError ? describedBy ? `${describedBy} ${errorId}` : errorId : describedBy,
|
|
178
216
|
errorEl: hasError ? /* @__PURE__ */ jsx("p", { id: errorId, className: FIELD_ERROR_CLASS, children: error }) : null
|
|
179
217
|
};
|
|
@@ -212,7 +250,8 @@ const Input = forwardRef(function Input2({
|
|
|
212
250
|
const { isInvalid, describedBy, errorEl } = useFieldError(
|
|
213
251
|
error,
|
|
214
252
|
invalid,
|
|
215
|
-
rest["aria-describedby"]
|
|
253
|
+
rest["aria-describedby"],
|
|
254
|
+
rest["aria-invalid"]
|
|
216
255
|
);
|
|
217
256
|
const asDisplay = useMediaQuery(PHONE_QUERY, false) && variant === "display";
|
|
218
257
|
const isPassword = type === "password";
|
|
@@ -296,15 +335,19 @@ const Input = forwardRef(function Input2({
|
|
|
296
335
|
] }) });
|
|
297
336
|
});
|
|
298
337
|
Input.displayName = "Input";
|
|
299
|
-
const
|
|
338
|
+
const SELECT_SM = "h-7 py-0 pl-2 pr-7 text-xs";
|
|
339
|
+
const Select = forwardRef(function Select2({ className, label, id, children, invalid, error, hint, size, selectClassName, ...rest }, ref) {
|
|
300
340
|
const generated = useId();
|
|
341
|
+
const nativeSize = typeof size === "number" ? size : void 0;
|
|
342
|
+
const small = size === "sm" && label === void 0;
|
|
301
343
|
const fieldId = id ?? generated;
|
|
302
344
|
const { isInvalid, describedBy, errorEl } = useFieldError(
|
|
303
345
|
error,
|
|
304
346
|
invalid,
|
|
305
|
-
rest["aria-describedby"]
|
|
347
|
+
rest["aria-describedby"],
|
|
348
|
+
rest["aria-invalid"]
|
|
306
349
|
);
|
|
307
|
-
const chevron = rest.disabled ? null : /* @__PURE__ */ jsx(FieldChevron, {});
|
|
350
|
+
const chevron = rest.disabled ? null : /* @__PURE__ */ jsx(FieldChevron, { className: small ? "right-1.5 size-3.5" : void 0 });
|
|
308
351
|
if (label === void 0) {
|
|
309
352
|
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
310
353
|
/* @__PURE__ */ jsx(
|
|
@@ -312,10 +355,17 @@ const Select = forwardRef(function Select2({ className, label, id, children, inv
|
|
|
312
355
|
{
|
|
313
356
|
ref,
|
|
314
357
|
id,
|
|
358
|
+
size: nativeSize,
|
|
315
359
|
...rest,
|
|
316
360
|
"aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
|
|
317
361
|
"aria-describedby": describedBy,
|
|
318
|
-
className: cn(
|
|
362
|
+
className: cn(
|
|
363
|
+
FIELD_BASE,
|
|
364
|
+
"appearance-none pr-9",
|
|
365
|
+
small && SELECT_SM,
|
|
366
|
+
selectClassName,
|
|
367
|
+
isInvalid && FIELD_INVALID
|
|
368
|
+
),
|
|
319
369
|
children
|
|
320
370
|
}
|
|
321
371
|
),
|
|
@@ -328,6 +378,7 @@ const Select = forwardRef(function Select2({ className, label, id, children, inv
|
|
|
328
378
|
{
|
|
329
379
|
ref,
|
|
330
380
|
id: fieldId,
|
|
381
|
+
size: nativeSize,
|
|
331
382
|
...rest,
|
|
332
383
|
"aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
|
|
333
384
|
"aria-describedby": describedBy,
|
|
@@ -335,6 +386,7 @@ const Select = forwardRef(function Select2({ className, label, id, children, inv
|
|
|
335
386
|
FIELD_BASE,
|
|
336
387
|
FIELD_FLOATING_PAD,
|
|
337
388
|
"peer appearance-none pr-9",
|
|
389
|
+
selectClassName,
|
|
338
390
|
isInvalid && FIELD_INVALID
|
|
339
391
|
),
|
|
340
392
|
children
|
|
@@ -350,7 +402,8 @@ const Textarea = forwardRef(function Textarea2({ className, label, id, placehold
|
|
|
350
402
|
const { isInvalid, describedBy, errorEl } = useFieldError(
|
|
351
403
|
error,
|
|
352
404
|
invalid,
|
|
353
|
-
rest["aria-describedby"]
|
|
405
|
+
rest["aria-describedby"],
|
|
406
|
+
rest["aria-invalid"]
|
|
354
407
|
);
|
|
355
408
|
if (label === void 0) {
|
|
356
409
|
return /* @__PURE__ */ jsx(FieldGroup, { errorEl, children: /* @__PURE__ */ jsx(
|
|
@@ -485,6 +538,10 @@ function EmptyState({ title, hint, className, ...rest }) {
|
|
|
485
538
|
}
|
|
486
539
|
);
|
|
487
540
|
}
|
|
541
|
+
const DEFAULT_TABS_LABELS = {
|
|
542
|
+
add: "Add tab",
|
|
543
|
+
remove: (tab) => `Remove ${tab}`
|
|
544
|
+
};
|
|
488
545
|
const TABLIST_CLASSES = "flex gap-1 overflow-x-auto overflow-y-hidden border-b border-[var(--border)]";
|
|
489
546
|
const TABLIST_WRAP_CLASSES = "flex flex-wrap gap-1.5 md:flex-nowrap md:gap-1 md:overflow-x-auto md:overflow-y-hidden md:border-b md:border-[var(--border)]";
|
|
490
547
|
const TAB_CLASSES = "whitespace-nowrap px-3 py-2 text-sm font-medium transition-colors border-b-2 -mb-px focus:outline-none focus:ring-2 focus:ring-[var(--border-strong)]";
|
|
@@ -493,6 +550,10 @@ const TAB_INACTIVE_CLASSES = "border-transparent text-[var(--text-muted)] hover:
|
|
|
493
550
|
const TAB_WRAP_CLASSES = "whitespace-nowrap rounded-md px-2.5 py-2 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--border-strong)] md:rounded-none md:border-b-2 md:-mb-px md:bg-transparent md:px-3 md:py-2 md:text-sm";
|
|
494
551
|
const TAB_WRAP_ACTIVE_CLASSES = "bg-[var(--brand)] text-[var(--brand-contrast)] md:border-[var(--text-primary)] md:text-[var(--text-primary)]";
|
|
495
552
|
const TAB_WRAP_INACTIVE_CLASSES = "bg-[var(--bg-surface)] text-[var(--text-primary)] md:border-transparent md:text-[var(--text-muted)] md:hover:border-[var(--border-strong)] md:hover:text-[var(--text-secondary)]";
|
|
553
|
+
const TAB_REMOVABLE_CLASSES = "pe-8 md:pe-8";
|
|
554
|
+
const TAB_EMPTY_CLASSES = "-mx-1.5 rounded border border-dashed border-[var(--border-strong)] px-1.5";
|
|
555
|
+
const TAB_ADD_CLASSES = "inline-flex items-center gap-1 whitespace-nowrap rounded-md px-3 py-2 text-sm font-medium text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--border-strong)] disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-4";
|
|
556
|
+
const TAB_ADD_WRAP_CLASSES = "px-2.5 text-xs md:px-3 md:text-sm";
|
|
496
557
|
function Tabs({
|
|
497
558
|
tabs,
|
|
498
559
|
active,
|
|
@@ -501,10 +562,46 @@ function Tabs({
|
|
|
501
562
|
wrap = false,
|
|
502
563
|
label,
|
|
503
564
|
panelId,
|
|
565
|
+
onRemove,
|
|
566
|
+
onAdd,
|
|
567
|
+
addLabel,
|
|
568
|
+
labels,
|
|
569
|
+
busy = false,
|
|
504
570
|
"aria-label": ariaLabel,
|
|
505
571
|
...rest
|
|
506
572
|
}) {
|
|
507
|
-
const
|
|
573
|
+
const text = useKitLabels("tabs", DEFAULT_TABS_LABELS, labels);
|
|
574
|
+
const stripRef = useRef(null);
|
|
575
|
+
const isRemovable = (tab) => onRemove !== void 0 && tab.removable !== false;
|
|
576
|
+
const nameOf = (tab) => tab.name ?? (typeof tab.label === "string" ? tab.label : tab.id);
|
|
577
|
+
const pendingFocus = useRef(null);
|
|
578
|
+
const requestRemove = (id) => {
|
|
579
|
+
const index = tabs.findIndex((t) => t.id === id);
|
|
580
|
+
pendingFocus.current = {
|
|
581
|
+
removed: id,
|
|
582
|
+
wasActive: id === active,
|
|
583
|
+
neighbour: (tabs[index + 1] ?? tabs[index - 1])?.id
|
|
584
|
+
};
|
|
585
|
+
onRemove?.(id);
|
|
586
|
+
};
|
|
587
|
+
useLayoutEffect(() => {
|
|
588
|
+
const pending = pendingFocus.current;
|
|
589
|
+
if (!pending || tabs.some((t) => t.id === pending.removed)) return;
|
|
590
|
+
pendingFocus.current = null;
|
|
591
|
+
const strip = stripRef.current;
|
|
592
|
+
if (!strip) return;
|
|
593
|
+
const focused = document.activeElement;
|
|
594
|
+
if (focused && focused !== document.body && !strip.contains(focused)) return;
|
|
595
|
+
const target = !pending.wasActive && tabs.some((t) => t.id === pending.neighbour) ? pending.neighbour : active;
|
|
596
|
+
const index = tabs.findIndex((t) => t.id === target);
|
|
597
|
+
strip.querySelectorAll('[role="tab"]')[index]?.focus();
|
|
598
|
+
}, [tabs, active]);
|
|
599
|
+
const onTabKeyDown = (e, tab) => {
|
|
600
|
+
if (e.key === "Delete" && isRemovable(tab)) {
|
|
601
|
+
e.preventDefault();
|
|
602
|
+
if (!busy) requestRemove(tab.id);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
508
605
|
const step = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0;
|
|
509
606
|
if (step === 0 && e.key !== "Home" && e.key !== "End") return;
|
|
510
607
|
const strip = e.currentTarget.closest('[role="tablist"]');
|
|
@@ -516,15 +613,20 @@ function Tabs({
|
|
|
516
613
|
const to = e.key === "Home" ? 0 : e.key === "End" ? items.length - 1 : (from + step + items.length) % items.length;
|
|
517
614
|
items[to]?.focus();
|
|
518
615
|
};
|
|
519
|
-
|
|
616
|
+
const tablist = /* @__PURE__ */ jsx(
|
|
520
617
|
"div",
|
|
521
618
|
{
|
|
522
619
|
...rest,
|
|
620
|
+
ref: stripRef,
|
|
523
621
|
role: "tablist",
|
|
524
622
|
"aria-label": ariaLabel ?? label,
|
|
525
|
-
className: cn(
|
|
623
|
+
className: cn(
|
|
624
|
+
wrap ? TABLIST_WRAP_CLASSES : TABLIST_CLASSES,
|
|
625
|
+
onAdd ? "min-w-0" : className
|
|
626
|
+
),
|
|
526
627
|
children: tabs.map((tab) => {
|
|
527
628
|
const isActive = tab.id === active;
|
|
629
|
+
const removable = isRemovable(tab);
|
|
528
630
|
const shared = {
|
|
529
631
|
role: "tab",
|
|
530
632
|
"aria-selected": isActive,
|
|
@@ -544,17 +646,24 @@ function Tabs({
|
|
|
544
646
|
// Only the open tab: see `panelId`.
|
|
545
647
|
"aria-controls": isActive ? panelId : void 0,
|
|
546
648
|
id: isActive && panelId ? `${panelId}-tab` : void 0,
|
|
547
|
-
|
|
649
|
+
"aria-keyshortcuts": removable ? "Delete" : void 0,
|
|
650
|
+
onKeyDown: (e) => onTabKeyDown(e, tab),
|
|
548
651
|
className: cn(
|
|
549
652
|
wrap ? TAB_WRAP_CLASSES : TAB_CLASSES,
|
|
550
|
-
wrap ? isActive ? TAB_WRAP_ACTIVE_CLASSES : TAB_WRAP_INACTIVE_CLASSES : isActive ? TAB_ACTIVE_CLASSES : TAB_INACTIVE_CLASSES
|
|
653
|
+
wrap ? isActive ? TAB_WRAP_ACTIVE_CLASSES : TAB_WRAP_INACTIVE_CLASSES : isActive ? TAB_ACTIVE_CLASSES : TAB_INACTIVE_CLASSES,
|
|
654
|
+
removable && TAB_REMOVABLE_CLASSES
|
|
551
655
|
)
|
|
552
656
|
};
|
|
553
|
-
const inner = /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
554
|
-
tab.
|
|
657
|
+
const inner = /* @__PURE__ */ jsxs("span", { className: cn("inline-flex items-center gap-1.5", tab.empty && TAB_EMPTY_CLASSES), children: [
|
|
658
|
+
tab.icon != null && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "inline-flex shrink-0 items-center gap-0.5", children: tab.icon }),
|
|
659
|
+
tab.detail != null ? /* @__PURE__ */ jsxs("span", { className: "flex flex-col items-start text-start", children: [
|
|
660
|
+
/* @__PURE__ */ jsx("span", { children: tab.label }),
|
|
661
|
+
" ",
|
|
662
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-normal leading-tight text-[var(--text-muted)]", children: tab.detail })
|
|
663
|
+
] }) : tab.label,
|
|
555
664
|
tab.badge != null ? tab.badge : null
|
|
556
665
|
] });
|
|
557
|
-
|
|
666
|
+
const element = tab.href ? /* @__PURE__ */ jsx(
|
|
558
667
|
"a",
|
|
559
668
|
{
|
|
560
669
|
...shared,
|
|
@@ -566,7 +675,7 @@ function Tabs({
|
|
|
566
675
|
onChange(tab.id);
|
|
567
676
|
},
|
|
568
677
|
onKeyDown: (e) => {
|
|
569
|
-
onTabKeyDown(e);
|
|
678
|
+
onTabKeyDown(e, tab);
|
|
570
679
|
if (e.defaultPrevented || e.key !== " ") return;
|
|
571
680
|
e.preventDefault();
|
|
572
681
|
onChange(tab.id);
|
|
@@ -575,9 +684,72 @@ function Tabs({
|
|
|
575
684
|
},
|
|
576
685
|
tab.id
|
|
577
686
|
) : /* @__PURE__ */ jsx("button", { ...shared, onClick: () => onChange(tab.id), children: inner }, tab.id);
|
|
687
|
+
if (!removable) return element;
|
|
688
|
+
return (
|
|
689
|
+
// The × cannot go INSIDE the tab: a button in a button is invalid HTML, and
|
|
690
|
+
// a tab's children are presentational, so a reader would flatten it into the
|
|
691
|
+
// tab's name. It sits beside the tab, over the gutter the tab reserves.
|
|
692
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex shrink-0", children: [
|
|
693
|
+
element,
|
|
694
|
+
isActive && /* @__PURE__ */ jsx(
|
|
695
|
+
IconButton,
|
|
696
|
+
{
|
|
697
|
+
size: "2xs",
|
|
698
|
+
tone: "danger",
|
|
699
|
+
tabIndex: -1,
|
|
700
|
+
type: "button",
|
|
701
|
+
"aria-label": text.remove(nameOf(tab)),
|
|
702
|
+
disabled: busy,
|
|
703
|
+
onClick: () => requestRemove(tab.id),
|
|
704
|
+
className: cn(
|
|
705
|
+
"absolute inset-y-0 end-1 my-auto",
|
|
706
|
+
// On a wrapped strip's filled chip the quiet grey would vanish into
|
|
707
|
+
// the brand fill below `md`.
|
|
708
|
+
wrap && "text-[var(--brand-contrast)] md:text-[var(--text-placeholder)]"
|
|
709
|
+
),
|
|
710
|
+
children: /* @__PURE__ */ jsx(X, {})
|
|
711
|
+
}
|
|
712
|
+
)
|
|
713
|
+
] }, tab.id)
|
|
714
|
+
);
|
|
578
715
|
})
|
|
579
716
|
}
|
|
580
717
|
);
|
|
718
|
+
if (!onAdd) return tablist;
|
|
719
|
+
return /* @__PURE__ */ jsxs(
|
|
720
|
+
"div",
|
|
721
|
+
{
|
|
722
|
+
className: cn(
|
|
723
|
+
wrap ? "flex flex-wrap items-end gap-1.5 md:flex-nowrap md:gap-0" : "flex items-end",
|
|
724
|
+
className
|
|
725
|
+
),
|
|
726
|
+
children: [
|
|
727
|
+
tablist,
|
|
728
|
+
/* @__PURE__ */ jsx(
|
|
729
|
+
"div",
|
|
730
|
+
{
|
|
731
|
+
className: cn(
|
|
732
|
+
"flex flex-1 items-center",
|
|
733
|
+
wrap ? "md:self-stretch md:border-b md:border-[var(--border)]" : "self-stretch border-b border-[var(--border)]"
|
|
734
|
+
),
|
|
735
|
+
children: /* @__PURE__ */ jsxs(
|
|
736
|
+
"button",
|
|
737
|
+
{
|
|
738
|
+
type: "button",
|
|
739
|
+
onClick: onAdd,
|
|
740
|
+
disabled: busy,
|
|
741
|
+
className: cn(TAB_ADD_CLASSES, wrap && TAB_ADD_WRAP_CLASSES),
|
|
742
|
+
children: [
|
|
743
|
+
/* @__PURE__ */ jsx(Plus, { "aria-hidden": true }),
|
|
744
|
+
addLabel ?? text.add
|
|
745
|
+
]
|
|
746
|
+
}
|
|
747
|
+
)
|
|
748
|
+
}
|
|
749
|
+
)
|
|
750
|
+
]
|
|
751
|
+
}
|
|
752
|
+
);
|
|
581
753
|
}
|
|
582
754
|
export {
|
|
583
755
|
Button,
|
|
@@ -589,6 +761,7 @@ export {
|
|
|
589
761
|
CardHeader,
|
|
590
762
|
CardTitle,
|
|
591
763
|
DEFAULT_PASSWORD_REVEAL_LABELS,
|
|
764
|
+
DEFAULT_TABS_LABELS,
|
|
592
765
|
EmptyState,
|
|
593
766
|
FIELD_BASE,
|
|
594
767
|
FIELD_DISPLAY,
|
|
@@ -605,6 +778,7 @@ export {
|
|
|
605
778
|
FloatingField,
|
|
606
779
|
IconButton,
|
|
607
780
|
Input,
|
|
781
|
+
Label,
|
|
608
782
|
PHONE_QUERY,
|
|
609
783
|
Select,
|
|
610
784
|
Spinner,
|