@signal9/era-ui 30.4.0 → 30.5.1
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/CHANGELOG.md +12 -0
- package/dist/ui/card/card.svelte +36 -6
- package/dist/ui/card/card.svelte.d.ts +29 -8
- package/dist/ui/meter/meter.svelte +68 -22
- package/dist/ui/meter/meter.svelte.d.ts +21 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [30.5.1](https://github.com/sig-nine/era-ui/compare/v30.5.0...v30.5.1) (2026-08-16)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* **card:** a pressable card depresses, and the tones say what they paint ([7890b96](https://github.com/sig-nine/era-ui/commit/7890b969b5064df8a6716d530e21a82f23e68601))
|
|
6
|
+
|
|
7
|
+
## [30.5.0](https://github.com/sig-nine/era-ui/compare/v30.4.0...v30.5.0) (2026-08-16)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **meter:** start and end snippets caption what the range means ([1a6bf54](https://github.com/sig-nine/era-ui/commit/1a6bf544b5b3992649f66c5e9a05f24057f495cd))
|
|
12
|
+
|
|
1
13
|
## [30.4.0](https://github.com/sig-nine/era-ui/compare/v30.3.0...v30.4.0) (2026-08-16)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/dist/ui/card/card.svelte
CHANGED
|
@@ -32,8 +32,17 @@
|
|
|
32
32
|
// was: the even-gap law, broken by default, in the component that
|
|
33
33
|
// bounds more consumer content than any other.
|
|
34
34
|
sectioned: { true: 'p-0', false: 'p-card' },
|
|
35
|
+
// A CARD YOU CAN PRESS DEPRESSES. `active:shadow-pressed` is on every
|
|
36
|
+
// other era control and was missing here, so a pressable card was the
|
|
37
|
+
// one thing in the library that lit up on hover and then did nothing
|
|
38
|
+
// under the finger — on bevel, where the pressed face is an inverted
|
|
39
|
+
// chisel, that is the difference between a control and a picture of
|
|
40
|
+
// one. Inert on flat (the token is `none`) and on glass (it resolves
|
|
41
|
+
// to the resting shadow), so only the surface that has a pressed face
|
|
42
|
+
// shows it. No content sink to go with it: the sink moves a control's
|
|
43
|
+
// single content wrapper, and a card's children are the consumer's.
|
|
35
44
|
interactive: {
|
|
36
|
-
true: 'cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50'
|
|
45
|
+
true: 'cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50'
|
|
37
46
|
}
|
|
38
47
|
},
|
|
39
48
|
defaultVariants: { tone: 'elevated', sectioned: false, interactive: false }
|
|
@@ -93,11 +102,32 @@
|
|
|
93
102
|
ref?: CardElement | null;
|
|
94
103
|
/**
|
|
95
104
|
* Surface tier the card paints on.
|
|
96
|
-
* - `elevated` (default): the lifted surface
|
|
97
|
-
*
|
|
98
|
-
* - `
|
|
99
|
-
*
|
|
100
|
-
* - `
|
|
105
|
+
* - `elevated` (default): the lifted surface, with a resting shadow. Use
|
|
106
|
+
* on the page background.
|
|
107
|
+
* - `surface`: the base surface tier, no shadow. Use when nesting cards
|
|
108
|
+
* inside another elevated container.
|
|
109
|
+
* - `well`: recessed. For content the surrounding UI puts INTO the card —
|
|
110
|
+
* a tool's output pane, a transcript body — the container twin of a
|
|
111
|
+
* text field's fill.
|
|
112
|
+
* - `flat`: no background; only the rounded/padded shell. For grouping
|
|
113
|
+
* content visually without adding contrast.
|
|
114
|
+
*
|
|
115
|
+
* READ THE CLASSES AND THEY LOOK SWAPPED. They are not, and this is
|
|
116
|
+
* worth a sentence because a consumer read them, believed it, and
|
|
117
|
+
* shipped flat cards for months. `surface` paints `bg-well` and `well`
|
|
118
|
+
* paints `bg-field`, because era's utility names describe FILLS rather
|
|
119
|
+
* than tiers: `bg-well` is the surface's own resting fill (it is what a
|
|
120
|
+
* panel is painted with, and it pairs with `shadow-well` when you want
|
|
121
|
+
* recessed chrome), and `bg-field` is the recessed fill a text input
|
|
122
|
+
* sits in. The tone names are the tiers; the class names are the paints.
|
|
123
|
+
*
|
|
124
|
+
* A CARD THAT IS PRESSABLE SHOULD USUALLY BE `elevated`. Passing `href`
|
|
125
|
+
* or `onclick` turns on the interactive face — hover, focus, press — but
|
|
126
|
+
* it deliberately does NOT override the tier you asked for, so a
|
|
127
|
+
* `surface` link card has no resting lift and reads flatter than the
|
|
128
|
+
* static cards around it. That combination was reported as a bug and it
|
|
129
|
+
* is a choice: a tone that silently changed under an event handler would
|
|
130
|
+
* be a worse one.
|
|
101
131
|
*/
|
|
102
132
|
tone?: 'elevated' | 'surface' | 'well' | 'flat';
|
|
103
133
|
/** Rendered above the body with a subtle divider. */
|
|
@@ -10,7 +10,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
|
|
|
10
10
|
false: "p-card";
|
|
11
11
|
};
|
|
12
12
|
interactive: {
|
|
13
|
-
true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
|
|
13
|
+
true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
|
|
14
14
|
};
|
|
15
15
|
}, undefined, "block w-full rounded-bar text-left text-fg", {
|
|
16
16
|
tone: {
|
|
@@ -24,7 +24,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
|
|
|
24
24
|
false: "p-card";
|
|
25
25
|
};
|
|
26
26
|
interactive: {
|
|
27
|
-
true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
|
|
27
|
+
true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
|
|
28
28
|
};
|
|
29
29
|
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
30
30
|
tone: {
|
|
@@ -38,7 +38,7 @@ export declare const cardVariants: import("tailwind-variants").TVReturnType<{
|
|
|
38
38
|
false: "p-card";
|
|
39
39
|
};
|
|
40
40
|
interactive: {
|
|
41
|
-
true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
|
|
41
|
+
true: "cursor-pointer hover:bg-highlight hover:shadow-highlight focus-visible:bg-highlight active:shadow-pressed era-interactive aria-disabled:cursor-not-allowed aria-disabled:opacity-50";
|
|
42
42
|
};
|
|
43
43
|
}, undefined>>;
|
|
44
44
|
import type { Snippet } from 'svelte';
|
|
@@ -50,11 +50,32 @@ type $$ComponentProps = HTMLAttributes<HTMLElement> & AnchorBits & ButtonBits &
|
|
|
50
50
|
ref?: CardElement | null;
|
|
51
51
|
/**
|
|
52
52
|
* Surface tier the card paints on.
|
|
53
|
-
* - `elevated` (default): the lifted surface
|
|
54
|
-
*
|
|
55
|
-
* - `
|
|
56
|
-
*
|
|
57
|
-
* - `
|
|
53
|
+
* - `elevated` (default): the lifted surface, with a resting shadow. Use
|
|
54
|
+
* on the page background.
|
|
55
|
+
* - `surface`: the base surface tier, no shadow. Use when nesting cards
|
|
56
|
+
* inside another elevated container.
|
|
57
|
+
* - `well`: recessed. For content the surrounding UI puts INTO the card —
|
|
58
|
+
* a tool's output pane, a transcript body — the container twin of a
|
|
59
|
+
* text field's fill.
|
|
60
|
+
* - `flat`: no background; only the rounded/padded shell. For grouping
|
|
61
|
+
* content visually without adding contrast.
|
|
62
|
+
*
|
|
63
|
+
* READ THE CLASSES AND THEY LOOK SWAPPED. They are not, and this is
|
|
64
|
+
* worth a sentence because a consumer read them, believed it, and
|
|
65
|
+
* shipped flat cards for months. `surface` paints `bg-well` and `well`
|
|
66
|
+
* paints `bg-field`, because era's utility names describe FILLS rather
|
|
67
|
+
* than tiers: `bg-well` is the surface's own resting fill (it is what a
|
|
68
|
+
* panel is painted with, and it pairs with `shadow-well` when you want
|
|
69
|
+
* recessed chrome), and `bg-field` is the recessed fill a text input
|
|
70
|
+
* sits in. The tone names are the tiers; the class names are the paints.
|
|
71
|
+
*
|
|
72
|
+
* A CARD THAT IS PRESSABLE SHOULD USUALLY BE `elevated`. Passing `href`
|
|
73
|
+
* or `onclick` turns on the interactive face — hover, focus, press — but
|
|
74
|
+
* it deliberately does NOT override the tier you asked for, so a
|
|
75
|
+
* `surface` link card has no resting lift and reads flatter than the
|
|
76
|
+
* static cards around it. That combination was reported as a bug and it
|
|
77
|
+
* is a choice: a tone that silently changed under an event handler would
|
|
78
|
+
* be a worse one.
|
|
58
79
|
*/
|
|
59
80
|
tone?: 'elevated' | 'surface' | 'well' | 'flat';
|
|
60
81
|
/** Rendered above the body with a subtle divider. */
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
37
|
<script lang="ts">
|
|
38
|
+
import type { Snippet } from 'svelte';
|
|
38
39
|
import { Meter } from 'bits-ui';
|
|
39
40
|
import { cn } from '../../utils/index.js';
|
|
40
41
|
|
|
@@ -44,6 +45,8 @@
|
|
|
44
45
|
min = 0,
|
|
45
46
|
max = 100,
|
|
46
47
|
tone = 'default',
|
|
48
|
+
start,
|
|
49
|
+
end,
|
|
47
50
|
class: className,
|
|
48
51
|
...restProps
|
|
49
52
|
}: Meter.RootProps & {
|
|
@@ -57,30 +60,73 @@
|
|
|
57
60
|
* `aria-label` / a visible label, never rely on the hue alone.
|
|
58
61
|
*/
|
|
59
62
|
tone?: 'default' | 'accent' | 'destructive' | 'success' | 'warning' | 'info';
|
|
63
|
+
/**
|
|
64
|
+
* Captions under the two ends of the bar — what the range MEANS.
|
|
65
|
+
*
|
|
66
|
+
* A meter states a proportion and leaves the reader to work out what of.
|
|
67
|
+
* "0m used" on the left and "50h before approval" on the right is not
|
|
68
|
+
* decoration, it is the reading; every consumer of a bare meter ends up
|
|
69
|
+
* hand-rolling the same flex row underneath it, which is where this came
|
|
70
|
+
* from. `start` is left, `end` is right, either may stand alone.
|
|
71
|
+
*
|
|
72
|
+
* NOT ANNOUNCED. They are captions, so a screen reader gets the meter's
|
|
73
|
+
* value and its own `aria-label`, not these. If the range needs saying
|
|
74
|
+
* out loud, say it in the label too — the same rule `tone` carries.
|
|
75
|
+
*
|
|
76
|
+
* THE DOM IS UNCHANGED WITHOUT THEM. With neither snippet this renders
|
|
77
|
+
* exactly the element it always did, so `class` still lands on the track
|
|
78
|
+
* and the geometry audits still walk the same box. Passing one wraps the
|
|
79
|
+
* track in a column, and `class` still lands on the track.
|
|
80
|
+
*/
|
|
81
|
+
start?: Snippet;
|
|
82
|
+
end?: Snippet;
|
|
60
83
|
} = $props();
|
|
61
84
|
|
|
62
85
|
let percentage = $derived(((value - min) / (max - min)) * 100);
|
|
63
86
|
</script>
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
{#snippet track()}
|
|
89
|
+
<Meter.Root
|
|
90
|
+
bind:ref
|
|
91
|
+
{value}
|
|
92
|
+
{min}
|
|
93
|
+
{max}
|
|
94
|
+
data-tone={tone}
|
|
95
|
+
class={cn(
|
|
96
|
+
// bg-field, not bg-elevated: the track is recessed (era-track-well), so it
|
|
97
|
+
// wears the recessed tier. See Checkbox for the full version of this note.
|
|
98
|
+
// era-track-well, not shadow-well: the fill below covers the whole box,
|
|
99
|
+
// and an inset shadow paints under its own descendants — so the plain
|
|
100
|
+
// well survived only on the empty side of the value.
|
|
101
|
+
'era-track-well h-(--era-sp) w-full overflow-hidden rounded-control bg-field',
|
|
102
|
+
className
|
|
103
|
+
)}
|
|
104
|
+
{...restProps}
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
class={meterFillVariants({ tone })}
|
|
108
|
+
style="transform: translateX(-{100 - percentage}%)"
|
|
109
|
+
></div>
|
|
110
|
+
</Meter.Root>
|
|
111
|
+
{/snippet}
|
|
112
|
+
|
|
113
|
+
<!--
|
|
114
|
+
The wrapper exists ONLY when a caption does. Rendering it always would change
|
|
115
|
+
the element every existing consumer measures and styles — the same opt-in the
|
|
116
|
+
Button and Chip label boxes are built on, for the same reason.
|
|
117
|
+
|
|
118
|
+
`justify-between` rather than a grid: with one caption it pins to its own end,
|
|
119
|
+
which is what a lone "0 used" or a lone "quota" should do. min-w-0 + truncate
|
|
120
|
+
because a caption is prose and the bar's width is not negotiable.
|
|
121
|
+
-->
|
|
122
|
+
{#if start || end}
|
|
123
|
+
<div class="flex flex-col gap-gutter">
|
|
124
|
+
{@render track()}
|
|
125
|
+
<div class="flex items-baseline justify-between gap-gutter text-body text-muted">
|
|
126
|
+
<span class="min-w-0 truncate">{@render start?.()}</span>
|
|
127
|
+
<span class="min-w-0 truncate text-right">{@render end?.()}</span>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
{:else}
|
|
131
|
+
{@render track()}
|
|
132
|
+
{/if}
|
|
@@ -26,6 +26,7 @@ export declare const meterFillVariants: import("tailwind-variants").TVReturnType
|
|
|
26
26
|
info: "bg-info";
|
|
27
27
|
};
|
|
28
28
|
}, undefined>>;
|
|
29
|
+
import type { Snippet } from 'svelte';
|
|
29
30
|
import { Meter } from 'bits-ui';
|
|
30
31
|
type $$ComponentProps = Meter.RootProps & {
|
|
31
32
|
/**
|
|
@@ -38,6 +39,26 @@ type $$ComponentProps = Meter.RootProps & {
|
|
|
38
39
|
* `aria-label` / a visible label, never rely on the hue alone.
|
|
39
40
|
*/
|
|
40
41
|
tone?: 'default' | 'accent' | 'destructive' | 'success' | 'warning' | 'info';
|
|
42
|
+
/**
|
|
43
|
+
* Captions under the two ends of the bar — what the range MEANS.
|
|
44
|
+
*
|
|
45
|
+
* A meter states a proportion and leaves the reader to work out what of.
|
|
46
|
+
* "0m used" on the left and "50h before approval" on the right is not
|
|
47
|
+
* decoration, it is the reading; every consumer of a bare meter ends up
|
|
48
|
+
* hand-rolling the same flex row underneath it, which is where this came
|
|
49
|
+
* from. `start` is left, `end` is right, either may stand alone.
|
|
50
|
+
*
|
|
51
|
+
* NOT ANNOUNCED. They are captions, so a screen reader gets the meter's
|
|
52
|
+
* value and its own `aria-label`, not these. If the range needs saying
|
|
53
|
+
* out loud, say it in the label too — the same rule `tone` carries.
|
|
54
|
+
*
|
|
55
|
+
* THE DOM IS UNCHANGED WITHOUT THEM. With neither snippet this renders
|
|
56
|
+
* exactly the element it always did, so `class` still lands on the track
|
|
57
|
+
* and the geometry audits still walk the same box. Passing one wraps the
|
|
58
|
+
* track in a column, and `class` still lands on the track.
|
|
59
|
+
*/
|
|
60
|
+
start?: Snippet;
|
|
61
|
+
end?: Snippet;
|
|
41
62
|
};
|
|
42
63
|
declare const Meter: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
43
64
|
type Meter = ReturnType<typeof Meter>;
|