@signal9/era-ui 30.8.0 → 32.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/ai/artifact/artifact.svelte +1 -1
  3. package/dist/ai/conversations/conversations-item.svelte +1 -1
  4. package/dist/ai/plan/plan-header.svelte +1 -1
  5. package/dist/ai/prompt-input/prompt-input-attachments.svelte +2 -6
  6. package/dist/ai/swarm/swarm-node.svelte +1 -1
  7. package/dist/ai/todo/todo-header.svelte +1 -1
  8. package/dist/ai/todo/todo-item.svelte +1 -1
  9. package/dist/ai/tool/tool-chip.svelte +1 -1
  10. package/dist/ai/web-preview/web-preview.svelte +2 -2
  11. package/dist/ai/workflow/node.svelte +1 -1
  12. package/dist/dev/audit/audits/wall-is-isotropic.js +1 -1
  13. package/dist/docs/css-utilities.js +9 -5
  14. package/dist/era-ui.css +1 -1
  15. package/dist/generated-docs/badge.md +1 -1
  16. package/dist/generated-docs/bar.md +22 -4
  17. package/dist/generated-docs/button.md +38 -4
  18. package/dist/generated-docs/card.md +35 -5
  19. package/dist/generated-docs/chip.md +16 -0
  20. package/dist/generated-docs/llms-full.txt +370 -70
  21. package/dist/generated-docs/llms.txt +8 -4
  22. package/dist/generated-docs/manifest.json +85 -28
  23. package/dist/generated-docs/meter.md +79 -1
  24. package/dist/generated-docs/nav.md +8 -1
  25. package/dist/generated-docs/separator.md +18 -0
  26. package/dist/generated-docs/utilities.json +68 -15
  27. package/dist/generated-docs/utilities.md +145 -50
  28. package/dist/os/notification-center.svelte +1 -1
  29. package/dist/os/taskbar.svelte +1 -1
  30. package/dist/os/toast.svelte +1 -1
  31. package/dist/styles/density.css +15 -25
  32. package/dist/styles/index.css +74 -48
  33. package/dist/styles/motion.css +1 -1
  34. package/dist/styles/prose.css +1 -1
  35. package/dist/styles/surfaces/bevel.css +16 -19
  36. package/dist/styles/surfaces/contract.css +50 -0
  37. package/dist/styles/surfaces/flat.css +27 -0
  38. package/dist/styles/surfaces/glass.css +11 -8
  39. package/dist/ui/accordion/accordion-trigger.svelte +1 -1
  40. package/dist/ui/alert-dialog/alert-dialog-content.svelte +1 -1
  41. package/dist/ui/badge/badge.svelte +8 -2
  42. package/dist/ui/badge/badge.svelte.d.ts +1 -1
  43. package/dist/ui/bar/bar.svelte +95 -26
  44. package/dist/ui/bar/bar.svelte.d.ts +71 -31
  45. package/dist/ui/button/button.svelte +6 -0
  46. package/dist/ui/button/button.svelte.d.ts +2 -0
  47. package/dist/ui/button/variants.d.ts +3 -3
  48. package/dist/ui/button/variants.js +6 -11
  49. package/dist/ui/calendar/calendar-day.svelte +1 -1
  50. package/dist/ui/card/card.svelte +23 -10
  51. package/dist/ui/card/card.svelte.d.ts +19 -7
  52. package/dist/ui/chip/chip.svelte +5 -2
  53. package/dist/ui/chip/chip.svelte.d.ts +6 -6
  54. package/dist/ui/collapsible/collapsible-trigger.svelte +1 -1
  55. package/dist/ui/combobox/combobox-content.svelte +1 -1
  56. package/dist/ui/combobox/combobox-trigger.svelte +1 -1
  57. package/dist/ui/command/command-item.svelte +1 -1
  58. package/dist/ui/command/command-link-item.svelte +1 -1
  59. package/dist/ui/command/command-root.svelte +5 -7
  60. package/dist/ui/command-bar/command-bar.svelte +3 -3
  61. package/dist/ui/context-menu/context-menu-content.svelte +1 -4
  62. package/dist/ui/context-menu/context-menu-sub-content.svelte +1 -4
  63. package/dist/ui/date-field/date-field-segment.svelte +1 -1
  64. package/dist/ui/date-picker/date-picker-content.svelte +1 -1
  65. package/dist/ui/date-picker/date-picker-day.svelte +1 -1
  66. package/dist/ui/date-picker/date-picker-segment.svelte +1 -1
  67. package/dist/ui/date-picker/date-picker-trigger.svelte +1 -1
  68. package/dist/ui/date-range-field/date-range-field-segment.svelte +1 -1
  69. package/dist/ui/date-range-picker/date-range-picker-content.svelte +1 -1
  70. package/dist/ui/date-range-picker/date-range-picker-day.svelte +1 -1
  71. package/dist/ui/date-range-picker/date-range-picker-segment.svelte +1 -1
  72. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +1 -1
  73. package/dist/ui/dialog/dialog-content.svelte +20 -2
  74. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  75. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -4
  76. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -1
  77. package/dist/ui/file-upload/file-upload.svelte +1 -1
  78. package/dist/ui/input/variants.d.ts +6 -6
  79. package/dist/ui/input/variants.js +2 -2
  80. package/dist/ui/kv/kv.svelte +1 -1
  81. package/dist/ui/link-preview/link-preview-content.svelte +1 -1
  82. package/dist/ui/menubar/menubar-content.svelte +1 -1
  83. package/dist/ui/menubar/menubar-root.svelte +1 -1
  84. package/dist/ui/menubar/menubar-sub-content.svelte +1 -4
  85. package/dist/ui/menubar/menubar-trigger.svelte +1 -1
  86. package/dist/ui/meter/meter.svelte +1 -2
  87. package/dist/ui/nav/nav-bar.svelte +9 -4
  88. package/dist/ui/nav/nav-bar.svelte.d.ts +2 -1
  89. package/dist/ui/nav/nav-item.svelte +1 -0
  90. package/dist/ui/pane/pane-root.svelte +3 -12
  91. package/dist/ui/pane/pane-tab.svelte +1 -1
  92. package/dist/ui/popover/popover-content.svelte +1 -1
  93. package/dist/ui/provider/surfaces.d.ts +1 -1
  94. package/dist/ui/provider/surfaces.js +1 -1
  95. package/dist/ui/select/select-content.svelte +1 -1
  96. package/dist/ui/select/select-trigger.svelte +1 -1
  97. package/dist/ui/sheet/sheet-content.svelte +1 -1
  98. package/dist/ui/sheet/sheet-content.svelte.d.ts +1 -1
  99. package/dist/ui/slider/slider-thumb.svelte +1 -1
  100. package/dist/ui/step/step-summary.svelte +1 -1
  101. package/dist/ui/switch/switch.svelte +3 -3
  102. package/dist/ui/time-field/time-field-segment.svelte +1 -1
  103. package/dist/ui/time-range-field/time-range-field-segment.svelte +1 -1
  104. package/dist/ui/timeline/timeline-step.svelte +1 -1
  105. package/dist/ui/toggle-group/toggle-group-item.svelte +2 -2
  106. package/dist/ui/toggle-group/toggle-group-item.svelte.d.ts +6 -6
  107. package/dist/ui/toggle-group/toggle-group-root.svelte +1 -2
  108. package/dist/ui/toggle-group/toggle-group-root.svelte.d.ts +3 -3
  109. package/dist/ui/tooltip/tooltip-content.svelte +1 -1
  110. package/dist/ui/tree/tree-item.svelte +1 -1
  111. package/dist/ui/video-player/video-player.svelte +2 -2
  112. package/dist/utils/tw-merge-config.d.ts +6 -1
  113. package/dist/utils/tw-merge-config.js +6 -2
  114. package/package.json +5 -3
  115. package/skill/SKILL.md +26 -21
  116. package/dist/styles/surfaces/base.css +0 -257
