@signal9/era-ui 4.11.0 → 4.11.2

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.
@@ -78,9 +78,21 @@
78
78
  * that gap; pushing it darker closes it. */
79
79
  --color-destructive: oklch(0.65 0.2 25); /* #f14d4c */
80
80
  --color-destructive-fg: oklch(0.96 0 0);
81
- --color-success: oklch(0.65 0.17 145);
81
+ /* The semantic trio is one family: same lightness and chroma as destructive,
82
+ * varying only in hue, so a success fill and a destructive fill carry the same
83
+ * weight — which is what makes the pane's traffic lights read as three lights
84
+ * rather than three unrelated colours.
85
+ *
86
+ * Green follows exactly (0.65 / 0.2). Yellow CANNOT, and this is a gamut wall
87
+ * rather than a preference: sRGB holds no dark saturated yellow. At the red's
88
+ * lightness, hue 85 caps at chroma 0.13 and renders #b3880f — brown, not
89
+ * yellow. So warning takes the deepest lightness that still reads yellow at its
90
+ * own ceiling (0.75 is the max chroma for that lightness), which closes most of
91
+ * the gap: measured against the page, the three now sit at 5.9 / 7.0 / 9.3
92
+ * where they sat at 5.9 / 7.0 / 11.1. */
93
+ --color-success: oklch(0.65 0.2 145);
82
94
  --color-success-fg: oklch(0.96 0 0);
83
- --color-warning: oklch(0.8 0.16 85);
95
+ --color-warning: oklch(0.75 0.15 85);
84
96
  --color-warning-fg: oklch(0.162 0 0);
85
97
  }
86
98
 
@@ -117,7 +129,10 @@
117
129
  --color-primary-hover: color-mix(in oklch, var(--color-primary) 85%, black);
118
130
  --color-destructive: oklch(0.65 0.2 25); /* #f14d4c */
119
131
  --color-destructive-fg: oklch(0.98 0 0);
120
- --color-success: oklch(0.5 0.17 145);
132
+ /* Chroma matched to destructive like the dark theme; the LIGHTNESSES stay
133
+ * where they are, because here they carry white text — green at the red's
134
+ * 0.65 drops to 2.99:1 against white, under the 3:1 a filled glyph needs. */
135
+ --color-success: oklch(0.5 0.2 145);
121
136
  --color-success-fg: oklch(0.98 0 0);
122
137
  --color-warning: oklch(0.72 0.15 85);
123
138
  --color-warning-fg: oklch(0.162 0 0);
@@ -39,11 +39,11 @@
39
39
  compoundVariants: [
40
40
  // md ⊃ xxs = (3sp − sp − 10)/2 — the named token.
41
41
  { size: 'md', content: 'xxs', class: 'gap-(--era-xxs-inset-md) px-(--era-xxs-inset-md)' },
42
- // sm ⊃ xxs = (2sp + 6 sp 10)/2 = (sp 4)/2, which is ALSO (h-xxs − text)/2
43
- // at 14px text — the same number --era-inset-xxs already carries, so the
44
- // identity is reused rather than a fifth token invented. (lg ⊃ xxs has no
42
+ // sm ⊃ xxs its own token, not the text inset it used to borrow. The two
43
+ // were the same number only while type was 14px at every density; dense's
44
+ // 12px broke the tie and left this pairing a pixel wide. (lg ⊃ xxs has no
45
45
  // token because nothing pairs a page header with pill-tier controls.)
46
- { size: 'sm', content: 'xxs', class: 'gap-(--era-inset-xxs) px-(--era-inset-xxs)' }
46
+ { size: 'sm', content: 'xxs', class: 'gap-(--era-xxs-inset-sm) px-(--era-xxs-inset-sm)' }
47
47
  ],
48
48
  defaultVariants: { size: 'lg', content: 'tier' }
49
49
  });
@@ -68,15 +68,24 @@
68
68
  Popover.Trigger, etc.) that open on pointerdown — otherwise dismissing
69
69
  a chip inside a trigger would also toggle the trigger.
70
70
  -->
