@spaethtech/svelte-ui 0.6.1-dev.32.fbb6fa6 → 0.6.1-dev.34.cf577c5
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.
|
@@ -162,7 +162,9 @@ examples):
|
|
|
162
162
|
`onShow(ctx)`/`onHide(ctx)` control focus — set `ctx.autoFocus` to a selector/element/`false`,
|
|
163
163
|
`ctx.restoreFocus` likewise) · `ConfirmDialog` (title + message + Confirm/Cancel, built on Dialog)
|
|
164
164
|
· `Popup` `Menu` · `tooltip` (a Svelte `use:` action) · `anchored` (positioning engine)
|
|
165
|
-
- **Navigation:** `TabStrip` `
|
|
165
|
+
- **Navigation:** `TabStrip` (tabs; `size`/`variant`, `gap`/`pad` bordered spacer cells, `height` for a
|
|
166
|
+
fixed-height bar with bottom-aligned tabs, `activeSurface` to flow the active tab into the content
|
|
167
|
+
below) · `SideBarMenu`
|
|
166
168
|
- **Feedback:** `Alert` `Banner` `Badge` `Toaster` + `toast`
|
|
167
169
|
- **Layout:** `Card` `CardHeader` `CardBody` `CardFooter` · **`Grid`** (auto-placed equal-cell grid;
|
|
168
170
|
`columns` + `gap`)
|
|
@@ -41,11 +41,20 @@
|
|
|
41
41
|
* Not CSS `gap` — a real cell, so the base line stays continuous through it. Excludes the grow
|
|
42
42
|
* filler and the leading/trailing cells (those are `pad`). */
|
|
43
43
|
gap?: number;
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
/** Leading + trailing bordered spacer cells (before the first tab, after the last), in `rem`
|
|
45
|
+
* (default `0`). Cells, not CSS padding, so the base line runs through them. For vertical room,
|
|
46
|
+
* set `height` (the tabs bottom-align to the base line). */
|
|
47
|
+
pad?: number;
|
|
48
|
+
/** Total strip height, in `rem` (applied as `min-height`, so it never clips a taller tab). The tab
|
|
49
|
+
* row bottom-aligns to the base line and the extra space breathes ABOVE it. `size` still sets the
|
|
50
|
+
* tab height; `height` sets the whole bar. Unset → the strip hugs the tab row. */
|
|
51
|
+
height?: number;
|
|
52
|
+
/** CSS colour for the ACTIVE tab's fill AND its bottom border — so the base line disappears under
|
|
53
|
+
* it and the tab reads as one surface with the content below. Default `"transparent"` (reveals
|
|
54
|
+
* whatever surface the strip sits on — today's behaviour). Set a token/colour to force a specific
|
|
55
|
+
* surface, e.g. `activeSurface="var(--ui-color-background)"` when the strip sits in a container
|
|
56
|
+
* whose background differs from the content below. Any CSS colour value. */
|
|
57
|
+
activeSurface?: string;
|
|
49
58
|
onselect?: (index: number) => void;
|
|
50
59
|
right?: Snippet;
|
|
51
60
|
/** Optional aria-label for the strip when it renders as a navigation landmark
|
|
@@ -63,6 +72,8 @@
|
|
|
63
72
|
borderless = false,
|
|
64
73
|
gap = 0,
|
|
65
74
|
pad = 0,
|
|
75
|
+
height,
|
|
76
|
+
activeSurface = "transparent",
|
|
66
77
|
onselect,
|
|
67
78
|
right,
|
|
68
79
|
"aria-label": ariaLabel,
|
|
@@ -98,11 +109,6 @@
|
|
|
98
109
|
const svgClass = $derived(responsiveClasses(size, svgMap));
|
|
99
110
|
const gapClass = $derived(responsiveClasses(size, gapMap));
|
|
100
111
|
|
|
101
|
-
// Normalize `pad` (scalar = both; `{ top, sides }` = independent). `sides` → leading/trailing spacer
|
|
102
|
-
// cells; `top` → the strip's padding-top. In rem.
|
|
103
|
-
const padTop = $derived(typeof pad === "number" ? pad : (pad.top ?? 0));
|
|
104
|
-
const padSides = $derived(typeof pad === "number" ? pad : (pad.sides ?? 0));
|
|
105
|
-
|
|
106
112
|
// Mode → label visibility. `expanded` shows the label span inline; `collapsed` hides it. Tabs
|
|
107
113
|
// without an icon show the label regardless (falling back gracefully — nothing disappears).
|
|
108
114
|
// Responsive shape supported: `{ base: 'collapsed', sm: 'expanded' }` collapses on mobile only.
|
|
@@ -231,13 +237,18 @@
|
|
|
231
237
|
].join(" "),
|
|
232
238
|
);
|
|
233
239
|
|
|
234
|
-
// Selected:
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
// Selected: label + icon take `var(--ui-accent)` (the variant colour, or plain text for
|
|
241
|
+
// neutral/ghost — icons inherit via currentColor), the clearest signal of the strip's variant. The
|
|
242
|
+
// FILL + bottom-border colour are driven by `activeSurface` via inline `style` (below) — a dynamic
|
|
243
|
+
// colour can't be a Tailwind class (the scanner never sees a runtime-built arbitrary value). Default
|
|
244
|
+
// `transparent`: the tab shows whatever surface the strip sits on and the base line breaks under it
|
|
245
|
+
// (the folder-tab effect on ANY background) — identical to before.
|
|
246
|
+
const selectedStyleClass = "[color:var(--ui-accent)]";
|
|
247
|
+
// Applied only to the active tab. `background-color` = the fill; `border-bottom-color` = the same, so
|
|
248
|
+
// the base-line segment under the active tab vanishes into that surface.
|
|
249
|
+
const selectedStyle = $derived(
|
|
250
|
+
`background-color:${activeSurface}; border-bottom-color:${activeSurface}`,
|
|
251
|
+
);
|
|
241
252
|
|
|
242
253
|
// `ghost` is the "transparent, hover-tint only" grey variant (per `types/variants.ts`): unlike
|
|
243
254
|
// `neutral`, its inactive tabs have NO resting fill — they sit on the bare surface and only tint on
|
|
@@ -277,6 +288,7 @@
|
|
|
277
288
|
: isActive
|
|
278
289
|
? selectedStyleClass
|
|
279
290
|
: `${unselectedStyleClass} ${lineBottom}`}
|
|
291
|
+
{@const tabStyle = isActive && !t.disabled ? selectedStyle : undefined}
|
|
280
292
|
{@const tabTabIndex = t.disabled ? -1 : semanticMode === "tablist" ? (isRoving ? 0 : -1) : 0}
|
|
281
293
|
{@const tabAriaCurrent =
|
|
282
294
|
semanticMode === "navigation" && isActive ? ("page" as const) : undefined}
|
|
@@ -306,6 +318,7 @@
|
|
|
306
318
|
focusedIndex = i;
|
|
307
319
|
}}
|
|
308
320
|
class={`${tabBase} ${stateClass}`}
|
|
321
|
+
style={tabStyle}
|
|
309
322
|
>
|
|
310
323
|
{#if t.icon}{@render t.icon()}{/if}
|
|
311
324
|
<span class={`truncate ${t.icon ? labelClass : "inline-block"}`}>{t.label}</span>
|
|
@@ -329,6 +342,7 @@
|
|
|
329
342
|
focusedIndex = i;
|
|
330
343
|
}}
|
|
331
344
|
class={`${tabBase} ${stateClass}`}
|
|
345
|
+
style={tabStyle}
|
|
332
346
|
disabled={t.disabled}
|
|
333
347
|
>
|
|
334
348
|
{#if t.icon}{@render t.icon()}{/if}
|
|
@@ -346,12 +360,12 @@
|
|
|
346
360
|
role={semanticMode === "tablist" ? "tablist" : "navigation"}
|
|
347
361
|
aria-orientation={semanticMode === "tablist" ? "horizontal" : undefined}
|
|
348
362
|
aria-label={semanticMode === "navigation" ? ariaLabel : undefined}
|
|
349
|
-
class={`ui-accent flex items-
|
|
350
|
-
style={`${accentVar}
|
|
363
|
+
class={`ui-accent flex items-end overflow-x-auto list-none m-0 p-0 ${cls}`}
|
|
364
|
+
style={`${accentVar}${height != null ? ` min-height:${height}rem;` : ""} ${styleProp ?? ""}`}
|
|
351
365
|
{...restProps}
|
|
352
366
|
>
|
|
353
367
|
<!-- Leading pad cell (before the first left tab). -->
|
|
354
|
-
{#if
|
|
368
|
+
{#if pad > 0 && leftTabs.length > 0}{@render spacer(pad)}{/if}
|
|
355
369
|
|
|
356
370
|
{#each leftTabs as { t, i }, idx (i)}
|
|
357
371
|
{@render tabItem({ t, i })}
|
|
@@ -360,7 +374,7 @@
|
|
|
360
374
|
{/each}
|
|
361
375
|
|
|
362
376
|
<!-- Trailing pad cell when the left cluster is the last thing (no right tabs). -->
|
|
363
|
-
{#if
|
|
377
|
+
{#if pad > 0 && leftTabs.length > 0 && rightTabs.length === 0}{@render spacer(pad)}{/if}
|
|
364
378
|
|
|
365
379
|
<!-- Grow filler (the "+1"): an empty list item that stretches to eat the free space and carry the
|
|
366
380
|
base line across it. It sits BETWEEN the left and right clusters, so it lands last (only left
|
|
@@ -378,7 +392,7 @@
|
|
|
378
392
|
{/if}
|
|
379
393
|
|
|
380
394
|
<!-- Leading pad cell for a right-only strip (no left cluster). -->
|
|
381
|
-
{#if
|
|
395
|
+
{#if pad > 0 && rightTabs.length > 0 && leftTabs.length === 0}{@render spacer(pad)}{/if}
|
|
382
396
|
|
|
383
397
|
{#each rightTabs as { t, i }, idx (i)}
|
|
384
398
|
{@render tabItem({ t, i })}
|
|
@@ -386,5 +400,5 @@
|
|
|
386
400
|
{/each}
|
|
387
401
|
|
|
388
402
|
<!-- Trailing pad cell (after the last right tab). -->
|
|
389
|
-
{#if
|
|
403
|
+
{#if pad > 0 && rightTabs.length > 0}{@render spacer(pad)}{/if}
|
|
390
404
|
</ul>
|
|
@@ -34,14 +34,20 @@ interface Props extends Omit<HTMLAttributes<HTMLUListElement>, "onselect"> {
|
|
|
34
34
|
* Not CSS `gap` — a real cell, so the base line stays continuous through it. Excludes the grow
|
|
35
35
|
* filler and the leading/trailing cells (those are `pad`). */
|
|
36
36
|
gap?: number;
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
/** Leading + trailing bordered spacer cells (before the first tab, after the last), in `rem`
|
|
38
|
+
* (default `0`). Cells, not CSS padding, so the base line runs through them. For vertical room,
|
|
39
|
+
* set `height` (the tabs bottom-align to the base line). */
|
|
40
|
+
pad?: number;
|
|
41
|
+
/** Total strip height, in `rem` (applied as `min-height`, so it never clips a taller tab). The tab
|
|
42
|
+
* row bottom-aligns to the base line and the extra space breathes ABOVE it. `size` still sets the
|
|
43
|
+
* tab height; `height` sets the whole bar. Unset → the strip hugs the tab row. */
|
|
44
|
+
height?: number;
|
|
45
|
+
/** CSS colour for the ACTIVE tab's fill AND its bottom border — so the base line disappears under
|
|
46
|
+
* it and the tab reads as one surface with the content below. Default `"transparent"` (reveals
|
|
47
|
+
* whatever surface the strip sits on — today's behaviour). Set a token/colour to force a specific
|
|
48
|
+
* surface, e.g. `activeSurface="var(--ui-color-background)"` when the strip sits in a container
|
|
49
|
+
* whose background differs from the content below. Any CSS colour value. */
|
|
50
|
+
activeSurface?: string;
|
|
45
51
|
onselect?: (index: number) => void;
|
|
46
52
|
right?: Snippet;
|
|
47
53
|
/** Optional aria-label for the strip when it renders as a navigation landmark
|
package/docs/components.md
CHANGED
|
@@ -197,6 +197,22 @@ Thin wrappers over `Input`, each preconfigured with an MDI icon (rendered into I
|
|
|
197
197
|
- **Location**: `src/lib/components/NumberInput.svelte`
|
|
198
198
|
- **Features**: number formatting and validation
|
|
199
199
|
|
|
200
|
+
## Navigation
|
|
201
|
+
|
|
202
|
+
### TabStrip
|
|
203
|
+
|
|
204
|
+
A horizontal tab bar (renders as a `tablist` of buttons, or a `navigation` of links when any tab has
|
|
205
|
+
`href`). Tabs split into left / right clusters; a continuous base line runs under the strip and breaks
|
|
206
|
+
under the active tab. Fully demoed at `/tabstrip`.
|
|
207
|
+
|
|
208
|
+
- **Location**: `src/lib/components/TabStrip/TabStrip.svelte`
|
|
209
|
+
- **Axes**: `size`, `variant` (both `Responsive`)
|
|
210
|
+
- **Props**: `tabs` (`TabDefinition[]`), `activeTabIndex` (bindable), `mode` (`'expanded'` |
|
|
211
|
+
`'collapsed'`, responsive), `borderless`, `gap` (rem — bordered cells between tabs), `pad` (rem —
|
|
212
|
+
leading/trailing side cells), **`height`** (rem — total bar height; tabs bottom-align, space above),
|
|
213
|
+
**`activeSurface`** (CSS colour for the active tab's fill + bottom border; default `"transparent"`),
|
|
214
|
+
`right` (Snippet), `aria-label`.
|
|
215
|
+
|
|
200
216
|
## Data Components
|
|
201
217
|
|
|
202
218
|
These pair with the headless **`@spaethtech/svelte-ui/data`** layer (query-language parser/AST, `createGrid`,
|