@@ -23,6 +23,10 @@
23
23
  * shading reads as lighting, not tilt. Do not remove it.
24
24
  */
25
25
  [data-surface='bevel'] {
26
+ --era-border-color: transparent;
27
+ --era-divider-color: var(--color-border);
28
+ --era-surface-bg: var(--color-2);
29
+ --era-field-bg: var(--color-hover);
26
30
  /* Horizontal cast ratio — the light-direction knob (see header). Below 1
27
31
  * rotates the light toward the top; the vertical cast stays a full pixel. */
28
32
  --era-bevel-side: 0.6;
@@ -46,7 +50,7 @@
46
50
  ),
47
51
  var(--era-bevel-rim);
48
52
  /* ELEVATION IS A CHISEL, NOT A TONE — bevel's whole premise, and the reason
49
- * it opts out of the raised tier's tone step. base now mixes the elevated
53
+ * it opts out of the flat implementation's raised-tier tone step. Flat mixes the elevated
50
54
  * fill 6% toward --color-bright so flat can separate a floating panel
51
55
  * without drawing an edge; bevel already separates one with the slab below,
52
56
  * and a panel that was ALSO a lighter grey would read as two mechanisms
@@ -93,19 +97,28 @@
93
97
  ),
94
98
  var(--era-bevel-rim);
95
99
  --era-shadow-pressed: var(--era-shadow-well);
100
+ --era-highlight: light-dark(
101
+ color-mix(in oklch, var(--color-bright) 9%, transparent),
102
+ color-mix(in oklch, var(--color-bright) 8%, transparent)
103
+ );
104
+ --era-fill: var(--era-highlight);
96
105
  /* The BOX never moves on press, so releasing doesn't pop the button up.
97
106
  * Instead the CONTENT sinks straight DOWN 1px while
98
107
  * the frame holds still, so the icon/label depresses like a Win95 keycap
99
108
  * travelling into its socket. An earlier version also nudged it 1px RIGHT (into
100
109
  * the sunken bottom-right corner) but the sideways drift read as the glyph
101
110
  * sliding off rather than pressing in — vertical-only is the truer key press.
102
- * The smallest icon buttons override this to half see below the block. */
111
+ * Compact controls select the half-step travel from the same contract. */
103
112
  --era-press-sink-x: 0px;
