@rebasepro/ui 0.19.1 → 0.19.2-canary.g09316f6

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.
@@ -121,7 +121,10 @@ html.dark {
121
121
  }
122
122
 
123
123
  .typography-button {
124
- @apply text-sm font-semibold tracking-wide;
124
+ /* 500, sentence case, no tracking. Semibold + tracking-wide was the Rubik-era
125
+ button voice and it made every control shout equally; the reference sets
126
+ controls at 500 and lets the primary FILL carry emphasis, not the label. */
127
+ @apply text-sm font-medium;
125
128
  }
126
129
 
127
130
  /* ---------------------------------------------------------------------------
@@ -90,6 +90,18 @@
90
90
  --control-xl: 56px; /* buttons only */
91
91
  --control-2xl: 64px; /* buttons only */
92
92
 
93
+ /* Radius ladder. Tailwind's defaults (6 / 8 / 12) read as square beside
94
+ the reference, whose controls sit at ~10px and cards at 12–14; matching
95
+ it outright (8 / 10 / 14) read as borrowed. This sits between: the two
96
+ utilities the product uses for controls and containers move up one or
97
+ two pixels, chips keep the default. Overriding the utilities rather than
98
+ the call sites moves every control, card, menu and sheet together; no
99
+ call site names a number. `rounded-full` is a rule, not a size, and it
100
+ is rare: search fields, the switch and avatars. */
101
+ --radius-md: 6px; /* chip, code well, tile, segment (Tailwind's own value) */
102
+ --radius-lg: 9px; /* control, row highlight, menu, folder tab, segmented track */
103
+ --radius-xl: 13px; /* card, sheet, dialog */
104
+
93
105
  /* Colors */
94
106
  --color-primary: #0070F4;
95
107
  --color-primary-light: oklch(from var(--color-primary) calc(l + 0.15) c h);
@@ -166,3 +178,106 @@
166
178
  --color-surface-accent-900: #172033;
167
179
  --color-surface-accent-950: #0f172a;
168
180
  }
