@dorsk/tsumikit 0.49.0 → 0.51.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 +64 -3
- package/dist/components/atoms/Badge.svelte +7 -7
- package/dist/components/atoms/Button.svelte +32 -32
- package/dist/components/atoms/Divider.svelte +107 -0
- package/dist/components/atoms/Divider.svelte.d.ts +17 -0
- package/dist/components/atoms/Input.svelte +8 -8
- package/dist/components/atoms/Select.svelte +10 -10
- package/dist/components/atoms/Text.svelte +33 -31
- package/dist/components/atoms/Textarea.svelte +7 -7
- package/dist/components/layouts/MasterDetail.svelte +12 -2
- package/dist/components/layouts/MasterDetail.svelte.d.ts +4 -0
- package/dist/components/layouts/NavBar.svelte +170 -0
- package/dist/components/layouts/NavBar.svelte.d.ts +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -154,6 +154,60 @@ for a light theme (`:root` defaults to dark, so dark themes may omit it).
|
|
|
154
154
|
- Adding a built-in theme to the kit = one entry in `THEMES`
|
|
155
155
|
(`stores/theme.svelte.ts`) + one block in `styles/themes.css`.
|
|
156
156
|
|
|
157
|
+
### Styling a component from outside
|
|
158
|
+
|
|
159
|
+
A Svelte consumer's **scoped** CSS cannot reach a child component's markup — the
|
|
160
|
+
scoping attribute is only added to elements the consumer itself renders. `class`
|
|
161
|
+
passthrough therefore only helps for layout hooks (margin, flex, grid placement).
|
|
162
|
+
CSS custom properties *do* cross the component boundary, so they are the
|
|
163
|
+
sanctioned escape hatch for everything else.
|
|
164
|
+
|
|
165
|
+
- **Naming:** `--<prefix>-<axis>`, where `<prefix>` is the component's short
|
|
166
|
+
prefix (`btn`, `badge`, `txt`, `select`, `input`, `textarea`, `card`, `md`, …)
|
|
167
|
+
and `<axis>` is one of `bg`, `fg`, `border`, `size`, `radius`, plus a few
|
|
168
|
+
component-specific ones (`--btn-tone`, `--badge-max-width`, `--gauge-w`).
|
|
169
|
+
`Text` uses `txt` because `--text*` is already the theme's foreground token.
|
|
170
|
+
- **Setting them:** per instance via `style`, or on any ancestor for a subtree.
|
|
171
|
+
|
|
172
|
+
```svelte
|
|
173
|
+
<Button style="--btn-bg: var(--c-violet); --btn-fg: #fff; --btn-radius: 0">Ship</Button>
|
|
174
|
+
|
|
175
|
+
<div class="danger-zone">
|
|
176
|
+
<Button>Delete</Button> <!-- inherits the block below -->
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<style>
|
|
180
|
+
.danger-zone { --btn-border: var(--danger); --btn-fg: var(--danger); }
|
|
181
|
+
</style>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- Every property is read as `var(--x, <default>)`, so leaving it unset renders
|
|
185
|
+
exactly as before.
|
|
186
|
+
- **`:global()` into kit internals is unsupported.** Class names like `.btn`,
|
|
187
|
+
`.badge`, `.select-wrap` or `.textarea` are private and change without a major
|
|
188
|
+
bump. If an axis you need is missing, open an issue — do not reach in.
|
|
189
|
+
|
|
190
|
+
| component | published properties |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| `Button` | `--btn-bg`, `--btn-fg`, `--btn-border`, `--btn-size`, `--btn-radius`, `--btn-tone`, `--btn-on`, `--btn-box` |
|
|
193
|
+
| `Badge` | `--badge-bg`, `--badge-fg`, `--badge-border`, `--badge-radius`, `--badge-tone`, `--badge-max-width` |
|
|
194
|
+
| `Text` | `--txt-fg`, `--txt-size` |
|
|
195
|
+
| `Select` | `--select-bg`, `--select-fg`, `--select-border`, `--select-size`, `--select-radius` |
|
|
196
|
+
| `Input` | `--input-bg`, `--input-fg`, `--input-border`, `--input-size`, `--input-radius` |
|
|
197
|
+
| `Textarea` | `--textarea-bg`, `--textarea-fg`, `--textarea-border`, `--textarea-size`, `--textarea-radius` |
|
|
198
|
+
| `Card` | `--card-pad`, `--card-gap`, `--card-border-style` |
|
|
199
|
+
| `Divider` | `--divider-color`, `--divider-spacing` |
|
|
200
|
+
| `Gauge` | `--gauge-w`, `--gauge-h`, `--gauge-fill` |
|
|
201
|
+
| `MasterDetail` | `--md-list-w`, `--md-gap`, `--md-divider` |
|
|
202
|
+
| `NavBar` | `--navbar-max` |
|
|
203
|
+
| `Callout` | `--callout-tone` |
|
|
204
|
+
| `Fieldset` | `--fieldset-pad`, `--fieldset-border` |
|
|
205
|
+
| `GitRef` | `--git-ref-tone`, `--git-ref-max-width` |
|
|
206
|
+
| `EmptyState` | `--empty-tone` |
|
|
207
|
+
|
|
208
|
+
`tests/css-custom-property-contract.test.js` reads this table and fails if a
|
|
209
|
+
listed property is not actually read by its component, so the docs cannot drift.
|
|
210
|
+
|
|
157
211
|
## Components
|
|
158
212
|
|
|
159
213
|
**Atoms:** Text, Heading, Button, Input (`icon` inset leading glyph,
|
|
@@ -166,7 +220,9 @@ threshold tones via `warnAt`/`dangerAt`, `corner` snippet), Artwork (lazy cover
|
|
|
166
220
|
fallback, `aspect`, `status` overlay), Card (`tone` tints the surface for inline banners), Badge
|
|
167
221
|
(`tone` semantic palette or `color` for any CSS colour, `size` xs/sm/md, `dot`,
|
|
168
222
|
`icon`, `numeric`, `truncate`, `variant="text"`; all tints derive from
|
|
169
|
-
`--badge-tone`), Dot (`ring` dark halo over artwork),
|
|
223
|
+
`--badge-tone`), Dot (`ring` dark halo over artwork), Divider (standalone
|
|
224
|
+
horizontal/vertical hairline, `tone`, `spacing`, optional centred label,
|
|
225
|
+
`decorative` to drop the `separator` role), Link (`tone`, `underline`
|
|
170
226
|
always/hover/none, `align`), Icon (open registry — pass a `children` snippet for
|
|
171
227
|
any custom SVG).
|
|
172
228
|
|
|
@@ -219,12 +275,17 @@ sidebar on desktop, overlay drawer on mobile, optionally resizable;
|
|
|
219
275
|
the content column only, `stickySidebar` pins it to the viewport, and
|
|
220
276
|
`sidebarPadding="none" | "sm" | "md"` sets the aside gutter — the header and its
|
|
221
277
|
children are `min-width: 0` so a wide title/actions row can't widen the grid on
|
|
222
|
-
mobile),
|
|
278
|
+
mobile), NavBar (horizontal route bar — `<nav>` + `<a aria-current="page">`,
|
|
279
|
+
icon/emoji + label + count badge; `placement="bar"` pins it to the bottom edge
|
|
280
|
+
over `--z-nav` with `--safe-bottom` padding, `orientation` stacks or inlines the
|
|
281
|
+
glyph, `maxWidth` centres the row), NavItem
|
|
223
282
|
(collapses to an icon rail when the sidebar is narrow; `icon` from the
|
|
224
283
|
registry, `iconPath` / `iconChildren` for custom glyphs, `activeStyle="bar"`
|
|
225
284
|
for the inset-bar active look), MasterDetail (list + detail columns that
|
|
226
285
|
become two pages — one pane at a time with a sticky 44px back header — below
|
|
227
|
-
their own `breakpoint`, so `selected` can be driven from the URL
|
|
286
|
+
their own `breakpoint`, so `selected` can be driven from the URL; `gap` spaces
|
|
287
|
+
the columns and `divider="none"` drops the seam, both also settable as the
|
|
288
|
+
`--md-gap` / `--md-divider` custom properties), Container, Stack
|
|
228
289
|
(vertical), Cluster (wrapping row; `stackAt="xs|sm|md|lg"` makes it its own
|
|
229
290
|
query container and stacks children full-width below 18/30/40/48rem — phone
|
|
230
291
|
action rows without a viewport query), AutoGrid (intrinsically responsive
|
|
@@ -166,13 +166,13 @@
|
|
|
166
166
|
align-items: center;
|
|
167
167
|
gap: var(--sp-1);
|
|
168
168
|
padding: 0.15rem var(--sp-2);
|
|
169
|
-
border-radius: var(--r-pill);
|
|
169
|
+
border-radius: var(--badge-radius, var(--r-pill));
|
|
170
170
|
font-size: var(--fs-xs);
|
|
171
171
|
font-weight: var(--fw-medium);
|
|
172
172
|
line-height: 1.4;
|
|
173
|
-
background: var(--bg-elevated-2);
|
|
174
|
-
color: var(--text-muted);
|
|
175
|
-
border: 1px solid var(--border);
|
|
173
|
+
background: var(--badge-bg, var(--bg-elevated-2));
|
|
174
|
+
color: var(--badge-fg, var(--text-muted));
|
|
175
|
+
border: 1px solid var(--badge-border, var(--border));
|
|
176
176
|
white-space: nowrap;
|
|
177
177
|
max-width: var(--badge-max-width, 100%);
|
|
178
178
|
}
|
|
@@ -188,9 +188,9 @@
|
|
|
188
188
|
padding: 0.05rem var(--sp-2);
|
|
189
189
|
}
|
|
190
190
|
.toned {
|
|
191
|
-
color: var(--badge-tone);
|
|
192
|
-
border-color: color-mix(in srgb, var(--badge-tone) 40%, transparent);
|
|
193
|
-
background: color-mix(in srgb, var(--badge-tone) 12%, transparent);
|
|
191
|
+
color: var(--badge-fg, var(--badge-tone));
|
|
192
|
+
border-color: var(--badge-border, color-mix(in srgb, var(--badge-tone) 40%, transparent));
|
|
193
|
+
background: var(--badge-bg, color-mix(in srgb, var(--badge-tone) 12%, transparent));
|
|
194
194
|
}
|
|
195
195
|
.text {
|
|
196
196
|
padding: 0;
|
|
@@ -165,11 +165,11 @@
|
|
|
165
165
|
justify-content: center;
|
|
166
166
|
gap: var(--sp-2);
|
|
167
167
|
padding: var(--sp-2) var(--sp-4);
|
|
168
|
-
min-height: var(--control-height-default);
|
|
169
|
-
border: 1px solid var(--border-strong);
|
|
170
|
-
border-radius: var(--r-md);
|
|
171
|
-
background: var(--surface);
|
|
172
|
-
color: var(--text);
|
|
168
|
+
min-height: var(--btn-size, var(--control-height-default));
|
|
169
|
+
border: 1px solid var(--btn-border, var(--border-strong));
|
|
170
|
+
border-radius: var(--btn-radius, var(--r-md));
|
|
171
|
+
background: var(--btn-bg, var(--surface));
|
|
172
|
+
color: var(--btn-fg, var(--text));
|
|
173
173
|
font-weight: var(--fw-medium);
|
|
174
174
|
font-size: var(--fs-sm);
|
|
175
175
|
line-height: 1;
|
|
@@ -188,9 +188,9 @@
|
|
|
188
188
|
cursor: not-allowed;
|
|
189
189
|
}
|
|
190
190
|
.btn-primary {
|
|
191
|
-
background: var(--accent);
|
|
192
|
-
border-color: var(--accent);
|
|
193
|
-
color: var(--text-on-accent);
|
|
191
|
+
background: var(--btn-bg, var(--accent));
|
|
192
|
+
border-color: var(--btn-border, var(--accent));
|
|
193
|
+
color: var(--btn-fg, var(--text-on-accent));
|
|
194
194
|
font-weight: var(--fw-semibold);
|
|
195
195
|
}
|
|
196
196
|
.btn-primary:hover:not(:disabled) {
|
|
@@ -205,21 +205,21 @@
|
|
|
205
205
|
border-color: var(--danger);
|
|
206
206
|
}
|
|
207
207
|
.btn-ghost {
|
|
208
|
-
background: transparent;
|
|
209
|
-
border-color: transparent;
|
|
208
|
+
background: var(--btn-bg, transparent);
|
|
209
|
+
border-color: var(--btn-border, transparent);
|
|
210
210
|
}
|
|
211
211
|
.btn-ghost:hover:not(:disabled) {
|
|
212
212
|
background: var(--bg-elevated-2);
|
|
213
213
|
border-color: transparent;
|
|
214
214
|
}
|
|
215
215
|
.btn-sm {
|
|
216
|
-
height: var(--control-height-compact);
|
|
217
|
-
min-height: var(--control-height-compact);
|
|
216
|
+
height: var(--btn-size, var(--control-height-compact));
|
|
217
|
+
min-height: var(--btn-size, var(--control-height-compact));
|
|
218
218
|
padding: var(--sp-1) var(--sp-3);
|
|
219
219
|
font-size: var(--fs-xs);
|
|
220
220
|
}
|
|
221
221
|
.btn-lg {
|
|
222
|
-
min-height: var(--control-height-large);
|
|
222
|
+
min-height: var(--btn-size, var(--control-height-large));
|
|
223
223
|
padding: var(--sp-3) var(--sp-5);
|
|
224
224
|
font-size: var(--fs-base);
|
|
225
225
|
}
|
|
@@ -298,13 +298,13 @@
|
|
|
298
298
|
align-items: center;
|
|
299
299
|
justify-content: center;
|
|
300
300
|
gap: var(--sp-2);
|
|
301
|
-
height: var(--control-height);
|
|
302
|
-
min-height: var(--control-height);
|
|
301
|
+
height: var(--btn-size, var(--control-height));
|
|
302
|
+
min-height: var(--btn-size, var(--control-height));
|
|
303
303
|
padding: 0 var(--sp-3);
|
|
304
|
-
border: 1px solid var(--border-strong);
|
|
305
|
-
border-radius: var(--r-md);
|
|
306
|
-
background: var(--surface);
|
|
307
|
-
color: var(--text);
|
|
304
|
+
border: 1px solid var(--btn-border, var(--border-strong));
|
|
305
|
+
border-radius: var(--btn-radius, var(--r-md));
|
|
306
|
+
background: var(--btn-bg, var(--surface));
|
|
307
|
+
color: var(--btn-fg, var(--text));
|
|
308
308
|
font-weight: var(--fw-medium);
|
|
309
309
|
font-size: var(--fs-sm);
|
|
310
310
|
line-height: 1;
|
|
@@ -326,9 +326,9 @@
|
|
|
326
326
|
the primary action with the neutral --surface; restore the accent fill when
|
|
327
327
|
both are present. */
|
|
328
328
|
.btn-control.btn-primary {
|
|
329
|
-
background: var(--accent);
|
|
330
|
-
border-color: var(--accent);
|
|
331
|
-
color: var(--text-on-accent);
|
|
329
|
+
background: var(--btn-bg, var(--accent));
|
|
330
|
+
border-color: var(--btn-border, var(--accent));
|
|
331
|
+
color: var(--btn-fg, var(--text-on-accent));
|
|
332
332
|
}
|
|
333
333
|
.btn-control.btn-primary:hover:not(:disabled) {
|
|
334
334
|
border-color: var(--accent);
|
|
@@ -389,7 +389,7 @@
|
|
|
389
389
|
align-self: flex-start;
|
|
390
390
|
}
|
|
391
391
|
.btn-pill {
|
|
392
|
-
border-radius: var(--r-pill);
|
|
392
|
+
border-radius: var(--btn-radius, var(--r-pill));
|
|
393
393
|
}
|
|
394
394
|
/* Link variant: no box, inherits the surrounding text size. Placed after the
|
|
395
395
|
size rules so their min-height doesn't reapply; a tone wins over --link. */
|
|
@@ -419,23 +419,23 @@
|
|
|
419
419
|
}
|
|
420
420
|
/* `square`: side = the height contract in force (size tier or control). */
|
|
421
421
|
.btn-square {
|
|
422
|
-
width: var(--control-height-default);
|
|
423
|
-
min-width: var(--control-height-default);
|
|
422
|
+
width: var(--btn-size, var(--control-height-default));
|
|
423
|
+
min-width: var(--btn-size, var(--control-height-default));
|
|
424
424
|
padding: 0;
|
|
425
425
|
flex: none;
|
|
426
426
|
}
|
|
427
427
|
.btn-square.btn-sm {
|
|
428
|
-
width: var(--control-height-compact);
|
|
429
|
-
min-width: var(--control-height-compact);
|
|
428
|
+
width: var(--btn-size, var(--control-height-compact));
|
|
429
|
+
min-width: var(--btn-size, var(--control-height-compact));
|
|
430
430
|
}
|
|
431
431
|
.btn-square.btn-lg {
|
|
432
|
-
width: var(--control-height-large);
|
|
433
|
-
min-width: var(--control-height-large);
|
|
434
|
-
height: var(--control-height-large);
|
|
432
|
+
width: var(--btn-size, var(--control-height-large));
|
|
433
|
+
min-width: var(--btn-size, var(--control-height-large));
|
|
434
|
+
height: var(--btn-size, var(--control-height-large));
|
|
435
435
|
}
|
|
436
436
|
.btn-square.btn-control {
|
|
437
|
-
width: var(--control-height);
|
|
438
|
-
min-width: var(--control-height);
|
|
437
|
+
width: var(--btn-size, var(--control-height));
|
|
438
|
+
min-width: var(--btn-size, var(--control-height));
|
|
439
439
|
}
|
|
440
440
|
/* `box`: explicit square scale, wins over icon/chip/square/size sizing. */
|
|
441
441
|
.btn-box {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
// Standalone hairline between two siblings. Horizontal by default; the
|
|
5
|
+
// vertical form stretches to the flex parent's cross size. With `children`
|
|
6
|
+
// the rule splits around a centred label ("or", "3 older messages") — a
|
|
7
|
+
// labelled divider is never decorative, so it keeps role="separator".
|
|
8
|
+
type Own = {
|
|
9
|
+
orientation?: 'horizontal' | 'vertical';
|
|
10
|
+
/** Margin along the axis (any CSS length). */
|
|
11
|
+
spacing?: string;
|
|
12
|
+
tone?: 'default' | 'strong' | 'faint';
|
|
13
|
+
/** Centred label turning the rule into rule + text. */
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
/** Purely visual (aria-hidden) rather than a semantic separator. */
|
|
16
|
+
decorative?: boolean;
|
|
17
|
+
class?: string;
|
|
18
|
+
};
|
|
19
|
+
let {
|
|
20
|
+
orientation = 'horizontal',
|
|
21
|
+
spacing,
|
|
22
|
+
tone = 'default',
|
|
23
|
+
children,
|
|
24
|
+
decorative = false,
|
|
25
|
+
class: klass = '',
|
|
26
|
+
...rest
|
|
27
|
+
}: Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own = $props();
|
|
28
|
+
|
|
29
|
+
const vertical = $derived(orientation === 'vertical');
|
|
30
|
+
const semantic = $derived(!decorative || !!children);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div
|
|
34
|
+
class="divider {orientation} tone-{tone} {klass}"
|
|
35
|
+
class:labelled={!!children}
|
|
36
|
+
data-tsu="Divider"
|
|
37
|
+
role={semantic ? 'separator' : undefined}
|
|
38
|
+
aria-orientation={semantic ? orientation : undefined}
|
|
39
|
+
aria-hidden={semantic ? undefined : 'true'}
|
|
40
|
+
style:--divider-spacing={spacing}
|
|
41
|
+
{...rest}
|
|
42
|
+
>
|
|
43
|
+
{#if children}
|
|
44
|
+
<span class="divider-label">{@render children()}</span>
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
.divider {
|
|
50
|
+
--divider-color: var(--border);
|
|
51
|
+
--divider-spacing: var(--sp-3);
|
|
52
|
+
flex: none;
|
|
53
|
+
border: 0;
|
|
54
|
+
}
|
|
55
|
+
.tone-strong {
|
|
56
|
+
--divider-color: var(--border-strong);
|
|
57
|
+
}
|
|
58
|
+
.tone-faint {
|
|
59
|
+
--divider-color: color-mix(in srgb, var(--border) 45%, transparent);
|
|
60
|
+
}
|
|
61
|
+
.horizontal {
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 1px;
|
|
64
|
+
background: var(--divider-color);
|
|
65
|
+
margin-block: var(--divider-spacing);
|
|
66
|
+
}
|
|
67
|
+
.vertical {
|
|
68
|
+
align-self: stretch;
|
|
69
|
+
width: 1px;
|
|
70
|
+
min-height: 1em;
|
|
71
|
+
background: var(--divider-color);
|
|
72
|
+
margin-inline: var(--divider-spacing);
|
|
73
|
+
}
|
|
74
|
+
.horizontal.labelled,
|
|
75
|
+
.vertical.labelled {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: var(--sp-2);
|
|
79
|
+
background: none;
|
|
80
|
+
}
|
|
81
|
+
.horizontal.labelled {
|
|
82
|
+
height: auto;
|
|
83
|
+
}
|
|
84
|
+
.vertical.labelled {
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
width: auto;
|
|
87
|
+
}
|
|
88
|
+
.horizontal.labelled::before,
|
|
89
|
+
.horizontal.labelled::after {
|
|
90
|
+
content: '';
|
|
91
|
+
flex: 1;
|
|
92
|
+
height: 1px;
|
|
93
|
+
background: var(--divider-color);
|
|
94
|
+
}
|
|
95
|
+
.vertical.labelled::before,
|
|
96
|
+
.vertical.labelled::after {
|
|
97
|
+
content: '';
|
|
98
|
+
flex: 1;
|
|
99
|
+
width: 1px;
|
|
100
|
+
background: var(--divider-color);
|
|
101
|
+
}
|
|
102
|
+
.divider-label {
|
|
103
|
+
color: var(--text-muted);
|
|
104
|
+
font-size: var(--fs-sm);
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
}
|
|
107
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Own = {
|
|
4
|
+
orientation?: 'horizontal' | 'vertical';
|
|
5
|
+
/** Margin along the axis (any CSS length). */
|
|
6
|
+
spacing?: string;
|
|
7
|
+
tone?: 'default' | 'strong' | 'faint';
|
|
8
|
+
/** Centred label turning the rule into rule + text. */
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
/** Purely visual (aria-hidden) rather than a semantic separator. */
|
|
11
|
+
decorative?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
};
|
|
14
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own;
|
|
15
|
+
declare const Divider: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
16
|
+
type Divider = ReturnType<typeof Divider>;
|
|
17
|
+
export default Divider;
|
|
@@ -130,13 +130,13 @@
|
|
|
130
130
|
<style>
|
|
131
131
|
.input {
|
|
132
132
|
width: 100%;
|
|
133
|
-
min-height: var(--control-height-default);
|
|
133
|
+
min-height: var(--input-size, var(--control-height-default));
|
|
134
134
|
padding: var(--sp-2) var(--sp-3);
|
|
135
135
|
line-height: var(--lh-tight);
|
|
136
|
-
background: var(--bg);
|
|
137
|
-
border: 1px solid var(--border-strong);
|
|
138
|
-
border-radius: var(--r-md);
|
|
139
|
-
color: var(--text);
|
|
136
|
+
background: var(--input-bg, var(--bg));
|
|
137
|
+
border: 1px solid var(--input-border, var(--border-strong));
|
|
138
|
+
border-radius: var(--input-radius, var(--r-md));
|
|
139
|
+
color: var(--input-fg, var(--text));
|
|
140
140
|
transition: border-color 0.12s var(--ease);
|
|
141
141
|
}
|
|
142
142
|
.input:focus {
|
|
@@ -148,12 +148,12 @@
|
|
|
148
148
|
outline-offset: var(--focus-ring-offset);
|
|
149
149
|
}
|
|
150
150
|
.input-sm {
|
|
151
|
-
min-height: var(--control-height-compact);
|
|
151
|
+
min-height: var(--input-size, var(--control-height-compact));
|
|
152
152
|
padding: var(--sp-1) var(--sp-2);
|
|
153
153
|
font-size: var(--fs-sm);
|
|
154
154
|
}
|
|
155
155
|
.input-lg {
|
|
156
|
-
min-height: var(--control-height-large);
|
|
156
|
+
min-height: var(--input-size, var(--control-height-large));
|
|
157
157
|
padding: var(--sp-3) var(--sp-4);
|
|
158
158
|
font-size: var(--fs-base);
|
|
159
159
|
}
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
flex: none;
|
|
167
167
|
}
|
|
168
168
|
.input-pill {
|
|
169
|
-
border-radius: var(--r-pill);
|
|
169
|
+
border-radius: var(--input-radius, var(--r-pill));
|
|
170
170
|
padding-inline: var(--sp-4);
|
|
171
171
|
}
|
|
172
172
|
.input[aria-invalid='true'] {
|
|
@@ -205,13 +205,13 @@
|
|
|
205
205
|
}
|
|
206
206
|
.select {
|
|
207
207
|
width: 100%;
|
|
208
|
-
min-height: var(--control-height-default);
|
|
208
|
+
min-height: var(--select-size, var(--control-height-default));
|
|
209
209
|
padding: var(--sp-2) var(--sp-3);
|
|
210
210
|
line-height: var(--lh-tight);
|
|
211
|
-
background: var(--bg);
|
|
212
|
-
border: 1px solid var(--border-strong);
|
|
213
|
-
border-radius: var(--r-md);
|
|
214
|
-
color: var(--text);
|
|
211
|
+
background: var(--select-bg, var(--bg));
|
|
212
|
+
border: 1px solid var(--select-border, var(--border-strong));
|
|
213
|
+
border-radius: var(--select-radius, var(--r-md));
|
|
214
|
+
color: var(--select-fg, var(--text));
|
|
215
215
|
transition: border-color 0.12s var(--ease);
|
|
216
216
|
}
|
|
217
217
|
/* Default variant: drop the OS chevron, reserve room for our own. */
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
}
|
|
227
227
|
/* Compact inline form for dense headers/toolbars. */
|
|
228
228
|
.select.select-lg {
|
|
229
|
-
min-height: var(--control-height-large);
|
|
229
|
+
min-height: var(--select-size, var(--control-height-large));
|
|
230
230
|
padding: var(--sp-3) var(--sp-4);
|
|
231
231
|
font-size: var(--fs-base);
|
|
232
232
|
}
|
|
@@ -240,15 +240,15 @@
|
|
|
240
240
|
/* Toolbar contract: size="sm" shares the compact control height so it lines up
|
|
241
241
|
with Button size="sm", Popover size="sm" and SegmentedControl size="sm". */
|
|
242
242
|
.select.select-sm {
|
|
243
|
-
height: var(--control-height-compact);
|
|
243
|
+
height: var(--select-size, var(--control-height-compact));
|
|
244
244
|
}
|
|
245
245
|
.select.w-auto {
|
|
246
246
|
width: auto;
|
|
247
247
|
}
|
|
248
248
|
.select.embedded {
|
|
249
|
-
background: var(--bg-elevated-2);
|
|
249
|
+
background: var(--select-bg, var(--bg-elevated-2));
|
|
250
250
|
border: none;
|
|
251
|
-
border-radius: var(--r-sm);
|
|
251
|
+
border-radius: var(--select-radius, var(--r-sm));
|
|
252
252
|
}
|
|
253
253
|
.select:focus {
|
|
254
254
|
outline: none;
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
gap: var(--sp-2);
|
|
282
282
|
padding: 0 var(--sp-3);
|
|
283
283
|
padding-right: calc(var(--sp-3) + 1.25rem);
|
|
284
|
-
color: var(--text);
|
|
284
|
+
color: var(--select-fg, var(--text));
|
|
285
285
|
pointer-events: none;
|
|
286
286
|
overflow: hidden;
|
|
287
287
|
}
|
|
@@ -95,29 +95,31 @@
|
|
|
95
95
|
/* Base: inherit everything — a bare <Text> renders like a plain span. */
|
|
96
96
|
.text {
|
|
97
97
|
margin: 0;
|
|
98
|
+
color: var(--txt-fg, inherit);
|
|
99
|
+
font-size: var(--txt-size, inherit);
|
|
98
100
|
}
|
|
99
101
|
/* Variants (presets) — listed before tone/weight/size so those override. */
|
|
100
102
|
.v-body {
|
|
101
|
-
font-size: var(--fs-base);
|
|
103
|
+
font-size: var(--txt-size, var(--fs-base));
|
|
102
104
|
line-height: var(--lh-normal);
|
|
103
|
-
color: var(--text);
|
|
105
|
+
color: var(--txt-fg, var(--text));
|
|
104
106
|
}
|
|
105
107
|
.v-label {
|
|
106
|
-
font-size: var(--fs-sm);
|
|
108
|
+
font-size: var(--txt-size, var(--fs-sm));
|
|
107
109
|
font-weight: var(--fw-medium);
|
|
108
|
-
color: var(--text-muted);
|
|
110
|
+
color: var(--txt-fg, var(--text-muted));
|
|
109
111
|
}
|
|
110
112
|
.v-caption {
|
|
111
|
-
font-size: var(--fs-xs);
|
|
112
|
-
color: var(--text-faint);
|
|
113
|
+
font-size: var(--txt-size, var(--fs-xs));
|
|
114
|
+
color: var(--txt-fg, var(--text-faint));
|
|
113
115
|
}
|
|
114
116
|
.v-code {
|
|
115
117
|
font-family: var(--font-mono);
|
|
116
|
-
font-size: 0.92em;
|
|
118
|
+
font-size: var(--txt-size, 0.92em);
|
|
117
119
|
}
|
|
118
120
|
.v-eyebrow {
|
|
119
|
-
font-size: var(--fs-xs);
|
|
120
|
-
color: var(--text-muted);
|
|
121
|
+
font-size: var(--txt-size, var(--fs-xs));
|
|
122
|
+
color: var(--txt-fg, var(--text-muted));
|
|
121
123
|
font-weight: var(--fw-medium);
|
|
122
124
|
text-transform: uppercase;
|
|
123
125
|
letter-spacing: 0.04em;
|
|
@@ -127,28 +129,28 @@
|
|
|
127
129
|
font-family: var(--font-mono);
|
|
128
130
|
}
|
|
129
131
|
.tone-default {
|
|
130
|
-
color: var(--text);
|
|
132
|
+
color: var(--txt-fg, var(--text));
|
|
131
133
|
}
|
|
132
134
|
.tone-muted {
|
|
133
|
-
color: var(--text-muted);
|
|
135
|
+
color: var(--txt-fg, var(--text-muted));
|
|
134
136
|
}
|
|
135
137
|
.tone-faint {
|
|
136
|
-
color: var(--text-faint);
|
|
138
|
+
color: var(--txt-fg, var(--text-faint));
|
|
137
139
|
}
|
|
138
140
|
.tone-success {
|
|
139
|
-
color: var(--ok);
|
|
141
|
+
color: var(--txt-fg, var(--ok));
|
|
140
142
|
}
|
|
141
143
|
.tone-warn {
|
|
142
|
-
color: var(--warn);
|
|
144
|
+
color: var(--txt-fg, var(--warn));
|
|
143
145
|
}
|
|
144
146
|
.tone-danger {
|
|
145
|
-
color: var(--danger);
|
|
147
|
+
color: var(--txt-fg, var(--danger));
|
|
146
148
|
}
|
|
147
149
|
.tone-info {
|
|
148
|
-
color: var(--info);
|
|
150
|
+
color: var(--txt-fg, var(--info));
|
|
149
151
|
}
|
|
150
152
|
.tone-accent {
|
|
151
|
-
color: var(--accent);
|
|
153
|
+
color: var(--txt-fg, var(--accent));
|
|
152
154
|
}
|
|
153
155
|
/* Weight — overrides variant weight. */
|
|
154
156
|
.fw-normal {
|
|
@@ -165,46 +167,46 @@
|
|
|
165
167
|
}
|
|
166
168
|
/* Size — overrides variant size (listed last so it wins). */
|
|
167
169
|
.fs-xs {
|
|
168
|
-
font-size: var(--fs-xs);
|
|
170
|
+
font-size: var(--txt-size, var(--fs-xs));
|
|
169
171
|
}
|
|
170
172
|
.fs-sm {
|
|
171
|
-
font-size: var(--fs-sm);
|
|
173
|
+
font-size: var(--txt-size, var(--fs-sm));
|
|
172
174
|
}
|
|
173
175
|
.fs-base {
|
|
174
|
-
font-size: var(--fs-base);
|
|
176
|
+
font-size: var(--txt-size, var(--fs-base));
|
|
175
177
|
}
|
|
176
178
|
.fs-md {
|
|
177
|
-
font-size: var(--fs-md);
|
|
179
|
+
font-size: var(--txt-size, var(--fs-md));
|
|
178
180
|
}
|
|
179
181
|
.fs-lg {
|
|
180
|
-
font-size: var(--fs-lg);
|
|
182
|
+
font-size: var(--txt-size, var(--fs-lg));
|
|
181
183
|
}
|
|
182
184
|
.fs-xl {
|
|
183
|
-
font-size: var(--fs-xl);
|
|
185
|
+
font-size: var(--txt-size, var(--fs-xl));
|
|
184
186
|
}
|
|
185
187
|
.fs-2xl {
|
|
186
|
-
font-size: var(--fs-2xl);
|
|
188
|
+
font-size: var(--txt-size, var(--fs-2xl));
|
|
187
189
|
}
|
|
188
190
|
.noscale.fs-xs {
|
|
189
|
-
font-size: 12px;
|
|
191
|
+
font-size: var(--txt-size, 12px);
|
|
190
192
|
}
|
|
191
193
|
.noscale.fs-sm {
|
|
192
|
-
font-size: 13px;
|
|
194
|
+
font-size: var(--txt-size, 13px);
|
|
193
195
|
}
|
|
194
196
|
.noscale.fs-base {
|
|
195
|
-
font-size: 15px;
|
|
197
|
+
font-size: var(--txt-size, 15px);
|
|
196
198
|
}
|
|
197
199
|
.noscale.fs-md {
|
|
198
|
-
font-size: 16px;
|
|
200
|
+
font-size: var(--txt-size, 16px);
|
|
199
201
|
}
|
|
200
202
|
.noscale.fs-lg {
|
|
201
|
-
font-size: 18px;
|
|
203
|
+
font-size: var(--txt-size, 18px);
|
|
202
204
|
}
|
|
203
205
|
.noscale.fs-xl {
|
|
204
|
-
font-size: 22px;
|
|
206
|
+
font-size: var(--txt-size, 22px);
|
|
205
207
|
}
|
|
206
208
|
.noscale.fs-2xl {
|
|
207
|
-
font-size: 28px;
|
|
209
|
+
font-size: var(--txt-size, 28px);
|
|
208
210
|
}
|
|
209
211
|
.italic {
|
|
210
212
|
font-style: italic;
|
|
@@ -223,16 +223,16 @@
|
|
|
223
223
|
--sp-3 top+bottom would overshoot it and a `rows={1}` textarea would
|
|
224
224
|
render taller than the buttons it sits beside. */
|
|
225
225
|
padding: var(--sp-2) var(--sp-3);
|
|
226
|
-
background: var(--bg);
|
|
227
|
-
border: 1px solid var(--border-strong);
|
|
228
|
-
border-radius: var(--r-md);
|
|
229
|
-
color: var(--text);
|
|
226
|
+
background: var(--textarea-bg, var(--bg));
|
|
227
|
+
border: 1px solid var(--textarea-border, var(--border-strong));
|
|
228
|
+
border-radius: var(--textarea-radius, var(--r-md));
|
|
229
|
+
color: var(--textarea-fg, var(--text));
|
|
230
230
|
transition: border-color 0.12s var(--ease);
|
|
231
231
|
/* Custom handle replaces the native grip; never show the native one. */
|
|
232
232
|
resize: none;
|
|
233
233
|
/* Match the single-row height of Button/Input so a `rows={1}` textarea
|
|
234
234
|
lines up with them; the native `rows` attribute grows it from here. */
|
|
235
|
-
min-height: 2.5rem;
|
|
235
|
+
min-height: var(--textarea-size, 2.5rem);
|
|
236
236
|
line-height: var(--lh-tight);
|
|
237
237
|
font-family: inherit;
|
|
238
238
|
}
|
|
@@ -250,12 +250,12 @@
|
|
|
250
250
|
.textarea-sm {
|
|
251
251
|
padding: var(--sp-1) var(--sp-2);
|
|
252
252
|
font-size: var(--fs-sm);
|
|
253
|
-
min-height: 2rem;
|
|
253
|
+
min-height: var(--textarea-size, 2rem);
|
|
254
254
|
}
|
|
255
255
|
.textarea-lg {
|
|
256
256
|
padding: var(--sp-3) var(--sp-4);
|
|
257
257
|
font-size: var(--fs-base);
|
|
258
|
-
min-height: var(--control-height-large);
|
|
258
|
+
min-height: var(--textarea-size, var(--control-height-large));
|
|
259
259
|
}
|
|
260
260
|
.textarea[aria-invalid='true'],
|
|
261
261
|
.textarea[aria-invalid='true']:focus {
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
empty,
|
|
16
16
|
listWidth = '17rem',
|
|
17
17
|
breakpoint = '48rem',
|
|
18
|
+
gap = '0',
|
|
19
|
+
divider = 'border',
|
|
18
20
|
selected = false,
|
|
19
21
|
onback,
|
|
20
22
|
backLabel = 'Back',
|
|
@@ -32,6 +34,10 @@
|
|
|
32
34
|
listWidth?: string;
|
|
33
35
|
/** Own-width threshold (px/em/rem) below which the panes become two pages. */
|
|
34
36
|
breakpoint?: string;
|
|
37
|
+
/** Space between the two columns (any CSS length). */
|
|
38
|
+
gap?: string;
|
|
39
|
+
/** Seam between the panes; `--md-divider` overrides it with any border shorthand. */
|
|
40
|
+
divider?: 'border' | 'none';
|
|
35
41
|
selected?: boolean;
|
|
36
42
|
onback?: () => void;
|
|
37
43
|
backLabel?: string;
|
|
@@ -72,7 +78,9 @@
|
|
|
72
78
|
class="md {klass}"
|
|
73
79
|
class:mobile
|
|
74
80
|
class:selected
|
|
75
|
-
style="--md-list-w: {listWidth};
|
|
81
|
+
style="--md-list-w: {listWidth}; --md-gap: {gap}; --md-divider: {divider === 'none'
|
|
82
|
+
? 'none'
|
|
83
|
+
: '1px solid var(--border)'};{style ? ` ${style}` : ''}"
|
|
76
84
|
data-tsu="MasterDetail"
|
|
77
85
|
{...rest}
|
|
78
86
|
>
|
|
@@ -113,6 +121,7 @@
|
|
|
113
121
|
.md {
|
|
114
122
|
display: grid;
|
|
115
123
|
grid-template-columns: var(--md-list-w) minmax(0, 1fr);
|
|
124
|
+
gap: var(--md-gap, 0);
|
|
116
125
|
min-width: 0;
|
|
117
126
|
min-height: 0;
|
|
118
127
|
height: 100%;
|
|
@@ -127,7 +136,7 @@
|
|
|
127
136
|
-webkit-overflow-scrolling: touch;
|
|
128
137
|
}
|
|
129
138
|
.md-list {
|
|
130
|
-
border-right: 1px solid var(--border);
|
|
139
|
+
border-right: var(--md-divider, 1px solid var(--border));
|
|
131
140
|
}
|
|
132
141
|
.md-detail {
|
|
133
142
|
display: flex;
|
|
@@ -183,6 +192,7 @@
|
|
|
183
192
|
|
|
184
193
|
.md.mobile {
|
|
185
194
|
grid-template-columns: minmax(0, 1fr);
|
|
195
|
+
gap: 0;
|
|
186
196
|
}
|
|
187
197
|
.md.mobile .md-list {
|
|
188
198
|
border-right: 0;
|
|
@@ -9,6 +9,10 @@ type $$ComponentProps = {
|
|
|
9
9
|
listWidth?: string;
|
|
10
10
|
/** Own-width threshold (px/em/rem) below which the panes become two pages. */
|
|
11
11
|
breakpoint?: string;
|
|
12
|
+
/** Space between the two columns (any CSS length). */
|
|
13
|
+
gap?: string;
|
|
14
|
+
/** Seam between the panes; `--md-divider` overrides it with any border shorthand. */
|
|
15
|
+
divider?: 'border' | 'none';
|
|
12
16
|
selected?: boolean;
|
|
13
17
|
onback?: () => void;
|
|
14
18
|
backLabel?: string;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Horizontal route navigation: a `<nav>` landmark of `<a>` links, one marked
|
|
3
|
+
// `aria-current="page"`. `placement="bar"` pins it to the bottom edge over
|
|
4
|
+
// `--z-nav` and pads for the iOS home indicator with `--safe-bottom`;
|
|
5
|
+
// `placement="inline"` is a plain row for a header. `orientation` stacks the
|
|
6
|
+
// icon over the label (phone bar) or sits it beside (header).
|
|
7
|
+
import Icon from '../atoms/Icon.svelte';
|
|
8
|
+
import type { IconName } from '../atoms/Icon.svelte';
|
|
9
|
+
|
|
10
|
+
export interface NavBarItem {
|
|
11
|
+
href: string;
|
|
12
|
+
label: string;
|
|
13
|
+
icon?: IconName;
|
|
14
|
+
/** Rendered instead of `icon` when set. */
|
|
15
|
+
emoji?: string;
|
|
16
|
+
/** Count or short text pinned to the glyph. 0 / '' render nothing. */
|
|
17
|
+
badge?: number | string;
|
|
18
|
+
current?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
items,
|
|
23
|
+
label,
|
|
24
|
+
placement = 'inline',
|
|
25
|
+
orientation = 'stacked',
|
|
26
|
+
maxWidth,
|
|
27
|
+
class: klass = '',
|
|
28
|
+
style: styleProp = '',
|
|
29
|
+
...rest
|
|
30
|
+
}: {
|
|
31
|
+
items: NavBarItem[];
|
|
32
|
+
/** Accessible name of the `<nav>` landmark. */
|
|
33
|
+
label: string;
|
|
34
|
+
/** 'bar' = fixed to the bottom edge with safe-area padding. */
|
|
35
|
+
placement?: 'bar' | 'inline';
|
|
36
|
+
/** Icon over label vs icon beside label. */
|
|
37
|
+
orientation?: 'stacked' | 'inline';
|
|
38
|
+
/** Caps and centres the row of links. */
|
|
39
|
+
maxWidth?: string;
|
|
40
|
+
class?: string;
|
|
41
|
+
style?: string;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
} = $props();
|
|
44
|
+
|
|
45
|
+
const hasBadge = (badge: NavBarItem['badge']) =>
|
|
46
|
+
badge !== undefined && badge !== null && badge !== '' && badge !== 0;
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<nav
|
|
50
|
+
data-tsu="NavBar"
|
|
51
|
+
aria-label={label}
|
|
52
|
+
class="navbar {klass}"
|
|
53
|
+
class:bar={placement === 'bar'}
|
|
54
|
+
class:stacked={orientation === 'stacked'}
|
|
55
|
+
style="{maxWidth ? `--navbar-max: ${maxWidth};` : ''}{styleProp}"
|
|
56
|
+
{...rest}
|
|
57
|
+
>
|
|
58
|
+
<div class="navbar-inner">
|
|
59
|
+
{#each items as item (item.href)}
|
|
60
|
+
<a
|
|
61
|
+
href={item.href}
|
|
62
|
+
class="navbar-link"
|
|
63
|
+
class:current={item.current}
|
|
64
|
+
aria-current={item.current ? 'page' : undefined}
|
|
65
|
+
>
|
|
66
|
+
<span class="navbar-glyph">
|
|
67
|
+
{#if item.emoji}
|
|
68
|
+
<span class="navbar-emoji" aria-hidden="true">{item.emoji}</span>
|
|
69
|
+
{:else if item.icon}
|
|
70
|
+
<Icon name={item.icon} size={20} />
|
|
71
|
+
{/if}
|
|
72
|
+
{#if hasBadge(item.badge)}
|
|
73
|
+
<span class="navbar-badge">{item.badge}</span>
|
|
74
|
+
{/if}
|
|
75
|
+
</span>
|
|
76
|
+
<span class="navbar-label">{item.label}</span>
|
|
77
|
+
</a>
|
|
78
|
+
{/each}
|
|
79
|
+
</div>
|
|
80
|
+
</nav>
|
|
81
|
+
|
|
82
|
+
<style>
|
|
83
|
+
.navbar {
|
|
84
|
+
--navbar-max: none;
|
|
85
|
+
min-width: 0;
|
|
86
|
+
}
|
|
87
|
+
.navbar-inner {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: stretch;
|
|
90
|
+
justify-content: space-around;
|
|
91
|
+
gap: var(--sp-1);
|
|
92
|
+
min-width: 0;
|
|
93
|
+
max-width: var(--navbar-max);
|
|
94
|
+
margin-inline: auto;
|
|
95
|
+
min-height: var(--nav-h);
|
|
96
|
+
}
|
|
97
|
+
.bar {
|
|
98
|
+
position: fixed;
|
|
99
|
+
inset-inline: 0;
|
|
100
|
+
bottom: 0;
|
|
101
|
+
z-index: var(--z-nav);
|
|
102
|
+
padding-bottom: var(--safe-bottom);
|
|
103
|
+
padding-inline: max(var(--safe-left), var(--sp-1)) max(var(--safe-right), var(--sp-1));
|
|
104
|
+
background: color-mix(in srgb, var(--bg-elevated) 88%, transparent);
|
|
105
|
+
backdrop-filter: blur(12px);
|
|
106
|
+
border-top: 1px solid var(--border);
|
|
107
|
+
}
|
|
108
|
+
.navbar-link {
|
|
109
|
+
position: relative;
|
|
110
|
+
display: flex;
|
|
111
|
+
flex: 1 1 0;
|
|
112
|
+
min-width: 0;
|
|
113
|
+
flex-direction: row;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
gap: var(--sp-2);
|
|
117
|
+
padding: var(--sp-2);
|
|
118
|
+
border-radius: var(--r-md);
|
|
119
|
+
color: var(--text-muted);
|
|
120
|
+
font-size: var(--fs-sm);
|
|
121
|
+
font-weight: var(--fw-medium);
|
|
122
|
+
text-decoration: none;
|
|
123
|
+
transition:
|
|
124
|
+
background 0.12s var(--ease),
|
|
125
|
+
color 0.12s var(--ease);
|
|
126
|
+
}
|
|
127
|
+
.stacked .navbar-link {
|
|
128
|
+
flex-direction: column;
|
|
129
|
+
gap: 2px;
|
|
130
|
+
font-size: var(--fs-xs);
|
|
131
|
+
}
|
|
132
|
+
.navbar-link:hover {
|
|
133
|
+
background: var(--bg-elevated-2);
|
|
134
|
+
color: var(--text);
|
|
135
|
+
text-decoration: none;
|
|
136
|
+
}
|
|
137
|
+
.navbar-link.current {
|
|
138
|
+
color: var(--accent);
|
|
139
|
+
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
140
|
+
}
|
|
141
|
+
.navbar-glyph {
|
|
142
|
+
position: relative;
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
flex: none;
|
|
145
|
+
align-items: center;
|
|
146
|
+
}
|
|
147
|
+
.navbar-emoji {
|
|
148
|
+
font-size: 1.15em;
|
|
149
|
+
line-height: 1;
|
|
150
|
+
}
|
|
151
|
+
.navbar-label {
|
|
152
|
+
min-width: 0;
|
|
153
|
+
overflow: hidden;
|
|
154
|
+
text-overflow: ellipsis;
|
|
155
|
+
white-space: nowrap;
|
|
156
|
+
}
|
|
157
|
+
.navbar-badge {
|
|
158
|
+
position: absolute;
|
|
159
|
+
top: -0.35rem;
|
|
160
|
+
left: calc(100% - 0.35rem);
|
|
161
|
+
padding-inline: 0.25rem;
|
|
162
|
+
min-width: 1rem;
|
|
163
|
+
border-radius: var(--r-pill);
|
|
164
|
+
background: var(--accent);
|
|
165
|
+
color: var(--text-on-accent);
|
|
166
|
+
font-size: var(--fs-xs);
|
|
167
|
+
line-height: 1rem;
|
|
168
|
+
text-align: center;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IconName } from '../atoms/Icon.svelte';
|
|
2
|
+
export interface NavBarItem {
|
|
3
|
+
href: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: IconName;
|
|
6
|
+
/** Rendered instead of `icon` when set. */
|
|
7
|
+
emoji?: string;
|
|
8
|
+
/** Count or short text pinned to the glyph. 0 / '' render nothing. */
|
|
9
|
+
badge?: number | string;
|
|
10
|
+
current?: boolean;
|
|
11
|
+
}
|
|
12
|
+
type $$ComponentProps = {
|
|
13
|
+
items: NavBarItem[];
|
|
14
|
+
/** Accessible name of the `<nav>` landmark. */
|
|
15
|
+
label: string;
|
|
16
|
+
/** 'bar' = fixed to the bottom edge with safe-area padding. */
|
|
17
|
+
placement?: 'bar' | 'inline';
|
|
18
|
+
/** Icon over label vs icon beside label. */
|
|
19
|
+
orientation?: 'stacked' | 'inline';
|
|
20
|
+
/** Caps and centres the row of links. */
|
|
21
|
+
maxWidth?: string;
|
|
22
|
+
class?: string;
|
|
23
|
+
style?: string;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
declare const NavBar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
27
|
+
type NavBar = ReturnType<typeof NavBar>;
|
|
28
|
+
export default NavBar;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { default as Badge } from './components/atoms/Badge.svelte';
|
|
|
6
6
|
export { default as Button } from './components/atoms/Button.svelte';
|
|
7
7
|
export { default as Card } from './components/atoms/Card.svelte';
|
|
8
8
|
export { default as Checkbox } from './components/atoms/Checkbox.svelte';
|
|
9
|
+
export { default as Divider } from './components/atoms/Divider.svelte';
|
|
9
10
|
export { default as Dot } from './components/atoms/Dot.svelte';
|
|
10
11
|
export { default as Gauge, type GaugeTone } from './components/atoms/Gauge.svelte';
|
|
11
12
|
export { default as Heading } from './components/atoms/Heading.svelte';
|
|
@@ -30,6 +31,7 @@ export { default as AutoGrid } from './components/layouts/AutoGrid.svelte';
|
|
|
30
31
|
export { default as Cluster } from './components/layouts/Cluster.svelte';
|
|
31
32
|
export { default as Container } from './components/layouts/Container.svelte';
|
|
32
33
|
export { default as MasterDetail } from './components/layouts/MasterDetail.svelte';
|
|
34
|
+
export { default as NavBar, type NavBarItem } from './components/layouts/NavBar.svelte';
|
|
33
35
|
export { default as NavItem } from './components/layouts/NavItem.svelte';
|
|
34
36
|
export { default as NavSection } from './components/layouts/NavSection.svelte';
|
|
35
37
|
export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export { default as Badge } from './components/atoms/Badge.svelte';
|
|
|
12
12
|
export { default as Button } from './components/atoms/Button.svelte';
|
|
13
13
|
export { default as Card } from './components/atoms/Card.svelte';
|
|
14
14
|
export { default as Checkbox } from './components/atoms/Checkbox.svelte';
|
|
15
|
+
export { default as Divider } from './components/atoms/Divider.svelte';
|
|
15
16
|
export { default as Dot } from './components/atoms/Dot.svelte';
|
|
16
17
|
export { default as Gauge } from './components/atoms/Gauge.svelte';
|
|
17
18
|
export { default as Heading } from './components/atoms/Heading.svelte';
|
|
@@ -36,6 +37,7 @@ export { default as AutoGrid } from './components/layouts/AutoGrid.svelte';
|
|
|
36
37
|
export { default as Cluster } from './components/layouts/Cluster.svelte';
|
|
37
38
|
export { default as Container } from './components/layouts/Container.svelte';
|
|
38
39
|
export { default as MasterDetail } from './components/layouts/MasterDetail.svelte';
|
|
40
|
+
export { default as NavBar } from './components/layouts/NavBar.svelte';
|
|
39
41
|
export { default as NavItem } from './components/layouts/NavItem.svelte';
|
|
40
42
|
export { default as NavSection } from './components/layouts/NavSection.svelte';
|
|
41
43
|
export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
|
package/package.json
CHANGED