104
113
  --era-press-sink-y: 1px;
114
+ --era-press-sink-compact-x: 0px;
115
+ --era-press-sink-compact-y: 0.5px;
105
116
  /* Floating panels wear a 2-layer chisel frame; row highlights inset past
106
117
  * it with a concentric radius (square under data-corners="square", as
107
118
  * the era had it — Win95 drew its selection bar inside the border). */
108
119
  --era-panel-inset: 2px;
120
+ --era-overlay-bg: color-mix(in oklch, var(--color-1) 60%, transparent);
121
+ --era-backdrop-filter: none;
109
122
  /* Text selection is the period highlight bar: hard, fully opaque, in the
110
123
  * ACCENT — Win95 painted selections with the system highlight color, not
111
124
  * inverse video. Under the neutral theme primary is the fg step, so it
@@ -113,21 +126,5 @@
113
126
  * colored selection. Crisp by definition: no glow, no shadow. */
114
127
  --era-selection-bg: var(--color-primary);
115
128
  --era-selection-fg: var(--color-primary-fg);
116
- }
117
-
118
- /*
119
- * SMALL-BUTTON SINK — the depression, scaled to the keycap.
120
- *
121
- * The 1px content sink is tuned for a default (h-control) button, where it is ~4% of
122
- * the box. On the h-icon tier — size="xs" and size="icon", the 14px squares a
123
- * pane's close X is built from — that same 1px is ~7% of the box and the glyph,
124
- * which fills the button, visibly drops too far on press. Half the sink
125
- * (proportional: 14/24 ≈ 0.58, rounded to 0.5) puts the small keycap's travel
126
- * back in step with the large one. Overriding the token (not the transform)
127
- * means the existing `[&:active>*]` rule keeps applying it; the content wrapper
128
- * inherits the value.
129
- */
130
- [data-surface='bevel'] button[data-size='xs'],
131
- [data-surface='bevel'] button[data-size='icon'] {
132
- --era-press-sink-y: 0.5px;
129
+ --era-selection-shadow: none;
133
130
  }
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Era UI — material contract
3
+ *
4
+ * Components choose a MATERIAL ROLE; surface implementations provide values.
5
+ * This file contains only relationships shared by every implementation and the
6
+ * global consumers of those values. It deliberately contains no flat, glass,
7
+ * or bevel palette and no component selector.
8
+ *
9
+ * Required primitives (enforced by check:surfaces):
10
+ * paint --era-surface-bg, --era-surface-bg-elevated, --era-field-bg
11
+ * relief --era-shadow, --era-shadow-lg
12
+ * interaction --era-highlight, --era-fill, full/compact press travel
13
+ * frame --era-divider-color, --era-panel-inset, --era-backdrop-filter
14
+ * environment --era-border-color, --era-overlay-bg
15
+ * selection --era-selection-bg/fg/shadow
16
+ *
17
+ * Derived values remain live var() expressions so a nested surface re-resolves
18
+ * them from its own primitives. A surface overrides a derived value only when
19
+ * its material genuinely needs a different treatment.
20
+ */
21
+ :root,
22
+ [data-surface] {
23
+ --era-divider-faded: color-mix(in oklch, var(--era-divider-color) 40%, transparent);
24
+ --era-field-highlight: color-mix(in oklch, var(--era-field-bg) 96%, var(--color-bright));
25
+ --era-shadow-well: var(--era-shadow);
26
+ --era-shadow-pressed: var(--era-shadow);
27
+ --era-highlight-shadow: var(--era-shadow);
28
+ }
29
+
30
+ /* Radius re-derives per element because density/corners may change without a
31
+ * new surface boundary. No surface-specific derived shadow belongs here. */
32
+ * {
33
+ --era-item-rd: max(0px, calc(var(--era-rd-control) - var(--era-panel-inset)));
34
+ }
35
+
36
+ ::selection {
37
+ background-color: var(--era-selection-bg);
38
+ color: var(--era-selection-fg);
39
+ text-shadow: var(--era-selection-shadow);
40
+ }
41
+
42
+ /* Translucent materials become opaque without changing component markup. */
43
+ @media (prefers-reduced-transparency: reduce) {
44
+ :root,
45
+ [data-surface] {
46
+ --era-highlight: var(--color-hover);
47
+ --era-fill: var(--color-hover);
48
+ --era-backdrop-filter: none;
49
+ }
50
+ }
@@ -0,0 +1,27 @@
1
+ /* Flat — solid tone steps with no relief chrome. */
2
+ :root,
3
+ [data-surface='flat'] {
4
+ --era-border-color: transparent;
5
+ --era-divider-color: var(--color-border);
6
+ --era-surface-bg: var(--color-2);
7
+ /* A symmetric tone step separates floating content without drawing an edge. */
8
+ --era-surface-bg-elevated: color-mix(in oklch, var(--era-surface-bg) 94%, var(--color-bright));
9
+ --era-field-bg: var(--color-hover);
10
+ --era-shadow: none;
11
+ --era-shadow-lg: none;
12
+ --era-highlight: light-dark(
13
+ color-mix(in oklch, var(--color-bright) 9%, transparent),
14
+ color-mix(in oklch, var(--color-bright) 8%, transparent)
15
+ );
16
+ --era-fill: var(--era-highlight);
17
+ --era-press-sink-x: 0px;
18
+ --era-press-sink-y: 0px;
19
+ --era-press-sink-compact-x: 0px;
20
+ --era-press-sink-compact-y: 0px;
21
+ --era-panel-inset: 0px;
22
+ --era-overlay-bg: color-mix(in oklch, var(--color-1) 60%, transparent);
23
+ --era-backdrop-filter: none;
24
+ --era-selection-bg: var(--color-bright);
25
+ --era-selection-fg: var(--color-1);
26
+ --era-selection-shadow: none;
27
+ }
@@ -16,6 +16,7 @@
16
16
  * treatment (macOS-style) so row highlights never touch the rim or arcs.