181
+
182
+ /* The semantic surfaces as Tailwind colors. `inline` is load-bearing: it makes
183
+ `bg-surface-card` emit `var(--surface-card)` at the element, so the value
184
+ resolves under whichever dark selector the consumer uses. In a plain
185
+ `@theme` the alias would be computed once on `:root` and stay light. The
186
+ variables themselves are defined at the bottom of this file. */
187
+ @theme inline {
188
+ --color-surface-frame: var(--surface-frame);
189
+ --color-surface-sheet: var(--surface-sheet);
190
+ --color-surface-card: var(--surface-card);
191
+ --color-surface-card-hover: var(--surface-card-hover);
192
+ --color-surface-raised: var(--surface-raised);
193
+ --color-surface-raised-hover: var(--surface-raised-hover);
194
+ --color-surface-lifted: var(--surface-lifted);
195
+ --color-surface-field: var(--surface-field);
196
+ --color-surface-field-hover: var(--surface-field-hover);
197
+ --color-surface-hover: var(--surface-hover);
198
+ --color-surface-active: var(--surface-active);
199
+ --color-surface-scrim: var(--surface-scrim);
200
+ --color-surface-well: var(--surface-well);
201
+ --color-hairline: var(--hairline);
202
+ --color-hairline-strong: var(--hairline-strong);
203
+ }
204
+
205
+ /* ---------------------------------------------------------------------------
206
+ * Semantic surfaces — one role, two themes. Added 2026-09-08.
207
+ *
208
+ * Measured on demo.rebase.pro in dark mode, the panel used exactly two opaque
209
+ * surface values: the ground (#0a0a0a) and the content sheet (#111111). Every
210
+ * card painted `dark:bg-surface-900` — the GROUND value — on top of the sheet,
211
+ * so cards were holes cut into the surface they sat on, and the largest areas
212
+ * on screen carried the darkest value. Retuning the numbers cannot fix that:
213
+ * the sheet used `800` and cards used `900`, and `900` must stay darker.
214
+ *
215
+ * So a surface is now named by ROLE and the theme decides the value. Two rules:
216
+ *
217
+ * 1. Every nested surface is one step lifted from the surface it sits on.
218
+ * Lifted means lighter on both themes; the light frame is the greyest
219
+ * thing on the page. Fields are the one exception — a field is inset, so
220
+ * it is an alpha fill that goes the other way.
221
+ * 2. A hairline marks an object, not a region. Cards on the sheet, floating
222
+ * surfaces and fields get a line. The sheet edge, tiles inside a card,
223
+ * rows, tracks, buttons and chips do not; the lightness step separates
224
+ * them.
225
+ *
226
+ * These are plain custom properties, deliberately outside `@theme`, so they
227
+ * can switch on the theme selector. The `@theme inline` entries above alias
228
+ * them into Tailwind, and `inline` matters: it makes `bg-surface-card` emit
229
+ * `var(--surface-card)` at the element, which resolves under whichever dark
230
+ * selector the consumer uses (`.dark` in the panel, `[data-theme=dark]` on the
231
+ * marketing site). Without `inline` the value would be fixed on `:root`.
232
+ *
233
+ * Interaction fills are alpha so they compose over whatever they land on. The
234
+ * solid surfaces carry their own hover value because an alpha hover on a solid
235
+ * background REPLACES it — white at 6% over the sheet is darker than the card
236
+ * it was meant to lift.
237
+ *
238
+ * The full reasoning, with the measurements, is in
239
+ * docs/plans/surface-system-2026-09.md and docs/design/instatic-distilled.html.
240
+ * ------------------------------------------------------------------------- */
241
+ /* No `.light` class here on purpose: nothing in the app sets one, and third-party
242
+ widgets (React Flow) stamp `light` on their own root, which re-lit every token
243
+ under them on a dark page. The light ladder is the root; `[data-theme]` and
244
+ `.dark` are the only switches. */
245
+ :root,
246
+ [data-theme="light"] {
247
+ --surface-frame: #f0f0f2;
248
+ --surface-sheet: #f9f9fa;
249
+ --surface-card: #ffffff;
250
+ --surface-card-hover: #f7f7f8;
251
+ --surface-raised: #ececee;
252
+ --surface-raised-hover: #e3e3e6;
253
+ --surface-lifted: #ffffff;
254
+ --surface-field: rgb(0 0 0 / 0.035);
255
+ --surface-field-hover: rgb(0 0 0 / 0.055);
256
+ --surface-hover: rgb(0 0 0 / 0.04);
257
+ --surface-active: rgb(0 0 0 / 0.07);
258
+ --surface-scrim: rgb(0 0 0 / 0.4);
259
+ /* The one surface that goes DOWN the ladder on purpose: a code well, a
260
+ query, a log tail, a connection string, recessed into its card. */
261
+ --surface-well: #f5f5f5;
262
+ --hairline: rgb(0 0 0 / 0.08);
263
+ --hairline-strong: rgb(0 0 0 / 0.13);
264
+ }
265
+
266
+ .dark,
267
+ [data-theme="dark"] {
268
+ --surface-frame: #0a0a0a;
269
+ --surface-sheet: #131313;
270
+ --surface-card: #181818;
271
+ --surface-card-hover: #1d1d1d;
272
+ --surface-raised: #242424;
273
+ --surface-raised-hover: #2c2c2c;
274
+ --surface-lifted: #2c2c2c;
275
+ --surface-field: rgb(255 255 255 / 0.04);
276
+ --surface-field-hover: rgb(255 255 255 / 0.07);
277
+ --surface-hover: rgb(255 255 255 / 0.06);
278
+ --surface-active: rgb(255 255 255 / 0.09);
279
+ --surface-scrim: rgb(0 0 0 / 0.6);
280
+ --surface-well: #0a0a0a;
281
+ --hairline: rgb(255 255 255 / 0.08);
282
+ --hairline-strong: rgb(255 255 255 / 0.14);
283
+ }
package/dist/styles.d.ts CHANGED
@@ -27,7 +27,7 @@ export declare const controlPaddingMixin: {
27
27
  readonly "2xl": "px-10";
28
28
  };
29
29
  export declare const focusedDisabled = "focus-visible:ring-0 focus-visible:ring-offset-0";