71
+ <!-- The glyph steps up a sub-tier, icon-xs -> icon-xxs (10 -> 12px at dense),
72
+ the same exception Pane.Close takes and for the same reason: a dismiss is
73
+ a control you have to hit, and half of a Lucide glyph's box is padding
74
+ inside its own viewBox, so icon-xs drew a 5px × in a 14px button. 12
75
+ leaves a 1px inset on the BOX while the ink gains a fifth of its size.
76
+ It has to be written as [&_svg]: because that is the selector Button's
77
+ icon variant uses — the bare size-* that sat on the <X> lost to it and
78
+ had no effect at all. -->
71
79
  <Button
72
80
  icon
73
81
  size="icon"
74
82
  tone="destructive"
83
+ class="[&_svg]:size-(--era-icon-xxs)"
75
84
  onclick={ondismiss}
76
85
  onpointerdown={(e: PointerEvent) => e.stopPropagation()}
77
86
  onpointerup={(e: PointerEvent) => e.stopPropagation()}
78
87
  >
79
- <X class="size-(--era-h-xs)" />
88
+ <X />
80
89
  </Button>
81
90
  {/if}
82
91
  </div>
@@ -23,23 +23,39 @@
23
23
  const copyText = () => code ?? ref?.querySelector('code')?.textContent ?? '';
24
24
  </script>
25
25
 
26
+ <!-- The same material a text field paints: the recessed fill (--era-field-bg)
27
+ under the well shadow, with the field's own side padding. A code block is
28
+ content the surrounding UI puts INTO the page — the same relationship a
29
+ textarea has to its form — and it used to read as RAISED chrome (bg-surface
30
+ + --era-shadow), which said the opposite. Sharing the tokens rather than the
31
+ variants keeps them in step without pretending this is a field: there is no
32
+ hover or focus tint, because there is nothing to type into.
33
+
34
+ The radius stays rd-lg where the field's is rd-md, and that is the one
35
+ deliberate difference: a container's radius follows its corner REGION, and
36
+ this corner holds the sm-tier copy button at --era-inset-md, so rd-sm + that
37
+ inset is what the corner has to be. An md radius here would leave the button
38
+ visibly off its curve. -->
26
39
  <div
27
40
  bind:this={ref}
28
- class={cn('relative rounded-(--era-rd-lg) bg-surface shadow-(--era-shadow)', className)}
41
+ class={cn(
42
+ 'relative rounded-(--era-rd-lg) bg-(--era-field-bg) shadow-(--era-shadow-well)',
43
+ className
44
+ )}
29
45
  {...restProps}
30
46
  >
31
47
  <pre
