@eifi1/ui-kit 0.7.1 → 0.8.1
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 +32 -17
- package/dist/chart.d.ts +3 -2
- package/dist/components/alert-banner.d.ts +59 -10
- package/dist/components/alert-banner.js +114 -8
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +3 -0
- package/dist/components/button-group.d.ts +26 -0
- package/dist/components/button-group.js +49 -0
- package/dist/components/button-group.js.map +1 -0
- package/dist/components/calculator.d.ts +3 -0
- package/dist/components/chart-zoom.d.ts +63 -14
- package/dist/components/chart-zoom.js +82 -16
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/chip.d.ts +67 -2
- package/dist/components/chip.js +73 -8
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +120 -0
- package/dist/components/confirm-dialog.js +96 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/copy-button.d.ts +62 -0
- package/dist/components/copy-button.js +95 -0
- package/dist/components/copy-button.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +5 -1
- package/dist/components/data-table-pagination.js +71 -58
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.d.ts +28 -5
- package/dist/components/data-table-sort.js +11 -7
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +544 -423
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +51 -5
- package/dist/components/date-picker.js +152 -40
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +60 -0
- package/dist/components/description-list.js +112 -0
- package/dist/components/description-list.js.map +1 -0
- package/dist/components/disclosure.d.ts +37 -2
- package/dist/components/disclosure.js +14 -5
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/facing-pair.d.ts +2 -0
- package/dist/components/file-button.d.ts +17 -1
- package/dist/components/file-button.js +8 -1
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +54 -9
- package/dist/components/file-dropzone.js +100 -70
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/floating-panel.d.ts +107 -0
- package/dist/components/floating-panel.js +206 -0
- package/dist/components/floating-panel.js.map +1 -0
- package/dist/components/full-bleed-dialog.d.ts +12 -1
- package/dist/components/full-bleed-dialog.js +28 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/modal.d.ts +6 -2
- package/dist/components/modal.js +2 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/number-field.d.ts +3 -0
- package/dist/components/number-input.d.ts +3 -0
- package/dist/components/numpad-sheet.d.ts +3 -0
- package/dist/components/progress-bar.d.ts +54 -0
- package/dist/components/progress-bar.js +94 -0
- package/dist/components/progress-bar.js.map +1 -0
- package/dist/components/scroll-area.d.ts +53 -0
- package/dist/components/scroll-area.js +86 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/separator.d.ts +23 -0
- package/dist/components/separator.js +24 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/series-chart-ticks.d.ts +34 -1
- package/dist/components/series-chart-ticks.js +83 -1
- package/dist/components/series-chart-ticks.js.map +1 -1
- package/dist/components/series-chart.d.ts +314 -19
- package/dist/components/series-chart.js +510 -120
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/skeleton.d.ts +36 -0
- package/dist/components/skeleton.js +35 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/stat-tile.d.ts +60 -6
- package/dist/components/stat-tile.js +60 -26
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/table.d.ts +83 -0
- package/dist/components/table.js +166 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/toggle-legend.d.ts +3 -2
- package/dist/components/toggle-legend.js +1 -1
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/tree-view.d.ts +129 -0
- package/dist/components/tree-view.js +376 -0
- package/dist/components/tree-view.js.map +1 -0
- package/dist/components/treemap.d.ts +9 -2
- package/dist/components/treemap.js +6 -2
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/ui.d.ts +94 -13
- package/dist/components/ui.js +111 -29
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/{data-table-filters-noy0Abvi.d.ts → data-table-filters-CF1PXqjQ.d.ts} +73 -3
- package/dist/data-table.d.ts +2 -2
- package/dist/data-table.js.map +1 -1
- package/dist/hooks/use-copy-to-clipboard.d.ts +28 -0
- package/dist/hooks/use-copy-to-clipboard.js +81 -0
- package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
- package/dist/hooks/use-debounce.d.ts +43 -0
- package/dist/hooks/use-debounce.js +56 -0
- package/dist/hooks/use-debounce.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +72 -0
- package/dist/hooks/use-file-drop.js +57 -0
- package/dist/hooks/use-file-drop.js.map +1 -0
- package/dist/i18n/defaults.d.ts +3 -0
- package/dist/i18n/defaults.js +7 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -0
- package/dist/i18n/kit-labels.js +6 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +43 -0
- package/dist/i18n/locales/de-CH-informal.js +8 -0
- package/dist/i18n/locales/de-CH-informal.js.map +1 -0
- package/dist/i18n/locales/de-CH.d.ts +3 -0
- package/dist/i18n/locales/de-CH.js +1 -14
- package/dist/i18n/locales/de-CH.js.map +1 -1
- package/dist/i18n/locales/de-informal.d.ts +64 -0
- package/dist/i18n/locales/de-informal.js +37 -0
- package/dist/i18n/locales/de-informal.js.map +1 -0
- package/dist/i18n/locales/de.d.ts +3 -0
- package/dist/i18n/locales/de.js +25 -0
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +3 -0
- package/dist/i18n/locales/es.js +25 -0
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +3 -0
- package/dist/i18n/locales/fr.js +25 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +3 -0
- package/dist/i18n/locales/hu.js +25 -0
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +3 -0
- package/dist/i18n/locales/it.js +25 -0
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +3 -0
- package/dist/i18n/locales/zh.js +25 -0
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/swiss.d.ts +13 -0
- package/dist/i18n/swiss.js +19 -0
- package/dist/i18n/swiss.js.map +1 -0
- package/dist/index.d.ts +25 -10
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/dates.d.ts +37 -1
- package/dist/lib/dates.js +19 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/search/command-palette.d.ts +15 -1
- package/dist/search/command-palette.js +10 -2
- package/dist/search/command-palette.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +35 -2
- package/dist/wizard/stepper-nav.js +54 -22
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +107 -5
- package/dist/wizard/types.js +2 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +141 -21
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/wizard-summary.d.ts +10 -2
- package/dist/wizard/wizard-summary.js +3 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +4 -1
- package/dist/wizard.js.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-banner.tsx +211 -20
- package/src/components/button-group.tsx +75 -0
- package/src/components/chart-zoom.tsx +158 -20
- package/src/components/chip.tsx +160 -9
- package/src/components/confirm-dialog.tsx +242 -0
- package/src/components/copy-button.tsx +158 -0
- package/src/components/data-table-pagination.tsx +19 -6
- package/src/components/data-table-sort.ts +49 -10
- package/src/components/data-table.tsx +293 -24
- package/src/components/date-picker.tsx +262 -57
- package/src/components/description-list.tsx +174 -0
- package/src/components/disclosure.tsx +58 -5
- package/src/components/file-button.tsx +22 -2
- package/src/components/file-dropzone.tsx +198 -100
- package/src/components/floating-panel.tsx +341 -0
- package/src/components/full-bleed-dialog.tsx +53 -5
- package/src/components/modal.tsx +9 -2
- package/src/components/progress-bar.tsx +163 -0
- package/src/components/scroll-area.tsx +129 -0
- package/src/components/separator.tsx +37 -0
- package/src/components/series-chart-ticks.ts +135 -0
- package/src/components/series-chart.tsx +934 -67
- package/src/components/skeleton.tsx +65 -0
- package/src/components/stat-tile.tsx +160 -27
- package/src/components/table.tsx +263 -0
- package/src/components/toggle-legend.tsx +4 -3
- package/src/components/tree-view.tsx +589 -0
- package/src/components/treemap.tsx +13 -2
- package/src/components/ui.tsx +270 -39
- package/src/data-table.ts +2 -0
- package/src/hooks/use-copy-to-clipboard.ts +122 -0
- package/src/hooks/use-debounce.ts +105 -0
- package/src/hooks/use-file-drop.ts +123 -0
- package/src/i18n/defaults.ts +6 -0
- package/src/i18n/kit-labels.tsx +19 -0
- package/src/i18n/locales/de-CH-informal.ts +10 -0
- package/src/i18n/locales/de-CH.ts +1 -16
- package/src/i18n/locales/de-informal.ts +61 -0
- package/src/i18n/locales/de.ts +25 -0
- package/src/i18n/locales/es.ts +25 -0
- package/src/i18n/locales/fr.ts +25 -0
- package/src/i18n/locales/hu.ts +25 -0
- package/src/i18n/locales/it.ts +25 -0
- package/src/i18n/locales/zh.ts +25 -0
- package/src/i18n/swiss.ts +25 -0
- package/src/index.ts +29 -0
- package/src/lib/dates.ts +48 -0
- package/src/search/command-palette.tsx +29 -3
- package/src/wizard/stepper-nav.tsx +102 -28
- package/src/wizard/types.ts +106 -4
- package/src/wizard/use-wizard.ts +211 -31
- package/src/wizard/wizard-summary.tsx +24 -12
- package/src/wizard.ts +3 -2
package/dist/components/ui.d.ts
CHANGED
|
@@ -3,17 +3,40 @@ import { ButtonHTMLAttributes, Ref, SelectHTMLAttributes, ReactNode, InputHTMLAt
|
|
|
3
3
|
import { TooltipSide } from './tooltip.js';
|
|
4
4
|
import '../hooks/use-anchored-rect.js';
|
|
5
5
|
|
|
6
|
-
type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "brand";
|
|
6
|
+
type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "brand" | "link";
|
|
7
|
+
type ButtonSize = "sm" | "md";
|
|
8
|
+
/** The second argument of {@link buttonClasses} in its options form. */
|
|
9
|
+
interface ButtonClassesOptions {
|
|
10
|
+
/** See {@link ButtonProps.size}. */
|
|
11
|
+
size?: ButtonSize;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
7
14
|
/**
|
|
8
15
|
* Button classes for the rare case where the styling must land on a non-`<button>`
|
|
9
16
|
* element that {@link Button} can't render — e.g. a router `<Link>` or a Radix
|
|
10
17
|
* AlertDialog Action/Cancel (which must stay the Radix element). Everywhere a real
|
|
11
|
-
* button works, prefer `<Button>`. Draws from the same base
|
|
18
|
+
* button works, prefer `<Button>`. Draws from the same base, size and variant maps as
|
|
12
19
|
* `<Button>`, so the two stay in lockstep.
|
|
20
|
+
*
|
|
21
|
+
* The second argument is either the extra classes (the pre-0.8.0 form) or
|
|
22
|
+
* `{ size, className }` — `buttonClasses("secondary", { size: "sm" })` for keksdose's
|
|
23
|
+
* compact toolbar links.
|
|
24
|
+
*/
|
|
25
|
+
declare function buttonClasses(variant?: ButtonVariant, classNameOrOptions?: string | ButtonClassesOptions): string;
|
|
26
|
+
/**
|
|
27
|
+
* ⚠️ No default `type`: like a native `<button>`, a `Button` inside a `<form>` SUBMITS
|
|
28
|
+
* it unless you pass `type="button"`. Kept native on purpose — a form's own submit
|
|
29
|
+
* button relies on it, and changing the default would silently stop those forms from
|
|
30
|
+
* submitting. Pass `type="button"` for every other action in a form (keksdose had six
|
|
31
|
+
* link buttons submitting their form).
|
|
13
32
|
*/
|
|
14
|
-
declare function buttonClasses(variant?: ButtonVariant, className?: string): string;
|
|
15
33
|
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
16
34
|
variant?: ButtonVariant;
|
|
35
|
+
/** `md` (default) is the page's action button. `sm` is the compact one — 12px text
|
|
36
|
+
* and `px-2 py-1` — for the secondary actions in a toolbar, a card header or a
|
|
37
|
+
* table's header row (keksdose writes `px-2 py-1 text-xs` over `secondary` by hand
|
|
38
|
+
* there). Every variant takes either size. */
|
|
39
|
+
size?: ButtonSize;
|
|
17
40
|
/** In a flex row next to a taller labelled field, fill the field's height so the
|
|
18
41
|
* two line up. No effect outside a flex row. */
|
|
19
42
|
stretch?: boolean;
|
|
@@ -22,23 +45,40 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
22
45
|
* declared here only because `ButtonHTMLAttributes` does not carry it. */
|
|
23
46
|
ref?: Ref<HTMLButtonElement>;
|
|
24
47
|
}
|
|
25
|
-
declare function Button({ variant, stretch, className, ...rest }: ButtonProps): react.JSX.Element;
|
|
26
|
-
type IconButtonSize = "md" | "sm" | "xs" | "2xs";
|
|
48
|
+
declare function Button({ variant, size, stretch, className, ...rest }: ButtonProps): react.JSX.Element;
|
|
49
|
+
type IconButtonSize = "lg" | "md" | "sm" | "xs" | "2xs";
|
|
27
50
|
declare const ICON_BUTTON_TONES: {
|
|
28
51
|
readonly default: "";
|
|
29
52
|
readonly muted: "text-[var(--text-placeholder)] hover:bg-[var(--bg-surface-2)] hover:text-[var(--text-primary)]";
|
|
30
53
|
readonly danger: "text-[var(--text-placeholder)] hover:bg-[var(--danger-bg)] hover:text-[var(--danger)] focus:ring-[var(--danger-border)]";
|
|
54
|
+
readonly warning: "text-[var(--warning)] hover:bg-[var(--warning-bg)] focus:ring-[var(--warning-border)]";
|
|
55
|
+
readonly info: "text-[var(--info)] hover:bg-[var(--info-bg)] focus:ring-[var(--info-border)]";
|
|
31
56
|
};
|
|
32
57
|
interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
/** Any {@link ButtonVariant}, or `overlay` — a round translucent disc for use over
|
|
59
|
+
* an image (see `ICON_BUTTON_OVERLAY`). */
|
|
60
|
+
variant?: ButtonVariant | "overlay";
|
|
61
|
+
/** Box size: lg = 44px (a phone's touch target), md = 36px (matches the top bar),
|
|
62
|
+
* sm = 32px — all three with a 20px icon;
|
|
35
63
|
* xs = 28px with a 16px icon (an action in a list row), 2xs = 24px with a 14px
|
|
36
64
|
* icon (an action on a chip or a tab). */
|
|
37
65
|
size?: IconButtonSize;
|
|
38
66
|
/** Glyph colour over the variant. `muted`: placeholder grey, full text colour on
|
|
39
67
|
* hover. `danger`: the same grey at rest, `--danger` on hover and focus — for a
|
|
40
|
-
* remove/delete that repeats down a list.
|
|
68
|
+
* remove/delete that repeats down a list. `warning`: amber at rest — a flag that
|
|
69
|
+
* wants attention. `info`: sky at rest — a notice-worthy but harmless action
|
|
70
|
+
* (keksdose's reconcile). Default: the variant's own colours. */
|
|
41
71
|
tone?: keyof typeof ICON_BUTTON_TONES;
|
|
72
|
+
/**
|
|
73
|
+
* Make it a toggle button. `true` sets `aria-pressed="true"` and draws the "on" look
|
|
74
|
+
* (brand glyph on the quiet brand fill); `false` sets `aria-pressed="false"` with the
|
|
75
|
+
* normal look, so a screen reader still hears a toggle that is off. Left out, it is
|
|
76
|
+
* an ordinary button with no `aria-pressed` — or whatever `aria-pressed` the caller
|
|
77
|
+
* passes. For keksdose's budget share toggle (budgets-page:291), which paints its own
|
|
78
|
+
* brand colour over a ghost button today. Keep the `aria-label` the same in both
|
|
79
|
+
* states ("Share budget"): the pressed state already says whether it is on.
|
|
80
|
+
*/
|
|
81
|
+
pressed?: boolean;
|
|
42
82
|
/** Keep the click (and the Enter/Space that produces it) from reaching an
|
|
43
83
|
* ancestor's handler — for an action inside a clickable table row or card.
|
|
44
84
|
*
|
|
@@ -302,8 +342,18 @@ interface CardProps extends ComponentPropsWithoutRef<"div"> {
|
|
|
302
342
|
* content cards on data pages; leave off for centered dialog/panel cards.
|
|
303
343
|
*/
|
|
304
344
|
flush?: boolean;
|
|
345
|
+
/**
|
|
346
|
+
* `inset`: a panel INSIDE a card rather than a card on the page — the raised
|
|
347
|
+
* `--bg-surface-2`, a small radius, `p-3` of its own and no border or shadow, since
|
|
348
|
+
* it is already sitting on the card that has them. Lenkbank's results blocks and
|
|
349
|
+
* corner panels write this by hand (`rounded-md bg-surface-2 p-3`) under a Card of
|
|
350
|
+
* their own. Unlike the default card it carries its padding, because every one of
|
|
351
|
+
* those copies wanted the same one; a caller's `p-*` still wins. `flush` is ignored:
|
|
352
|
+
* an inset panel never runs edge to edge.
|
|
353
|
+
*/
|
|
354
|
+
variant?: "default" | "inset";
|
|
305
355
|
}
|
|
306
|
-
declare function Card({ className, children, flush, ...rest }: CardProps): react.JSX.Element;
|
|
356
|
+
declare function Card({ className, children, flush, variant, ...rest }: CardProps): react.JSX.Element;
|
|
307
357
|
/** The sub-parts add nothing to a `<div>`'s props — they are the SAME element with a
|
|
308
358
|
* `data-slot` and a padding rhythm — so each name is an alias rather than an empty
|
|
309
359
|
* interface pretending to be more. They exist so a consumer's own wrapper can say
|
|
@@ -354,11 +404,22 @@ declare function Spinner({ className, label, ...rest }: SpinnerProps): react.JSX
|
|
|
354
404
|
interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
|
|
355
405
|
/** The box renders `title` and `hint` in its own two-line rhythm, which is what makes
|
|
356
406
|
* every empty state in three apps look like the same thing — so there is no
|
|
357
|
-
* `children` slot to put arbitrary content in.
|
|
407
|
+
* `children` slot to put arbitrary content in. The two slots below are the only
|
|
408
|
+
* other things an empty state has turned out to need, and each has a fixed place. */
|
|
358
409
|
title: string;
|
|
359
410
|
hint?: string;
|
|
411
|
+
/** A glyph ABOVE the title — kastlan's InboxEmptyState (an inbox), keksdose's
|
|
412
|
+
* offline card (a cloud with a slash). Sized by the box (`[&_svg]:size-8`) and
|
|
413
|
+
* muted, so five call sites cannot pick five sizes; hidden from assistive tech,
|
|
414
|
+
* because the title already says what it shows. */
|
|
415
|
+
icon?: ReactNode;
|
|
416
|
+
/** What to do about it, BELOW the hint: keksdose's query-state cards (Retry), its
|
|
417
|
+
* error boundary (Retry / Reload / Go home — pass all three in a fragment, they
|
|
418
|
+
* wrap and centre as one row), kastlan's "Create a rule". Buttons or links the
|
|
419
|
+
* caller renders; the box only places them. */
|
|
420
|
+
action?: ReactNode;
|
|
360
421
|
}
|
|
361
|
-
declare function EmptyState({ title, hint, className, ...rest }: EmptyStateProps): react.JSX.Element;
|
|
422
|
+
declare function EmptyState({ title, hint, icon, action, className, ...rest }: EmptyStateProps): react.JSX.Element;
|
|
362
423
|
/** One tab of a {@link Tabs} strip. */
|
|
363
424
|
interface TabItem<T extends string> {
|
|
364
425
|
id: T;
|
|
@@ -520,7 +581,27 @@ interface TabsProps<T extends string> extends Omit<ComponentPropsWithoutRef<"div
|
|
|
520
581
|
/** An add or a remove is in flight: the × and the add button are disabled and
|
|
521
582
|
* Delete is ignored, so a double click cannot remove two tabs. Nothing moves. */
|
|
522
583
|
busy?: boolean;
|
|
584
|
+
/**
|
|
585
|
+
* `"vertical"`: the same strip as a SIDE NAV — one item per row, the open one
|
|
586
|
+
* filled, ↑/↓ walking it (Home/End as before) and `aria-orientation="vertical"` on
|
|
587
|
+
* the tablist. For keksdose's admin page and settings page, which each hand-built
|
|
588
|
+
* the identical sidebar with none of this strip's keyboard, roving tab stop or
|
|
589
|
+
* routed-`href` handling. Everything else — `href` tabs, `badge`, `panelId`,
|
|
590
|
+
* `onRemove`, `onAdd` — means exactly what it means on the horizontal strip; the
|
|
591
|
+
* badge moves to the row's end.
|
|
592
|
+
*
|
|
593
|
+
* **On a phone ({@link PHONE_QUERY}) it becomes the horizontal strip**, `wrap` and
|
|
594
|
+
* all, rather than staying a column. A side nav only works beside its content; on
|
|
595
|
+
* a phone it has to go ABOVE it, and eight full-width rows there push the panel
|
|
596
|
+
* the user picked below the fold on every visit — the two keksdose pages both
|
|
597
|
+
* collapsed theirs into a scrolling row by hand for exactly that reason. The
|
|
598
|
+
* orientation is switched in JavaScript, not with `md:` classes, so the
|
|
599
|
+
* `aria-orientation` a screen reader hears and the arrow keys that work always
|
|
600
|
+
* match the layout on screen. The caller's own two-column layout has to stack at
|
|
601
|
+
* the same breakpoint (`md:grid-cols-[14rem_1fr]`).
|
|
602
|
+
*/
|
|
603
|
+
orientation?: "horizontal" | "vertical";
|
|
523
604
|
}
|
|
524
|
-
declare function Tabs<T extends string>({ tabs, active, onChange, className, wrap, label, panelId, onRemove, removeOn, onAdd, addLabel, labels, busy, "aria-label": ariaLabel, ...rest }: TabsProps<T>): react.JSX.Element;
|
|
605
|
+
declare function Tabs<T extends string>({ tabs, active, onChange, className, wrap, label, panelId, onRemove, removeOn, onAdd, addLabel, labels, busy, orientation, "aria-label": ariaLabel, ...rest }: TabsProps<T>): react.JSX.Element;
|
|
525
606
|
|
|
526
|
-
export { Button, type ButtonProps, type ButtonVariant, Card, CardAction, type CardActionProps, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, DEFAULT_PASSWORD_REVEAL_LABELS, DEFAULT_TABS_LABELS, EmptyState, type EmptyStateProps, FIELD_BASE, FIELD_DISPLAY, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FIELD_WRITABLE_LOOK, FLOATING_INPUT_CLASS, FLOATING_LABEL_CLASS, FLOATING_LABEL_STATIC, FieldChevron, type FieldChevronProps, FieldHint, type FieldHintProps, FieldLabel, type FieldLabelProps, FloatingField, type FloatingFieldProps, IconButton, type IconButtonProps, type IconButtonSize, Input, type InputProps, Label, type LabelProps, PHONE_QUERY, type PasswordRevealLabels, Select, type SelectProps, Spinner, type SpinnerProps, type TabItem, Tabs, type TabsLabels, type TabsProps, Textarea, type TextareaProps, buttonClasses, resolvePasswordRevealLabels };
|
|
607
|
+
export { Button, type ButtonClassesOptions, type ButtonProps, type ButtonSize, type ButtonVariant, Card, CardAction, type CardActionProps, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, DEFAULT_PASSWORD_REVEAL_LABELS, DEFAULT_TABS_LABELS, EmptyState, type EmptyStateProps, FIELD_BASE, FIELD_DISPLAY, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FIELD_WRITABLE_LOOK, FLOATING_INPUT_CLASS, FLOATING_LABEL_CLASS, FLOATING_LABEL_STATIC, FieldChevron, type FieldChevronProps, FieldHint, type FieldHintProps, FieldLabel, type FieldLabelProps, FloatingField, type FloatingFieldProps, IconButton, type IconButtonProps, type IconButtonSize, Input, type InputProps, Label, type LabelProps, PHONE_QUERY, type PasswordRevealLabels, Select, type SelectProps, Spinner, type SpinnerProps, type TabItem, Tabs, type TabsLabels, type TabsProps, Textarea, type TextareaProps, buttonClasses, resolvePasswordRevealLabels };
|
package/dist/components/ui.js
CHANGED
|
@@ -7,24 +7,38 @@ import { horizontalStep } from "../lib/direction.js";
|
|
|
7
7
|
import { useMediaQuery } from "../hooks/use-media-query.js";
|
|
8
8
|
import { Tooltip } from "./tooltip.js";
|
|
9
9
|
import { DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels.js";
|
|
10
|
-
const BUTTON_BASE = "inline-flex items-center justify-center
|
|
10
|
+
const BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
11
|
+
const BUTTON_SIZES = {
|
|
12
|
+
md: "gap-2 px-3 py-2 text-sm",
|
|
13
|
+
sm: "gap-1.5 px-2 py-1 text-xs"
|
|
14
|
+
};
|
|
11
15
|
const buttonVariantClasses = {
|
|
12
16
|
primary: "border border-[var(--border)] bg-[var(--bg-surface-2)] text-[var(--text-primary)] hover:bg-[var(--border)] focus:ring-[var(--brand)]",
|
|
13
17
|
secondary: "border border-[var(--border)] bg-transparent text-[var(--text-primary)] hover:bg-[var(--bg-surface-2)] focus:ring-[var(--brand)]",
|
|
14
18
|
ghost: "bg-transparent text-[var(--text-primary)] hover:bg-[var(--bg-surface-2)] focus:ring-[var(--brand)]",
|
|
15
19
|
danger: "bg-[var(--danger)] text-[var(--danger-contrast)] hover:bg-[var(--danger-hover)] focus:ring-[var(--danger-border)]",
|
|
16
|
-
brand: "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)] focus:ring-[var(--brand)]"
|
|
20
|
+
brand: "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)] focus:ring-[var(--brand)]",
|
|
21
|
+
// A text link that is still a `<button>` — keksdose's six hand-rolled
|
|
22
|
+
// `<button className="text-brand underline">` sites ("Resend code", "Show all",
|
|
23
|
+
// "Undo" in a toast…), which act rather than navigate and so must not be anchors.
|
|
24
|
+
// The box padding goes (`p-0`) so it sits in a sentence at the text's own size, but
|
|
25
|
+
// the base's `focus:ring-2` stays: those copies had `outline-none` and no ring, so
|
|
26
|
+
// a keyboard user tabbing onto them saw nothing at all. `rounded-sm` keeps that
|
|
27
|
+
// ring hugging the word instead of drawing a pill round it.
|
|
28
|
+
link: "rounded-sm p-0 bg-transparent text-[var(--brand)] underline-offset-4 hover:underline focus:ring-[var(--brand)]"
|
|
17
29
|
};
|
|
18
|
-
function buttonClasses(variant = "primary",
|
|
19
|
-
|
|
30
|
+
function buttonClasses(variant = "primary", classNameOrOptions) {
|
|
31
|
+
const { size = "md", className } = typeof classNameOrOptions === "object" ? classNameOrOptions : { className: classNameOrOptions };
|
|
32
|
+
return cn(BUTTON_BASE, BUTTON_SIZES[size], buttonVariantClasses[variant], className);
|
|
20
33
|
}
|
|
21
|
-
function Button({ variant = "primary", stretch, className, ...rest }) {
|
|
34
|
+
function Button({ variant = "primary", size = "md", stretch, className, ...rest }) {
|
|
22
35
|
return /* @__PURE__ */ jsx(
|
|
23
36
|
"button",
|
|
24
37
|
{
|
|
25
38
|
...rest,
|
|
26
39
|
className: cn(
|
|
27
40
|
BUTTON_BASE,
|
|
41
|
+
BUTTON_SIZES[size],
|
|
28
42
|
// In a flex row next to a taller labelled field, `stretch` makes the button
|
|
29
43
|
// fill the field's height so the two line up (self-stretch overrides the row's
|
|
30
44
|
// align-items). No effect outside a flex row / when it's already the tallest.
|
|
@@ -37,6 +51,9 @@ function Button({ variant = "primary", stretch, className, ...rest }) {
|
|
|
37
51
|
}
|
|
38
52
|
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";
|
|
39
53
|
const ICON_BUTTON_SIZES = {
|
|
54
|
+
// The 44px touch target (WCAG 2.5.5's size) with the same 20px glyph — keksdose's
|
|
55
|
+
// bulk-action bars write `size-11` by hand over an `md` button to get it on phones.
|
|
56
|
+
lg: "size-11 [&_svg]:size-5",
|
|
40
57
|
md: "size-9 [&_svg]:size-5",
|
|
41
58
|
sm: "size-8 [&_svg]:size-5",
|
|
42
59
|
xs: "size-7 rounded [&_svg]:size-4",
|
|
@@ -45,14 +62,39 @@ const ICON_BUTTON_SIZES = {
|
|
|
45
62
|
const ICON_BUTTON_TONES = {
|
|
46
63
|
default: "",
|
|
47
64
|
muted: "text-[var(--text-placeholder)] hover:bg-[var(--bg-surface-2)] hover:text-[var(--text-primary)]",
|
|
48
|
-
danger: "text-[var(--text-placeholder)] hover:bg-[var(--danger-bg)] hover:text-[var(--danger)] focus:ring-[var(--danger-border)]"
|
|
65
|
+
danger: "text-[var(--text-placeholder)] hover:bg-[var(--danger-bg)] hover:text-[var(--danger)] focus:ring-[var(--danger-border)]",
|
|
66
|
+
// Amber AT REST, unlike `danger`: a warning icon button is the one on the row that
|
|
67
|
+
// needs attention (keksdose's "needs review" flag on a transaction, the stale-rate
|
|
68
|
+
// marker), not an action repeated down a list — quiet grey would hide the very
|
|
69
|
+
// thing it is there to point out.
|
|
70
|
+
warning: "text-[var(--warning)] hover:bg-[var(--warning-bg)] focus:ring-[var(--warning-border)]",
|
|
71
|
+
// Sky at rest, for the same reason as `warning`: keksdose's reconcile action on an
|
|
72
|
+
// account row (accounts-page:867) is the one on the row to notice, and it is
|
|
73
|
+
// informational rather than a problem, so it takes the `--info` family.
|
|
74
|
+
info: "text-[var(--info)] hover:bg-[var(--info-bg)] focus:ring-[var(--info-border)]"
|
|
75
|
+
};
|
|
76
|
+
const ICON_BUTTON_DISABLED_REST = {
|
|
77
|
+
primary: "disabled:hover:bg-[var(--bg-surface-2)]",
|
|
78
|
+
secondary: "disabled:hover:bg-transparent",
|
|
79
|
+
ghost: "disabled:hover:bg-transparent",
|
|
80
|
+
danger: "disabled:hover:bg-[var(--danger)]",
|
|
81
|
+
brand: "disabled:hover:bg-[var(--brand)]",
|
|
82
|
+
link: "disabled:hover:bg-transparent disabled:hover:no-underline",
|
|
83
|
+
overlay: "disabled:hover:bg-[color-mix(in_srgb,var(--bg-inverse)_60%,transparent)]"
|
|
49
84
|
};
|
|
50
|
-
const
|
|
85
|
+
const ICON_BUTTON_TONES_DISABLED_REST = {
|
|
86
|
+
muted: "disabled:hover:text-[var(--text-placeholder)]",
|
|
87
|
+
danger: "disabled:hover:text-[var(--text-placeholder)]"
|
|
88
|
+
};
|
|
89
|
+
const ICON_BUTTON_PRESSED = "bg-[var(--brand-bg)] text-[var(--brand)] hover:bg-[var(--brand-bg-hover)] hover:text-[var(--brand)] disabled:hover:bg-[var(--brand-bg)] disabled:hover:text-[var(--brand)]";
|
|
90
|
+
const ICON_BUTTON_OVERLAY = "rounded-full bg-[color-mix(in_srgb,var(--bg-inverse)_60%,transparent)] text-[var(--text-inverse)] backdrop-blur-sm hover:bg-[color-mix(in_srgb,var(--bg-inverse)_75%,transparent)] focus:ring-[var(--text-inverse)]";
|
|
91
|
+
const IconButton = forwardRef(function IconButton2({ variant = "ghost", size = "md", tone = "default", pressed, stopPropagation, className, onClick, onKeyDown, ...rest }, ref) {
|
|
51
92
|
return /* @__PURE__ */ jsx(
|
|
52
93
|
"button",
|
|
53
94
|
{
|
|
54
95
|
ref,
|
|
55
96
|
...rest,
|
|
97
|
+
"aria-pressed": pressed ?? rest["aria-pressed"],
|
|
56
98
|
onClick: (e) => {
|
|
57
99
|
if (stopPropagation) e.stopPropagation();
|
|
58
100
|
onClick?.(e);
|
|
@@ -64,8 +106,12 @@ const IconButton = forwardRef(function IconButton2({ variant = "ghost", size = "
|
|
|
64
106
|
className: cn(
|
|
65
107
|
ICON_BUTTON_BASE,
|
|
66
108
|
ICON_BUTTON_SIZES[size],
|
|
67
|
-
|
|
109
|
+
// After the size, so the overlay's `rounded-full` beats the small sizes' `rounded`.
|
|
110
|
+
variant === "overlay" ? ICON_BUTTON_OVERLAY : buttonVariantClasses[variant],
|
|
111
|
+
ICON_BUTTON_DISABLED_REST[variant],
|
|
68
112
|
ICON_BUTTON_TONES[tone],
|
|
113
|
+
ICON_BUTTON_TONES_DISABLED_REST[tone],
|
|
114
|
+
pressed && ICON_BUTTON_PRESSED,
|
|
69
115
|
className
|
|
70
116
|
)
|
|
71
117
|
}
|
|
@@ -439,17 +485,19 @@ const Textarea = forwardRef(function Textarea2({ className, label, id, placehold
|
|
|
439
485
|
) }) });
|
|
440
486
|
});
|
|
441
487
|
Textarea.displayName = "Textarea";
|
|
442
|
-
function Card({ className, children, flush, ...rest }) {
|
|
488
|
+
function Card({ className, children, flush, variant = "default", ...rest }) {
|
|
443
489
|
return /* @__PURE__ */ jsx(
|
|
444
490
|
"div",
|
|
445
491
|
{
|
|
446
492
|
...rest,
|
|
447
493
|
className: cn(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
494
|
+
variant === "inset" ? "rounded-md bg-[var(--bg-surface-2)] p-3" : cn(
|
|
495
|
+
// Surface + border are theme tokens so the palette switcher (feedback
|
|
496
|
+
// #307) can re-skin every card; a caller's own bg-*/border-* override
|
|
497
|
+
// still wins via tailwind-merge.
|
|
498
|
+
"bg-[var(--bg-surface)]",
|
|
499
|
+
flush ? "border-y border-[var(--border)] md:rounded-lg md:border md:shadow-sm" : "rounded-lg border border-[var(--border)] shadow-sm"
|
|
500
|
+
),
|
|
453
501
|
className
|
|
454
502
|
),
|
|
455
503
|
children
|
|
@@ -528,7 +576,7 @@ function Spinner({ className, label, ...rest }) {
|
|
|
528
576
|
" "
|
|
529
577
|
] }) });
|
|
530
578
|
}
|
|
531
|
-
function EmptyState({ title, hint, className, ...rest }) {
|
|
579
|
+
function EmptyState({ title, hint, icon, action, className, ...rest }) {
|
|
532
580
|
return /* @__PURE__ */ jsxs(
|
|
533
581
|
"div",
|
|
534
582
|
{
|
|
@@ -538,8 +586,10 @@ function EmptyState({ title, hint, className, ...rest }) {
|
|
|
538
586
|
className
|
|
539
587
|
),
|
|
540
588
|
children: [
|
|
589
|
+
icon != null && /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "mb-3 text-[var(--text-placeholder)] [&_svg]:size-8", children: icon }),
|
|
541
590
|
/* @__PURE__ */ jsx("div", { className: "font-medium text-[var(--text-secondary)]", children: title }),
|
|
542
|
-
hint && /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs", children: hint })
|
|
591
|
+
hint && /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs", children: hint }),
|
|
592
|
+
action != null && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center justify-center gap-2", children: action })
|
|
543
593
|
]
|
|
544
594
|
}
|
|
545
595
|
);
|
|
@@ -549,6 +599,10 @@ const DEFAULT_TABS_LABELS = {
|
|
|
549
599
|
remove: (tab) => `Remove ${tab}`
|
|
550
600
|
};
|
|
551
601
|
const TABLIST_CLASSES = "flex gap-1 overflow-x-auto overflow-y-hidden border-b border-[var(--border)]";
|
|
602
|
+
const TABLIST_VERTICAL_CLASSES = "flex flex-col gap-0.5";
|
|
603
|
+
const TAB_VERTICAL_CLASSES = "flex w-full items-center rounded-md px-3 py-2 text-start text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--border-strong)]";
|
|
604
|
+
const TAB_VERTICAL_ACTIVE_CLASSES = "bg-[var(--bg-active)] text-[var(--text-primary)]";
|
|
605
|
+
const TAB_VERTICAL_INACTIVE_CLASSES = "text-[var(--text-muted)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]";
|
|
552
606
|
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)]";
|
|
553
607
|
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)]";
|
|
554
608
|
const TAB_ACTIVE_CLASSES = "border-[var(--text-primary)] text-[var(--text-primary)]";
|
|
@@ -574,10 +628,13 @@ function Tabs({
|
|
|
574
628
|
addLabel,
|
|
575
629
|
labels,
|
|
576
630
|
busy = false,
|
|
631
|
+
orientation = "horizontal",
|
|
577
632
|
"aria-label": ariaLabel,
|
|
578
633
|
...rest
|
|
579
634
|
}) {
|
|
580
635
|
const text = useKitLabels("tabs", DEFAULT_TABS_LABELS, labels);
|
|
636
|
+
const phone = useMediaQuery(PHONE_QUERY, false);
|
|
637
|
+
const vertical = orientation === "vertical" && !phone;
|
|
581
638
|
const stripRef = useRef(null);
|
|
582
639
|
const isRemovable = (tab) => onRemove !== void 0 && tab.removable !== false;
|
|
583
640
|
const deletesOnKey = (tab) => isRemovable(tab) && (removeOn === "every" || tab.id === active);
|
|
@@ -610,7 +667,7 @@ function Tabs({
|
|
|
610
667
|
if (!busy) requestRemove(tab.id);
|
|
611
668
|
return;
|
|
612
669
|
}
|
|
613
|
-
const step = horizontalStep(e.key, e.currentTarget);
|
|
670
|
+
const step = vertical ? e.key === "ArrowDown" ? 1 : e.key === "ArrowUp" ? -1 : 0 : horizontalStep(e.key, e.currentTarget);
|
|
614
671
|
if (step === 0 && e.key !== "Home" && e.key !== "End") return;
|
|
615
672
|
const strip = e.currentTarget.closest('[role="tablist"]');
|
|
616
673
|
if (!strip) return;
|
|
@@ -628,8 +685,9 @@ function Tabs({
|
|
|
628
685
|
ref: stripRef,
|
|
629
686
|
role: "tablist",
|
|
630
687
|
"aria-label": ariaLabel ?? label,
|
|
688
|
+
"aria-orientation": vertical ? "vertical" : void 0,
|
|
631
689
|
className: cn(
|
|
632
|
-
wrap ? TABLIST_WRAP_CLASSES : TABLIST_CLASSES,
|
|
690
|
+
vertical ? TABLIST_VERTICAL_CLASSES : wrap ? TABLIST_WRAP_CLASSES : TABLIST_CLASSES,
|
|
633
691
|
onAdd ? "min-w-0" : className
|
|
634
692
|
),
|
|
635
693
|
children: tabs.map((tab) => {
|
|
@@ -656,21 +714,36 @@ function Tabs({
|
|
|
656
714
|
id: isActive && panelId ? `${panelId}-tab` : void 0,
|
|
657
715
|
"aria-keyshortcuts": deletesOnKey(tab) ? "Delete" : void 0,
|
|
658
716
|
onKeyDown: (e) => onTabKeyDown(e, tab),
|
|
659
|
-
className: cn(
|
|
717
|
+
className: vertical ? cn(
|
|
718
|
+
TAB_VERTICAL_CLASSES,
|
|
719
|
+
isActive ? TAB_VERTICAL_ACTIVE_CLASSES : TAB_VERTICAL_INACTIVE_CLASSES,
|
|
720
|
+
removable && "pe-8"
|
|
721
|
+
) : cn(
|
|
660
722
|
wrap ? TAB_WRAP_CLASSES : TAB_CLASSES,
|
|
661
723
|
wrap ? isActive ? TAB_WRAP_ACTIVE_CLASSES : TAB_WRAP_INACTIVE_CLASSES : isActive ? TAB_ACTIVE_CLASSES : TAB_INACTIVE_CLASSES,
|
|
662
724
|
removable && TAB_REMOVABLE_CLASSES
|
|
663
725
|
)
|
|
664
726
|
};
|
|
665
|
-
const inner = /* @__PURE__ */ jsxs(
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
727
|
+
const inner = /* @__PURE__ */ jsxs(
|
|
728
|
+
"span",
|
|
729
|
+
{
|
|
730
|
+
className: cn(
|
|
731
|
+
// In a side nav the row is the full width, so the label takes it and a
|
|
732
|
+
// badge lands at the row's end, where a column of counts lines up.
|
|
733
|
+
vertical ? "flex min-w-0 flex-1 items-center gap-1.5" : "inline-flex items-center gap-1.5",
|
|
734
|
+
tab.empty && TAB_EMPTY_CLASSES
|
|
735
|
+
),
|
|
736
|
+
children: [
|
|
737
|
+
tab.icon != null && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "inline-flex shrink-0 items-center gap-0.5", children: tab.icon }),
|
|
738
|
+
tab.detail != null ? /* @__PURE__ */ jsxs("span", { className: "flex flex-col items-start text-start", children: [
|
|
739
|
+
/* @__PURE__ */ jsx("span", { children: tab.label }),
|
|
740
|
+
" ",
|
|
741
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-normal leading-tight text-[var(--text-muted)]", children: tab.detail })
|
|
742
|
+
] }) : tab.label,
|
|
743
|
+
tab.badge != null ? vertical ? /* @__PURE__ */ jsx("span", { className: "ms-auto flex shrink-0 items-center", children: tab.badge }) : tab.badge : null
|
|
744
|
+
]
|
|
745
|
+
}
|
|
746
|
+
);
|
|
674
747
|
const element = tab.href ? /* @__PURE__ */ jsx(
|
|
675
748
|
"a",
|
|
676
749
|
{
|
|
@@ -697,7 +770,7 @@ function Tabs({
|
|
|
697
770
|
// The × cannot go INSIDE the tab: a button in a button is invalid HTML, and
|
|
698
771
|
// a tab's children are presentational, so a reader would flatten it into the
|
|
699
772
|
// tab's name. It sits beside the tab, over the gutter the tab reserves.
|
|
700
|
-
/* @__PURE__ */ jsxs("div", { className: "relative flex shrink-0", children: [
|
|
773
|
+
/* @__PURE__ */ jsxs("div", { className: cn("relative flex", !vertical && "shrink-0"), children: [
|
|
701
774
|
element,
|
|
702
775
|
isActive && /* @__PURE__ */ jsx(
|
|
703
776
|
IconButton,
|
|
@@ -724,6 +797,15 @@ function Tabs({
|
|
|
724
797
|
}
|
|
725
798
|
);
|
|
726
799
|
if (!onAdd) return tablist;
|
|
800
|
+
if (vertical) {
|
|
801
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-0.5", className), children: [
|
|
802
|
+
tablist,
|
|
803
|
+
/* @__PURE__ */ jsxs("button", { type: "button", onClick: onAdd, disabled: busy, className: TAB_ADD_CLASSES, children: [
|
|
804
|
+
/* @__PURE__ */ jsx(Plus, { "aria-hidden": true }),
|
|
805
|
+
addLabel ?? text.add
|
|
806
|
+
] })
|
|
807
|
+
] });
|
|
808
|
+
}
|
|
727
809
|
return /* @__PURE__ */ jsxs(
|
|
728
810
|
"div",
|
|
729
811
|
{
|