@dxos/ui-theme 0.10.0 → 0.11.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 (57) hide show
  1. package/dist/lib/browser/index.mjs +0 -62
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +0 -62
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/plugin/node-cjs/main.css +311 -189
  8. package/dist/plugin/node-cjs/main.css.map +3 -3
  9. package/dist/plugin/node-cjs/meta.json +1 -1
  10. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs +2 -1
  11. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs.map +3 -3
  12. package/dist/plugin/node-esm/main.css +311 -189
  13. package/dist/plugin/node-esm/main.css.map +3 -3
  14. package/dist/plugin/node-esm/meta.json +1 -1
  15. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs +2 -1
  16. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs.map +3 -3
  17. package/dist/types/src/Theme.stories.d.ts.map +1 -1
  18. package/dist/types/src/defs.d.ts +0 -5
  19. package/dist/types/src/defs.d.ts.map +1 -1
  20. package/dist/types/src/fragments/hover.d.ts +0 -2
  21. package/dist/types/src/fragments/hover.d.ts.map +1 -1
  22. package/dist/types/src/fragments/index.d.ts +0 -1
  23. package/dist/types/src/fragments/index.d.ts.map +1 -1
  24. package/dist/types/src/plugins/ThemePlugin.d.ts.map +1 -1
  25. package/dist/types/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +11 -6
  27. package/src/Theme.stories.tsx +3 -1
  28. package/src/css/DESIGN_SYSTEM.md +89 -51
  29. package/src/css/base/base.css +39 -0
  30. package/src/css/components/button.css +21 -16
  31. package/src/css/components/callout.css +140 -0
  32. package/src/css/components/input.css +36 -0
  33. package/src/css/components/state.css +1 -11
  34. package/src/css/components/state.md +1 -1
  35. package/src/css/components/surface.css +165 -58
  36. package/src/css/components/tag.css +13 -1
  37. package/src/css/integrations/codemirror.css +15 -0
  38. package/src/css/layout/main.css +26 -2
  39. package/src/css/layout/native.css +1 -0
  40. package/src/css/theme/roles.css +92 -0
  41. package/src/css/theme/spacing.css +63 -54
  42. package/src/css/theme/surfaces.css +215 -0
  43. package/src/css/theme/text.css +6 -2
  44. package/src/css/utilities.css +13 -9
  45. package/src/defs.ts +0 -25
  46. package/src/fragments/hover.ts +0 -3
  47. package/src/fragments/index.ts +0 -1
  48. package/src/main.css +8 -10
  49. package/src/plugins/ThemePlugin.ts +50 -40
  50. package/src/plugins/main.css +1 -1
  51. package/src/tokens.css +22 -0
  52. package/dist/types/src/fragments/density.d.ts +0 -4
  53. package/dist/types/src/fragments/density.d.ts.map +0 -1
  54. package/src/css/components/panel.css +0 -117
  55. package/src/css/theme/semantic.css +0 -244
  56. package/src/fragments/AUDIT.md +0 -55
  57. package/src/fragments/density.ts +0 -43
@@ -2,108 +2,215 @@
2
2
  * Surfaces
3
3
  *
4
4
  * A surface zone paints a background AND publishes its colour as `--surface-bg`, so descendant
5
- * controls can derive a visible hover/current highlight from the actual surface (see the
6
- * derivation block below) and surface-matching elements (avatars, gradients) can read it directly.
5
+ * aspects (bar, well, hover, current, selected) derive from the actual surface and stay visible at
6
+ * any elevation, and surface-matching elements (avatars, gradients) can read it directly.
7
7
  *
8
- * Always enter a surface via one of these `dx-*-surface` classes. A bare `bg-*-surface` utility
9
- * paints the colour but does NOT publish `--surface-bg`, so control hover collapses onto the
10
- * base-surface fallback and can become invisible when the surface shares that elevation.
8
+ * Enter a zone with `data-surface="<level>"` the levels are the ladder in `theme/surfaces.css`.
9
+ * The `dx-*-surface` classes are role-named aliases of the same thing, kept because they read well
10
+ * at call sites (`dx-card-surface` says more than `data-surface="raised"`).
11
+ *
12
+ * A bare `bg-*-surface` utility paints the colour but does NOT publish `--surface-bg`, so every
13
+ * aspect inside it collapses onto the base-surface fallback — never use one to enter a zone.
11
14
  */
