@signal9/era-ui 12.1.2 → 13.0.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/dist/dev/audit-overlay.svelte +1 -1
- package/dist/docs/css-utilities.js +6 -1
- package/dist/docs/notes.md +12 -11
- package/dist/era-ui.css +1 -1
- package/dist/generated-docs/badge.md +3 -0
- package/dist/generated-docs/button.md +4 -0
- package/dist/generated-docs/chip.md +3 -0
- package/dist/generated-docs/llms-full.txt +53 -31
- package/dist/generated-docs/llms.txt +1 -1
- package/dist/generated-docs/manifest.json +17 -17
- package/dist/generated-docs/notes.md +12 -11
- package/dist/generated-docs/utilities.json +10 -10
- package/dist/generated-docs/utilities.md +30 -19
- package/dist/os/index.d.ts +0 -1
- package/dist/os/index.js +0 -1
- package/dist/os/notification-center.svelte +1 -2
- package/dist/os/taskbar.svelte +2 -3
- package/dist/os/toaster.svelte +2 -3
- package/dist/styles/index.css +77 -12
- package/dist/styles/layers.css +48 -0
- package/dist/styles/surfaces/base.css +4 -2
- package/dist/styles/surfaces/glass.css +0 -13
- package/dist/theme/index.d.ts +87 -0
- package/dist/theme/index.js +86 -0
- package/dist/ui/alert-dialog/alert-dialog-content.svelte +2 -2
- package/dist/ui/alert-dialog/alert-dialog-overlay.svelte +1 -1
- package/dist/ui/badge/badge.svelte +14 -1
- package/dist/ui/badge/badge.svelte.d.ts +4 -0
- package/dist/ui/button/button.svelte +37 -7
- package/dist/ui/button/button.svelte.d.ts +6 -0
- package/dist/ui/chip/chip.svelte +14 -1
- package/dist/ui/chip/chip.svelte.d.ts +4 -0
- package/dist/ui/combobox/combobox-content.svelte +1 -1
- package/dist/ui/command-bar/command-bar.svelte +1 -1
- package/dist/ui/context-menu/context-menu-content.svelte +1 -1
- package/dist/ui/context-menu/context-menu-sub-content.svelte +1 -1
- package/dist/ui/date-picker/date-picker-content.svelte +1 -1
- package/dist/ui/date-range-picker/date-range-picker-content.svelte +1 -1
- package/dist/ui/dialog/dialog-content.svelte +1 -1
- package/dist/ui/dialog/dialog-overlay.svelte +5 -1
- package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/dist/ui/link-preview/link-preview-content.svelte +1 -1
- package/dist/ui/menubar/menubar-content.svelte +1 -1
- package/dist/ui/menubar/menubar-sub-content.svelte +1 -1
- package/dist/ui/popover/popover-content.svelte +1 -1
- package/dist/ui/select/select-content.svelte +1 -1
- package/dist/ui/sheet/sheet-content.svelte +1 -1
- package/dist/ui/sheet/sheet-content.svelte.d.ts +1 -1
- package/dist/ui/tooltip/tooltip-content.svelte +1 -1
- package/package.json +9 -12
- package/dist/apps/index.d.ts +0 -20
- package/dist/apps/index.js +0 -20
- package/dist/apps/llm-shell/index.d.ts +0 -2
- package/dist/apps/llm-shell/index.js +0 -1
- package/dist/apps/llm-shell/llm-shell.svelte +0 -334
- package/dist/apps/llm-shell/llm-shell.svelte.d.ts +0 -57
- package/dist/apps/llm-shell/llm-swarm.svelte +0 -79
- package/dist/apps/llm-shell/llm-swarm.svelte.d.ts +0 -25
- package/dist/apps/llm-shell/types.d.ts +0 -103
- package/dist/apps/llm-shell/types.js +0 -1
- package/dist/apps/notes/editor/bubble-menu.svelte.d.ts +0 -11
- package/dist/apps/notes/editor/bubble-menu.svelte.js +0 -165
- package/dist/apps/notes/editor/bubble-toolbar.svelte +0 -55
- package/dist/apps/notes/editor/bubble-toolbar.svelte.d.ts +0 -22
- package/dist/apps/notes/editor/extensions.d.ts +0 -18
- package/dist/apps/notes/editor/extensions.js +0 -88
- package/dist/apps/notes/editor/floating.d.ts +0 -25
- package/dist/apps/notes/editor/floating.js +0 -54
- package/dist/apps/notes/editor/link.d.ts +0 -9
- package/dist/apps/notes/editor/link.js +0 -22
- package/dist/apps/notes/editor/list-cleanup-rule.d.ts +0 -14
- package/dist/apps/notes/editor/list-cleanup-rule.js +0 -47
- package/dist/apps/notes/editor/slash-command.svelte.d.ts +0 -22
- package/dist/apps/notes/editor/slash-command.svelte.js +0 -215
- package/dist/apps/notes/editor/slash-menu.svelte +0 -82
- package/dist/apps/notes/editor/slash-menu.svelte.d.ts +0 -23
- package/dist/apps/notes/index.d.ts +0 -11
- package/dist/apps/notes/index.js +0 -13
- package/dist/apps/notes/markdown.d.ts +0 -12
- package/dist/apps/notes/markdown.js +0 -165
- package/dist/apps/notes/note-editor.svelte +0 -176
- package/dist/apps/notes/note-editor.svelte.d.ts +0 -36
- package/dist/apps/notes/notes-store.svelte.d.ts +0 -64
- package/dist/apps/notes/notes-store.svelte.js +0 -234
- package/dist/apps/notes/notes.svelte +0 -501
- package/dist/apps/notes/notes.svelte.d.ts +0 -53
- package/dist/apps/notes/tree.d.ts +0 -70
- package/dist/apps/notes/tree.js +0 -185
- package/dist/apps/notes/types.d.ts +0 -49
- package/dist/apps/notes/types.js +0 -11
- package/dist/os/layers.d.ts +0 -23
- package/dist/os/layers.js +0 -23
package/dist/styles/index.css
CHANGED
|
@@ -1,14 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/* ------------------------------------------------------------------ */
|
|
2
|
+
/* CASCADE LAYERS */
|
|
3
|
+
/* */
|
|
4
|
+
/* era's token CSS is LAYERED so a consumer can override it without */
|
|
5
|
+
/* a specificity fight. Unlayered styles beat every layer, so a */
|
|
6
|
+
/* consumer's own `:root[data-theme='mine']` block — written plainly, */
|
|
7
|
+
/* no !important, no selector padding — now wins against era's, which */
|
|
8
|
+
/* is what defining a theme downstream should feel like. Before this */
|
|
9
|
+
/* everything era shipped was unlayered, so it outranked the whole of */
|
|
10
|
+
/* a consumer's own `@layer`ed Tailwind CSS. */
|
|
11
|
+
/* */
|
|
12
|
+
/* WHAT IS DELIBERATELY NOT LAYERED, because layering would change */
|
|
13
|
+
/* what it means rather than just where it sits: */
|
|
14
|
+
/* */
|
|
15
|
+
/* typography.css and motion.css contain `!important` — the font */
|
|
16
|
+
/* axis hammer and the reduced-motion guarantee. Layer order is */
|
|
17
|
+
/* REVERSED for important declarations, so moving these into a */
|
|
18
|
+
/* layer would make them beat a consumer's unlayered !important */
|
|
19
|
+
/* instead of losing to it. That is a stronger claim than era has */
|
|
20
|
+
/* any business making about someone else's page. */
|
|
21
|
+
/* */
|
|
22
|
+
/* The .era-text-trim.truncate collision guard (below) has to */
|
|
23
|
+
/* outrank two single-class utilities, and utilities are layered. */
|
|
24
|
+
/* Unlayered is precisely what lets it win. */
|
|
25
|
+
/* */
|
|
26
|
+
/* @utility and @theme are Tailwind's to place; it already layers */
|
|
27
|
+
/* them. */
|
|
28
|
+
/* ------------------------------------------------------------------ */
|
|
29
|
+
@layer era.tokens, era.base;
|
|
30
|
+
|
|
31
|
+
@import './themes.css' layer(era.tokens);
|
|
32
|
+
@import './density.css' layer(era.tokens);
|
|
33
|
+
/* Stacking contract — global tiers only; see the file for the local/global split. */
|
|
34
|
+
@import './layers.css' layer(era.tokens);
|
|
3
35
|
/* Motion axis (data-motion) — owns --era-duration + --era-ease. Imported before
|
|
4
|
-
* surfaces so a [data-surface] block can override --era-ease as a delta.
|
|
36
|
+
* surfaces so a [data-surface] block can override --era-ease as a delta.
|
|
37
|
+
* UNLAYERED: carries the prefers-reduced-motion !important — see above. */
|
|
5
38
|
@import './motion.css';
|
|
6
39
|
/* Surfaces: base contract first, then each surface's deltas. Order matters —
|
|
7
40
|
* the base block and a surface's block target the same element, and the
|
|
8
|
-
* surface's declarations must win. */
|
|
9
|
-
@import './surfaces/base.css';
|
|
10
|
-
@import './surfaces/glass.css';
|
|
11
|
-
@import './surfaces/bevel.css';
|
|
41
|
+
* surface's declarations must win. Same layer, so source order still decides. */
|
|
42
|
+
@import './surfaces/base.css' layer(era.tokens);
|
|
43
|
+
@import './surfaces/glass.css' layer(era.tokens);
|
|
44
|
+
@import './surfaces/bevel.css' layer(era.tokens);
|
|
45
|
+
/* UNLAYERED: carries the font-axis !important — see above. */
|
|
12
46
|
@import './typography.css';
|
|
13
47
|
|
|
14
48
|
/* WHAT IS NOT HERE: vidstack's stylesheets, which the VideoPlayer needs and
|
|
@@ -35,13 +69,14 @@
|
|
|
35
69
|
@source '../ui';
|
|
36
70
|
@source '../ai';
|
|
37
71
|
@source '../os';
|
|
38
|
-
@source '../apps';
|
|
39
72
|
|
|
40
73
|
/* Lucide icons use an SVG presentation attribute for stroke width by default.
|
|
41
74
|
* Set it from an Era token globally so every imported Lucide component tracks
|
|
42
75
|
* the same optical weight as adjacent body text and can be tuned in one place. */
|
|
43
|
-
|
|
44
|
-
|
|
76
|
+
@layer era.base {
|
|
77
|
+
:where(.lucide) {
|
|
78
|
+
stroke-width: var(--era-glyph-stroke-width);
|
|
79
|
+
}
|
|
45
80
|
}
|
|
46
81
|
|
|
47
82
|
/* The mask glyph is the one piece of type the UA picks for you, so era picks it
|
|
@@ -67,8 +102,10 @@
|
|
|
67
102
|
* type="text" and masking it ourselves, which costs password-manager detection
|
|
68
103
|
* and the screen-reader "password" announcement. Deliberately not done — the
|
|
69
104
|
* semantics are worth more than the corner. */
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
@layer era.base {
|
|
106
|
+
:where(input[type='password']) {
|
|
107
|
+
-webkit-text-security: var(--era-mask-glyph, disc);
|
|
108
|
+
}
|
|
72
109
|
}
|
|
73
110
|
|
|
74
111
|
/* Shared interactive-state recipe: quiet focus, disabled affordance.
|
|
@@ -351,6 +388,24 @@
|
|
|
351
388
|
text-box: trim-both ex alphabetic;
|
|
352
389
|
}
|
|
353
390
|
|
|
391
|
+
/* Tailwind v4 cannot resolve var() inside backdrop-blur-[], so the filter
|
|
392
|
+
* is a registered utility, gated to glass subtrees.
|
|
393
|
+
*
|
|
394
|
+
* It lives HERE rather than in surfaces/glass.css, where a surface's own
|
|
395
|
+
* chrome would naturally sit, because glass.css is imported into a cascade
|
|
396
|
+
* layer and `@utility` cannot be nested inside one. Surface-scoped RULES stay
|
|
397
|
+
* in their surface file; surface-scoped UTILITIES have to come out.
|
|
398
|
+
*
|
|
399
|
+
* @use the backdrop blur for a floating panel (popover, dialog, taskbar) — it
|
|
400
|
+
* applies only inside a data-surface="glass" subtree and is inert elsewhere,
|
|
401
|
+
* so it is safe to leave on a panel that renders under every surface. */
|
|
402
|
+
@utility glass-blur {
|
|
403
|
+
[data-surface='glass'] & {
|
|
404
|
+
backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
|
|
405
|
+
-webkit-backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
354
409
|
/* In-progress text shimmer — a bright sweep through muted text (the standard
|
|
355
410
|
* "working…" label treatment in streaming UIs). Timing derives from the motion
|
|
356
411
|
* axis (10× --era-duration ≈ 1.5s at normal, 2.6s at extra); at instant the
|
|
@@ -541,6 +596,16 @@
|
|
|
541
596
|
* bg-field-highlight its focused/hovered face. */
|
|
542
597
|
--color-field: var(--era-field-bg);
|
|
543
598
|
--color-field-highlight: var(--era-field-highlight);
|
|
599
|
+
/* @use the stacking tier for chrome that renders at the document root —
|
|
600
|
+
* z-chrome (taskbar) < z-drawer (slide-over) < z-overlay (every dialog,
|
|
601
|
+
* menu, popover and scrim) < z-toast < z-debug. A component ordering its
|
|
602
|
+
* OWN parts inside an isolated context keeps small integers instead; see
|
|
603
|
+
* styles/layers.css for why the two are different things. */
|
|
604
|
+
--z-index-chrome: var(--era-z-chrome);
|
|
605
|
+
--z-index-drawer: var(--era-z-drawer);
|
|
606
|
+
--z-index-overlay: var(--era-z-overlay);
|
|
607
|
+
--z-index-toast: var(--era-z-toast);
|
|
608
|
+
--z-index-debug: var(--era-z-debug);
|
|
544
609
|
/* Motion — bare transition utilities default to the motion axis, so
|
|
545
610
|
* `transition-colors` alone is on-design (0s at instant, longer at extra).
|
|
546
611
|
* ease-base/duration-base name the axis explicitly for overrides. */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------ */
|
|
2
|
+
/* THE STACKING CONTRACT */
|
|
3
|
+
/* */
|
|
4
|
+
/* Two kinds of z-index exist in this library, and conflating them is */
|
|
5
|
+
/* what turns stacking into whack-a-mole. */
|
|
6
|
+
/* */
|
|
7
|
+
/* GLOBAL TIERS — elements that render at the document root, either */
|
|
8
|
+
/* because they are shell chrome or because a portal put them there. */
|
|
9
|
+
/* These CAN collide with each other, from anywhere in the tree, so */
|
|
10
|
+
/* their order is a contract and they are named here. Nothing in the */
|
|
11
|
+
/* library may write a raw z-index in this band; scripts/check-layers */
|
|
12
|
+
/* fails the build if it does. */
|
|
13
|
+
/* */
|
|
14
|
+
/* LOCAL STACKING — a component ordering its own parts (a video's */
|
|
15
|
+
/* poster under its controls, a pane's resize edges over its body). */
|
|
16
|
+
/* These stay small integers on purpose: they are meaningless outside */
|
|
17
|
+
/* their own component and naming them with a global tier would claim */
|
|
18
|
+
/* a relationship that does not exist. What makes them safe is not */
|
|
19
|
+
/* their value but ISOLATION — the ancestor establishing a stacking */
|
|
20
|
+
/* context, so the number cannot escape. The OS Desktop is the */
|
|
21
|
+
/* load-bearing example: it is `isolate`, so panes hand out 1..N */
|
|
22
|
+
/* freely and no pane can ever climb over a menu or a toast. */
|
|
23
|
+
/* */
|
|
24
|
+
/* The tiers are spaced to leave room, and 50 is deliberate: bits-ui */
|
|
25
|
+
/* portals its dialogs, menus and tooltips at z-50, so era's own */
|
|
26
|
+
/* floating chrome sits in that same band rather than fighting it. */
|
|
27
|
+
/* Anything that must beat a menu goes above; chrome that a menu must */
|
|
28
|
+
/* open over goes below. */
|
|
29
|
+
/* ------------------------------------------------------------------ */
|
|
30
|
+
:root {
|
|
31
|
+
/* Shell chrome — a taskbar or dock. BELOW the overlay band, so a
|
|
32
|
+
* dropdown anchored to the taskbar opens over the taskbar. */
|
|
33
|
+
--era-z-chrome: 30;
|
|
34
|
+
/* Slide-over panels that hang off the chrome (a notification centre).
|
|
35
|
+
* Above the chrome it emerges from, still under menus. */
|
|
36
|
+
--era-z-drawer: 40;
|
|
37
|
+
/* Every floating panel and every scrim: dialog, popover, select, menu,
|
|
38
|
+
* tooltip, command bar. Shares bits-ui's own tier — within it, DOM order
|
|
39
|
+
* decides, which is what makes a popover opened from inside a dialog land
|
|
40
|
+
* above the dialog without either knowing about the other. */
|
|
41
|
+
--era-z-overlay: 50;
|
|
42
|
+
/* Transient system messages. Above the overlay band on purpose: a toast
|
|
43
|
+
* that a modal can hide is a toast you will miss. */
|
|
44
|
+
--era-z-toast: 55;
|
|
45
|
+
/* Development overlays (the audit harness). Above everything it inspects,
|
|
46
|
+
* because inspecting a dialog means drawing on top of one. */
|
|
47
|
+
--era-z-debug: 60;
|
|
48
|
+
}
|
|
@@ -196,8 +196,10 @@
|
|
|
196
196
|
* a bevel thumb sliding along a track), scope a rule to the surface and
|
|
197
197
|
* target the component's stable part, e.g. in the surface's own file:
|
|
198
198
|
* [data-surface='<name>'] [data-slot='switch-thumb'] { … }
|
|
199
|
-
* Keyframes
|
|
200
|
-
*
|
|
199
|
+
* Keyframes live in that surface's file. Surface-only UTILITIES cannot:
|
|
200
|
+
* the surface files are imported into a cascade layer, and `@utility` may
|
|
201
|
+
* not be nested inside one — so they live in styles/index.css alongside
|
|
202
|
+
* the rest (precedent: `glass-blur`, which is gated to glass subtrees).
|
|
201
203
|
*
|
|
202
204
|
* Reduced-motion is handled centrally in motion.css — surfaces need not.
|
|
203
205
|
*/
|
|
@@ -155,16 +155,3 @@
|
|
|
155
155
|
--era-selection-shadow: none;
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
/* Tailwind v4 cannot resolve var() inside backdrop-blur-[], so the filter
|
|
160
|
-
* is a registered utility, gated to glass subtrees.
|
|
161
|
-
*
|
|
162
|
-
* @use the backdrop blur for a floating panel (popover, dialog, taskbar) — it
|
|
163
|
-
* applies only inside a data-surface="glass" subtree and is inert elsewhere,
|
|
164
|
-
* so it is safe to leave on a panel that renders under every surface. */
|
|
165
|
-
@utility glass-blur {
|
|
166
|
-
[data-surface='glass'] & {
|
|
167
|
-
backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
|
|
168
|
-
-webkit-backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoring a theme, with the contract in the type system.
|
|
3
|
+
*
|
|
4
|
+
* A theme was a block of hand-written CSS, and the set of tokens it had to
|
|
5
|
+
* define existed only in the heads of the people who had written one before.
|
|
6
|
+
* Nothing checked it. The failure mode is not a build error — it is a token
|
|
7
|
+
* that quietly inherits the DEFAULT theme's value and looks almost right: era's
|
|
8
|
+
* own `sand` shipped with accent foregrounds inherited from a palette whose
|
|
9
|
+
* floor was half a lightness step away, and the result measured 2.48:1 before
|
|
10
|
+
* anyone noticed it by eye.
|
|
11
|
+
*
|
|
12
|
+
* So the contract is a type. Every token the system's `@theme` block depends on
|
|
13
|
+
* is a required field, `themeCss()` turns a checked object into the CSS text,
|
|
14
|
+
* and omitting one is a compile error rather than a contrast bug.
|
|
15
|
+
*
|
|
16
|
+
* Three tokens are deliberately OPTIONAL, because era defines them once at the
|
|
17
|
+
* root and a theme is not expected to restate them: `bright`, `link` and
|
|
18
|
+
* `muted`. Setting them overrides the default; leaving them out inherits it,
|
|
19
|
+
* which is what era's own themes do.
|
|
20
|
+
*
|
|
21
|
+
* This is an AUTHORING helper, not a runtime. It returns a string. Put it in a
|
|
22
|
+
* `.css.ts`-style build step, a `<style>` in your root layout, or paste the
|
|
23
|
+
* output — era's own themes stay hand-written CSS because their values carry
|
|
24
|
+
* measured rationale in comments that generated output would lose.
|
|
25
|
+
*/
|
|
26
|
+
/** The 12-step ramp, background (1) through to the brightest ink (12). */
|
|
27
|
+
export type Scale12 = readonly [
|
|
28
|
+
string,
|
|
29
|
+
string,
|
|
30
|
+
string,
|
|
31
|
+
string,
|
|
32
|
+
string,
|
|
33
|
+
string,
|
|
34
|
+
string,
|
|
35
|
+
string,
|
|
36
|
+
string,
|
|
37
|
+
string,
|
|
38
|
+
string,
|
|
39
|
+
string
|
|
40
|
+
];
|
|
41
|
+
/** `step(5)` → `var(--color-5)` — point a semantic token at a rung. */
|
|
42
|
+
export declare const step: (n: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) => string;
|
|
43
|
+
export interface ThemeMode {
|
|
44
|
+
/** Drives `color-scheme`, and with it the UA's own form controls and scrollbars. */
|
|
45
|
+
scheme: 'dark' | 'light';
|
|
46
|
+
/** The ramp. Every other token may reference a rung with `step()`. */
|
|
47
|
+
scale: Scale12;
|
|
48
|
+
/** Chrome. */
|
|
49
|
+
border: string;
|
|
50
|
+
surface: string;
|
|
51
|
+
hover: string;
|
|
52
|
+
fg: string;
|
|
53
|
+
/** Accents, each with the ink that sits ON it — the pair is the point. */
|
|
54
|
+
primary: string;
|
|
55
|
+
primaryFg: string;
|
|
56
|
+
primaryHover: string;
|
|
57
|
+
destructive: string;
|
|
58
|
+
destructiveFg: string;
|
|
59
|
+
success: string;
|
|
60
|
+
successFg: string;
|
|
61
|
+
warning: string;
|
|
62
|
+
warningFg: string;
|
|
63
|
+
/** Optional — era defines these at the root; set to override. */
|
|
64
|
+
bright?: string;
|
|
65
|
+
link?: string;
|
|
66
|
+
muted?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Anything else the theme owns, as bare token names: `{ 'color-ink':
|
|
69
|
+
* 'oklch(0.24 0.03 60)' }`. era's `sand` needs exactly this — its palette has
|
|
70
|
+
* no rung dark enough to serve as accent ink, so it defines its own.
|
|
71
|
+
*/
|
|
72
|
+
extra?: Record<string, string>;
|
|
73
|
+
}
|
|
74
|
+
export interface Theme {
|
|
75
|
+
/** Selected with `data-theme="<name>"`. */
|
|
76
|
+
name: string;
|
|
77
|
+
dark: ThemeMode;
|
|
78
|
+
light: ThemeMode;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The CSS for a theme: a dark block, a `prefers-color-scheme: light` block, and
|
|
82
|
+
* the explicit `.light` / `.dark` class escapes that let a subtree opt out.
|
|
83
|
+
*
|
|
84
|
+
* Emitted into `@layer era.tokens` so it sits exactly where era's own themes
|
|
85
|
+
* sit — a consumer's later, unlayered CSS still wins over both.
|
|
86
|
+
*/
|
|
87
|
+
export declare function themeCss(theme: Theme): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoring a theme, with the contract in the type system.
|
|
3
|
+
*
|
|
4
|
+
* A theme was a block of hand-written CSS, and the set of tokens it had to
|
|
5
|
+
* define existed only in the heads of the people who had written one before.
|
|
6
|
+
* Nothing checked it. The failure mode is not a build error — it is a token
|
|
7
|
+
* that quietly inherits the DEFAULT theme's value and looks almost right: era's
|
|
8
|
+
* own `sand` shipped with accent foregrounds inherited from a palette whose
|
|
9
|
+
* floor was half a lightness step away, and the result measured 2.48:1 before
|
|
10
|
+
* anyone noticed it by eye.
|
|
11
|
+
*
|
|
12
|
+
* So the contract is a type. Every token the system's `@theme` block depends on
|
|
13
|
+
* is a required field, `themeCss()` turns a checked object into the CSS text,
|
|
14
|
+
* and omitting one is a compile error rather than a contrast bug.
|
|
15
|
+
*
|
|
16
|
+
* Three tokens are deliberately OPTIONAL, because era defines them once at the
|
|
17
|
+
* root and a theme is not expected to restate them: `bright`, `link` and
|
|
18
|
+
* `muted`. Setting them overrides the default; leaving them out inherits it,
|
|
19
|
+
* which is what era's own themes do.
|
|
20
|
+
*
|
|
21
|
+
* This is an AUTHORING helper, not a runtime. It returns a string. Put it in a
|
|
22
|
+
* `.css.ts`-style build step, a `<style>` in your root layout, or paste the
|
|
23
|
+
* output — era's own themes stay hand-written CSS because their values carry
|
|
24
|
+
* measured rationale in comments that generated output would lose.
|
|
25
|
+
*/
|
|
26
|
+
/** `step(5)` → `var(--color-5)` — point a semantic token at a rung. */
|
|
27
|
+
export const step = (n) => `var(--color-${n})`;
|
|
28
|
+
const TOKEN = {
|
|
29
|
+
border: '--color-border',
|
|
30
|
+
surface: '--color-surface',
|
|
31
|
+
hover: '--color-hover',
|
|
32
|
+
fg: '--color-fg',
|
|
33
|
+
primary: '--color-primary',
|
|
34
|
+
primaryFg: '--color-primary-fg',
|
|
35
|
+
primaryHover: '--color-primary-hover',
|
|
36
|
+
destructive: '--color-destructive',
|
|
37
|
+
destructiveFg: '--color-destructive-fg',
|
|
38
|
+
success: '--color-success',
|
|
39
|
+
successFg: '--color-success-fg',
|
|
40
|
+
warning: '--color-warning',
|
|
41
|
+
warningFg: '--color-warning-fg',
|
|
42
|
+
bright: '--color-bright',
|
|
43
|
+
link: '--color-link',
|
|
44
|
+
muted: '--color-muted'
|
|
45
|
+
};
|
|
46
|
+
function modeBody(mode, indent) {
|
|
47
|
+
const lines = [`${indent}color-scheme: ${mode.scheme};`, ''];
|
|
48
|
+
mode.scale.forEach((value, i) => lines.push(`${indent}--color-${i + 1}: ${value};`));
|
|
49
|
+
lines.push('');
|
|
50
|
+
for (const [key, token] of Object.entries(TOKEN)) {
|
|
51
|
+
const value = mode[key];
|
|
52
|
+
if (typeof value === 'string')
|
|
53
|
+
lines.push(`${indent}${token}: ${value};`);
|
|
54
|
+
}
|
|
55
|
+
for (const [name, value] of Object.entries(mode.extra ?? {})) {
|
|
56
|
+
lines.push(`${indent}--${name.replace(/^--/, '')}: ${value};`);
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The CSS for a theme: a dark block, a `prefers-color-scheme: light` block, and
|
|
62
|
+
* the explicit `.light` / `.dark` class escapes that let a subtree opt out.
|
|
63
|
+
*
|
|
64
|
+
* Emitted into `@layer era.tokens` so it sits exactly where era's own themes
|
|
65
|
+
* sit — a consumer's later, unlayered CSS still wins over both.
|
|
66
|
+
*/
|
|
67
|
+
export function themeCss(theme) {
|
|
68
|
+
const { name, dark, light } = theme;
|
|
69
|
+
return `@layer era.tokens {
|
|
70
|
+
\t:root[data-theme='${name}'],
|
|
71
|
+
\t[data-theme='${name}'].dark {
|
|
72
|
+
${modeBody(dark, '\t\t')}
|
|
73
|
+
\t}
|
|
74
|
+
|
|
75
|
+
\t@media (prefers-color-scheme: light) {
|
|
76
|
+
\t\t:root[data-theme='${name}']:not(.dark) {
|
|
77
|
+
${modeBody(light, '\t\t\t')}
|
|
78
|
+
\t\t}
|
|
79
|
+
\t}
|
|
80
|
+
|
|
81
|
+
\t[data-theme='${name}'].light {
|
|
82
|
+
${modeBody(light, '\t\t')}
|
|
83
|
+
\t}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<AlertDialog.Portal>
|
|
14
|
-
<AlertDialog.Overlay class="fixed inset-0 z-
|
|
14
|
+
<AlertDialog.Overlay class="fixed inset-0 z-overlay bg-overlay" />
|
|
15
15
|
<AlertDialog.Content
|
|
16
16
|
bind:ref
|
|
17
17
|
class={cn(
|
|
18
|
-
'fixed top-1/2 left-1/2 z-
|
|
18
|
+
'fixed top-1/2 left-1/2 z-overlay flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-gutter rounded-bar border border-divider bg-well p-card shadow-lg glass-blur',
|
|
19
19
|
className
|
|
20
20
|
)}
|
|
21
21
|
{...restProps}
|
|
@@ -40,16 +40,29 @@
|
|
|
40
40
|
let {
|
|
41
41
|
ref = $bindable(null),
|
|
42
42
|
tone = 'default',
|
|
43
|
+
lead,
|
|
43
44
|
children,
|
|
44
45
|
class: className,
|
|
45
46
|
...restProps
|
|
46
47
|
}: HTMLAttributes<HTMLSpanElement> & {
|
|
47
48
|
ref?: HTMLSpanElement | null;
|
|
48
49
|
tone?: 'default' | 'accent' | 'destructive' | 'success' | 'warning';
|
|
50
|
+
/** Icon rendered BEFORE the label, outside it. Use this rather than putting
|
|
51
|
+
* an icon in `children`: the label then gets its own box, which is the only
|
|
52
|
+
* place era's ink centring can reach. */
|
|
53
|
+
lead?: Snippet;
|
|
49
54
|
children?: Snippet;
|
|
50
55
|
} = $props();
|
|
51
56
|
</script>
|
|
52
57
|
|
|
53
58
|
<span bind:this={ref} class={cn(badgeVariants({ tone }), className)} {...restProps}>
|
|
54
|
-
{
|
|
59
|
+
{#if lead}
|
|
60
|
+
{@render lead()}
|
|
61
|
+
{/if}
|
|
62
|
+
<!-- The label box exists ONLY when `lead` is used. Rendering it for every call
|
|
63
|
+
site changed the DOM everywhere: a `display:contents` span has no box, and
|
|
64
|
+
the geometry audits walk real boxes. Opt-in keeps the legacy shape
|
|
65
|
+
byte-identical and gives the new shape a real box for the CAP-band trim a token wants. -->
|
|
66
|
+
{#if lead}<span class="era-text-trim-caps">{@render children?.()}</span
|
|
67
|
+
>{:else}{@render children?.()}{/if}
|
|
55
68
|
</span>
|
|
@@ -28,6 +28,10 @@ import type { HTMLAttributes } from 'svelte/elements';
|
|
|
28
28
|
type $$ComponentProps = HTMLAttributes<HTMLSpanElement> & {
|
|
29
29
|
ref?: HTMLSpanElement | null;
|
|
30
30
|
tone?: 'default' | 'accent' | 'destructive' | 'success' | 'warning';
|
|
31
|
+
/** Icon rendered BEFORE the label, outside it. Use this rather than putting
|
|
32
|
+
* an icon in `children`: the label then gets its own box, which is the only
|
|
33
|
+
* place era's ink centring can reach. */
|
|
34
|
+
lead?: Snippet;
|
|
31
35
|
children?: Snippet;
|
|
32
36
|
};
|
|
33
37
|
declare const Badge: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
loading = false,
|
|
16
16
|
loadingLabel,
|
|
17
17
|
disabled,
|
|
18
|
+
lead,
|
|
19
|
+
trail,
|
|
18
20
|
children,
|
|
19
21
|
class: className,
|
|
20
22
|
...restProps
|
|
@@ -32,6 +34,12 @@
|
|
|
32
34
|
loading?: boolean;
|
|
33
35
|
/** Screen-reader-only label announced while `loading` is true. */
|
|
34
36
|
loadingLabel?: string;
|
|
37
|
+
/** Icon rendered BEFORE the label, outside it. Use this rather than putting
|
|
38
|
+
* an icon in `children`: the label then gets its own box, which is the only
|
|
39
|
+
* place era's ink centring can reach (see era-label-ink). */
|
|
40
|
+
lead?: Snippet;
|
|
41
|
+
/** Icon rendered AFTER the label, outside it. Same reason as `lead`. */
|
|
42
|
+
trail?: Snippet;
|
|
35
43
|
children?: Snippet;
|
|
36
44
|
} = $props();
|
|
37
45
|
|
|
@@ -71,12 +79,22 @@
|
|
|
71
79
|
forwards the button's own gap so the icon/label spacing is unchanged.
|
|
72
80
|
-->
|
|
73
81
|
<!--
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
THE LABEL GETS ITS OWN BOX, which is what `lead`/`trail` are for.
|
|
83
|
+
|
|
84
|
+
Bare text in a flex container is an ANONYMOUS flex item, and
|
|
85
|
+
`text-box-trim` can never reach one — so a label beside an icon is centred
|
|
86
|
+
by font metrics rather than by its typographic band, and no CSS can change
|
|
87
|
+
that. A <span> that is a flex item is BLOCKIFIED, so the trim does apply to
|
|
88
|
+
it; putting the icon OUTSIDE that span is what removes the anonymous item.
|
|
89
|
+
|
|
90
|
+
WHAT THIS IS AND IS NOT WORTH: measured on a real button across all four
|
|
91
|
+
density modes and all three shipped stacks, the legacy shape and this one
|
|
92
|
+
both centre their ink to 0px. So this is not a pixel fix — it is an API
|
|
93
|
+
fix. It makes the label ADDRESSABLE, which is what turns
|
|
94
|
+
typography/anonymous-text from a law era permanently violates into one it
|
|
95
|
+
can satisfy, and it is the shape every comparable library uses for
|
|
96
|
+
icon+label. The legacy `<Button><Check /> Save</Button>` still renders
|
|
97
|
+
unchanged, and the audit goes on reporting it.
|
|
80
98
|
-->
|
|
81
99
|
<span
|
|
82
100
|
data-button-content
|
|
@@ -86,6 +104,18 @@
|
|
|
86
104
|
<LoaderCircle class="size-icon shrink-0 animate-spin" aria-hidden="true" />
|
|
87
105
|
{#if loadingLabel}<span class="sr-only">{loadingLabel}</span>{/if}
|
|
88
106
|
{/if}
|
|
89
|
-
|
|
107
|
+
<!-- The label box exists ONLY when `lead`/`trail` are used. Rendering it
|
|
108
|
+
unconditionally changed the DOM for every legacy call site: a
|
|
109
|
+
`display:contents` span has no box, and the geometry audits walk real
|
|
110
|
+
boxes, so an icon button's nesting measurements went to pieces. Opting
|
|
111
|
+
in keeps the old shape byte-identical and gives the new shape a real
|
|
112
|
+
box for the trim to act on. -->
|
|
113
|
+
{#if lead || trail}
|
|
114
|
+
{@render lead?.()}
|
|
115
|
+
{#if children}<span class="era-text-trim">{@render children()}</span>{/if}
|
|
116
|
+
{@render trail?.()}
|
|
117
|
+
{:else}
|
|
118
|
+
{@render children?.()}
|
|
119
|
+
{/if}
|
|
90
120
|
</span>
|
|
91
121
|
</Button.Root>
|
|
@@ -14,6 +14,12 @@ type $$ComponentProps = Button.RootProps & {
|
|
|
14
14
|
loading?: boolean;
|
|
15
15
|
/** Screen-reader-only label announced while `loading` is true. */
|
|
16
16
|
loadingLabel?: string;
|
|
17
|
+
/** Icon rendered BEFORE the label, outside it. Use this rather than putting
|
|
18
|
+
* an icon in `children`: the label then gets its own box, which is the only
|
|
19
|
+
* place era's ink centring can reach (see era-label-ink). */
|
|
20
|
+
lead?: Snippet;
|
|
21
|
+
/** Icon rendered AFTER the label, outside it. Same reason as `lead`. */
|
|
22
|
+
trail?: Snippet;
|
|
17
23
|
children?: Snippet;
|
|
18
24
|
};
|
|
19
25
|
declare const Button: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
package/dist/ui/chip/chip.svelte
CHANGED
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
ref = $bindable(null),
|
|
69
69
|
size = 'chip',
|
|
70
70
|
ondismiss,
|
|
71
|
+
lead,
|
|
71
72
|
children,
|
|
72
73
|
class: className,
|
|
73
74
|
...restProps
|
|
@@ -77,6 +78,10 @@
|
|
|
77
78
|
* that nests inside an md container (a select trigger, a button). */
|
|
78
79
|
size?: 'chip' | 'pill';
|
|
79
80
|
ondismiss?: () => void;
|
|
81
|
+
/** Icon rendered BEFORE the label, outside it. Use this rather than putting
|
|
82
|
+
* an icon in `children`: the label then gets its own box, which is the only
|
|
83
|
+
* place era's ink centring can reach. */
|
|
84
|
+
lead?: Snippet;
|
|
80
85
|
children?: Snippet;
|
|
81
86
|
} = $props();
|
|
82
87
|
</script>
|
|
@@ -86,7 +91,15 @@
|
|
|
86
91
|
class={cn(chipVariants({ size, dismissible: !!ondismiss }), className)}
|
|
87
92
|
{...restProps}
|
|
88
93
|
>
|
|
89
|
-
{
|
|
94
|
+
{#if lead}
|
|
95
|
+
{@render lead()}
|
|
96
|
+
{/if}
|
|
97
|
+
<!-- The label box exists ONLY when `lead` is used. Rendering it for every call
|
|
98
|
+
site changed the DOM everywhere: a `display:contents` span has no box, and
|
|
99
|
+
the geometry audits walk real boxes. Opt-in keeps the legacy shape
|
|
100
|
+
byte-identical and gives the new shape a real box for the trim. -->
|
|
101
|
+
{#if lead}<span class="era-text-trim">{@render children?.()}</span
|
|
102
|
+
>{:else}{@render children?.()}{/if}
|
|
90
103
|
{#if ondismiss}
|
|
91
104
|
<!--
|
|
92
105
|
Stop pointerdown/up from bubbling to ancestor triggers (Select.Trigger,
|
|
@@ -34,6 +34,10 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
34
34
|
* that nests inside an md container (a select trigger, a button). */
|
|
35
35
|
size?: 'chip' | 'pill';
|
|
36
36
|
ondismiss?: () => void;
|
|
37
|
+
/** Icon rendered BEFORE the label, outside it. Use this rather than putting
|
|
38
|
+
* an icon in `children`: the label then gets its own box, which is the only
|
|
39
|
+
* place era's ink centring can reach. */
|
|
40
|
+
lead?: Snippet;
|
|
37
41
|
children?: Snippet;
|
|
38
42
|
};
|
|
39
43
|
declare const Chip: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
bind:ref
|
|
16
16
|
align="start"
|
|
17
17
|
class={cn(
|
|
18
|
-
'z-
|
|
18
|
+
'z-overlay w-max min-w-(--bits-combobox-anchor-width) overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
|
|
19
19
|
className
|
|
20
20
|
)}
|
|
21
21
|
{sideOffset}
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
role="listbox"
|
|
267
267
|
tabindex="-1"
|
|
268
268
|
aria-label="Commands"
|
|
269
|
-
class="absolute top-full left-0 z-
|
|
269
|
+
class="absolute top-full left-0 z-overlay max-h-64 w-full overflow-x-hidden overflow-y-auto bg-elevated shadow-lg glass-blur"
|
|
270
270
|
onmousedown={(e) => e.preventDefault()}
|
|
271
271
|
>
|
|
272
272
|
{#if !filtered.length}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// 2px frame that stops a full-width highlight from breaking their chrome.
|
|
22
22
|
// A fixed gutter was tried here and rejected: it padded flat menus too.
|
|
23
23
|
class={cn(
|
|
24
|
-
'z-
|
|
24
|
+
'z-overlay w-max overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
|
|
25
25
|
className
|
|
26
26
|
)}
|
|
27
27
|
{sideOffset}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<ContextMenu.SubContent
|
|
16
16
|
bind:ref
|
|
17
17
|
class={cn(
|
|
18
|
-
'z-
|
|
18
|
+
'z-overlay w-max overflow-hidden rounded-control bg-elevated p-menu shadow-lg glass-blur',
|
|
19
19
|
className
|
|
20
20
|
)}
|
|
21
21
|
{sideOffset}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<DatePicker.Content
|
|
15
15
|
bind:ref
|
|
16
16
|
align="start"
|
|
17
|
-
class={cn('z-
|
|
17
|
+
class={cn('z-overlay rounded-bar bg-elevated p-content shadow-lg glass-blur', className)}
|
|
18
18
|
{sideOffset}
|
|
19
19
|
{...restProps}
|
|
20
20
|
>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<DateRangePicker.Content
|
|
15
15
|
bind:ref
|
|
16
16
|
align="start"
|
|
17
|
-
class={cn('z-
|
|
17
|
+
class={cn('z-overlay rounded-bar bg-elevated p-content shadow-lg glass-blur', className)}
|
|
18
18
|
{sideOffset}
|
|
19
19
|
{...restProps}
|
|
20
20
|
>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<Dialog.Content
|
|
31
31
|
bind:ref
|
|
32
32
|
class={cn(
|
|
33
|
-
'fixed top-1/2 left-1/2 z-
|
|
33
|
+
'fixed top-1/2 left-1/2 z-overlay flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col border border-divider bg-well shadow-lg glass-blur',
|
|
34
34
|
header ? 'rounded-control' : 'gap-gutter rounded-bar p-card',
|
|
35
35
|
className
|
|
36
36
|
)}
|