@spaethtech/svelte-ui 0.7.1-dev.40.3d22ac0 → 0.7.1-dev.42.aca96e0
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/.claude/skills/themes/SKILL.md +4 -0
- package/dist/components/CardFooter.svelte +3 -4
- package/dist/components/CardHeader.svelte +3 -4
- package/dist/components/SideBarMenu/SideBarMenu.svelte +87 -29
- package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +26 -2
- package/dist/index.d.ts +1 -1
- package/dist/theme.css +9 -6
- package/package.json +1 -1
|
@@ -67,6 +67,8 @@ variant token at shared strengths, so every tinted surface is calculated identic
|
|
|
67
67
|
|
|
68
68
|
```
|
|
69
69
|
--ui-tint-soft 12% soft resting fill (Badge fill, Alert `filled`)
|
|
70
|
+
--ui-tint-divider 30% soft internal divider (CardHeader/CardFooter separators; the default
|
|
71
|
+
secondary divider is `--ui-border-color`)
|
|
70
72
|
--ui-tint-border 50% bordered surface, REST (Badge/Alert/Banner ring · Card/DataTable/Input/
|
|
71
73
|
TextArea frame · Button outline)
|
|
72
74
|
--ui-tint-border-hover 80% bordered field, HOVER (Input/TextArea/Card/DataTable/Button — the
|
|
@@ -78,6 +80,8 @@ e.g. `color-mix(in srgb, var(<variant-token>) var(--ui-tint-soft), transparent)`
|
|
|
78
80
|
primary@these; a `neutral` one is text@these. Theme-independent (a theme MAY override them to tune the
|
|
79
81
|
whole tint language). **Every bordered surface uses ONE calc** — `--ui-tint-border` at rest,
|
|
80
82
|
`--ui-tint-border-hover` on hover/focus, from the variant token — with **no `secondary` special case**.
|
|
83
|
+
Internal **dividers** (Card header/footer separators) use the softer `--ui-tint-divider` (30%) tier,
|
|
84
|
+
likewise one calc per variant — so a divider always reads lighter than the frame at any variant.
|
|
81
85
|
`--ui-border-color-strong`/`-strong-hover` have been **REMOVED** (migrate any consumer use to
|
|
82
86
|
`color-mix(in srgb, var(--ui-color-secondary) var(--ui-tint-border), transparent)`, hover →
|
|
83
87
|
`--ui-tint-border-hover`). (Distinct from the interactive tints above, which mix from `--ui-accent`.)
|
|
@@ -36,11 +36,10 @@
|
|
|
36
36
|
const sepGap: Record<Size, string> = { sm: "mt-2", md: "mt-3", lg: "mt-4" };
|
|
37
37
|
// The separator is a SOFT internal divider, consistent with the other section splits/footers in the
|
|
38
38
|
// library (DatePicker/TimePicker/TimeRangeInput) — deliberately lighter than the Card frame + fields.
|
|
39
|
-
//
|
|
39
|
+
// One calc for EVERY variant: the variant's token mixed at `--ui-tint-divider` (30%), not the 50%
|
|
40
|
+
// frame strength. `secondary` resolves identically to the shared `--ui-border-color`.
|
|
40
41
|
const sepColor = $derived(
|
|
41
|
-
variant
|
|
42
|
-
? "var(--ui-border-color)"
|
|
43
|
-
: `color-mix(in srgb, var(${variantToken[variant]}) var(--ui-tint-border), transparent)`,
|
|
42
|
+
`color-mix(in srgb, var(${variantToken[variant]}) var(--ui-tint-divider), transparent)`,
|
|
44
43
|
);
|
|
45
44
|
</script>
|
|
46
45
|
|
|
@@ -36,11 +36,10 @@
|
|
|
36
36
|
const sepGap: Record<Size, string> = { sm: "mb-2", md: "mb-3", lg: "mb-4" };
|
|
37
37
|
// The separator is a SOFT internal divider, consistent with the other section splits/footers in the
|
|
38
38
|
// library (DatePicker/TimePicker/TimeRangeInput) — deliberately lighter than the Card frame + fields.
|
|
39
|
-
//
|
|
39
|
+
// One calc for EVERY variant: the variant's token mixed at `--ui-tint-divider` (30%), not the 50%
|
|
40
|
+
// frame strength. `secondary` resolves identically to the shared `--ui-border-color`.
|
|
40
41
|
const sepColor = $derived(
|
|
41
|
-
variant
|
|
42
|
-
? "var(--ui-border-color)"
|
|
43
|
-
: `color-mix(in srgb, var(${variantToken[variant]}) var(--ui-tint-border), transparent)`,
|
|
42
|
+
`color-mix(in srgb, var(${variantToken[variant]}) var(--ui-tint-divider), transparent)`,
|
|
44
43
|
);
|
|
45
44
|
</script>
|
|
46
45
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* bottom-anchored group (pushed to the strip's bottom edge), and hover-open
|
|
5
5
|
* popouts for parent items.
|
|
6
6
|
*
|
|
7
|
-
* Driven by data — pass `
|
|
7
|
+
* Driven by data — pass `items` / `bottomItems` arrays of {@link SideBarMenuItem}.
|
|
8
8
|
* Each item declares its own icon (Snippet), label, link, badge, and optional
|
|
9
9
|
* children. Children render in a top-layer Popup on hover or click.
|
|
10
10
|
*
|
|
@@ -33,6 +33,18 @@
|
|
|
33
33
|
*/
|
|
34
34
|
export type SideBarMenuMode = "stepped" | "drawer" | "icon" | "expanded";
|
|
35
35
|
|
|
36
|
+
/** Imperative handle exposed via `bind:api`. */
|
|
37
|
+
export type SideBarMenuApi = {
|
|
38
|
+
/** Reset all transient state — close the drawer, clear the open popout, return step level to 0. */
|
|
39
|
+
reset: () => void;
|
|
40
|
+
/** Open the drawer (drawer/stepped modes). */
|
|
41
|
+
open: () => void;
|
|
42
|
+
/** Close the drawer. */
|
|
43
|
+
close: () => void;
|
|
44
|
+
/** Set the stepped-mode step level (`0` = top). */
|
|
45
|
+
setStepLevel: (n: number) => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
36
48
|
export type SideBarMenuBadgeVariant = "error" | "warning" | "info" | "success";
|
|
37
49
|
|
|
38
50
|
export type SideBarMenuBadge = {
|
|
@@ -69,6 +81,15 @@
|
|
|
69
81
|
badge?: SideBarMenuBadge;
|
|
70
82
|
/** v1 renders only the first level; deeper levels emit a DEV warning. */
|
|
71
83
|
children?: SideBarMenuItem[];
|
|
84
|
+
/** Action row (no `link`) — renders a `<button>` and calls this on click. For a "toggle"
|
|
85
|
+
* row (an AI panel, etc.) or a non-navigating action. Runs after the drawer/popout reset. */
|
|
86
|
+
onclick?: (e: MouseEvent) => void;
|
|
87
|
+
/** Custom popout content, rendered INSTEAD of `children` in the popout (separators, hotkeys,
|
|
88
|
+
* an account/identity block…). The row still needs an `icon`/`label`; this is just its popout. */
|
|
89
|
+
popout?: import("svelte").Snippet;
|
|
90
|
+
/** Opt this anchor out of client-side routing — stamps `data-sveltekit-reload` (SvelteKit) so
|
|
91
|
+
* the link forces a full-browser navigation (sign-out endpoints, downloads, external redirects). */
|
|
92
|
+
reload?: boolean;
|
|
72
93
|
};
|
|
73
94
|
</script>
|
|
74
95
|
|
|
@@ -89,7 +110,7 @@
|
|
|
89
110
|
type ModesMap = Partial<Record<Breakpoint, SideBarMenuMode>>;
|
|
90
111
|
|
|
91
112
|
let {
|
|
92
|
-
|
|
113
|
+
items = [],
|
|
93
114
|
bottomItems = [],
|
|
94
115
|
currentPath,
|
|
95
116
|
size = "md",
|
|
@@ -100,9 +121,10 @@
|
|
|
100
121
|
open = $bindable(false),
|
|
101
122
|
stepLevel = $bindable(0),
|
|
102
123
|
hamburger = true,
|
|
124
|
+
api = $bindable(),
|
|
103
125
|
class: additionalClass = "",
|
|
104
126
|
}: {
|
|
105
|
-
|
|
127
|
+
items?: SideBarMenuItem[];
|
|
106
128
|
bottomItems?: SideBarMenuItem[];
|
|
107
129
|
currentPath: string;
|
|
108
130
|
size?: Size;
|
|
@@ -149,6 +171,10 @@
|
|
|
149
171
|
* and binds to `open`.
|
|
150
172
|
*/
|
|
151
173
|
hamburger?: boolean;
|
|
174
|
+
/** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
|
|
175
|
+
* (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
|
|
176
|
+
* any open popout and returns a stepped drawer to the top. */
|
|
177
|
+
api?: SideBarMenuApi;
|
|
152
178
|
class?: string;
|
|
153
179
|
} = $props();
|
|
154
180
|
|
|
@@ -202,23 +228,23 @@
|
|
|
202
228
|
|
|
203
229
|
// ── DEV depth warning ───────────────────────────────────────────
|
|
204
230
|
if (DEV) {
|
|
205
|
-
const walk = (
|
|
206
|
-
for (const it of
|
|
231
|
+
const walk = (list: SideBarMenuItem[], depth: number, path: string[]) => {
|
|
232
|
+
for (const it of list) {
|
|
233
|
+
const here = [...path, it.label];
|
|
207
234
|
if (it.children?.length) {
|
|
208
235
|
if (depth >= 1) {
|
|
209
236
|
console.warn(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'"',
|
|
237
|
+
`SideBarMenu: nesting deeper than 1 level is not yet rendered; flattening ` +
|
|
238
|
+
`grandchildren of "${here.join(" › ")}"`,
|
|
213
239
|
);
|
|
214
240
|
return;
|
|
215
241
|
}
|
|
216
|
-
walk(it.children, depth + 1);
|
|
242
|
+
walk(it.children, depth + 1, here);
|
|
217
243
|
}
|
|
218
244
|
}
|
|
219
245
|
};
|
|
220
|
-
walk(
|
|
221
|
-
walk(bottomItems, 0);
|
|
246
|
+
walk(items, 0, []);
|
|
247
|
+
walk(bottomItems, 0, []);
|
|
222
248
|
}
|
|
223
249
|
|
|
224
250
|
// ── Size axis ───────────────────────────────────────────────────
|
|
@@ -327,6 +353,15 @@
|
|
|
327
353
|
stepLevel = 1;
|
|
328
354
|
}
|
|
329
355
|
|
|
356
|
+
// Imperative handle for consumers (bound via `bind:api`). Assigned once — the closures read the
|
|
357
|
+
// reactive `open`/`stepLevel`/`openId` live, so a single assignment stays correct.
|
|
358
|
+
api = {
|
|
359
|
+
reset: closeAfterNav,
|
|
360
|
+
open: () => (open = true),
|
|
361
|
+
close: () => (open = false),
|
|
362
|
+
setStepLevel: (n: number) => (stepLevel = n),
|
|
363
|
+
};
|
|
364
|
+
|
|
330
365
|
// Label / chevron visibility are now driven by the active mode
|
|
331
366
|
// instead of by responsive class strings — the matchMedia effect
|
|
332
367
|
// above keeps `currentMode` (and therefore `labelsVisible`) in sync
|
|
@@ -503,18 +538,21 @@
|
|
|
503
538
|
`chevronVisibility` (not `labelClassResolved`) so the span
|
|
504
539
|
stays `inline-flex` and the SVG centers vertically. -->
|
|
505
540
|
<span
|
|
506
|
-
class="shrink-0 items-center justify-center opacity-60
|
|
541
|
+
class="shrink-0 items-center justify-center opacity-60 transition-transform duration-150 {isOpen
|
|
542
|
+
? 'rotate-180'
|
|
543
|
+
: ''} {sz.chevronBox} {sz.chevronSvg} {chevronClass}"
|
|
507
544
|
>
|
|
508
545
|
<IconChevronRight />
|
|
509
546
|
</span>
|
|
510
547
|
{/if}
|
|
511
548
|
{/snippet}
|
|
512
549
|
|
|
513
|
-
<!-- Single row template — used by
|
|
550
|
+
<!-- Single row template — used by items / bottomItems and the popout. -->
|
|
514
551
|
{#snippet row(item: SideBarMenuItem, opts: { id?: ItemId; isPopoutChild?: boolean })}
|
|
515
552
|
{@const active = isItemActive(item)}
|
|
553
|
+
{@const ownActive = !!item.link && currentPath.startsWith(item.link)}
|
|
516
554
|
{@const hasChildren = (item.children?.length ?? 0) > 0}
|
|
517
|
-
{@const isParent = hasChildren && !opts.isPopoutChild}
|
|
555
|
+
{@const isParent = (hasChildren || !!item.popout) && !opts.isPopoutChild}
|
|
518
556
|
{@const hasLink = !!item.link}
|
|
519
557
|
{@const isOpen = !!opts.id && openId === opts.id}
|
|
520
558
|
{@const rowClass = `group flex items-center w-full ${sz.row} lg:pr-3 transition-colors cursor-pointer ${active ? "[background-color:var(--ui-color-primary)] text-white" : isOpen ? "[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]" : "hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"}`}
|
|
@@ -523,7 +561,8 @@
|
|
|
523
561
|
href={item.link}
|
|
524
562
|
use:refIntoMap={opts.id}
|
|
525
563
|
class={rowClass}
|
|
526
|
-
|
|
564
|
+
data-sveltekit-reload={item.reload ? "" : undefined}
|
|
565
|
+
aria-current={ownActive ? "page" : undefined}
|
|
527
566
|
use:tooltip={{
|
|
528
567
|
text: item.label,
|
|
529
568
|
side: "right",
|
|
@@ -569,6 +608,21 @@
|
|
|
569
608
|
>
|
|
570
609
|
{@render rowBody(item, isParent, !!opts.isPopoutChild, isOpen)}
|
|
571
610
|
</button>
|
|
611
|
+
{:else if item.onclick}
|
|
612
|
+
<!-- Action row: no link, no children/popout — a `<button>` that runs `onclick`
|
|
613
|
+
(e.g. an AI-panel toggle), then resets the drawer/popout like a leaf nav. -->
|
|
614
|
+
<button
|
|
615
|
+
type="button"
|
|
616
|
+
use:refIntoMap={opts.id}
|
|
617
|
+
class="{rowClass} text-left"
|
|
618
|
+
use:tooltip={{ text: item.label, side: "right" }}
|
|
619
|
+
onclick={(e) => {
|
|
620
|
+
item.onclick!(e);
|
|
621
|
+
closeAfterNav();
|
|
622
|
+
}}
|
|
623
|
+
>
|
|
624
|
+
{@render rowBody(item, false, !!opts.isPopoutChild, false)}
|
|
625
|
+
</button>
|
|
572
626
|
{:else}
|
|
573
627
|
<!-- Malformed item: no link, no children. Render visibly so the
|
|
574
628
|
consumer notices, but with disabled-style cursor + opacity. -->
|
|
@@ -591,13 +645,17 @@
|
|
|
591
645
|
onmouseenter={cancelClose}
|
|
592
646
|
onmouseleave={scheduleClose}
|
|
593
647
|
>
|
|
594
|
-
|
|
595
|
-
{
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
648
|
+
{#if item.popout}
|
|
649
|
+
{@render item.popout()}
|
|
650
|
+
{:else}
|
|
651
|
+
<ul class="flex flex-col">
|
|
652
|
+
{#each item.children ?? [] as child (child.label)}
|
|
653
|
+
<li class="list-none" role="none">
|
|
654
|
+
{@render row(child, { isPopoutChild: true })}
|
|
655
|
+
</li>
|
|
656
|
+
{/each}
|
|
657
|
+
</ul>
|
|
658
|
+
{/if}
|
|
601
659
|
</div>
|
|
602
660
|
{/snippet}
|
|
603
661
|
|
|
@@ -659,15 +717,15 @@
|
|
|
659
717
|
{/each}
|
|
660
718
|
</ul>
|
|
661
719
|
{:else}
|
|
662
|
-
{#if
|
|
720
|
+
{#if items.length > 0}
|
|
663
721
|
<ul class="flex flex-col">
|
|
664
|
-
{#each
|
|
722
|
+
{#each items as item, idx (item.label)}
|
|
665
723
|
{@const id = `top:${idx}` as ItemId}
|
|
666
724
|
<li class="list-none">
|
|
667
725
|
{@render row(item, { id })}
|
|
668
726
|
</li>
|
|
669
|
-
{#if (item.children?.length ?? 0) > 0 && !isStepped}
|
|
670
|
-
{@const geom = geometryFor(id, "top", idx,
|
|
727
|
+
{#if ((item.children?.length ?? 0) > 0 || !!item.popout) && !isStepped}
|
|
728
|
+
{@const geom = geometryFor(id, "top", idx, items.length, item.children?.length ?? 1)}
|
|
671
729
|
<Popup
|
|
672
730
|
anchor={geom.anchor}
|
|
673
731
|
open={openId === id}
|
|
@@ -685,7 +743,7 @@
|
|
|
685
743
|
</ul>
|
|
686
744
|
{/if}
|
|
687
745
|
|
|
688
|
-
{#if
|
|
746
|
+
{#if items.length > 0 && bottomItems.length > 0}
|
|
689
747
|
<div class="flex-1"></div>
|
|
690
748
|
{/if}
|
|
691
749
|
|
|
@@ -696,13 +754,13 @@
|
|
|
696
754
|
<li class="list-none">
|
|
697
755
|
{@render row(item, { id })}
|
|
698
756
|
</li>
|
|
699
|
-
{#if (item.children?.length ?? 0) > 0 && !isStepped}
|
|
757
|
+
{#if ((item.children?.length ?? 0) > 0 || !!item.popout) && !isStepped}
|
|
700
758
|
{@const geom = geometryFor(
|
|
701
759
|
id,
|
|
702
760
|
"bottom",
|
|
703
761
|
idx,
|
|
704
762
|
bottomItems.length,
|
|
705
|
-
item.children
|
|
763
|
+
item.children?.length ?? 1,
|
|
706
764
|
)}
|
|
707
765
|
<Popup
|
|
708
766
|
anchor={geom.anchor}
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
* Designed for wide desktop.
|
|
19
19
|
*/
|
|
20
20
|
export type SideBarMenuMode = "stepped" | "drawer" | "icon" | "expanded";
|
|
21
|
+
/** Imperative handle exposed via `bind:api`. */
|
|
22
|
+
export type SideBarMenuApi = {
|
|
23
|
+
/** Reset all transient state — close the drawer, clear the open popout, return step level to 0. */
|
|
24
|
+
reset: () => void;
|
|
25
|
+
/** Open the drawer (drawer/stepped modes). */
|
|
26
|
+
open: () => void;
|
|
27
|
+
/** Close the drawer. */
|
|
28
|
+
close: () => void;
|
|
29
|
+
/** Set the stepped-mode step level (`0` = top). */
|
|
30
|
+
setStepLevel: (n: number) => void;
|
|
31
|
+
};
|
|
21
32
|
export type SideBarMenuBadgeVariant = "error" | "warning" | "info" | "success";
|
|
22
33
|
export type SideBarMenuBadge = {
|
|
23
34
|
/** Color of the indicator. */
|
|
@@ -50,12 +61,21 @@ export type SideBarMenuItem = {
|
|
|
50
61
|
badge?: SideBarMenuBadge;
|
|
51
62
|
/** v1 renders only the first level; deeper levels emit a DEV warning. */
|
|
52
63
|
children?: SideBarMenuItem[];
|
|
64
|
+
/** Action row (no `link`) — renders a `<button>` and calls this on click. For a "toggle"
|
|
65
|
+
* row (an AI panel, etc.) or a non-navigating action. Runs after the drawer/popout reset. */
|
|
66
|
+
onclick?: (e: MouseEvent) => void;
|
|
67
|
+
/** Custom popout content, rendered INSTEAD of `children` in the popout (separators, hotkeys,
|
|
68
|
+
* an account/identity block…). The row still needs an `icon`/`label`; this is just its popout. */
|
|
69
|
+
popout?: import("svelte").Snippet;
|
|
70
|
+
/** Opt this anchor out of client-side routing — stamps `data-sveltekit-reload` (SvelteKit) so
|
|
71
|
+
* the link forces a full-browser navigation (sign-out endpoints, downloads, external redirects). */
|
|
72
|
+
reload?: boolean;
|
|
53
73
|
};
|
|
54
74
|
import type { Size } from "../../types/sizes.js";
|
|
55
75
|
import { type Breakpoint } from "../../types/breakpoints.js";
|
|
56
76
|
type ModesMap = Partial<Record<Breakpoint, SideBarMenuMode>>;
|
|
57
77
|
type $$ComponentProps = {
|
|
58
|
-
|
|
78
|
+
items?: SideBarMenuItem[];
|
|
59
79
|
bottomItems?: SideBarMenuItem[];
|
|
60
80
|
currentPath: string;
|
|
61
81
|
size?: Size;
|
|
@@ -102,8 +122,12 @@ type $$ComponentProps = {
|
|
|
102
122
|
* and binds to `open`.
|
|
103
123
|
*/
|
|
104
124
|
hamburger?: boolean;
|
|
125
|
+
/** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
|
|
126
|
+
* (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
|
|
127
|
+
* any open popout and returns a stepped drawer to the top. */
|
|
128
|
+
api?: SideBarMenuApi;
|
|
105
129
|
class?: string;
|
|
106
130
|
};
|
|
107
|
-
declare const SideBarMenu: import("svelte").Component<$$ComponentProps, {}, "open" | "stepLevel">;
|
|
131
|
+
declare const SideBarMenu: import("svelte").Component<$$ComponentProps, {}, "open" | "stepLevel" | "api">;
|
|
108
132
|
type SideBarMenu = ReturnType<typeof SideBarMenu>;
|
|
109
133
|
export default SideBarMenu;
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export type { Columns, Gap } from "./components/field-group.js";
|
|
|
41
41
|
export { default as Disclosure } from "./components/Disclosure.svelte";
|
|
42
42
|
export { default as Accordion } from "./components/Accordion.svelte";
|
|
43
43
|
export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
44
|
-
export type { SideBarMenuItem, SideBarMenuBadge, SideBarMenuBadgeVariant, } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
44
|
+
export type { SideBarMenuItem, SideBarMenuBadge, SideBarMenuBadgeVariant, SideBarMenuApi, } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
45
45
|
export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
|
|
46
46
|
export type { TabDefinition } from "./components/TabStrip/TabStrip.svelte";
|
|
47
47
|
export { default as DataTable } from "./components/DataTable.svelte";
|
package/dist/theme.css
CHANGED
|
@@ -67,9 +67,10 @@
|
|
|
67
67
|
* Fixed, NOT scaled by `size`, so different-size controls share a
|
|
68
68
|
* corner. (Pills like Toggle/Badge use rounded-full; the tiny
|
|
69
69
|
* Checkbox box keeps its own small radius — not worth a token.)
|
|
70
|
-
* --ui-border-color soft internal divider (row separators, section splits)
|
|
70
|
+
* --ui-border-color soft internal divider (row separators, section splits) —
|
|
71
|
+
* the default (secondary) divider, mixed at `--ui-tint-divider` */
|
|
71
72
|
--ui-border-radius: 0.375rem;
|
|
72
|
-
--ui-border-color: color-mix(in srgb, var(--ui-color-secondary)
|
|
73
|
+
--ui-border-color: color-mix(in srgb, var(--ui-color-secondary) var(--ui-tint-divider), transparent);
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
/* One interactive-surface language, library-wide. A subtle tint of the ACCENT colour, so it lightens on
|
|
@@ -104,15 +105,17 @@
|
|
|
104
105
|
/* Tint SCALE for a variant-tinted surface (Badge chip, Card frame, `filled` Alert…). The colour is
|
|
105
106
|
* always the component's own variant token; these are the shared STRENGTHS it's mixed at, so every
|
|
106
107
|
* tinted surface is calculated the same way — change one number here, every component follows:
|
|
107
|
-
* fill:
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
108
|
+
* fill: color-mix(in srgb, var(<token>) var(--ui-tint-soft), transparent) soft resting fill
|
|
109
|
+
* divider: color-mix(in srgb, var(<token>) var(--ui-tint-divider), transparent) soft internal divider
|
|
110
|
+
* border: color-mix(in srgb, var(<token>) var(--ui-tint-border), transparent) frame / field border (rest)
|
|
111
|
+
* hover: color-mix(in srgb, var(<token>) var(--ui-tint-border-hover), transparent) bordered field, hover/focus
|
|
112
|
+
* text: color-mix(in srgb, var(<token>) var(--ui-tint-strong), var(--ui-color-text)) foreground
|
|
111
113
|
* A primary badge is primary@soft/border/strong; a neutral one is text@… — one calculation, any
|
|
112
114
|
* variant. `--ui-tint-border-hover` is the STRONGER step an interactive bordered field (Input,
|
|
113
115
|
* TextArea, an outline Button) moves to on hover/focus. Theme-independent constants (no colour → no
|
|
114
116
|
* per-theme scope needed) — but a theme MAY override these to tune the whole tint language. */
|
|
115
117
|
--ui-tint-soft: 12%;
|
|
118
|
+
--ui-tint-divider: 30%; /* soft internal dividers — deliberately lighter than the 50% frame/field border */
|
|
116
119
|
--ui-tint-border: 50%;
|
|
117
120
|
--ui-tint-border-hover: 80%;
|
|
118
121
|
--ui-tint-strong: 80%;
|