12
15
 
13
16
  @layer dx-components {
14
- .dx-base-surface {
15
- @apply bg-base-surface text-base-fg;
16
- --surface-bg: var(--color-base-surface);
17
+ /* The outer rails sit at the same depth as the deck gaps: they frame the panels rather than
18
+ belonging to them, so L0/R0 read one step behind the L1/R1 sidebars they flank. */
19
+ [data-surface='sunken'],
20
+ .dx-deck-surface {
21
+ --surface-bg: var(--dx-surface-sunken);
17
22
  }
18
23
 
19
- .dx-deck-surface {
20
- @apply bg-deck-surface text-base-fg;
21
- --surface-bg: var(--color-deck-surface);
24
+ [data-surface='chrome'],
25
+ .dx-sidebar-surface,
26
+ .dx-header-surface {
27
+ --surface-bg: var(--dx-surface-chrome);
22
28
  }
23
29
 
24
- .dx-sidebar-surface {
25
- @apply bg-sidebar-surface text-base-fg;
26
- --surface-bg: var(--color-sidebar-surface);
30
+ /* The rails are not levels: they carry their own tone per theme (see surfaces.css). */
31
+ .dx-l0-surface,
32
+ .dx-r0-surface {
33
+ --surface-bg: var(--color-l0-surface);
27
34
  }
28
35
 
29
- .dx-header-surface {
30
- @apply bg-header-surface text-base-fg;
31
- --surface-bg: var(--color-header-surface);
36
+ .dx-l1-surface,
37
+ .dx-r1-surface {
38
+ --surface-bg: var(--color-l1-surface);
39
+ }
40
+
41
+ [data-surface='base'],
42
+ .dx-base-surface,
43
+ .dx-attention-surface {
44
+ --surface-bg: var(--dx-surface-base);
32
45
  }
33
46
 
47
+ [data-surface='raised'],
34
48
  .dx-card-surface {
35
- @apply bg-card-surface text-base-fg;
36
- --surface-bg: var(--color-card-surface);
49
+ --surface-bg: var(--dx-surface-raised);
37
50
  }
38
51
 
39
- .dx-group-surface {
40
- @apply bg-group-surface text-base-fg;
41
- --surface-bg: var(--color-group-surface);
52
+ [data-surface='overlay'],
53
+ .dx-modal-surface {
54
+ --surface-bg: var(--dx-surface-overlay);
42
55
  }
43
56
 
44
- .dx-input-surface {
45
- @apply bg-input-surface text-base-fg;
46
- --surface-bg: var(--color-input-surface);
57
+ [data-surface='popup'],
58
+ .dx-popover-surface {
59
+ --surface-bg: var(--dx-surface-popup);
47
60
  }
48
61
 
62
+ /*
63
+ * Aspect zones. These do not pick a level — they take a step off whatever hosts them and then
64
+ * republish that as `--surface-bg`, so a control inside a toolbar or a well derives its own
65
+ * states from the aspect rather than from the level underneath it.
66
+ */
67
+ [data-surface='bar'],
49
68
  .dx-toolbar-surface {
50
- @apply bg-toolbar-surface text-base-fg;
51
69
  --surface-bg: var(--color-toolbar-surface);
52
70
  }
53
71
 
54
- .dx-modal-surface {
55
- @apply bg-modal-surface text-base-fg backdrop-blur-md;
56
- --surface-bg: var(--color-modal-surface);
72
+ [data-surface='well'],
73
+ .dx-group-surface,
74
+ .dx-input-surface {
75
+ --surface-bg: var(--color-group-surface);
57
76
  }
58
77
 
59
- .dx-popover-surface {
60
- @apply bg-popover-surface text-base-fg backdrop-blur-md;
61
- --surface-bg: var(--color-popover-surface);
78
+ /* Every zone paints its published surface and takes the default ink. */
79
+ [data-surface],
80
+ .dx-deck-surface,
81
+ .dx-sidebar-surface,
82
+ .dx-header-surface,
83
+ .dx-l0-surface,
84
+ .dx-l1-surface,
85
+ .dx-r0-surface,
86
+ .dx-r1-surface,
87
+ .dx-base-surface,
88
+ .dx-attention-surface,
89
+ .dx-card-surface,
90
+ .dx-modal-surface,
91
+ .dx-popover-surface,
92
+ .dx-toolbar-surface,
93
+ .dx-group-surface,
94
+ .dx-input-surface {
95
+ background-color: var(--surface-bg);
96
+ @apply text-base-fg;
62
97
  }
63
98
 
64
- .dx-attention-surface {
65
- @apply bg-attention-surface text-base-fg;
66
- --surface-bg: var(--color-attention-surface);
99
+ /* Floating levels frost whatever passes beneath them. */
100
+ [data-surface='overlay'],
101
+ [data-surface='popup'],
102
+ .dx-modal-surface,
103
+ .dx-popover-surface {
104
+ @apply backdrop-blur-md;
67
105
  }
68
106
  }
69
107
 
70
108
  @layer dx-tokens {
71
109
  /*
72
- * Re-derive the state colours (hover, current, selected) off the host surface for every context
73
- * that publishes its own --surface-bg. Custom-property substitution runs once per scope, so the
74
- * formula must be repeated here (it cannot simply inherit from semantic.css, which resolved it
75
- * against the root surface). Listing all surface zones in one block keeps the offsets identical
76
- * across surfaces — there is no per-surface divergence to tune.
110
+ * Aspect derivation. Custom-property substitution runs once per scope, so the formulas must be
111
+ * re-evaluated wherever `--surface-bg` changes these two blocks are that one place
112
+ * (surfaces.css declares the same formulas against the root fallback, for content outside any
113
+ * zone). Keep the offsets in sync with the Aspects block there.
77
114
  *
78
- * Magnitudes mirror semantic.css; selected-* track current-* via the aliases declared there.
115
+ * States re-derive in EVERY zone, including the aspect zones: a row inside a toolbar should
116
+ * highlight against the toolbar, not against the level beneath it.
79
117
  */
80
- .dx-base-surface,
118
+ [data-surface],
81
119
  .dx-deck-surface,
82
120
  .dx-sidebar-surface,
83
121
  .dx-header-surface,
122
+ .dx-l0-surface,
123
+ .dx-l1-surface,
124
+ .dx-r0-surface,
125
+ .dx-r1-surface,
126
+ .dx-base-surface,
127
+ .dx-attention-surface,
84
128
  .dx-card-surface,
85
- .dx-group-surface,
86
- .dx-input-surface,
87
- .dx-toolbar-surface,
88
129
  .dx-modal-surface,
89
130
  .dx-popover-surface,
90
- .dx-attention-surface,
131
+ .dx-toolbar-surface,
132
+ .dx-group-surface,
133
+ .dx-input-surface,
134
+ .dx-input,
91
135
  .dx-main-sidebar {
92
- --color-hover-surface: light-dark(
93
- oklch(from var(--surface-bg) calc(l - 0.08) c h),
94
- oklch(from var(--surface-bg) calc(l + 0.08) c h)
136
+ --color-hover-surface: oklch(
137
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-state) * 0.08) c h
138
+ );
139
+ --color-hover-surface-subtle: oklch(
140
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-state) * 0.02) c h
141
+ );
142
+ --color-current-surface: oklch(
143
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-state) * 0.1) c h
144
+ );
145
+ --color-current-surface-hover: oklch(
146
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-state) * 0.12) c h
147
+ );
148
+ --color-placeholder: oklch(
149
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-placeholder) * 0.3) c h
150
+ );
151
+ --color-scrollbar-thumb: oklch(
152
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-scrollbar) * 0.23) c h
153
+ );
154
+ --color-scrollbar-thumb-hover: oklch(
155
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-scrollbar) * 0.3) c h
156
+ );
157
+ --color-scrollbar-thumb-active: oklch(
158
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-scrollbar) * 0.34) c h
95
159
  );