30
- export declare const focusedInvisibleMixin = "focus:bg-opacity-70 focus:bg-surface-accent-100 focus:dark:bg-white/[0.07] focus:bg-surface-accent-100/70";
30
+ export declare const focusedInvisibleMixin = "focus:bg-surface-field-hover";
31
31
  /**
32
32
  * The focus ring — the single most-seen interaction state in the product, and
33
33
  * until now the least tended.
@@ -49,35 +49,57 @@ export declare const focusedClasses = "z-30 outline-none ring-2 ring-primary/60
49
49
  /**
50
50
  * Field surfaces.
51
51
  *
52
- * The inset top hairline is the whole difference between a field that reads as
53
- * a filled rectangle and one that reads as recessed into the surface. It is a
54
- * 1px inset highlight, not a gradient — the same device `.frame` already uses
55
- * on the marketing site, and the flat-surface rule stands.
52
+ * A field is the one surface that goes the other way: it is INSET into
53
+ * whatever holds it, so `surface-field` is an alpha fill rather than a solid,
54
+ * and it composes over a card, the sheet or a dialog alike. It also carries
55
+ * the hairline: a field is an object (DESIGN.md rule 2), and without the line
56
+ * a 4% fill on a card reads as a soft slab rather than a control — measured on
57
+ * the product form, eight of them in a row looked like a wall. The reference
58
+ * draws every input with its 1px edge, on both themes.
56
59
  *
57
- * Light mode gets none: on a white page the highlight has nothing to catch.
60
+ * The 1px inset top highlight that used to sit here was the recessed-edge
61
+ * device from before the field had a border; with a real hairline above it
62
+ * the two stacked into a doubled top edge. The border does its work now.
63
+ *
64
+ * Exactly one element carries this mixin per field — the box, never the
65
+ * input inside it — so the alpha fill and its hover composite once.
58
66
  */
59
- export declare const fieldBackgroundMixin = "bg-surface-accent-200/50 dark:bg-white/[0.055] dark:shadow-[inset_0_1px_0_rgb(255_255_255/0.045)]";
60
- export declare const fieldBackgroundInvisibleMixin = "bg-surface-accent-200/0 dark:bg-white/0";
61
- export declare const fieldBackgroundDisabledMixin = "bg-surface-accent-200/50 dark:bg-white/[0.03]";
67
+ export declare const fieldBackgroundMixin = "bg-surface-field border border-hairline";
68
+ export declare const fieldBackgroundInvisibleMixin = "bg-transparent";
69
+ export declare const fieldBackgroundDisabledMixin = "bg-surface-field/60";
62
70
  /**
63
71
  * `transition-colors` here, because every other interactive surface in the kit
64
72
  * has it and fields did not — `cardClickableMixin` eases, a TextField snapped.
65
73
  * On a form of eight fields that difference is the whole feel of the screen.
66
74
  */
67
- export declare const fieldBackgroundHoverMixin = "transition-colors duration-150 hover:bg-surface-accent-200/70 hover:dark:bg-white/[0.09]";
68
- export declare const defaultBorderMixin = "border-surface-200 dark:border-surface-700/60";
69
- export declare const paperMixin = "bg-white rounded-lg dark:bg-surface-900 border border-surface-200 dark:border-surface-700";
70
- export declare const cardMixin = "bg-white dark:bg-surface-900 rounded-xl border border-surface-200 dark:border-surface-700/60";
75
+ export declare const fieldBackgroundHoverMixin = "transition-colors duration-150 hover:bg-surface-field-hover";
76
+ /**
77
+ * The hairline. One value per theme (`--hairline` in theme.css), and where it
78
+ * goes matters more than what it is: a line marks an OBJECT — a card on the
79
+ * sheet, a field, a floating surface. It does not mark a region: a tile inside
80
+ * a card, a row, a segmented track, a button, a chip are fills, and the
81
+ * lightness step between them is the whole separation. One exception, and it
82
+ * is the sheet's own edge against the frame: that step is about 3 L* on both
83
+ * themes and did not read on a real screen, so the Scaffold draws this line
84
+ * there. Measured before this existed, the panel drew the same line on every
85
+ * card and every tile as well, and with only two surface values available the
86
+ * line was doing the work a step should have done.
87
+ */
88
+ export declare const defaultBorderMixin = "border-hairline";
89
+ export declare const paperMixin = "bg-surface-card rounded-lg border border-hairline-strong";
90
+ export declare const cardMixin = "bg-surface-card rounded-xl border border-hairline";
71
91
  /**
72
92
  * An inset well: code, a query, a log tail, a connection string.
73
93
  *
74
94
  * It must read as *recessed into* the surface holding it, which means darker
75
- * than that surface in dark mode — `surface-950` under a `surface-900` card.
76
- * The obvious-looking `surface-800` is a trap: `#111111` is LIGHTER than the
77
- * `#0a0a0a` card around it, so the well appears to float above the thing it is
78
- * set into. Pair with `font-mono`; this mixin carries the surface only.
95
+ * than that surface in dark mode. Cards now sit at `surface-card` (#181818),
96
+ * so #0a0a0a is already fourteen units below and reads as a well; #000 was
97
+ * tuned for the old #0a card and is now harsher than it needs to be. That value
98
+ * is the `surface-well` role (theme.css), the one surface that goes DOWN the
99
+ * ladder on purpose. Pair with `font-mono`; this mixin carries the surface
100
+ * only.
79
101
  */
