@signal9/era-ui 2.25.0 → 2.27.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.
@@ -36,7 +36,10 @@
36
36
  }
37
37
 
38
38
  /* Shared interactive-state recipe: quiet focus, disabled affordance.
39
- * Equivalent to `focus:outline-none disabled:cursor-not-allowed disabled:opacity-50`. */
39
+ * Equivalent to `focus:outline-none disabled:cursor-not-allowed disabled:opacity-50`.
40
+ *
41
+ * @use the focus/disabled recipe every era control carries — put it on a custom
42
+ * interactive element instead of hand-writing focus/disabled variants. */
40
43
  @utility era-interactive {
41
44
  &:focus {
42
45
  outline: none;
@@ -54,7 +57,11 @@
54
57
  * "extra" — matching every other era transition. Font-agnostic: it styles
55
58
  * decoration + colour only, so it reads correctly in mono, sans, or serif, and
56
59
  * the 0.2em offset scales with the type size. Keyboard focus mirrors hover so
57
- * the affordance is reachable without a pointer. */
60
+ * the affordance is reachable without a pointer.
61
+ *
62
+ * @use understated, motion-aware inline text link — the canonical style for a
63
+ * link in body copy or prose; prefer it over a hand-rolled underline or
64
+ * `Button variant="link"` (which is a control, not running text). */
58
65
  @utility era-link {
59
66
  /* The text is bright at rest and never changes — the link reads as a link
60
67
  * before you touch it. Only the UNDERLINE reacts: a faint half-strength-muted
@@ -71,7 +78,10 @@
71
78
  }
72
79
 
73
80
  /* Scrollable but chromeless — hides the scrollbar while keeping wheel / touch /
74
- * drag scrolling (e.g. an overflowing tab strip in a small pane handle). */
81
+ * drag scrolling (e.g. an overflowing tab strip in a small pane handle).
82
+ *
83
+ * @use hide the scrollbar on an overflowing strip or rail while keeping it
84
+ * scrollable — for a scroll region that should show its bar, use ScrollArea. */
75
85
  @utility scrollbar-none {
76
86
  scrollbar-width: none;
77
87
  &::-webkit-scrollbar {
@@ -101,6 +111,10 @@
101
111
  * command-input) and clipping display tokens (KV) correctly carry it. For
102
112
  * truncating text, put the trim on the fixed-height ROW and let the child
103
113
  * center via the row's items-center.
114
+ *
115
+ * @use vertically center single-line control text (a label beside an icon, an
116
+ * input's own text) without a translateY nudge — fixed-height elements only;
117
+ * see era-text-trim-caps for ALL-CAPS strings.
104
118
  */
105
119
  @utility era-text-trim {
106
120
  line-height: 1;
@@ -108,7 +122,10 @@
108
122
  }
109
123
 
110
124
  /* ALL-CAPS single-line text (kbd hints, acronym badges): the x-height band
111
- * misjudges caps-only strings — cap/alphabetic edges are the right box. */
125
+ * misjudges caps-only strings — cap/alphabetic edges are the right box.
126
+ *
127
+ * @use the era-text-trim variant for ALL-CAPS single-line strings (kbd hints,
128
+ * acronym badges) — never use it for mixed-case text. */
112
129
  @utility era-text-trim-caps {
113
130
  line-height: 1;
114
131
  text-box: trim-both cap alphabetic;
@@ -118,7 +135,10 @@
118
135
  * "working…" label treatment in streaming UIs). Timing derives from the motion
119
136
  * axis (10× --era-duration ≈ 1.5s at normal, 2.6s at extra); at instant the
120
137
  * duration is 0s and the sweep freezes on its base frame — plain muted text —
121
- * so reduced-motion and data-motion="instant" both kill it for free. */
138
+ * so reduced-motion and data-motion="instant" both kill it for free.
139
+ *
140
+ * @use the in-progress text treatment — put it on a "working…" / "thinking…"
141
+ * label while a task streams, instead of a spinner beside the text. */
122
142
  @utility era-shimmer {
123
143
  background: linear-gradient(
124
144
  110deg,
@@ -91,9 +91,64 @@
91
91
  * the icon/label depresses like a Win95 keycap pushed in from the top-left. */
92
92
  --era-press-sink-x: 1px;
93
93
  --era-press-sink-y: 1px;
94
+ /* Angle of the shade a pressed face sits under, DERIVED from the one light
95
+ * knob rather than picked by eye: the shadow runs along the light's own
96
+ * direction vector (side, 1), so relighting the surface relights it too.
97
+ * side=1 → 135deg (the classic corner-lit diagonal); side=0.6 → ~149deg, the
98
+ * light rotated toward straight-above. Falls from the TOP-LEFT because that is
99
+ * the corner a well leaves in shade (see --era-shadow-well: dark chisel on
100
+ * top/left, lit chisel on bottom/right — the exact inverse of a raised face). */
101
+ --era-bevel-press-angle: calc(180deg - atan2(var(--era-bevel-side), 1));
102
+ --era-bevel-press-shade: light-dark(oklch(0 0 0 / 0.2), oklch(0 0 0 / 0.28));
94
103
  /* Floating panels wear a 2-layer chisel frame; row highlights inset past
95
104
  * it with a concentric radius (square under data-corners="square", as
96
105
  * the era had it — Win95 drew its selection bar inside the border). */
97
106
  --era-panel-inset: 2px;
98
107
  --era-item-rd-scale: 1;
99
108
  }
109
+
110
+ /*
111
+ * PRESS SHADE — the shadow a sunken face sits under (icon buttons).
112
+ *
113
+ * Pressing a bevel button turns its face into a well, and the frame's lit edge
114
+ * then casts a shade down into it. The chisel alone can't express that: it draws
115
+ * the WALLS of the well but leaves the floor evenly lit, so a pressed icon looked
116
+ * exactly as bright as a raised one — the glyph read as floating above the recess
117
+ * instead of sitting in it.
118
+ *
119
+ * The film lies ABOVE the content, not under it, and that is the whole point: a
120
+ * shadow cast into a well falls across whatever is in the well, glyph included.
121
+ * The icon darkens WITH the face it sits on rather than hovering over it,
122
+ * untouched. It rides the frame, not the content — `[&:active>*]` sinks the
123
+ * icon 1px, but the shade holds still, because the frame is what casts it.
124
+ *
125
+ * The overlay is always mounted at opacity 0 so the fade can ride the motion
126
+ * axis (0s at instant → it snaps; longer at extra). A pseudo-element that only
127
+ * came into being on :active would have nothing to transition FROM.
128
+ *
129
+ * `link` buttons are excluded: they are chromeless tinted text with no face, and
130
+ * a film laid over prose is just dirt. Bevel is the only surface that does this —
131
+ * flat and glass express press as tone, and cartoon flattens the sticker.
132
+ */
133
+ [data-surface='bevel'] button:not([data-variant='link']):has(svg) {
134
+ position: relative;
135
+ }
136
+
137
+ [data-surface='bevel'] button:not([data-variant='link']):has(svg)::after {
138
+ content: '';
139
+ position: absolute;
140
+ inset: 0;
141
+ border-radius: inherit;
142
+ pointer-events: none;
143
+ opacity: 0;
144
+ background-image: linear-gradient(
145
+ var(--era-bevel-press-angle),
146
+ var(--era-bevel-press-shade),
147
+ transparent 70%
148
+ );
149
+ transition: opacity var(--era-duration) var(--era-ease);
150
+ }
151
+
152
+ [data-surface='bevel'] button:not([data-variant='link']):has(svg):active::after {
153
+ opacity: 1;
154
+ }
@@ -118,7 +118,11 @@
118
118
  }
119
119
 
120
120
  /* Tailwind v4 cannot resolve var() inside backdrop-blur-[], so the filter
121
- * is a registered utility, gated to glass subtrees. */
121
+ * is a registered utility, gated to glass subtrees.
122
+ *
123
+ * @use the backdrop blur for a floating panel (popover, dialog, taskbar) — it
124
+ * applies only inside a data-surface="glass" subtree and is inert elsewhere,
125
+ * so it is safe to leave on a panel that renders under every surface. */
122
126
  @utility glass-blur {
123
127
  [data-surface='glass'] & {
124
128
  backdrop-filter: blur(var(--era-blur)) saturate(var(--era-saturate));
@@ -46,12 +46,18 @@
46
46
  hover sweep is what surfaced this — a latched button correctly shows no hover
47
47
  delta (it must keep its pressed face), but with no attribute to read, that is
48
48
  indistinguishable from a control with no hover state at all.
49
+
50
+ `variant` is exposed for the same reason: a surface has to be able to tell a
51
+ chromed button from a chromeless `link` one. bevel's press shade is a film laid
52
+ over the button's face, which a link button does not have — and a class is not
53
+ something CSS can ask about.
49
54
  -->
50
55
  <Button.Root
51
56
  bind:ref
52
57
  disabled={effectiveDisabled}
53
58
  aria-busy={loading || undefined}
54
59
  data-state={active ? 'on' : undefined}
60
+ data-variant={variant}
55
61
  class={cn(buttonVariants({ variant, tone, size, icon, active }), className)}
56
62
  {...restProps}
57
63
  >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "2.25.0",
3
+ "version": "2.27.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",
@@ -65,7 +65,8 @@
65
65
  },
66
66
  "./css": "./dist/era-ui.css",
67
67
  "./styles/raw": "./dist/styles/index.css",
68
- "./styles/themes": "./dist/styles/themes.css"
68
+ "./styles/themes": "./dist/styles/themes.css",
69
+ "./utilities.json": "./dist/generated-docs/utilities.json"
69
70
  },
70
71
  "peerDependencies": {
71
72
  "svelte": "^5.0.0",
package/skill/SKILL.md CHANGED
@@ -115,6 +115,23 @@ motion-axis-aware for free.
115
115
  - Headings balance and prose wraps pretty automatically — don't re-add
116
116
  `text-balance`/`text-pretty` except on non-semantic prose elements.
117
117
 
118
+ ## CSS utilities (classes, not components)
119
+
120
+ Not everything era exposes is a component — these ride along with the stylesheet
121
+ and need no import. Reach for one before hand-rolling the same styling or bending
122
+ a component into the role. Full reference: `/utilities.md`; machine-readable list:
123
+ `/utilities.json` (or `@sig-nine/era-ui/utilities.json`).
124
+
125
+ | Class | Use it for |
126
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------- |
127
+ | `era-link` | inline text link in body copy/prose — NOT `Button variant="link"` (that's a control) and never a hand-rolled underline |
128
+ | `era-interactive` | the focus/disabled recipe on a custom control (every era wrapper carries it) |
129
+ | `era-text-trim` | ink-centering single-line control text beside an icon; fixed-height elements only |
130
+ | `era-text-trim-caps` | the same for ALL-CAPS strings (kbd hints, acronym badges) |
131
+ | `era-shimmer` | a "working…" / streaming label — motion-aware, dies at `instant` and under reduced-motion |
132
+ | `scrollbar-none` | an overflowing strip that scrolls with no visible bar (ScrollArea is for when the bar IS the design) |
133
+ | `glass-blur` | backdrop blur on a floating panel; inert outside a `data-surface="glass"` subtree |
134
+
118
135
  ## Composing the library
119
136
 
120
137
  ```svelte