96
- --color-hover-surface-subtle: light-dark(
97
- oklch(from var(--surface-bg) calc(l - 0.02) c h),
98
- oklch(from var(--surface-bg) calc(l + 0.02) c h)
160
+ --color-separator: oklch(
161
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-separator-line) * 0.166) c h
99
162
  );
100
- --color-current-surface: light-dark(
101
- oklch(from var(--surface-bg) calc(l - 0.1) c h),
102
- oklch(from var(--surface-bg) calc(l + 0.1) c h)
163
+ --color-input-separator: oklch(
164
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-separator) * 0.115) c h
103
165
  );
104
- --color-current-surface-hover: light-dark(
105
- oklch(from var(--surface-bg) calc(l - 0.12) c h),
106
- oklch(from var(--surface-bg) calc(l + 0.12) c h)
166
+ --color-subdued-separator: oklch(
167
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-separator) * 0.089) c h
107
168
  );
169
+ --color-primary-separator: oklch(
170
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-separator) * 0.064) c h
171
+ );
172
+
173
+ /*
174
+ * Aliases must be re-declared, not inherited: a `--color-x: var(--color-y)` alias substitutes
175
+ * at the scope that DECLARES it, so one written only at `:root` freezes to the root value and
176
+ * ignores the zone it is used in.
177
+ */
178
+ --color-selected-surface: var(--color-current-surface);
179
+ --color-selected-surface-hover: var(--color-current-surface-hover);
180
+ }
181
+
182
+ /*
183
+ * Bar and well re-derive only in LEVEL zones. Deriving them inside an aspect zone would be
184
+ * circular — that zone's own `--surface-bg` is defined *from* one of these tokens — so a bar
185
+ * nested in a bar keeps the outer step rather than compounding.
186
+ */
187
+ [data-surface='sunken'],
188
+ [data-surface='chrome'],
189
+ [data-surface='base'],
190
+ [data-surface='raised'],
191
+ [data-surface='overlay'],
192
+ [data-surface='popup'],
193
+ .dx-deck-surface,
194
+ .dx-sidebar-surface,
195
+ .dx-header-surface,
196
+ .dx-l0-surface,
197
+ .dx-l1-surface,
198
+ .dx-r0-surface,
199
+ .dx-r1-surface,
200
+ .dx-base-surface,
201
+ .dx-attention-surface,
202
+ .dx-card-surface,
203
+ .dx-modal-surface,
204
+ .dx-popover-surface,
205
+ .dx-main-sidebar {
206
+ --color-toolbar-surface: oklch(
207
+ from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-bar) * 0.025) c h
208
+ );
209
+ --color-group-surface: oklch(from var(--surface-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-well) * 0.05) c h);
210
+ --color-group-alt-surface: oklch(from var(--surface-bg) calc(l + var(--dx-lift) * 0.08) c h);
211
+
212
+ /* Re-declared for the same reason as the state aliases above. */
213
+ --color-input-surface: var(--color-group-surface);
214
+ --color-focus-surface: var(--color-group-surface);
108
215
  }