32
- class="overflow-x-auto p-(--era-inset-md) pr-[calc(var(--era-inset-md)+var(--era-h-sm)+var(--era-inset-md))] font-mono text-body leading-relaxed text-fg"><code
48
+ class="overflow-x-auto px-(--era-field-px) py-(--era-inset-md) pr-[calc(var(--era-field-px)+var(--era-h-sm)+var(--era-field-px))] font-mono text-body leading-relaxed text-fg"><code
33
49
  >{#if children}{@render children()}{:else}{code}{/if}</code
34
50
  ></pre>
35
51
  {#if copyable}
36
- <!-- bg-surface, not transparent: the button floats over a scrollable <pre>,
37
- so long lines would show through a chromeless button. The block's own
38
- fill is bg-surface, so at rest this is pixel-identical to no background
39
- — it just occludes whatever scrolls beneath. -->
52
+ <!-- Filled, not transparent: the button floats over a scrollable <pre>, so
53
+ long lines would show through a chromeless one. It carries the block's
54
+ OWN fill, so at rest it is pixel-identical to no background — it just
55
+ occludes whatever scrolls beneath. -->
40
56
  <CopyButton
41
57
  text={copyText}
42
- class="absolute top-[calc(var(--era-inset-md)+(var(--era-text)*1.625-var(--era-h-sm))/2)] right-[calc(var(--era-inset-md)+(var(--era-text)*1.625-var(--era-h-sm))/2)] bg-surface"
58
+ class="absolute top-[calc(var(--era-inset-md)+(var(--era-text)*1.625-var(--era-h-sm))/2)] right-[calc(var(--era-field-px)+(var(--era-text)*1.625-var(--era-h-sm))/2)] bg-(--era-field-bg)"
43
59
  />
44
60
  {/if}
45
61
  </div>
@@ -15,8 +15,16 @@
15
15
  <ContextMenu.Content
16
16
  bind:ref
17
17
  align="start"
18
+ // p-gutter, not p-(--era-panel-inset): the inset is SURFACE-owned and resolves
19
+ // to 0 on flat, so a menu there sat its rows flush against its own edges —
20
+ // the highlight ran edge to edge and the panel read as a list with no frame.
21
+ // The gutter is --era-gap, the same distance that separates the rows from
22
+ // each other, so a row sits an even gap from every edge (the pattern the
23
+ // conversations rail already documents). It is >= the panel inset at every
24
+ // density, so glass and bevel keep the frame protection the inset exists
25
+ // for.
18
26
  class={cn(
19
- 'z-50 w-max overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-(--era-panel-inset) shadow-(--era-shadow-lg) glass-blur',
27
+ 'z-50 w-max overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-gutter shadow-(--era-shadow-lg) glass-blur',
20
28
  className
21
29
  )}
22
30
  {sideOffset}
@@ -13,7 +13,7 @@
13
13
  <ContextMenu.Item
14
14
  bind:ref
15
15
  class={cn(
16
- 'flex h-(--era-h-md) cursor-pointer items-center rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
16
+ 'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
17
17
  className
18
18
  )}
19
19
  {...restProps}
@@ -15,8 +15,16 @@
15
15
  <DropdownMenu.Content
16
16
  bind:ref
17
17
  align="start"
18
+ // p-gutter, not p-(--era-panel-inset): the inset is SURFACE-owned and resolves
19
+ // to 0 on flat, so a menu there sat its rows flush against its own edges —
20
+ // the highlight ran edge to edge and the panel read as a list with no frame.
21
+ // The gutter is --era-gap, the same distance that separates the rows from
22
+ // each other, so a row sits an even gap from every edge (the pattern the
23
+ // conversations rail already documents). It is >= the panel inset at every
24
+ // density, so glass and bevel keep the frame protection the inset exists
25
+ // for.
18
26
  class={cn(
19
- 'z-50 w-max min-w-(--bits-dropdown-menu-anchor-width) overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-(--era-panel-inset) shadow-(--era-shadow-lg) glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
27
+ 'z-50 w-max min-w-(--bits-dropdown-menu-anchor-width) overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-gutter shadow-(--era-shadow-lg) glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
20
28
  className
21
29
  )}
22
30
  {sideOffset}
@@ -13,7 +13,7 @@
13
13
  <DropdownMenu.Item
14
14
  bind:ref
15
15
  class={cn(
16
- 'flex h-(--era-h-md) cursor-pointer items-center rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
16
+ 'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
17
17
  className
18
18
  )}
19
19
  {...restProps}
@@ -15,8 +15,16 @@
15
15
  <Menubar.Content
16
16
  bind:ref
17
17
  align="start"
18
+ // p-gutter, not p-(--era-panel-inset): the inset is SURFACE-owned and resolves
19
+ // to 0 on flat, so a menu there sat its rows flush against its own edges —
20
+ // the highlight ran edge to edge and the panel read as a list with no frame.
21
+ // The gutter is --era-gap, the same distance that separates the rows from
22
+ // each other, so a row sits an even gap from every edge (the pattern the
23
+ // conversations rail already documents). It is >= the panel inset at every
24
+ // density, so glass and bevel keep the frame protection the inset exists
25
+ // for.
18
26
  class={cn(
19
- 'z-50 w-max min-w-(--bits-menubar-menu-anchor-width) overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-(--era-panel-inset) shadow-(--era-shadow-lg) glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
27
+ 'z-50 w-max min-w-(--bits-menubar-menu-anchor-width) overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-gutter shadow-(--era-shadow-lg) glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
20
28
  className
21
29
  )}
22
30
  {sideOffset}
@@ -13,7 +13,7 @@
13
13
  <Menubar.Item
14
14
  bind:ref
15
15
  class={cn(
16
- 'flex h-(--era-h-md) cursor-pointer items-center rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
16
+ 'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
17
17
  className
18
18
  )}
19
19
  {...restProps}
@@ -34,7 +34,21 @@
34
34
  size="md"
35
35
  content="xxs"
36
36
  class={cn(
37
- 'shrink-0 rounded-t-none rounded-b-(--era-rd-md) [box-shadow:inset_0_-1px_0_var(--color-divider-faded)] select-none',
37
+ // The divider is drawn OUTSIDE the box (0 1px 0), not inset.
38
+ //
39
+ // An inset shadow paints over the last pixel row INSIDE the bar, which is a
40
+ // row belonging to the content's own gap: a centred 18px control in a 24px
41
+ // bar has 3px above and 3px below, and the inset line ate one of the three
42
+ // below it. Every child of the bar therefore read 3px on top and right but
43
+ // 2px on the bottom — even by the box, visibly tight to the eye, which
44
+ // measures to the LINE.
45
+ //
46
+ // Outset costs no layout (so the bar keeps its full 24px content box and the
47
+ // concentric 3px walls) and puts the line where it belongs: the boundary
48
+ // between the bar and the content beneath it, rather than a pixel of the
49
+ // bar itself. It still follows the bottom radius, so the divider curves up
50
+ // at the ends exactly as before.
51
+ 'shrink-0 rounded-t-none rounded-b-(--era-rd-md) [box-shadow:0_1px_0_var(--color-divider-faded)] select-none',
38
52
  className
39
53
  )}
40
54
  {...restProps}
@@ -14,8 +14,16 @@
14
14
  <Select.Content
15
15
  bind:ref
16
16
  align="start"
17
+ // p-gutter, not p-(--era-panel-inset): the inset is SURFACE-owned and resolves
18
+ // to 0 on flat, so a menu there sat its rows flush against its own edges —
19
+ // the highlight ran edge to edge and the panel read as a list with no frame.
20
+ // The gutter is --era-gap, the same distance that separates the rows from
21
+ // each other, so a row sits an even gap from every edge (the pattern the
22
+ // conversations rail already documents). It is >= the panel inset at every
23
+ // density, so glass and bevel keep the frame protection the inset exists
24
+ // for.
17
25
  class={cn(
18
- 'z-50 w-max min-w-(--bits-select-anchor-width) overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-(--era-panel-inset) shadow-(--era-shadow-lg) glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
26
+ 'z-50 w-max min-w-(--bits-select-anchor-width) overflow-hidden rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) p-gutter shadow-(--era-shadow-lg) glass-blur data-[side=bottom]:rounded-t-none data-[side=top]:rounded-b-none',
19
27
  className
20
28
  )}
21
29
  {sideOffset}
@@ -10,7 +10,7 @@
10
10
  <Select.Item
11
11
  bind:ref
12
12
  class={cn(
13
- 'flex h-(--era-h-md) cursor-pointer items-center rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
13
+ 'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-item-rd) px-(--era-inset-md) text-body text-fg data-[highlighted]:bg-(--era-highlight) data-[highlighted]:shadow-(--era-highlight-shadow)',
14
14
  className
15
15
  )}
16
16
  {...restProps}
@@ -1244,11 +1244,11 @@
1244
1244
  <ContextMenu.Content onOpenAutoFocus={(e: Event) => e.preventDefault()}>
1245
1245
  <ContextMenu.Item onSelect={togglePlay}>
1246
1246
  {#if ended}
1247
- <RotateCcw class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1247
+ <RotateCcw class="size-(--era-h-xs)" aria-hidden="true" />
1248
1248
  {:else if paused}
1249
- <Play class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1249
+ <Play class="size-(--era-h-xs)" aria-hidden="true" />
1250
1250
  {:else}
1251
- <Pause class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1251
+ <Pause class="size-(--era-h-xs)" aria-hidden="true" />
1252
1252
  {/if}
1253
1253
  <span>{playLabel}</span>
1254
1254
  </ContextMenu.Item>
@@ -1256,11 +1256,11 @@
1256
1256
  {#if showVolume && !mobileMode}
1257
1257
  <ContextMenu.Item onSelect={(e: Event) => remote.toggleMuted(e)}>
1258
1258
  {#if mutedState || volumeState === 0}
1259
- <VolumeX class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1259
+ <VolumeX class="size-(--era-h-xs)" aria-hidden="true" />
1260
1260
  {:else if volumeState < 0.5}
1261
- <Volume1 class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1261
+ <Volume1 class="size-(--era-h-xs)" aria-hidden="true" />
1262
1262
  {:else}
1263
- <Volume2 class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1263
+ <Volume2 class="size-(--era-h-xs)" aria-hidden="true" />
1264
1264
  {/if}
1265
1265
  <span>{muteLabel}</span>
1266
1266
  </ContextMenu.Item>
@@ -1269,7 +1269,7 @@
1269
1269
  {#if showSpeed && !mobileMode}
1270
1270
  <ContextMenu.Sub>
1271
1271
  <ContextMenu.SubTrigger>
1272
- <Gauge class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1272
+ <Gauge class="size-(--era-h-xs)" aria-hidden="true" />
1273
1273
  <span class="flex-1">Speed</span>
1274
1274
  <span class="ml-auto pl-(--era-gap) text-muted">
1275
1275
  {rateState === 1 ? '1x' : `${rateState}x`}
@@ -1293,7 +1293,7 @@
1293
1293
  {#if showQualityMenu && !mobileMode}
1294
1294
  <ContextMenu.Sub>
1295
1295
  <ContextMenu.SubTrigger>
1296
- <Settings class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1296
+ <Settings class="size-(--era-h-xs)" aria-hidden="true" />
1297
1297
  <span class="flex-1">Quality</span>
1298
1298
  <span class="ml-auto pl-(--era-gap) text-muted">{qualityHint}</span>
1299
1299
  </ContextMenu.SubTrigger>
@@ -1314,9 +1314,9 @@
1314
1314
  {#if showPip && canEnterPip && !mobileMode}
1315
1315
  <ContextMenu.Item onSelect={(e: Event) => remote.togglePictureInPicture(e)}>
1316
1316
  {#if pipState}
1317
- <PictureInPicture2 class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1317
+ <PictureInPicture2 class="size-(--era-h-xs)" aria-hidden="true" />
1318
1318
  {:else}
1319
- <PictureInPicture class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1319
+ <PictureInPicture class="size-(--era-h-xs)" aria-hidden="true" />
1320
1320
  {/if}
1321
1321
  <span>{pipState ? 'Exit picture-in-picture' : 'Picture-in-picture'}</span>
1322
1322
  </ContextMenu.Item>
@@ -1325,9 +1325,9 @@
1325
1325
  {#if canEnterFullscreen}
1326
1326
  <ContextMenu.Item onSelect={(e: Event) => remote.toggleFullscreen(FULLSCREEN_TARGET, e)}>
1327
1327
  {#if fullscreenState}
1328
- <Minimize class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1328
+ <Minimize class="size-(--era-h-xs)" aria-hidden="true" />
1329
1329
  {:else}
1330
- <Maximize class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1330
+ <Maximize class="size-(--era-h-xs)" aria-hidden="true" />
1331
1331
  {/if}
1332
1332
  <span>{fullscreenState ? 'Exit fullscreen' : 'Fullscreen'}</span>
1333
1333
  </ContextMenu.Item>
@@ -1336,7 +1336,7 @@
1336
1336
  <ContextMenu.Separator />
1337
1337
 
1338
1338
  <ContextMenu.Item onSelect={downloadVideo}>
1339
- <Download class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1339
+ <Download class="size-(--era-h-xs)" aria-hidden="true" />
1340
1340
  <span>Download video</span>
1341
1341
  </ContextMenu.Item>
1342
1342
  </ContextMenu.Content>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "4.11.0",
3
+ "version": "4.11.2",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",