17
17
  */
18
18
  [data-surface='glass'] {
19
+ --era-border-color: transparent;
19
20
  --era-divider-color: light-dark(
20
21
  color-mix(in oklch, var(--color-bright) 12%, transparent),
21
22
  color-mix(in oklch, var(--color-bright) 8%, transparent)
@@ -33,13 +34,13 @@
33
34
  color-mix(in oklch, var(--color-3) 85%, transparent)
34
35
  );
35
36
 
36
- /* The same tone the base defines, held at 72% so the frost still reads
37
+ /* The same tone the flat implementation defines, held at 72% so the frost still reads
37
38
  * through it — the material is the point. Restated rather than inherited
38
39
  * because a surface cannot reference the token it is overriding.
39
40
  * Light mode is why a field needs its own value here at all: at the panel's
40
41
  * own opacity it measured 1.00:1 against its backdrop, i.e. exactly nothing.
41
42
  *
42
- * --era-field-highlight is NOT restated: the base expresses it against
43
+ * --era-field-highlight is NOT restated: the contract derives it against
43
44
  * --era-field-bg, so it picks this value up and stays translucent. */
44
45
  --era-field-bg: color-mix(in oklch, var(--color-hover) 72%, transparent);
45
46
 
@@ -89,6 +90,10 @@
89
90
  * surface invented). Its highlight was always a wash, so pills inside lit
90
91
  * containers were never at risk here. */
91
92
  --era-fill: var(--color-hover);
93
+ --era-press-sink-x: 0px;
94
+ --era-press-sink-y: 0px;
95
+ --era-press-sink-compact-x: 0px;
96
+ --era-press-sink-compact-y: 0px;
92
97
  --era-highlight-shadow:
93
98
  inset 1.5px 1.5px 0 -1px
94
99
  light-dark(
@@ -127,10 +132,9 @@
127
132
  );
128
133
 
129
134
  --era-panel-inset: 2px;
130
- --era-blur: 20px;
131
- /* Blur alone reads muddy/gray; saturation restores the backdrop's color
132
- * the way Apple's vibrancy does. */
133
- --era-saturate: 180%;
135
+ --era-overlay-bg: color-mix(in oklch, var(--color-1) 60%, transparent);
136
+ /* Blur alone reads muddy/gray; saturation restores the backdrop's color. */
137
+ --era-backdrop-filter: blur(20px) saturate(180%);
134
138
  }
135
139
 
136
140
  /* Accessibility — honor reduced-transparency: solid fills, no filter,
@@ -139,8 +143,7 @@
139
143
  [data-surface='glass'] {
140
144
  --era-surface-bg: var(--color-2);
141
145
  --era-surface-bg-elevated: var(--color-3);
142
- --era-blur: 0px;
143
- --era-saturate: 100%;
146
+ --era-backdrop-filter: none;
144
147
  /* The field goes solid with everything else — it was left translucent
145
148
  * here, so a field kept frosting over its backdrop on the one setting
146
149
  * that asks for no frost. */
@@ -14,7 +14,7 @@
14
14
  <Accordion.Trigger
15
15
  bind:ref
16
16
  class={cn(
17
- 'flex h-control w-full items-center justify-between gap-icon-inset-chip rounded-control px-inset-control text-body text-fg hover:bg-highlight hover:shadow-highlight focus:outline-none disabled:opacity-50',
17
+ 'flex tier-control w-full items-center justify-between gap-icon-inset-chip px-inset-control text-body text-fg hover:bg-highlight hover:shadow-highlight focus:outline-none disabled:opacity-50',
18
18
  className
19
19
  )}
20
20
  {...restProps}
@@ -32,7 +32,7 @@
32
32
  bind:ref