109
216
  }
@@ -11,7 +11,19 @@
11
11
 
12
12
  .dx-tag--anchor {
13
13
  /* NOTE: Margin offsets padding so that border is flush with text. This should only apply to anchor tags inside of Codemirror. */
14
- @apply inline-block cursor-pointer px-[4px] py-0.5;
14
+ @apply inline-block cursor-pointer px-[8px];
15
+ /*
16
+ * Vertical padding on an inline-block grows the line box, so a line carrying a chip would be
17
+ * taller than its neighbours. The negative margin cancels that contribution, keeping editor
18
+ * rows a uniform height whether or not an item has been converted to a link.
19
+ */
20
+ padding-block: 2px;
21
+ margin-block: -2px;
22
+ /*
23
+ * `inline-block` makes this a block container, so an inherited hanging indent
24
+ * (list rows set a negative `text-indent`) would shift the label out of its own box.
25
+ */
26
+ text-indent: 0;
15
27
  /** TODO(burdon): Configure secondary color. */
16
28
  @apply bg-input-surface ring-inset ring ring-neutral-border rounded-sm;
17
29
  &:hover {
@@ -26,6 +26,21 @@
26
26
  --color-cm-comment-surface: light-dark(var(--color-teal-200), var(--color-teal-200));
27
27
  --color-cm-comment-current-surface: light-dark(var(--color-orange-200), var(--color-orange-200));
28
28
 
29
+ /* Branch diff: oriented as a preview of accepting the compare branch into the current branch —
30
+ the compare branch's content (what accepting adds) is shown as an addition, the current
31
+ branch's deviating content (what accepting removes) as a removal. */
32
+ --color-cm-diff-add-surface: light-dark(
33
+ oklch(from var(--color-green-500) l c h / 0.18),
34
+ oklch(from var(--color-green-400) l c h / 0.22)
35
+ );
36
+ --color-cm-diff-remove-surface: light-dark(
37
+ oklch(from var(--color-red-500) l c h / 0.16),
38
+ oklch(from var(--color-red-400) l c h / 0.22)
39
+ );
40
+ /* Saturated variants for the change-gutter markers (thin bars), kept aligned with the surfaces. */
41
+ --color-cm-diff-add-gutter: light-dark(var(--color-green-600), var(--color-green-400));
42
+ --color-cm-diff-remove-gutter: light-dark(var(--color-red-500), var(--color-red-400));
43
+
29
44
  /* Markdown syntax highlighting */
30
45
  --color-cm-code: light-dark(var(--color-stone-600), var(--color-stone-400));
31
46
  --color-cm-code-inline: light-dark(var(--color-green-500), var(--color-green-500));
@@ -80,9 +80,33 @@
80
80
  .dark & {
81
81
  @apply backdrop-blur-lg;
82
82
  }
83
- background-color: var(--color-sidebar-surface);
83
+ /*
84
+ * The sidebars ARE L1/R1 — nothing else applies `dx-l1-surface`, so this is where those tokens
85
+ * have to land or they have no effect at all. Aspect derivation for the subtree keys off
86
+ * `--surface-bg` (see surface.css); the chrome default covers a sidebar rendered without a side.
87
+ */
88
+ --surface-bg: var(--dx-surface-chrome);
89
+ background-color: var(--surface-bg);
90
+
91
+ &[data-side='is'] {
92
+ --surface-bg: var(--color-l1-surface);
93
+
94
+ /* Collapsed, the sidebar is exactly the rail's width and the rail overlays it, so the only
95
+ pixels that show are the rail's own. Wearing the rail's tone keeps the edges and the rail's
96
+ border from revealing a second surface behind it. */
97
+ &[data-state='collapsed'] {
98
+ --surface-bg: var(--color-l0-surface);
99
+ }
100
+ }
101
+
102
+ &[data-side='ie'] {
103
+ --surface-bg: var(--color-r1-surface);
104
+
105
+ &[data-state='collapsed'] {
106
+ --surface-bg: var(--color-r0-surface);
107
+ }
108
+ }
84
109
  color: var(--color-sidebar-fg);
85
- --surface-bg: var(--color-sidebar-surface);
86
110
 
87
111
  /* Current / selected items read one stop brighter than default body text. */
88
112
  & :is([aria-current]:not([aria-current='false']), [aria-selected='true']) {
@@ -10,6 +10,7 @@
10
10
  * The CSS properties below are for Electron/PWA compatibility.
11
11
  */
12
12
  .dx-app-drag {
13
+ user-select: none;
13
14
  -webkit-user-select: none;
14
15
  -webkit-app-region: drag;
15
16
  --app-region: drag;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Roles — named tokens that are not surfaces: text emphasis, control fills, accent, separators,
3
+ * scrollbar, and the off-ladder grid "paper" palette.
4
+ *
5
+ * The elevation ladder and everything derived from a host surface lives in `surfaces.css`.
6
+ *
7
+ * Naming convention: --color-{name}-{part}[-{state}] — see DESIGN_SYSTEM.md.
8
+ */
9
+
10
+ @theme {
11
+ /*
12
+ * Text emphasis — foregrounds at decreasing emphasis, anchored on --color-base-fg (strongest):
13
+ * base-fg → description → subdued (weakest). `placeholder` is host-derived — see surfaces.css.
14
+ * Tuned for legibility against the surfaces rather than mapped to the elevation ladder (which
15
+ * governs backgrounds, not text contrast), so these stay explicit neutrals.
16
+ */
17
+ --color-description: light-dark(var(--color-neutral-600), var(--color-neutral-400));
18
+ --color-subdued: light-dark(var(--color-neutral-500), var(--color-neutral-600));
19
+
20
+ /*
21
+ * Controls — the input/control fill is a deliberate off-ladder token (cf. the grid "paper"
22
+ * surfaces). It backs text inputs and the default/ghost button (bg-input-bg) and must contrast
23
+ * whatever surface it sits on: a subtle inset well in light mode, a raised fill in dark mode that
24
+ * reads lighter than the topmost surface. Do not map it to an elevation level.
25
+ */
26
+ /*
27
+ * Light is not a ramp stop: neutral-250 sat 0.14 below the near-white canvas, so a default button
28
+ * read as a dark slab next to an input fill 0.025 below it. Dark keeps neutral-700.
29
+ */
30
+ --color-input-bg: light-dark(oklch(0.87 0.001 190), var(--color-neutral-700));
31
+
32
+ /*
33
+ * A filled control's hover has to move ITS OWN fill. `--color-hover-surface` is derived from the
34
+ * host surface — correct for ghost/outline, which are transparent and let the host show through,
35
+ * but in light it sits above the fill, so a filled button lightened on hover into
36
+ * `--color-selected-surface`. Light-mode only: dark still uses the host tint (see button.css).
37
+ */
38
+ --color-input-bg-hover: oklch(
39
+ from var(--color-input-bg) calc(l + var(--dx-lift) * var(--dx-attenuate-state) * 0.08) c h
40
+ );
41
+ --color-input-fg: var(--color-base-fg);
42
+
43
+ /* Focus indicators are a global affordance, not a per-surface property. */
44
+ --color-focus-ring: light-dark(var(--color-blue-400), var(--color-blue-600));
45
+ --color-focus-ring-subtle: light-dark(var(--color-neutral-300), var(--color-neutral-600));
46
+
47
+ /* Accent (primary) */
48
+ --color-accent-bg: light-dark(var(--color-blue-600), var(--color-blue-700));
49
+ --color-accent-bg-hover: light-dark(var(--color-blue-700), var(--color-blue-800));
50
+ --color-accent-fg: var(--color-blue-100);
51
+ --color-accent-text: light-dark(var(--color-blue-600), var(--color-blue-400));
52
+ --color-accent-text-hover: light-dark(var(--color-blue-500), var(--color-blue-500));
53
+
54
+ /* Unaccented */
55
+ --color-un-accent: var(--color-neutral-400);
56
+ --color-un-accent-hover: var(--color-neutral-500);
57
+
58
+ /* Separator */
59
+ /* Surface-relative separators are host-derived — see the Aspects block in surfaces.css. */
60
+ --color-active-separator: light-dark(var(--color-blue-500), var(--color-blue-500));
61
+
62
+ /* Scrollbar */
63
+ /* The groove. The thumb is host-derived — see the Aspects block in surfaces.css. */
64
+ --color-scrollbar-track: light-dark(var(--color-neutral-50), var(--color-neutral-950));
65
+
66
+ /*
67
+ * Sheet / Grid — intentionally OFF the elevation ladder.
68
+ * NOTE: Update main.css when changing grid tokens (and restart dev server).
69
+ *
70
+ * These are "paper" surfaces for the data grid: maximum-contrast canvases (near-white in light,
71
+ * near-black in dark) plus saturated selection/comment accents. The elevation ladder is
72
+ * monotonic (light: lighter = higher), so it cannot express a surface that is simultaneously the
73
+ * lightest in light mode and the darkest in dark mode. Keep these as explicit neutrals/accents;
74
+ * do not fold them into --dx-surface-*.
75
+ */
76
+ --color-axis-surface: light-dark(var(--color-neutral-100), var(--color-neutral-900));
77
+ --color-axis-text: light-dark(var(--color-neutral-700), var(--color-neutral-300));
78
+ --color-axis-selected-surface: light-dark(var(--color-neutral-100), var(--color-neutral-900));
79
+ --color-axis-selected-text: light-dark(var(--color-neutral-100), var(--color-neutral-900));
80
+
81
+ --color-grid-surface: light-dark(var(--color-neutral-50), var(--color-neutral-950));
82
+ --color-grid-fg: light-dark(var(--color-neutral-950), var(--color-neutral-50));
83
+ --color-grid-line: light-dark(var(--color-neutral-200), var(--color-neutral-800));
84
+ --color-grid-highlight: oklch(from var(--color-emerald-500) l c h / 0.5);
85
+ --color-grid-selection-surface: light-dark(var(--color-blue-500), var(--color-blue-400));
86
+ --color-grid-selection-fg: light-dark(var(--color-neutral-50), var(--color-neutral-950));
87
+ --color-grid-comment: light-dark(var(--color-green-400), var(--color-green-600));
88
+ --color-grid-comment-active: light-dark(
89
+ oklch(from var(--color-green-400) l c h / 0.5),
90
+ oklch(from var(--color-green-600) l c h / 0.5)
91
+ );
92
+ }
@@ -23,10 +23,13 @@
23
23
 
24
24
  --spacing-tag-padding-block: 0.125rem;
25
25
 
26
+ /** The one spacing ramp: 4 / 8 / 12 / 16 / 24 / 32. Every other spacing family aliases onto it. */
26
27
  --spacing-trim-xs: 0.25rem;
27
28
  --spacing-trim-sm: 0.5rem;
28
29
  --spacing-trim-md: 0.75rem;
29
- --spacing-trim-lg: 1.5rem;
30
+ --spacing-trim-lg: 1rem;
31
+ --spacing-trim-xl: 1.5rem;
32
+ --spacing-trim-2xl: 2rem;
30
33
 
31
34
  /** Minimal trim to allow for ring/outline. */
32
35
  --spacing-form-chrome: var(--spacing-trim-xs);
@@ -34,39 +37,53 @@
34
37
  --spacing-form-section-gap: var(--spacing-trim-md);
35
38
 
36
39
  /**
37
- * Density: md (default).
40
+ * Control scale — the three canonical control heights. rem-based, so they scale with the
41
+ * pointer-coarse root font-size (16px fine / 20px coarse) and touch targets grow for free:
42
+ * sm 1.5rem = 24px fine / 30px coarse
43
+ * md 2rem = 32px fine / 40px coarse
44
+ * lg 2.5rem = 40px fine / 50px coarse
45
+ * The old 28px step and the `xs` density are gone; `sm` is the compact step.
38
46
  */
47
+ --dx-control-sm: 1.5rem;
48
+ --dx-control-md: 2rem;
49
+ --dx-control-lg: 2.5rem;
50
+
51
+ /**
52
+ * Density: md (default). One mechanism — a density class sets these two knobs and every control
53
+ * reads them, so density cascades to any subtree (including react-ui-menu items) with no React
54
+ * plumbing. `data-density` on a control remains a local override.
55
+ */
56
+ --dx-control: var(--dx-control-md);
57
+ --dx-control-pad: var(--spacing-trim-sm);
58
+ /* Text must fit inside --dx-control; at sm the default line box overshoots 24px. */
59
+ --dx-control-leading: normal;
39
60
  --spacing-form-padding: var(--spacing-trim-sm);
40
- --spacing-icon-button-padding: var(--spacing-trim-xs);
41
- --spacing-scroll-padding: 4px;
42
61
  }
43
62
 
44
63
  @layer dx-tokens {
45
- /**
46
- * Density: lg — large touch targets (~40px controls).
47
- */
48
- .dx-density-lg {
64
+ /** Density: lg — large touch targets. */
65
+ .dx-density-lg,
66
+ [data-density='lg'] {
67
+ --dx-control: var(--dx-control-lg);
68
+ --dx-control-pad: var(--spacing-trim-md);
49
69
  --spacing-form-padding: var(--spacing-trim-md);
50
- --spacing-icon-button-padding: var(--spacing-trim-sm);
51
- --spacing-scroll-padding: 8px;
52
70
  }
53
71
 
54
- /**
55
- * Density: sm — compact (~28px controls).
56
- */
57
- .dx-density-sm {
58
- --spacing-form-padding: var(--spacing-trim-xs);
59
- --spacing-icon-button-padding: var(--spacing-trim-xs);
60
- --spacing-scroll-padding: 2px;
72
+ /** Density: md — explicit reset so a nested region can restore the default inside lg/sm. */
73
+ .dx-density-md,
74
+ [data-density='md'] {
75
+ --dx-control: var(--dx-control-md);
76
+ --dx-control-pad: var(--spacing-trim-sm);
77
+ --spacing-form-padding: var(--spacing-trim-sm);
61
78
  }
62
79
 
63
- /**
64
- * Density: xs — ultra-compact (~24px controls).
65
- */
66
- .dx-density-xs {
80
+ /** Density: sm — compact. */
81
+ .dx-density-sm,
82
+ [data-density='sm'] {
83
+ --dx-control: var(--dx-control-sm);
84
+ --dx-control-pad: var(--spacing-trim-xs);
85
+ --dx-control-leading: 22px;
67
86
  --spacing-form-padding: var(--spacing-trim-xs);
68
- --spacing-icon-button-padding: var(--spacing-trim-xs);
69
- --spacing-scroll-padding: 2px;
70
87
  }
71
88
  }
72
89
 
@@ -79,40 +96,30 @@
79
96
  --dx-line-1: 1px;
80
97
  --dx-line-2: 2px;
81
98
 
82
- --dx-lacuna-12: 1.5rem;
83
- --dx-lacuna-3: 0.375rem;
84
- --dx-lacuna-4: 0.5rem;
85
- --dx-lacuna-6: 0.75rem;
86
-
99
+ /*
100
+ * Gutters source from the spacing ramp rather than carrying their own literals. The names are
101
+ * the gutter's own scale (form / dialog / card), so they intentionally do not line up with the
102
+ * ramp step names — sm is one ramp step, md two, lg four.
103
+ */
87
104
  /* Form: 8px */
88
- --dx-gutter-sm: 0.5rem;
105
+ --dx-gutter-sm: var(--spacing-trim-sm);
89
106
  /* Dialog: 16px */
90
- --dx-gutter-md: 1rem;
107
+ --dx-gutter-md: var(--spacing-trim-lg);
91
108
  /* Card: 32px */
92
- --dx-gutter-lg: 2rem;
109
+ --dx-gutter-lg: var(--spacing-trim-2xl);
93
110
  }
94
111
 
95
112
  :root {
96
- --dx-hair-line: 1px;
97
- --dx-landmark-line: 1px;
98
- --dx-modal-line: 1px;
99
- --dx-no-line: 0px;
100
- --dx-thick-line: 2px;
101
- --dx-focus-line: 2px;
102
-
103
113
  --dx-no-line: var(--dx-line-0);
104
114
  --dx-hair-line: var(--dx-line-1);
105
115
  --dx-thick-line: var(--dx-line-2);
106
116
 
117
+ --dx-landmark-line: var(--dx-hair-line);
107
118
  --dx-focus-offset: var(--dx-no-line);
108
119
  --dx-focus-line: var(--dx-thick-line);
109
120
  --dx-modal-line: var(--dx-hair-line);
110
121
  --dx-grid-focus-indicator-width: var(--dx-hair-line);
111
122
 
112
- --dx-input-md: var(--dx-lacuna-3);
113
- --dx-input-lg: var(--dx-lacuna-4);
114
- --dx-input-sm: var(--dx-lacuna-3);
115
-
116
123
  --dx-default-icons-size: 1rem; /* size=4 */
117
124
  }
118
125
 
@@ -121,18 +128,20 @@
121
128
  }
122
129
 
123
130
  :root {
124
- --dx-rail-content: 3rem;
125
- /** TODO(burdon): Audit. */
126
- /** TODO(burdon): Density (2 sizes). */
127
- --dx-rail-size: calc(var(--dx-rail-content) + 1px);
128
- --dx-rail-item: calc(var(--dx-rail-content) - 1rem);
129
- /* TODO(burdon): Added 1px for border. */
130
- /* TODO(burdon): Remove direct usage. */
131
- --dx-rail-action: calc(var(--dx-rail-content) - 0.5rem);
132
-
133
- --dx-topbar-size: var(--dx-rail-size); /* 50px */
134
- --dx-toolbar-size: var(--dx-rail-action); /* 40px */
135
- --dx-statusbar-size: var(--dx-rail-item); /* 32px */
131
+ /*
132
+ * Rails derive from the control scale rather than their own arithmetic, so a bar and the
133
+ * controls inside it land on the same grid. The old `+1px` border fudge is gone — a border
134
+ * belongs to the element's box, not to the size token (it leaked 49px into a dozen layouts).
135
+ * NOTE: px values assume the 16px (pointer: fine) root font-size; coarse is 20px (×1.25).
136
+ */
137
+ --dx-rail-content: 3rem; /* 48px the rail gutter, not a control */
138
+ --dx-rail-size: var(--dx-rail-content); /* 48px */
139
+ --dx-rail-item: var(--dx-control-md); /* 32px */
140
+ --dx-rail-action: var(--dx-control-lg); /* 40px */
141
+
142
+ --dx-topbar-size: var(--dx-rail-size); /* 48px */
143
+ --dx-toolbar-size: var(--dx-control-lg); /* 40px */
144
+ --dx-statusbar-size: var(--dx-control-md); /* 32px */
136
145
 
137
146
  /** NOTE: Sidebar optimized to show plank + companion with sidebar open on 16" MBP. */
138
147
  --dx-nav-sidebar-size: 350px;