@spaethtech/svelte-ui 0.1.10 → 0.3.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 +166 -42
- package/dist/components/Alert.svelte +119 -0
- package/dist/components/Alert.svelte.d.ts +29 -0
- package/dist/components/Badge/Badge.svelte +142 -69
- package/dist/components/Badge/Badge.svelte.d.ts +26 -6
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Banner.svelte +133 -0
- package/dist/components/Banner.svelte.d.ts +31 -0
- package/dist/components/Button.svelte +240 -0
- package/dist/components/Button.svelte.d.ts +33 -0
- package/dist/components/ButtonDropdown.svelte +145 -0
- package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
- package/dist/components/Calendar.svelte +259 -0
- package/dist/components/Calendar.svelte.d.ts +27 -0
- package/dist/components/Card.svelte +78 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CardBody.svelte +41 -0
- package/dist/components/CardBody.svelte.d.ts +17 -0
- package/dist/components/CardFooter.svelte +49 -0
- package/dist/components/CardFooter.svelte.d.ts +16 -0
- package/dist/components/CardHeader.svelte +49 -0
- package/dist/components/CardHeader.svelte.d.ts +16 -0
- package/dist/components/Checkbox.svelte +131 -0
- package/dist/components/Checkbox.svelte.d.ts +16 -0
- package/dist/components/DataTable.svelte +518 -0
- package/dist/components/DataTable.svelte.d.ts +74 -0
- package/dist/components/DatePicker.svelte +312 -0
- package/dist/components/DatePicker.svelte.d.ts +32 -0
- package/dist/components/DateTimeInput.svelte +93 -0
- package/dist/components/DateTimeInput.svelte.d.ts +20 -0
- package/dist/components/Dialog.svelte +132 -0
- package/dist/components/Dialog.svelte.d.ts +24 -0
- package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
- package/dist/components/EmailInput.svelte.d.ts +21 -0
- package/dist/components/Input.svelte +369 -0
- package/dist/components/Input.svelte.d.ts +35 -0
- package/dist/components/{List/List.svelte → List.svelte} +194 -133
- package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
- package/dist/components/Menu.svelte +117 -0
- package/dist/components/Menu.svelte.d.ts +20 -0
- package/dist/components/NotesEditor.svelte +127 -0
- package/dist/components/NotesEditor.svelte.d.ts +17 -0
- package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
- package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
- package/dist/components/PasswordInput.svelte +52 -0
- package/dist/components/PasswordInput.svelte.d.ts +20 -0
- package/dist/components/Popup.svelte +140 -0
- package/dist/components/Popup.svelte.d.ts +31 -0
- package/dist/components/Query.svelte +284 -0
- package/dist/components/Query.svelte.d.ts +15 -0
- package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
- package/dist/components/Rating.svelte.d.ts +13 -0
- package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
- package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
- package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
- package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
- package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
- package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
- package/dist/components/SideBarMenu/index.d.ts +2 -0
- package/dist/components/SideBarMenu/index.js +1 -0
- package/dist/components/TabStrip/TabStrip.svelte +384 -0
- package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
- package/dist/components/TabStrip/index.d.ts +3 -0
- package/dist/components/TabStrip/index.js +2 -0
- package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
- package/dist/components/TextArea.svelte.d.ts +38 -0
- package/dist/components/ThemeSelector.svelte +81 -0
- package/dist/components/ThemeSelector.svelte.d.ts +10 -0
- package/dist/components/TimePicker.svelte +148 -0
- package/dist/components/TimePicker.svelte.d.ts +28 -0
- package/dist/components/TimeRangeInput.svelte +203 -0
- package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
- package/dist/components/TimeSpinner.svelte +202 -0
- package/dist/components/TimeSpinner.svelte.d.ts +26 -0
- package/dist/components/Toast/Toaster.svelte +51 -0
- package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
- package/dist/components/Toast/toast.svelte.d.ts +29 -0
- package/dist/components/Toast/toast.svelte.js +27 -0
- package/dist/components/Toggle.svelte +93 -0
- package/dist/components/Toggle.svelte.d.ts +15 -0
- package/dist/data/dataset.d.ts +42 -0
- package/dist/data/dataset.js +3 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/data/query/ast.d.ts +62 -0
- package/dist/data/query/ast.js +12 -0
- package/dist/data/query/index.d.ts +3 -0
- package/dist/data/query/index.js +3 -0
- package/dist/data/query/lexer.d.ts +33 -0
- package/dist/data/query/lexer.js +225 -0
- package/dist/data/query/parser.d.ts +11 -0
- package/dist/data/query/parser.js +252 -0
- package/dist/data/table/grid.svelte.d.ts +57 -0
- package/dist/data/table/grid.svelte.js +139 -0
- package/dist/data/table/index.d.ts +2 -0
- package/dist/data/table/index.js +2 -0
- package/dist/data/table/types.d.ts +79 -0
- package/dist/index.d.ts +48 -22
- package/dist/index.js +45 -21
- package/dist/positioning/anchored.d.ts +61 -0
- package/dist/positioning/anchored.js +122 -0
- package/dist/positioning/tooltip.d.ts +41 -0
- package/dist/positioning/tooltip.js +147 -0
- package/dist/theme.css +205 -0
- package/dist/types/breakpoints.d.ts +24 -0
- package/dist/types/breakpoints.js +13 -0
- package/dist/types/responsive.d.ts +32 -0
- package/dist/types/responsive.js +54 -0
- package/dist/types/sizes.d.ts +10 -3
- package/dist/types/time.d.ts +19 -0
- package/dist/types/time.js +10 -0
- package/dist/types/variants.d.ts +8 -1
- package/dist/types/variants.js +16 -1
- package/package.json +93 -63
- package/dist/components/Button/Button.svelte +0 -172
- package/dist/components/Button/Button.svelte.d.ts +0 -32
- package/dist/components/Button/index.d.ts +0 -1
- package/dist/components/Button/index.js +0 -1
- package/dist/components/Dialog/Dialog.svelte +0 -101
- package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
- package/dist/components/Dialog/index.d.ts +0 -1
- package/dist/components/Dialog/index.js +0 -1
- package/dist/components/ElementManager/ElementManager.svelte +0 -397
- package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
- package/dist/components/ElementManager/index.d.ts +0 -1
- package/dist/components/ElementManager/index.js +0 -1
- package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
- package/dist/components/EmailInput/index.d.ts +0 -1
- package/dist/components/EmailInput/index.js +0 -1
- package/dist/components/Icon/Icon.svelte +0 -74
- package/dist/components/Icon/Icon.svelte.d.ts +0 -13
- package/dist/components/Icon/index.d.ts +0 -1
- package/dist/components/Icon/index.js +0 -1
- package/dist/components/Input/Input.svelte +0 -268
- package/dist/components/Input/Input.svelte.d.ts +0 -18
- package/dist/components/Input/index.d.ts +0 -1
- package/dist/components/Input/index.js +0 -1
- package/dist/components/List/index.d.ts +0 -1
- package/dist/components/List/index.js +0 -1
- package/dist/components/ListItem/ListItem.svelte +0 -175
- package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
- package/dist/components/ListItem/index.d.ts +0 -2
- package/dist/components/ListItem/index.js +0 -2
- package/dist/components/ListView/ListView.svelte +0 -463
- package/dist/components/ListView/ListView.svelte.d.ts +0 -37
- package/dist/components/ListView/index.d.ts +0 -2
- package/dist/components/ListView/index.js +0 -2
- package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
- package/dist/components/NodeGraph/BaseNode.js +0 -30
- package/dist/components/NodeGraph/Edge.svelte +0 -60
- package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
- package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
- package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
- package/dist/components/NodeGraph/Node.svelte +0 -100
- package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
- package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
- package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
- package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
- package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
- package/dist/components/NodeGraph/NodePort.svelte +0 -75
- package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
- package/dist/components/NodeGraph/decorators.d.ts +0 -81
- package/dist/components/NodeGraph/decorators.js +0 -148
- package/dist/components/NodeGraph/index.d.ts +0 -9
- package/dist/components/NodeGraph/index.js +0 -9
- package/dist/components/NodeGraph/types.d.ts +0 -94
- package/dist/components/NodeGraph/types.js +0 -33
- package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
- package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
- package/dist/components/NotesEditor/index.d.ts +0 -1
- package/dist/components/NotesEditor/index.js +0 -1
- package/dist/components/NumberInput/index.d.ts +0 -1
- package/dist/components/NumberInput/index.js +0 -1
- package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
- package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
- package/dist/components/PasswordInput/index.d.ts +0 -1
- package/dist/components/PasswordInput/index.js +0 -1
- package/dist/components/Popup/index.d.ts +0 -1
- package/dist/components/Popup/index.js +0 -1
- package/dist/components/Rating/Rating.svelte.d.ts +0 -7
- package/dist/components/Rating/index.d.ts +0 -1
- package/dist/components/Rating/index.js +0 -1
- package/dist/components/ScrollView/ScrollView.svelte +0 -285
- package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
- package/dist/components/ScrollView/index.d.ts +0 -1
- package/dist/components/ScrollView/index.js +0 -1
- package/dist/components/SearchInput/index.d.ts +0 -1
- package/dist/components/SearchInput/index.js +0 -1
- package/dist/components/Select/index.d.ts +0 -1
- package/dist/components/Select/index.js +0 -1
- package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
- package/dist/components/TextArea/index.d.ts +0 -1
- package/dist/components/TextArea/index.js +0 -1
- package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
- package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
- package/dist/components/ThemeSelector/index.d.ts +0 -1
- package/dist/components/ThemeSelector/index.js +0 -1
- package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
- package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
- package/dist/components/TooltipHandler/index.d.ts +0 -1
- package/dist/components/TooltipHandler/index.js +0 -1
- package/dist/styles/sizes.d.ts +0 -78
- package/dist/styles/sizes.js +0 -120
- package/dist/styles/variants.d.ts +0 -106
- package/dist/styles/variants.js +0 -194
- package/dist/types/ManagerTypes.d.ts +0 -42
- /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual / interaction mode at a given viewport tier.
|
|
3
|
+
*
|
|
4
|
+
* - `stepped` — `position: absolute`, slides in from the left,
|
|
5
|
+
* full-width. Parent items use click-to-step
|
|
6
|
+
* navigation (no hover popouts). Requires a
|
|
7
|
+
* hamburger trigger. Designed for phones.
|
|
8
|
+
* - `drawer` — `position: absolute`, slides in from the left,
|
|
9
|
+
* `--sbm-expanded-w` wide. Parent items show hover
|
|
10
|
+
* popouts to the right. Requires a hamburger.
|
|
11
|
+
* Designed for tablet portrait / phablet.
|
|
12
|
+
* - `icon` — `position: relative` (in flow), `--sbm-collapsed-w`
|
|
13
|
+
* wide, icon-only rows. Parent items show hover
|
|
14
|
+
* popouts. Designed for tablet landscape / narrow
|
|
15
|
+
* desktop.
|
|
16
|
+
* - `expanded` — `position: relative`, `--sbm-expanded-w` wide,
|
|
17
|
+
* icon + label rows. Parent items show hover popouts.
|
|
18
|
+
* Designed for wide desktop.
|
|
19
|
+
*/
|
|
20
|
+
export type SideBarMenuMode = "stepped" | "drawer" | "icon" | "expanded";
|
|
21
|
+
export type SideBarMenuBadgeVariant = "error" | "warning" | "info" | "success";
|
|
22
|
+
export type SideBarMenuBadge = {
|
|
23
|
+
/** Color of the indicator. */
|
|
24
|
+
variant: SideBarMenuBadgeVariant;
|
|
25
|
+
/**
|
|
26
|
+
* Optional numeric count. Present → filled pill with the number.
|
|
27
|
+
* Absent → bare icon (variant-coloured). Useful for "has-any" signals
|
|
28
|
+
* where the precise count isn't meaningful to the user.
|
|
29
|
+
*/
|
|
30
|
+
count?: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Icon component slot. Typed permissively because `unplugin-icons` emits
|
|
34
|
+
* its own callable shape (`typeof default`) that doesn't satisfy Svelte's
|
|
35
|
+
* strict `Component<…>` primitive in every consumer tsconfig. The runtime
|
|
36
|
+
* shape is always a Svelte component the renderer can `<Icon />` — TS just
|
|
37
|
+
* can't always prove it.
|
|
38
|
+
*/
|
|
39
|
+
export type SideBarMenuItemIcon = any;
|
|
40
|
+
export type SideBarMenuItem = {
|
|
41
|
+
/**
|
|
42
|
+
* Icon component constructor — same idiom as `MenuItem.icon`. Pass an
|
|
43
|
+
* imported MDI icon (e.g. `import IconActivity from '~icons/mdi/pulse'`)
|
|
44
|
+
* straight into the data array; no snippet wrapper needed.
|
|
45
|
+
*/
|
|
46
|
+
icon?: SideBarMenuItemIcon;
|
|
47
|
+
label: string;
|
|
48
|
+
/** href; absent when the item is a parent that only hosts children. */
|
|
49
|
+
link?: string;
|
|
50
|
+
badge?: SideBarMenuBadge;
|
|
51
|
+
/** v1 renders only the first level; deeper levels emit a DEV warning. */
|
|
52
|
+
children?: SideBarMenuItem[];
|
|
53
|
+
};
|
|
54
|
+
import type { Size } from "../../types/sizes.js";
|
|
55
|
+
import { type Breakpoint } from "../../types/breakpoints.js";
|
|
56
|
+
type ModesMap = Partial<Record<Breakpoint, SideBarMenuMode>>;
|
|
57
|
+
type $$ComponentProps = {
|
|
58
|
+
topItems?: SideBarMenuItem[];
|
|
59
|
+
bottomItems?: SideBarMenuItem[];
|
|
60
|
+
currentPath: string;
|
|
61
|
+
size?: Size;
|
|
62
|
+
/**
|
|
63
|
+
* Per-tier visual mode. Each key in this map sets the mode at and
|
|
64
|
+
* above that breakpoint. The active mode at any viewport is the
|
|
65
|
+
* mode for the highest matching tier.
|
|
66
|
+
*
|
|
67
|
+
* When omitted, defaults to
|
|
68
|
+
* `{ base: 'stepped', '2xs': 'drawer', xs: 'icon', [collapsedAt]: 'expanded' }`
|
|
69
|
+
* so the prop is opt-in for consumers happy with the standard
|
|
70
|
+
* phone→tablet→desktop progression.
|
|
71
|
+
*/
|
|
72
|
+
modes?: ModesMap;
|
|
73
|
+
/**
|
|
74
|
+
* Legacy shorthand: which tier the strip expands to icon + label
|
|
75
|
+
* at when `modes` isn't supplied. Equivalent to overriding only
|
|
76
|
+
* the `expanded` slot in the default `modes` map. Ignored when
|
|
77
|
+
* `modes` is set explicitly. Prefer passing `modes` directly.
|
|
78
|
+
*/
|
|
79
|
+
collapsedAt?: Exclude<Breakpoint, "base" | "4xs" | "3xs" | "2xs" | "xs">;
|
|
80
|
+
/** Pixel width of the strip in `icon` mode. */
|
|
81
|
+
collapsedWidth?: number;
|
|
82
|
+
/** Pixel width of the strip in `expanded` mode and the slide-in drawer when open. */
|
|
83
|
+
expandedWidth?: number;
|
|
84
|
+
/**
|
|
85
|
+
* Drawer open state when the active mode is `drawer` or `stepped`.
|
|
86
|
+
* Bind from the consumer to drive it from your own header button
|
|
87
|
+
* (set `hamburger={false}` to suppress the built-in trigger).
|
|
88
|
+
* Ignored in `icon` and `expanded` modes (strip is always in-layout).
|
|
89
|
+
*/
|
|
90
|
+
open?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Step-navigation level inside the `stepped`-mode drawer.
|
|
93
|
+
* `0` = top-level. `1` = inside a parent item's children list.
|
|
94
|
+
* Bind from the consumer so a header button can read it (e.g.
|
|
95
|
+
* to flip X → ← icon) and write it (set to `0` to navigate back).
|
|
96
|
+
*/
|
|
97
|
+
stepLevel?: number;
|
|
98
|
+
/**
|
|
99
|
+
* Render the built-in fixed-position hamburger button when the
|
|
100
|
+
* active mode is `drawer` or `stepped`. Set to `false` if the
|
|
101
|
+
* consumer renders its own trigger (typically inside a header)
|
|
102
|
+
* and binds to `open`.
|
|
103
|
+
*/
|
|
104
|
+
hamburger?: boolean;
|
|
105
|
+
class?: string;
|
|
106
|
+
};
|
|
107
|
+
declare const SideBarMenu: import("svelte").Component<$$ComponentProps, {}, "open" | "stepLevel">;
|
|
108
|
+
type SideBarMenu = ReturnType<typeof SideBarMenu>;
|
|
109
|
+
export default SideBarMenu;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./SideBarMenu.svelte";
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import type { Size } from "../../types/sizes.js";
|
|
5
|
+
import type { Variant } from "../../types/variants.js";
|
|
6
|
+
import { variantToken } from "../../types/variants.js";
|
|
7
|
+
import { responsiveClasses, resolveScalar, type Responsive } from "../../types/responsive.js";
|
|
8
|
+
import { tooltip } from "../../positioning/tooltip.js";
|
|
9
|
+
import Badge from "../Badge/Badge.svelte";
|
|
10
|
+
|
|
11
|
+
/** Display mode. `expanded` (default) shows icon + label. `collapsed` hides the label so each tab
|
|
12
|
+
* reads as icon-only, with a tooltip below carrying the label — useful on narrow viewports or
|
|
13
|
+
* for long tab lists. Accepts a scalar or `Responsive` map so the same strip can collapse only
|
|
14
|
+
* below a breakpoint (`{ base: 'collapsed', sm: 'expanded' }`). Tabs without an icon fall back
|
|
15
|
+
* to their label so nothing disappears — the mode is a hint, not a hard hide. */
|
|
16
|
+
type TabMode = "expanded" | "collapsed";
|
|
17
|
+
|
|
18
|
+
export interface TabDefinition {
|
|
19
|
+
label: string;
|
|
20
|
+
/** Present → tab renders as `<a href>`; browser handles nav. Absent → `<button type="button">`. */
|
|
21
|
+
href?: string;
|
|
22
|
+
icon?: Snippet;
|
|
23
|
+
badge?: string | number;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
position?: "left" | "right";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Props extends Omit<HTMLAttributes<HTMLUListElement>, "onselect"> {
|
|
29
|
+
tabs: TabDefinition[];
|
|
30
|
+
activeTabIndex?: number;
|
|
31
|
+
size?: Responsive<Size>;
|
|
32
|
+
variant?: Responsive<Variant>;
|
|
33
|
+
/** See TabMode. Default `'expanded'`. */
|
|
34
|
+
mode?: Responsive<TabMode>;
|
|
35
|
+
/** false (default): the base line is drawn — each tab plus the filler/spacer cells contribute a
|
|
36
|
+
* bottom border in the shared border colour, and the selected tab drops its bottom border so the
|
|
37
|
+
* line breaks under it (attached to the content below). true: no base line — tabs float (they
|
|
38
|
+
* keep their top/left/right borders). */
|
|
39
|
+
borderless?: boolean;
|
|
40
|
+
/** Spacer inserted as a bordered cell BETWEEN adjacent tabs, in `rem` (default `0` = tabs flush).
|
|
41
|
+
* Not CSS `gap` — a real cell, so the base line stays continuous through it. Excludes the grow
|
|
42
|
+
* filler and the leading/trailing cells (those are `pad`). */
|
|
43
|
+
gap?: number;
|
|
44
|
+
/** Leading + trailing bordered spacer cells (before the first tab, after the last), in `rem`, AND
|
|
45
|
+
* a `padding-top` of the same size on the strip (default `0`). Cells (not padding) horizontally
|
|
46
|
+
* so the base line runs through them; real padding at the top (no line there). */
|
|
47
|
+
pad?: number;
|
|
48
|
+
onselect?: (index: number) => void;
|
|
49
|
+
right?: Snippet;
|
|
50
|
+
/** Optional aria-label for the strip when it renders as a navigation landmark
|
|
51
|
+
* (i.e. any tab has `href`). Ignored in tablist mode. */
|
|
52
|
+
"aria-label"?: string;
|
|
53
|
+
class?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let {
|
|
57
|
+
tabs,
|
|
58
|
+
activeTabIndex = $bindable(0),
|
|
59
|
+
size = "md",
|
|
60
|
+
variant = "neutral",
|
|
61
|
+
mode = "expanded",
|
|
62
|
+
borderless = false,
|
|
63
|
+
gap = 0,
|
|
64
|
+
pad = 0,
|
|
65
|
+
onselect,
|
|
66
|
+
right,
|
|
67
|
+
"aria-label": ariaLabel,
|
|
68
|
+
class: cls = "",
|
|
69
|
+
style: styleProp = "",
|
|
70
|
+
...restProps
|
|
71
|
+
}: Props = $props();
|
|
72
|
+
|
|
73
|
+
// Navigation mode: manual keyboard activation is required by APG when tabs are anchors, so arrow
|
|
74
|
+
// keys never fire spurious navigations. Button-only strips get automatic (arrow moves selection).
|
|
75
|
+
const anyHref = $derived(tabs.some((t) => t.href != null));
|
|
76
|
+
const semanticMode = $derived<"tablist" | "navigation">(anyHref ? "navigation" : "tablist");
|
|
77
|
+
|
|
78
|
+
// Per-Size class maps → responsiveClasses handles scalar OR { base, xs, … }.
|
|
79
|
+
// Heights track --ui-height-*; horizontal padding + text + inline-icon svg scale with them.
|
|
80
|
+
const heightMap: Record<Size, string> = {
|
|
81
|
+
sm: "h-[var(--ui-height-sm)]",
|
|
82
|
+
md: "h-[var(--ui-height)]",
|
|
83
|
+
lg: "h-[var(--ui-height-lg)]",
|
|
84
|
+
};
|
|
85
|
+
const padMap: Record<Size, string> = { sm: "px-3", md: "px-4", lg: "px-5" };
|
|
86
|
+
const textMap: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-base" };
|
|
87
|
+
const svgMap: Record<Size, string> = {
|
|
88
|
+
sm: "[&_svg]:w-3.5 [&_svg]:h-3.5",
|
|
89
|
+
md: "[&_svg]:w-4 [&_svg]:h-4",
|
|
90
|
+
lg: "[&_svg]:w-5 [&_svg]:h-5",
|
|
91
|
+
};
|
|
92
|
+
const gapMap: Record<Size, string> = { sm: "gap-1", md: "gap-1.5", lg: "gap-2" };
|
|
93
|
+
|
|
94
|
+
const heightClass = $derived(responsiveClasses(size, heightMap));
|
|
95
|
+
const padClass = $derived(responsiveClasses(size, padMap));
|
|
96
|
+
const textClass = $derived(responsiveClasses(size, textMap));
|
|
97
|
+
const svgClass = $derived(responsiveClasses(size, svgMap));
|
|
98
|
+
const gapClass = $derived(responsiveClasses(size, gapMap));
|
|
99
|
+
|
|
100
|
+
// Mode → label visibility. `expanded` shows the label span inline; `collapsed` hides it. Tabs
|
|
101
|
+
// without an icon show the label regardless (falling back gracefully — nothing disappears).
|
|
102
|
+
// Responsive shape supported: `{ base: 'collapsed', sm: 'expanded' }` collapses on mobile only.
|
|
103
|
+
const modeMap: Record<TabMode, string> = { expanded: "inline-block", collapsed: "hidden" };
|
|
104
|
+
const labelClass = $derived(responsiveClasses(mode, modeMap));
|
|
105
|
+
|
|
106
|
+
// Variant tinting uses ONLY the shared `--ui-accent` mechanism — the exact idiom every other themed
|
|
107
|
+
// component uses (DataTable/List/Menu/CardBody), no per-component custom properties. We point
|
|
108
|
+
// `--ui-accent` at the variant's colour token via inline `style` (only for a real accent — neutral/ghost
|
|
109
|
+
// leave it at the default `--ui-color-text`), and the `.ui-accent` class on the <ul> re-mixes the shared
|
|
110
|
+
// interactive tints (`--ui-color-surface`/`--ui-color-active`) from it. Every colour further down is a
|
|
111
|
+
// STATIC Tailwind class reading `var(--ui-accent)` (or another shared token) — never runtime-interpolated
|
|
112
|
+
// (Tailwind only emits classes found literally in source). `variant` may be a Responsive map; tinting
|
|
113
|
+
// uses its base/scalar value (one inline custom prop can't be per-breakpoint) — same scalar as the badge.
|
|
114
|
+
const stripVariant = $derived(resolveScalar(variant, "neutral"));
|
|
115
|
+
const accentVar = $derived(
|
|
116
|
+
variantToken[stripVariant] === "--ui-color-text"
|
|
117
|
+
? ""
|
|
118
|
+
: `--ui-accent: var(${variantToken[stripVariant]});`,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Split into left / right clusters, preserving array order within each.
|
|
122
|
+
const leftTabs = $derived(
|
|
123
|
+
tabs.map((t, i) => ({ t, i })).filter(({ t }) => t.position !== "right"),
|
|
124
|
+
);
|
|
125
|
+
const rightTabs = $derived(
|
|
126
|
+
tabs.map((t, i) => ({ t, i })).filter(({ t }) => t.position === "right"),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Roving focus for the tablist keyboard pattern — one tab in the natural tab order at a time.
|
|
130
|
+
// `focusedIndex` shadows activeTabIndex for the automatic mode (where they change together) but
|
|
131
|
+
// diverges for the manual mode (where arrows only move focus).
|
|
132
|
+
let focusedIndex = $state<number | null>(null);
|
|
133
|
+
const rovingIndex = $derived(
|
|
134
|
+
focusedIndex ?? (activeTabIndex >= 0 && activeTabIndex < tabs.length ? activeTabIndex : 0),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
function activate(i: number) {
|
|
138
|
+
if (tabs[i]?.disabled) return;
|
|
139
|
+
activeTabIndex = i;
|
|
140
|
+
onselect?.(i);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Walk to the next / previous non-disabled tab, wrapping.
|
|
144
|
+
function nextEnabled(from: number, step: 1 | -1): number {
|
|
145
|
+
const n = tabs.length;
|
|
146
|
+
if (n === 0) return from;
|
|
147
|
+
let i = from;
|
|
148
|
+
for (let guard = 0; guard < n; guard++) {
|
|
149
|
+
i = (i + step + n) % n;
|
|
150
|
+
if (!tabs[i]?.disabled) return i;
|
|
151
|
+
}
|
|
152
|
+
return from;
|
|
153
|
+
}
|
|
154
|
+
function firstEnabled(): number {
|
|
155
|
+
const i = tabs.findIndex((t) => !t.disabled);
|
|
156
|
+
return i === -1 ? 0 : i;
|
|
157
|
+
}
|
|
158
|
+
function lastEnabled(): number {
|
|
159
|
+
for (let i = tabs.length - 1; i >= 0; i--) if (!tabs[i]?.disabled) return i;
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function handleKeydown(e: KeyboardEvent, i: number) {
|
|
164
|
+
let target: number | null = null;
|
|
165
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") target = nextEnabled(i, 1);
|
|
166
|
+
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") target = nextEnabled(i, -1);
|
|
167
|
+
else if (e.key === "Home") target = firstEnabled();
|
|
168
|
+
else if (e.key === "End") target = lastEnabled();
|
|
169
|
+
else if ((e.key === "Enter" || e.key === " ") && semanticMode === "tablist") {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
activate(i);
|
|
172
|
+
return;
|
|
173
|
+
} else return;
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
focusedIndex = target;
|
|
176
|
+
// Automatic activation: tablist mode moves selection with focus. Navigation mode moves focus only.
|
|
177
|
+
if (semanticMode === "tablist") activate(target);
|
|
178
|
+
// Move DOM focus to the newly-focused tab.
|
|
179
|
+
queueMicrotask(() => {
|
|
180
|
+
stripEl?.querySelectorAll<HTMLElement>("[data-tab-el]")?.[target!]?.focus();
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let stripEl: HTMLUListElement | undefined = $state();
|
|
185
|
+
|
|
186
|
+
// The strip has NO border of its own. The base line is composed from the BOTTOM border of every child
|
|
187
|
+
// (each tab, the grow filler, the right cluster) — they sit on a shared baseline (items-stretch), so
|
|
188
|
+
// their bottom borders join into one continuous 1px line, in the shared border colour (accent at
|
|
189
|
+
// --ui-tint-border). `borderless` makes that bottom border transparent → no line. The selected tab
|
|
190
|
+
// always keeps its bottom transparent, breaking the line so it opens into the content below.
|
|
191
|
+
// NB: these border classes are written out in full — Tailwind only emits arbitrary-property CSS for
|
|
192
|
+
// class strings it finds *literally* in source, so we can't factor the repeated color-mix into a
|
|
193
|
+
// variable and interpolate it (that's the same trap the old per-component var hid). List does the same.
|
|
194
|
+
const lineBottom = $derived(
|
|
195
|
+
borderless
|
|
196
|
+
? "border-b [border-bottom-color:transparent]"
|
|
197
|
+
: "border-b [border-bottom-color:color-mix(in_srgb,var(--ui-accent)_var(--ui-tint-border),transparent)]",
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Tab side borders (top/left/right). `borderless` makes these transparent too, so the whole tab has
|
|
201
|
+
// no visible edge — floating tabs, not open-bottom boxes. The 1px border box is kept (transparent)
|
|
202
|
+
// either way so heights never shift (same principle as Button/Card `borderless`). The BOTTOM border
|
|
203
|
+
// is handled separately by `lineBottom` (unselected/disabled) or the selected state. Written out in
|
|
204
|
+
// full for the same Tailwind-literal reason as `lineBottom` above.
|
|
205
|
+
const sideBorders = $derived(
|
|
206
|
+
borderless
|
|
207
|
+
? "border [border-top-color:transparent] [border-left-color:transparent] [border-right-color:transparent]"
|
|
208
|
+
: "border [border-top-color:color-mix(in_srgb,var(--ui-accent)_var(--ui-tint-border),transparent)] [border-left-color:color-mix(in_srgb,var(--ui-accent)_var(--ui-tint-border),transparent)] [border-right-color:color-mix(in_srgb,var(--ui-accent)_var(--ui-tint-border),transparent)]",
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Tab element base: shared by anchors and buttons. Draws top/left/right borders (the shared border
|
|
212
|
+
// colour) with rounded top corners; the BOTTOM border colour is state-driven (below) — it joins the
|
|
213
|
+
// base line (unselected/disabled) or breaks it (selected). All colours read `var(--ui-accent)`.
|
|
214
|
+
const tabBase = $derived(
|
|
215
|
+
[
|
|
216
|
+
"inline-flex items-center justify-center whitespace-nowrap select-none no-underline cursor-pointer",
|
|
217
|
+
"rounded-t-[var(--ui-border-radius)]",
|
|
218
|
+
`${sideBorders} transition-colors`,
|
|
219
|
+
"focus-visible:[outline:2px_solid_color-mix(in_srgb,var(--ui-color-text)_70%,transparent)] focus-visible:[outline-offset:-2px]",
|
|
220
|
+
heightClass,
|
|
221
|
+
padClass,
|
|
222
|
+
textClass,
|
|
223
|
+
svgClass,
|
|
224
|
+
gapClass,
|
|
225
|
+
].join(" "),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Selected: transparent fill, so the tab shows whatever surface the strip sits on (page background,
|
|
229
|
+
// a Card/panel, an inverse surface) and reads as one continuous surface with the content below it —
|
|
230
|
+
// the folder-tab effect on ANY background, not just the page. Label + icon take `var(--ui-accent)`
|
|
231
|
+
// (the variant colour, or plain text for neutral/ghost — icons inherit via currentColor), the
|
|
232
|
+
// clearest signal of the strip's variant. Bottom border transparent — the base line breaks under it.
|
|
233
|
+
const selectedStyleClass =
|
|
234
|
+
"[background-color:transparent] [color:var(--ui-accent)] [border-bottom-color:transparent]";
|
|
235
|
+
|
|
236
|
+
// `ghost` is the "transparent, hover-tint only" grey variant (per `types/variants.ts`): unlike
|
|
237
|
+
// `neutral`, its inactive tabs have NO resting fill — they sit on the bare surface and only tint on
|
|
238
|
+
// hover. Every other variant keeps the recessed `--ui-color-surface` section band. (Both greys share
|
|
239
|
+
// `--ui-accent = --ui-color-text`, so the resting fill is the ONLY thing that distinguishes them.)
|
|
240
|
+
const restingFill = $derived(
|
|
241
|
+
stripVariant === "ghost"
|
|
242
|
+
? "[background-color:transparent]"
|
|
243
|
+
: "[background-color:var(--ui-color-surface)]",
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Unselected: the resting fill (recessed band, or transparent for ghost); text/icon are the muted
|
|
247
|
+
// accent (`var(--ui-accent)` at 60%). Hover deepens the fill to --ui-color-active (12%, same for both
|
|
248
|
+
// greys) and lifts text/icon to the full accent. Bottom border joins the base line (via `lineBottom`).
|
|
249
|
+
const unselectedStyleClass = $derived(
|
|
250
|
+
`${restingFill} [color:color-mix(in_srgb,var(--ui-accent)_60%,transparent)]` +
|
|
251
|
+
" hover:[background-color:var(--ui-color-active)] hover:[color:var(--ui-accent)]",
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Disabled: the resting fill, faded + inert; bottom border joins the base line (via `lineBottom`).
|
|
255
|
+
const disabledStyleClass = $derived(
|
|
256
|
+
`opacity-50 cursor-not-allowed pointer-events-none ${restingFill}`,
|
|
257
|
+
);
|
|
258
|
+
</script>
|
|
259
|
+
|
|
260
|
+
{#snippet spacer(rem: number)}
|
|
261
|
+
<!-- A bordered spacer cell (gap between tabs, or pad at the ends). Carries the base line via
|
|
262
|
+
`lineBottom`; fixed width so it never collapses under overflow. Nothing to interact with. -->
|
|
263
|
+
<li aria-hidden="true" class={`shrink-0 ${lineBottom}`} style={`width:${rem}rem`}></li>
|
|
264
|
+
{/snippet}
|
|
265
|
+
|
|
266
|
+
{#snippet tabItem({ t, i }: { t: TabDefinition; i: number })}
|
|
267
|
+
{@const isActive = i === activeTabIndex}
|
|
268
|
+
{@const isRoving = i === rovingIndex}
|
|
269
|
+
{@const stateClass = t.disabled
|
|
270
|
+
? `${disabledStyleClass} ${lineBottom}`
|
|
271
|
+
: isActive
|
|
272
|
+
? selectedStyleClass
|
|
273
|
+
: `${unselectedStyleClass} ${lineBottom}`}
|
|
274
|
+
{@const tabTabIndex = t.disabled ? -1 : semanticMode === "tablist" ? (isRoving ? 0 : -1) : 0}
|
|
275
|
+
{@const tabAriaCurrent =
|
|
276
|
+
semanticMode === "navigation" && isActive ? ("page" as const) : undefined}
|
|
277
|
+
{@const badgeVariant = stripVariant}
|
|
278
|
+
<!-- Each tab is a list item. In tablist mode the <li> is presentational so the tablist/tab roles
|
|
279
|
+
on the <ul>/<button> aren't disturbed; the real <button>/<a> carries the styling + the base-line
|
|
280
|
+
bottom border. Using `contents` so the button/anchor stays the flex item (layout unchanged). -->
|
|
281
|
+
<li role={semanticMode === "tablist" ? "presentation" : undefined} class="contents">
|
|
282
|
+
{#if t.href}
|
|
283
|
+
<a
|
|
284
|
+
href={t.href}
|
|
285
|
+
data-tab-el
|
|
286
|
+
title={t.label}
|
|
287
|
+
use:tooltip={{ side: "bottom" }}
|
|
288
|
+
aria-disabled={t.disabled ? true : undefined}
|
|
289
|
+
aria-current={tabAriaCurrent}
|
|
290
|
+
tabindex={tabTabIndex}
|
|
291
|
+
onclick={(e) => {
|
|
292
|
+
if (t.disabled) {
|
|
293
|
+
e.preventDefault();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
activate(i);
|
|
297
|
+
}}
|
|
298
|
+
onkeydown={(e) => handleKeydown(e, i)}
|
|
299
|
+
onfocus={() => {
|
|
300
|
+
focusedIndex = i;
|
|
301
|
+
}}
|
|
302
|
+
class={`${tabBase} ${stateClass}`}
|
|
303
|
+
>
|
|
304
|
+
{#if t.icon}{@render t.icon()}{/if}
|
|
305
|
+
<span class={`truncate ${t.icon ? labelClass : "inline-block"}`}>{t.label}</span>
|
|
306
|
+
{#if t.badge != null}
|
|
307
|
+
<Badge variant={badgeVariant} {size} borderless>{t.badge}</Badge>
|
|
308
|
+
{/if}
|
|
309
|
+
</a>
|
|
310
|
+
{:else}
|
|
311
|
+
<button
|
|
312
|
+
type="button"
|
|
313
|
+
data-tab-el
|
|
314
|
+
title={t.label}
|
|
315
|
+
use:tooltip={{ side: "bottom" }}
|
|
316
|
+
role={semanticMode === "tablist" ? "tab" : undefined}
|
|
317
|
+
aria-selected={semanticMode === "tablist" ? isActive : undefined}
|
|
318
|
+
aria-disabled={t.disabled ? true : undefined}
|
|
319
|
+
tabindex={tabTabIndex}
|
|
320
|
+
onclick={() => activate(i)}
|
|
321
|
+
onkeydown={(e) => handleKeydown(e, i)}
|
|
322
|
+
onfocus={() => {
|
|
323
|
+
focusedIndex = i;
|
|
324
|
+
}}
|
|
325
|
+
class={`${tabBase} ${stateClass}`}
|
|
326
|
+
disabled={t.disabled}
|
|
327
|
+
>
|
|
328
|
+
{#if t.icon}{@render t.icon()}{/if}
|
|
329
|
+
<span class={`truncate ${t.icon ? labelClass : "inline-block"}`}>{t.label}</span>
|
|
330
|
+
{#if t.badge != null}
|
|
331
|
+
<Badge variant={badgeVariant} {size} borderless>{t.badge}</Badge>
|
|
332
|
+
{/if}
|
|
333
|
+
</button>
|
|
334
|
+
{/if}
|
|
335
|
+
</li>
|
|
336
|
+
{/snippet}
|
|
337
|
+
|
|
338
|
+
<ul
|
|
339
|
+
bind:this={stripEl}
|
|
340
|
+
role={semanticMode === "tablist" ? "tablist" : "navigation"}
|
|
341
|
+
aria-orientation={semanticMode === "tablist" ? "horizontal" : undefined}
|
|
342
|
+
aria-label={semanticMode === "navigation" ? ariaLabel : undefined}
|
|
343
|
+
class={`ui-accent flex items-stretch overflow-x-auto list-none m-0 p-0 ${cls}`}
|
|
344
|
+
style={`${accentVar} padding-top:${pad}rem; ${styleProp ?? ""}`}
|
|
345
|
+
{...restProps}
|
|
346
|
+
>
|
|
347
|
+
<!-- Leading pad cell (before the first left tab). -->
|
|
348
|
+
{#if pad > 0 && leftTabs.length > 0}{@render spacer(pad)}{/if}
|
|
349
|
+
|
|
350
|
+
{#each leftTabs as { t, i }, idx (i)}
|
|
351
|
+
{@render tabItem({ t, i })}
|
|
352
|
+
<!-- gap cell BETWEEN adjacent left tabs (never after the last). -->
|
|
353
|
+
{#if gap > 0 && idx < leftTabs.length - 1}{@render spacer(gap)}{/if}
|
|
354
|
+
{/each}
|
|
355
|
+
|
|
356
|
+
<!-- Trailing pad cell when the left cluster is the last thing (no right tabs). -->
|
|
357
|
+
{#if pad > 0 && leftTabs.length > 0 && rightTabs.length === 0}{@render spacer(pad)}{/if}
|
|
358
|
+
|
|
359
|
+
<!-- Grow filler (the "+1"): an empty list item that stretches to eat the free space and carry the
|
|
360
|
+
base line across it. It sits BETWEEN the left and right clusters, so it lands last (only left
|
|
361
|
+
tabs), in the middle (both clusters), or first (only right tabs) — pushing the right cluster to
|
|
362
|
+
the edge in every case. Nothing to interact with, so it's aria-hidden. -->
|
|
363
|
+
<li aria-hidden="true" class={`flex-1 ${lineBottom}`}></li>
|
|
364
|
+
|
|
365
|
+
{#if right}
|
|
366
|
+
<li
|
|
367
|
+
role="presentation"
|
|
368
|
+
class={`inline-flex items-center ${padClass} ${textClass} ${lineBottom} [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]`}
|
|
369
|
+
>
|
|
370
|
+
{@render right()}
|
|
371
|
+
</li>
|
|
372
|
+
{/if}
|
|
373
|
+
|
|
374
|
+
<!-- Leading pad cell for a right-only strip (no left cluster). -->
|
|
375
|
+
{#if pad > 0 && rightTabs.length > 0 && leftTabs.length === 0}{@render spacer(pad)}{/if}
|
|
376
|
+
|
|
377
|
+
{#each rightTabs as { t, i }, idx (i)}
|
|
378
|
+
{@render tabItem({ t, i })}
|
|
379
|
+
{#if gap > 0 && idx < rightTabs.length - 1}{@render spacer(gap)}{/if}
|
|
380
|
+
{/each}
|
|
381
|
+
|
|
382
|
+
<!-- Trailing pad cell (after the last right tab). -->
|
|
383
|
+
{#if pad > 0 && rightTabs.length > 0}{@render spacer(pad)}{/if}
|
|
384
|
+
</ul>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { Size } from "../../types/sizes.js";
|
|
4
|
+
import type { Variant } from "../../types/variants.js";
|
|
5
|
+
import { type Responsive } from "../../types/responsive.js";
|
|
6
|
+
/** Display mode. `expanded` (default) shows icon + label. `collapsed` hides the label so each tab
|
|
7
|
+
* reads as icon-only, with a tooltip below carrying the label — useful on narrow viewports or
|
|
8
|
+
* for long tab lists. Accepts a scalar or `Responsive` map so the same strip can collapse only
|
|
9
|
+
* below a breakpoint (`{ base: 'collapsed', sm: 'expanded' }`). Tabs without an icon fall back
|
|
10
|
+
* to their label so nothing disappears — the mode is a hint, not a hard hide. */
|
|
11
|
+
type TabMode = "expanded" | "collapsed";
|
|
12
|
+
export interface TabDefinition {
|
|
13
|
+
label: string;
|
|
14
|
+
/** Present → tab renders as `<a href>`; browser handles nav. Absent → `<button type="button">`. */
|
|
15
|
+
href?: string;
|
|
16
|
+
icon?: Snippet;
|
|
17
|
+
badge?: string | number;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
position?: "left" | "right";
|
|
20
|
+
}
|
|
21
|
+
interface Props extends Omit<HTMLAttributes<HTMLUListElement>, "onselect"> {
|
|
22
|
+
tabs: TabDefinition[];
|
|
23
|
+
activeTabIndex?: number;
|
|
24
|
+
size?: Responsive<Size>;
|
|
25
|
+
variant?: Responsive<Variant>;
|
|
26
|
+
/** See TabMode. Default `'expanded'`. */
|
|
27
|
+
mode?: Responsive<TabMode>;
|
|
28
|
+
/** false (default): the base line is drawn — each tab plus the filler/spacer cells contribute a
|
|
29
|
+
* bottom border in the shared border colour, and the selected tab drops its bottom border so the
|
|
30
|
+
* line breaks under it (attached to the content below). true: no base line — tabs float (they
|
|
31
|
+
* keep their top/left/right borders). */
|
|
32
|
+
borderless?: boolean;
|
|
33
|
+
/** Spacer inserted as a bordered cell BETWEEN adjacent tabs, in `rem` (default `0` = tabs flush).
|
|
34
|
+
* Not CSS `gap` — a real cell, so the base line stays continuous through it. Excludes the grow
|
|
35
|
+
* filler and the leading/trailing cells (those are `pad`). */
|
|
36
|
+
gap?: number;
|
|
37
|
+
/** Leading + trailing bordered spacer cells (before the first tab, after the last), in `rem`, AND
|
|
38
|
+
* a `padding-top` of the same size on the strip (default `0`). Cells (not padding) horizontally
|
|
39
|
+
* so the base line runs through them; real padding at the top (no line there). */
|
|
40
|
+
pad?: number;
|
|
41
|
+
onselect?: (index: number) => void;
|
|
42
|
+
right?: Snippet;
|
|
43
|
+
/** Optional aria-label for the strip when it renders as a navigation landmark
|
|
44
|
+
* (i.e. any tab has `href`). Ignored in tablist mode. */
|
|
45
|
+
"aria-label"?: string;
|
|
46
|
+
class?: string;
|
|
47
|
+
}
|
|
48
|
+
declare const TabStrip: import("svelte").Component<Props, {}, "activeTabIndex">;
|
|
49
|
+
type TabStrip = ReturnType<typeof TabStrip>;
|
|
50
|
+
export default TabStrip;
|