33
33
  {...relay?.attrs}
34
34
  class={cn(
35
- 'fixed top-1/2 left-1/2 z-overlay flex max-h-[85dvh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-gutter overflow-y-auto rounded-bar bg-well p-card shadow-lg glass-blur',
35
+ 'fixed top-1/2 left-1/2 z-overlay flex max-h-[85dvh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-gutter overflow-y-auto rounded-bar material-panel p-card',
36
36
  className
37
37
  )}
38
38
  {...restProps}
@@ -19,7 +19,7 @@
19
19
  // that cost. A badge holding an icon must stay flex to lay the icon out
20
20
  // beside the text, and keeps the general trim — its line-height:1 plus
21
21
  // flex centring, which is the best available when the text is anonymous.
22
- base: 'inline-flex h-pill min-w-pill shrink-0 items-center justify-center gap-gutter rounded-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink',
22
+ base: 'inline-flex min-w-pill shrink-0 items-center justify-center gap-gutter tier-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink',
23
23
  variants: {
24
24
  tone: {
25
25
  default: 'bg-fill text-fg',
@@ -56,7 +56,13 @@
56
56
  } = $props();
57
57
  </script>
58
58
 
59
- <span bind:this={ref} class={cn(badgeVariants({ tone }), className)} {...restProps}>
59
+ <span
60
+ bind:this={ref}
61
+ data-era-geometry="bounded"
62
+ data-era-tier="pill"
63
+ class={cn(badgeVariants({ tone }), className)}
64
+ {...restProps}
65
+ >
60
66
  {#if lead}
61
67
  {@render lead()}
62
68
  {/if}
@@ -7,7 +7,7 @@ export declare const badgeVariants: import("tailwind-variants").TVReturnType<{
7
7
  warning: "bg-warning text-warning-fg";
8
8
  info: "bg-info text-info-fg";
9
9
  };
10
- }, undefined, "inline-flex h-pill min-w-pill shrink-0 items-center justify-center gap-gutter rounded-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink", {
10
+ }, undefined, "inline-flex min-w-pill shrink-0 items-center justify-center gap-gutter tier-pill px-pill font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-inset-pill era-text-trim not-has-[svg]:era-token-ink", {
11
11
  tone: {
12
12
  default: "bg-fill text-fg";
13
13
  accent: "bg-primary text-primary-fg";
@@ -1,6 +1,28 @@
1
1
  <script lang="ts" module>
2
2
  import { cn, tv } from '../../utils/index.js';
3
3
 
4
+ export type BarSize = 'chip' | 'control' | 'bar';
5
+ export type BarContent = 'icon' | 'pill' | 'chip' | 'control';
6
+ export type BarGeometry =
7
+ | { size: 'chip'; content?: 'icon' | 'pill' }
8
+ | { size: 'control'; content?: 'icon' | 'pill' | 'chip' }
9
+ | { size?: 'bar'; content?: BarContent };
10
+
11
+ const defaultContent: Record<BarSize, BarContent> = {
12
+ chip: 'icon',
13
+ control: 'chip',
14
+ bar: 'control'
15
+ };
16
+
17
+ export function resolveBarContent(size: BarSize, content?: BarContent): BarContent {
18
+ const resolved = content ?? defaultContent[size];
19
+ const order = ['icon', 'pill', 'chip', 'control', 'bar'];
20
+ if (order.indexOf(resolved) >= order.indexOf(size)) {
21
+ throw new Error(`Bar size="${size}" cannot contain tier "${resolved}"`);
22
+ }
23
+ return resolved;
24
+ }
25
+
4
26
  // A Bar is a flex container sized to a tier height, holding smaller controls
5
27
  // at the even concentric gap. The height scales with density; pick the tier
6
28
  // that fits the content:
@@ -14,9 +36,9 @@
14
36
  base: 'flex items-center leading-none',
15
37
  variants: {
16
38
  size: {
17
- chip: 'h-chip rounded-chip',
18
- control: 'h-control rounded-control',
19
- bar: 'h-bar rounded-bar'
39
+ chip: 'tier-chip',
40
+ control: 'tier-control',
41
+ bar: 'tier-bar'
20
42
  },
21
43
  /**
22
44
  * The tier of the CONTROLS inside, because that is what the bar's own
@@ -26,11 +48,10 @@
26
48
  * had each restated the two-tier value by hand, and the geometry spec had
27
49
  * been rewritten twice chasing which token they were restating.
28
50
  *
29
- * - `tier` (default) one tier smaller, the pairing above. Each size gets
30
- * its OWN token, from the compounds below.
31
- * - `pill` the compact pairing: pills and fields (a filter beside a
32
- * pill-tier icon button, a title bar). Two tiers down, so the inset
33
- * differs per size and also comes from the compounds.
51
+ * Name the ACTUAL tier inside. Omitting it means the next canonical rung:
52
+ * chip⊃icon, control⊃chip, bar⊃control. There is deliberately no `tier`
53
+ * shorthand in the public API: it hid which pair supplied the inset and
54
+ * taught the false claim that every adjacent pair had the same value.
34
55
  *
35
56
  * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
36
57
  * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
@@ -53,8 +74,10 @@
53
74
  * compared ACROSS the axes. layout/wall-is-isotropic now does.
54
75
  */
55
76
  content: {
56
- tier: '',
57
- pill: ''
77
+ icon: '',
78
+ pill: '',
79
+ chip: '',
80
+ control: ''
58
81
  },
59
82
  /**
60
83
  * The flush header bar capping a panel — Pane.Handle, Dialog.Header,
@@ -80,27 +103,34 @@
80
103
  }
81
104
  },
82
105
  compoundVariants: [
83
- // content="tier" — one tier down, each pairing named for itself.
84
106
  // chip ⊃ icon = sp/2.
85
107
  {
86
108
  size: 'chip',
87
- content: 'tier',
109
+ content: 'icon',
88
110
  class: 'gap-icon-inset-chip px-icon-inset-chip'
89
111
  },
90
112
  // control ⊃ chip = (sp−6)/2 — the tight-but-present rung, 1/2/3.
91
113
  {
92
114
  size: 'control',
93
- content: 'tier',
115
+ content: 'chip',
94
116
  class: 'gap-chip-inset-control px-chip-inset-control'
95
117
  },
96
- // bar ⊃ control = sp/2, which is exactly --era-gap. era already calls
97
- // this pairing "the gutter" (see the rounded-row derivation in
98
- // styles/index.css), so it takes that name rather than borrowing
99
- // icon-inset-chip's — same number, but a reader can tell why.
118
+ // bar ⊃ control happens to equal the gutter, but it keeps a pair-specific
119
+ // name so geometry never borrows a layout token by numerical coincidence.
100
120
  {
101
121
  size: 'bar',
102
- content: 'tier',
103
- class: 'gap-gutter px-gutter'
122
+ content: 'control',
123
+ class: 'gap-control-inset-bar px-control-inset-bar'
124
+ },
125
+ {
126
+ size: 'control',
127
+ content: 'icon',
128
+ class: 'gap-icon-inset-control px-icon-inset-control'
129
+ },
130
+ {
131
+ size: 'bar',
132
+ content: 'icon',
133
+ class: 'gap-icon-inset-bar px-icon-inset-bar'
104
134
  },
105
135
  // control ⊃ pill = (3sp − sp − 10)/2 — the named token.
106
136
  {
@@ -115,6 +145,11 @@
115
145
  content: 'pill',
116
146
  class: 'gap-pill-inset-bar px-pill-inset-bar'
117
147
  },
148
+ {
149
+ size: 'bar',
150
+ content: 'chip',
151
+ class: 'gap-chip-inset-bar px-chip-inset-bar'
152
+ },
118
153
  // chip ⊃ pill — its own token, not the text inset it used to borrow. The two
119
154
  // were the same number only while type was 14px at every density; dense's
120
155
  // 12px broke the tie and left this pairing a pixel wide.
@@ -124,7 +159,7 @@
124
159
  class: 'gap-pill-inset-chip px-pill-inset-chip'
125
160
  }
126
161
  ],
127
- defaultVariants: { size: 'bar', content: 'tier', divider: false }
162
+ defaultVariants: { size: 'bar', content: 'control', divider: false }
128
163
  });
129
164
  </script>
130
165
 
@@ -135,8 +170,9 @@
135
170
  let {
136
171
  ref = $bindable(null),
137
172
  size = 'bar',
138
- content = 'tier',
173
+ content,
139
174
  divider = false,
175
+ topology = 'nested',
140
176
  children,
141
177
  class: className,
142
178
  ...restProps
@@ -146,16 +182,49 @@
146
182
  size?: 'chip' | 'control' | 'bar';
147
183
  /**
148
184
  * Tier of the controls inside — drives the bar's padding and gap so they
149
- * stay concentric to them. `tier` (default) is one tier smaller; `pill` is
150
- * the compact pairing (pills/fields in an sm or md bar).
185
+ * stay concentric to them. Omit this for the next canonical rung, or name
186
+ * the actual tier when the contents are smaller.
187
+ */
188
+ content?: BarContent;
189
+ /**
190
+ * The flush cap-a-panel chrome: an outset rule below, and a rounded
191
+ * BOTTOM only — the top defers to the panel's own radius.
192
+ *
193
+ * THE BOTTOM RADIUS IS LOAD-BEARING FOR THE RULE. The divider is an
194
+ * outset `box-shadow`, so it follows the box's corners: the rounded
195
+ * bottom is what curves the line up at the ends and makes the bar read
196
+ * as a long pill. Overriding the radius therefore changes the RULE, not
197
+ * just the corners. Measured against `cn`:
198
+ *
199
+ * - `rounded-none` drops both — a square bar with a dead-straight rule.
200
+ * That is the intended square mode (an in-app Notes bar passes it).
201
+ * - `rounded-b-*` or `rounded-b-none` replaces the curve — this is the
202
+ * one that surprises, because the rule changes shape when the caller
203
+ * thought they were only squaring a corner.
204
+ * - `rounded-t-*` is SAFE: it wins against `rounded-t-none` and leaves
205
+ * `rounded-b-control` standing, so the curve survives.
206
+ *
207
+ * A consumer lost the curve this way. The coupling was already written
208
+ * down — on the variant, inside `<script module>`, where someone
209
+ * hovering this prop never reaches it. Docs are only as good as the
210
+ * place the reader is standing.
151
211
  */
152
- content?: 'tier' | 'pill';
153
- /** The flush cap-a-panel chrome: outset divider below, bottom rd-md. */
154
212
  divider?: boolean;
213
+ /** `docked` removes one or more exposed curves; strict radius nesting does not apply. */
214
+ topology?: 'nested' | 'docked';
155
215
  children?: Snippet;
156
216
  } = $props();
217
+
218
+ const resolvedContent = $derived(resolveBarContent(size, content));
157
219
  </script>
158
220
 
159
- <div bind:this={ref} class={cn(barVariants({ size, content, divider }), className)} {...restProps}>
221
+ <div
222
+ bind:this={ref}
223
+ data-era-geometry={divider ? 'docked' : topology}
224
+ data-era-tier={size}
225
+ data-era-contains={resolvedContent}
226
+ class={cn(barVariants({ size, content: resolvedContent, divider }), className)}
227
+ {...restProps}
228
+ >
160
229
  {@render children?.()}
161
230
  </div>
@@ -1,8 +1,21 @@
1
+ export type BarSize = 'chip' | 'control' | 'bar';
2
+ export type BarContent = 'icon' | 'pill' | 'chip' | 'control';
3
+ export type BarGeometry = {
4
+ size: 'chip';
5
+ content?: 'icon' | 'pill';
6
+ } | {
7
+ size: 'control';
8
+ content?: 'icon' | 'pill' | 'chip';
9
+ } | {
10
+ size?: 'bar';
11
+ content?: BarContent;
12
+ };
13
+ export declare function resolveBarContent(size: BarSize, content?: BarContent): BarContent;
1
14
  export declare const barVariants: import("tailwind-variants").TVReturnType<{
2
15
  size: {
3
- chip: "h-chip rounded-chip";
4
- control: "h-control rounded-control";
5
- bar: "h-bar rounded-bar";
16
+ chip: "tier-chip";
17
+ control: "tier-control";
18
+ bar: "tier-bar";
6
19
  };
7
20
  /**
8
21
  * The tier of the CONTROLS inside, because that is what the bar's own
@@ -12,11 +25,10 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
12
25
  * had each restated the two-tier value by hand, and the geometry spec had
13
26
  * been rewritten twice chasing which token they were restating.
14
27
  *
15
- * - `tier` (default) one tier smaller, the pairing above. Each size gets
16
- * its OWN token, from the compounds below.
17
- * - `pill` the compact pairing: pills and fields (a filter beside a
18
- * pill-tier icon button, a title bar). Two tiers down, so the inset
19
- * differs per size and also comes from the compounds.
28
+ * Name the ACTUAL tier inside. Omitting it means the next canonical rung:
29
+ * chip⊃icon, control⊃chip, bar⊃control. There is deliberately no `tier`
30
+ * shorthand in the public API: it hid which pair supplied the inset and
31
+ * taught the false claim that every adjacent pair had the same value.
20
32
  *
21
33
  * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
22
34
  * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
@@ -39,8 +51,10 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
39
51
  * compared ACROSS the axes. layout/wall-is-isotropic now does.
40
52
  */
41
53
  content: {
42
- tier: "";
54
+ icon: "";
43
55
  pill: "";
56
+ chip: "";
57
+ control: "";
44
58
  };
45
59
  /**
46
60
  * The flush header bar capping a panel — Pane.Handle, Dialog.Header,
@@ -66,9 +80,9 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
66
80
  };
67
81
  }, undefined, "flex items-center leading-none", {
68
82
  size: {
69
- chip: "h-chip rounded-chip";
70
- control: "h-control rounded-control";
71
- bar: "h-bar rounded-bar";
83
+ chip: "tier-chip";
84
+ control: "tier-control";
85
+ bar: "tier-bar";
72
86
  };
73
87
  /**
74
88
  * The tier of the CONTROLS inside, because that is what the bar's own
@@ -78,11 +92,10 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
78
92
  * had each restated the two-tier value by hand, and the geometry spec had
79
93
  * been rewritten twice chasing which token they were restating.
80
94
  *
81
- * - `tier` (default) one tier smaller, the pairing above. Each size gets
82
- * its OWN token, from the compounds below.
83
- * - `pill` the compact pairing: pills and fields (a filter beside a
84
- * pill-tier icon button, a title bar). Two tiers down, so the inset
85
- * differs per size and also comes from the compounds.
95
+ * Name the ACTUAL tier inside. Omitting it means the next canonical rung:
96
+ * chip⊃icon, control⊃chip, bar⊃control. There is deliberately no `tier`
97
+ * shorthand in the public API: it hid which pair supplied the inset and
98
+ * taught the false claim that every adjacent pair had the same value.
86
99
  *
87
100
  * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
88
101
  * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
@@ -105,8 +118,10 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
105
118
  * compared ACROSS the axes. layout/wall-is-isotropic now does.
106
119
  */
107
120
  content: {
108
- tier: "";
121
+ icon: "";
109
122
  pill: "";
123
+ chip: "";
124
+ control: "";
110
125
  };
111
126
  /**
112
127
  * The flush header bar capping a panel — Pane.Handle, Dialog.Header,
@@ -132,9 +147,9 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
132
147
  };
133
148
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
134
149
  size: {
135
- chip: "h-chip rounded-chip";
136
- control: "h-control rounded-control";
137
- bar: "h-bar rounded-bar";
150
+ chip: "tier-chip";
151
+ control: "tier-control";
152
+ bar: "tier-bar";
138
153
  };
139
154
  /**
140
155
  * The tier of the CONTROLS inside, because that is what the bar's own
@@ -144,11 +159,10 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
144
159
  * had each restated the two-tier value by hand, and the geometry spec had
145
160
  * been rewritten twice chasing which token they were restating.
146
161
  *
147
- * - `tier` (default) one tier smaller, the pairing above. Each size gets
148
- * its OWN token, from the compounds below.
149
- * - `pill` the compact pairing: pills and fields (a filter beside a
150
- * pill-tier icon button, a title bar). Two tiers down, so the inset
151
- * differs per size and also comes from the compounds.
162
+ * Name the ACTUAL tier inside. Omitting it means the next canonical rung:
163
+ * chip⊃icon, control⊃chip, bar⊃control. There is deliberately no `tier`
164
+ * shorthand in the public API: it hid which pair supplied the inset and
165
+ * taught the false claim that every adjacent pair had the same value.
152
166
  *
153
167
  * THIS USED TO BE ONE CLASS FOR ALL THREE SIZES — `px-icon-inset-chip`,
154
168
  * on the stated reasoning that "chip⊃icon, control⊃chip and bar⊃control
@@ -171,8 +185,10 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
171
185
  * compared ACROSS the axes. layout/wall-is-isotropic now does.
172
186
  */
173
187
  content: {
174
- tier: "";
188
+ icon: "";
175
189
  pill: "";
190
+ chip: "";
191
+ control: "";
176
192
  };
177
193
  /**
178
194
  * The flush header bar capping a panel — Pane.Handle, Dialog.Header,
@@ -205,12 +221,36 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
205
221
  size?: 'chip' | 'control' | 'bar';
206
222
  /**
207
223
  * Tier of the controls inside — drives the bar's padding and gap so they
208
- * stay concentric to them. `tier` (default) is one tier smaller; `pill` is
209
- * the compact pairing (pills/fields in an sm or md bar).
224
+ * stay concentric to them. Omit this for the next canonical rung, or name
225
+ * the actual tier when the contents are smaller.
226
+ */
227
+ content?: BarContent;
228
+ /**
229
+ * The flush cap-a-panel chrome: an outset rule below, and a rounded
230
+ * BOTTOM only — the top defers to the panel's own radius.
231
+ *
232
+ * THE BOTTOM RADIUS IS LOAD-BEARING FOR THE RULE. The divider is an
233
+ * outset `box-shadow`, so it follows the box's corners: the rounded
234
+ * bottom is what curves the line up at the ends and makes the bar read
235
+ * as a long pill. Overriding the radius therefore changes the RULE, not
236
+ * just the corners. Measured against `cn`:
237
+ *
238
+ * - `rounded-none` drops both — a square bar with a dead-straight rule.
239
+ * That is the intended square mode (an in-app Notes bar passes it).
240
+ * - `rounded-b-*` or `rounded-b-none` replaces the curve — this is the
241
+ * one that surprises, because the rule changes shape when the caller
242
+ * thought they were only squaring a corner.
243
+ * - `rounded-t-*` is SAFE: it wins against `rounded-t-none` and leaves
244
+ * `rounded-b-control` standing, so the curve survives.
245
+ *
246
+ * A consumer lost the curve this way. The coupling was already written
247
+ * down — on the variant, inside `<script module>`, where someone
248
+ * hovering this prop never reaches it. Docs are only as good as the
249
+ * place the reader is standing.
210
250
  */
211
- content?: 'tier' | 'pill';
212
- /** The flush cap-a-panel chrome: outset divider below, bottom rd-md. */
213
251
  divider?: boolean;
252
+ /** `docked` removes one or more exposed curves; strict radius nesting does not apply. */
253
+ topology?: 'nested' | 'docked';
214
254
  children?: Snippet;
215
255
  };
216
256
  declare const Bar: import("svelte").Component<$$ComponentProps, {}, "ref">;