80
- export declare const codeSurfaceMixin = "bg-surface-100 dark:bg-surface-950 rounded-md";
102
+ export declare const codeSurfaceMixin = "bg-surface-well rounded-md";
81
103
  /**
82
104
  * The accent, used as TEXT.
83
105
  *
@@ -96,5 +118,20 @@ export declare const codeSurfaceMixin = "bg-surface-100 dark:bg-surface-950 roun
96
118
  * the other way and #0070F4 is already correct.
97
119
  */
98
120
  export declare const accentTextMixin = "text-primary dark:text-primary-light";
99
- export declare const cardClickableMixin = "hover:bg-primary/5 dark:hover:bg-primary/5 cursor-pointer transition-colors duration-150";
121
+ /**
122
+ * Hover on a SOLID surface goes one step lighter, and it stays neutral. The
123
+ * previous `hover:bg-primary/5` tinted every clickable card blue, which spent
124
+ * the brand colour on a state that carries no meaning; on the dark ladder it
125
+ * also read as a hue shift rather than a lift.
126
+ */
127
+ export declare const cardClickableMixin = "hover:bg-surface-card-hover cursor-pointer transition-colors duration-150";
100
128
  export declare const cardSelectedMixin = "bg-primary-bg/30 dark:bg-primary-bg/10 ring-1 ring-primary/75";
129
+ /**
130
+ * Hover on a TRANSPARENT ground — a nav item, a menu item, a list row, a tree
131
+ * row. `surface-hover` is an alpha fill, so it lifts whatever it lands on by
132
+ * the same amount, and `surface-active` is the same idea one step further for
133
+ * the selected row when no hue is wanted. Do not use these on a solid card:
134
+ * an alpha fill REPLACES a solid background rather than tinting it.
135
+ */
136
+ export declare const surfaceHoverMixin = "hover:bg-surface-hover transition-colors duration-150";
137
+ export declare const surfaceActiveMixin = "bg-surface-active";
package/dist/theme.css CHANGED
@@ -90,6 +90,18 @@
90
90
  --control-xl: 56px; /* buttons only */
91
91
  --control-2xl: 64px; /* buttons only */
92
92
 
93
+ /* Radius ladder. Tailwind's defaults (6 / 8 / 12) read as square beside
94
+ the reference, whose controls sit at ~10px and cards at 12–14; matching
95
+ it outright (8 / 10 / 14) read as borrowed. This sits between: the two
96
+ utilities the product uses for controls and containers move up one or
97
+ two pixels, chips keep the default. Overriding the utilities rather than
98
+ the call sites moves every control, card, menu and sheet together; no
99
+ call site names a number. `rounded-full` is a rule, not a size, and it
100
+ is rare: search fields, the switch and avatars. */
101
+ --radius-md: 6px; /* chip, code well, tile, segment (Tailwind's own value) */
102
+ --radius-lg: 9px; /* control, row highlight, menu, folder tab, segmented track */
103
+ --radius-xl: 13px; /* card, sheet, dialog */
104
+
93
105
  /* Colors */
94
106
  --color-primary: #0070F4;
95
107
  --color-primary-light: oklch(from var(--color-primary) calc(l + 0.15) c h);
@@ -166,3 +178,106 @@
166
178
  --color-surface-accent-900: #172033;
167
179
  --color-surface-accent-950: #0f172a;
168
180
  }
181
+
182
+ /* The semantic surfaces as Tailwind colors. `inline` is load-bearing: it makes
183
+ `bg-surface-card` emit `var(--surface-card)` at the element, so the value
184
+ resolves under whichever dark selector the consumer uses. In a plain
185
+ `@theme` the alias would be computed once on `:root` and stay light. The
186
+ variables themselves are defined at the bottom of this file. */
187
+ @theme inline {
188
+ --color-surface-frame: var(--surface-frame);
189
+ --color-surface-sheet: var(--surface-sheet);
190
+ --color-surface-card: var(--surface-card);
191
+ --color-surface-card-hover: var(--surface-card-hover);
192
+ --color-surface-raised: var(--surface-raised);
193
+ --color-surface-raised-hover: var(--surface-raised-hover);
194
+ --color-surface-lifted: var(--surface-lifted);
195
+ --color-surface-field: var(--surface-field);
196
+ --color-surface-field-hover: var(--surface-field-hover);
197
+ --color-surface-hover: var(--surface-hover);
198
+ --color-surface-active: var(--surface-active);
199
+ --color-surface-scrim: var(--surface-scrim);
200
+ --color-surface-well: var(--surface-well);
201
+ --color-hairline: var(--hairline);
202
+ --color-hairline-strong: var(--hairline-strong);
203
+ }
204
+
205
+ /* ---------------------------------------------------------------------------
206
+ * Semantic surfaces — one role, two themes. Added 2026-09-08.
207
+ *
208
+ * Measured on demo.rebase.pro in dark mode, the panel used exactly two opaque
209
+ * surface values: the ground (#0a0a0a) and the content sheet (#111111). Every
210
+ * card painted `dark:bg-surface-900` — the GROUND value — on top of the sheet,
211
+ * so cards were holes cut into the surface they sat on, and the largest areas
212
+ * on screen carried the darkest value. Retuning the numbers cannot fix that:
213
+ * the sheet used `800` and cards used `900`, and `900` must stay darker.
214
+ *
215
+ * So a surface is now named by ROLE and the theme decides the value. Two rules:
216
+ *
217
+ * 1. Every nested surface is one step lifted from the surface it sits on.
218
+ * Lifted means lighter on both themes; the light frame is the greyest
219
+ * thing on the page. Fields are the one exception — a field is inset, so
220
+ * it is an alpha fill that goes the other way.
221
+ * 2. A hairline marks an object, not a region. Cards on the sheet, floating
222
+ * surfaces and fields get a line. The sheet edge, tiles inside a card,
223
+ * rows, tracks, buttons and chips do not; the lightness step separates
224
+ * them.
225
+ *
226
+ * These are plain custom properties, deliberately outside `@theme`, so they
227
+ * can switch on the theme selector. The `@theme inline` entries above alias
228
+ * them into Tailwind, and `inline` matters: it makes `bg-surface-card` emit
229
+ * `var(--surface-card)` at the element, which resolves under whichever dark
230
+ * selector the consumer uses (`.dark` in the panel, `[data-theme=dark]` on the
231
+ * marketing site). Without `inline` the value would be fixed on `:root`.
232
+ *
233
+ * Interaction fills are alpha so they compose over whatever they land on. The
234
+ * solid surfaces carry their own hover value because an alpha hover on a solid
235
+ * background REPLACES it — white at 6% over the sheet is darker than the card
236
+ * it was meant to lift.
237
+ *
238
+ * The full reasoning, with the measurements, is in
239
+ * docs/plans/surface-system-2026-09.md and docs/design/instatic-distilled.html.
240
+ * ------------------------------------------------------------------------- */
241
+ /* No `.light` class here on purpose: nothing in the app sets one, and third-party
242
+ widgets (React Flow) stamp `light` on their own root, which re-lit every token
243
+ under them on a dark page. The light ladder is the root; `[data-theme]` and
244
+ `.dark` are the only switches. */
245
+ :root,
246
+ [data-theme="light"] {
247
+ --surface-frame: #f0f0f2;
248
+ --surface-sheet: #f9f9fa;
249
+ --surface-card: #ffffff;
250
+ --surface-card-hover: #f7f7f8;
251
+ --surface-raised: #ececee;
252
+ --surface-raised-hover: #e3e3e6;
253
+ --surface-lifted: #ffffff;
254
+ --surface-field: rgb(0 0 0 / 0.035);
255
+ --surface-field-hover: rgb(0 0 0 / 0.055);
256
+ --surface-hover: rgb(0 0 0 / 0.04);
257
+ --surface-active: rgb(0 0 0 / 0.07);
258
+ --surface-scrim: rgb(0 0 0 / 0.4);
259
+ /* The one surface that goes DOWN the ladder on purpose: a code well, a
260
+ query, a log tail, a connection string, recessed into its card. */
261
+ --surface-well: #f5f5f5;
262
+ --hairline: rgb(0 0 0 / 0.08);
263
+ --hairline-strong: rgb(0 0 0 / 0.13);
264
+ }
265
+
266
+ .dark,
267
+ [data-theme="dark"] {
268
+ --surface-frame: #0a0a0a;
269
+ --surface-sheet: #131313;
270
+ --surface-card: #181818;
271
+ --surface-card-hover: #1d1d1d;
272
+ --surface-raised: #242424;
273
+ --surface-raised-hover: #2c2c2c;
274
+ --surface-lifted: #2c2c2c;
275
+ --surface-field: rgb(255 255 255 / 0.04);
276
+ --surface-field-hover: rgb(255 255 255 / 0.07);
277
+ --surface-hover: rgb(255 255 255 / 0.06);
278
+ --surface-active: rgb(255 255 255 / 0.09);
279
+ --surface-scrim: rgb(0 0 0 / 0.6);
280
+ --surface-well: #0a0a0a;
281
+ --hairline: rgb(255 255 255 / 0.08);
282
+ --hairline-strong: rgb(255 255 255 / 0.14);
283
+ }
@@ -18,7 +18,29 @@ export type ChipColorScheme = {
18
18
  outlineText?: string;
19
19
  /** Outlined-variant ink in dark mode. */
20
20
  darkOutlineText?: string;
21
+ /**
22
+ * The `tinted` variant: the hue's solid stop at low alpha, as an `rgba()`
23
+ * string so it composes over a card, the sheet or a dialog alike, and an
24
+ * ink MEASURED on what that tint reads as over the page. A tint is the
25
+ * reference's rule for hue in the chrome — a 12–16% wash behind hue-coloured
26
+ * text — and it is what a status or enum chip paints by default now; the
27
+ * saturated `color` stop is the `filled` variant, kept for swatches.
28
+ */
29
+ tintColor?: string;
30
+ tintText?: string;
31
+ darkTintColor?: string;
32
+ darkTintText?: string;
21
33
  };
34
+ /**
35
+ * What a translucent colour reads as over an opaque one: a per-channel sRGB
36
+ * mix, which is how the browser composites it. The tint ink is pushed against
37
+ * this, never against the page alone — a 16% wash of the hue moves the
38
+ * background a long way toward the ink, and an ink that only cleared AA on
39
+ * the bare page lands near 3.5:1 on its own tint.
40
+ */
41
+ export declare function compositeOver(fgHex: string, alpha: number, bgHex: string): string;
42
+ /** Read an `rgba(r, g, b, a)` string back as what it composites to over `bgHex`. */
43
+ export declare function tintOnPage(rgba: string, bgHex: string): string;
22
44
  export declare function contrastRatio(a: string, b: string): number;
23
45
  export declare const CHIP_HUES: readonly ["blue", "cyan", "teal", "green", "yellow", "orange", "red", "pink", "purple", "gray", "indigo", "violet", "fuchsia", "rose", "emerald"];
24
46
  export type ChipHue = typeof CHIP_HUES[number];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/ui",
3
- "version": "0.19.1",
3
+ "version": "0.19.2-canary.g09316f6",
4
4
  "description": "Rebase design system — React components and design tokens built on Radix UI, Tailwind CSS and lucide-react",
5
5
  "keywords": [
6
6
  "rebase",