@nubisco/ui 1.60.2 → 2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubisco/ui",
3
- "version": "1.60.2",
3
+ "version": "2.0.0",
4
4
  "description": "Vue 3 UI component library",
5
5
  "packageManager": "pnpm@11.0.8",
6
6
  "repository": {
@@ -3,6 +3,7 @@
3
3
  @use 'config';
4
4
  @use 'variables/layout';
5
5
  @use 'variables/colors';
6
+ @use 'variables/layers';
6
7
  @use 'variables/type';
7
8
  @use 'variables/grid';
8
9
  @use 'variables/animations';
@@ -115,20 +116,20 @@
115
116
  // Layer 1: panels, cards, sidebar body
116
117
  // Layer 2: nested panels, inspector sections
117
118
  // Layer 3: overlays, popovers, modals (highest)
118
- --nb-c-layer-0: #{colors.get-color('french-gray', 100)};
119
- --nb-c-layer-1: #{colors.get-color('plain-white', 100)};
120
- --nb-c-layer-2: #{colors.get-color('french-gray', 100)};
121
- --nb-c-layer-3: #{colors.get-color('plain-white', 100)};
119
+ --nb-c-layer-0: #{layers.level(layers.$light, surface, 0)};
120
+ --nb-c-layer-1: #{layers.level(layers.$light, surface, 1)};
121
+ --nb-c-layer-2: #{layers.level(layers.$light, surface, 2)};
122
+ --nb-c-layer-3: #{layers.level(layers.$light, surface, 3)};
122
123
 
123
- --nb-c-layer-border-0: #{colors.get-color('french-gray', 250)};
124
- --nb-c-layer-border-1: #{colors.get-color('french-gray', 250)};
125
- --nb-c-layer-border-2: #{colors.get-color('french-gray', 300)};
126
- --nb-c-layer-border-3: #{colors.get-color('french-gray', 200)};
124
+ --nb-c-layer-border-0: #{layers.level(layers.$light, border, 0)};
125
+ --nb-c-layer-border-1: #{layers.level(layers.$light, border, 1)};
126
+ --nb-c-layer-border-2: #{layers.level(layers.$light, border, 2)};
127
+ --nb-c-layer-border-3: #{layers.level(layers.$light, border, 3)};
127
128
 
128
- --nb-c-layer-hover-0: #{colors.get-color('french-gray', 200)};
129
- --nb-c-layer-hover-1: #{colors.get-color('french-gray', 150)};
130
- --nb-c-layer-hover-2: #{colors.get-color('french-gray', 200)};
131
- --nb-c-layer-hover-3: #{colors.get-color('french-gray', 150)};
129
+ --nb-c-layer-hover-0: #{layers.level(layers.$light, hover, 0)};
130
+ --nb-c-layer-hover-1: #{layers.level(layers.$light, hover, 1)};
131
+ --nb-c-layer-hover-2: #{layers.level(layers.$light, hover, 2)};
132
+ --nb-c-layer-hover-3: #{layers.level(layers.$light, hover, 3)};
132
133
 
133
134
  // Semantic surface tokens — default to layer-1 (most components sit on layer-0)
134
135
  --nb-c-surface: var(--nb-c-layer-1);
@@ -193,16 +194,22 @@
193
194
  --nb-c-component-plain-border-a11y: var(--nb-c-nouveau-gray-300-a11y);
194
195
 
195
196
  // Text
196
- --nb-c-text: var(--nb-c-plain-black-900);
197
- --nb-c-text-a11y: var(--nb-c-plain-black-900-a11y);
198
- --nb-c-text-muted: var(--nb-c-nouveau-gray-600);
199
- --nb-c-text-muted-a11y: var(--nb-c-nouveau-gray-600-a11y);
200
- --nb-c-text-subtle: var(--nb-c-nouveau-gray-500);
201
- --nb-c-text-subtle-a11y: var(--nb-c-nouveau-gray-500-a11y);
197
+ --nb-c-text: #{layers.token(layers.$light, text)};
198
+ --nb-c-text-a11y: #{colors.color-contrast(
199
+ layers.token(layers.$light, text)
200
+ )};
201
+ --nb-c-text-muted: #{layers.token(layers.$light, text-muted)};
202
+ --nb-c-text-muted-a11y: #{colors.color-contrast(
203
+ layers.token(layers.$light, text-muted)
204
+ )};
205
+ --nb-c-text-subtle: #{layers.token(layers.$light, text-subtle)};
206
+ --nb-c-text-subtle-a11y: #{colors.color-contrast(
207
+ layers.token(layers.$light, text-subtle)
208
+ )};
202
209
 
203
210
  // Form field tokens: heights and padding relative to --nb-base-unit (8px)
204
- --nb-c-field-bg: var(--nb-c-french-gray-100);
205
- --nb-c-field-border: var(--nb-c-french-gray-500);
211
+ --nb-c-field-bg: #{layers.token(layers.$light, field-bg)};
212
+ --nb-c-field-border: #{layers.token(layers.$light, field-border)};
206
213
  // xs is the compact size for dense surfaces (inspectors, toolbars).
207
214
  // Distinctly smaller than sm; pair with a 13px field font. See the
208
215
  // "Building an inspector" guide in the docs before reaching for it.
@@ -219,8 +226,8 @@
219
226
  --nb-field-label-gap: calc(var(--nb-base-unit) * 0.75);
220
227
 
221
228
  // Background tiers
222
- --nb-c-bg: var(--nb-c-french-gray-100);
223
- --nb-c-bg-soft: var(--nb-c-french-gray-200);
229
+ --nb-c-bg: var(--nb-c-layer-0);
230
+ --nb-c-bg-soft: var(--nb-c-layer-1);
224
231
 
225
232
  // Discrete / muted interactive states
226
233
  --nb-c-discrete: var(--nb-c-nouveau-gray-200);
@@ -430,20 +437,20 @@
430
437
 
431
438
  // ── Layer system (dark) ───────────────────────────────────────────────
432
439
  // In dark mode, layers get progressively lighter as elevation increases
433
- --nb-c-layer-0: #{colors.get-color('nouveau-gray', 900)};
434
- --nb-c-layer-1: #{colors.get-color('plain-black', 850)};
435
- --nb-c-layer-2: #{colors.get-color('nouveau-gray', 800)};
436
- --nb-c-layer-3: #{colors.get-color('nouveau-gray', 750)};
440
+ --nb-c-layer-0: #{layers.level(layers.$dark, surface, 0)};
441
+ --nb-c-layer-1: #{layers.level(layers.$dark, surface, 1)};
442
+ --nb-c-layer-2: #{layers.level(layers.$dark, surface, 2)};
443
+ --nb-c-layer-3: #{layers.level(layers.$dark, surface, 3)};
437
444
 
438
- --nb-c-layer-border-0: #{colors.get-color('nouveau-gray', 750)};
439
- --nb-c-layer-border-1: #{colors.get-color('nouveau-gray', 700)};
440
- --nb-c-layer-border-2: #{colors.get-color('nouveau-gray', 650)};
441
- --nb-c-layer-border-3: #{colors.get-color('nouveau-gray', 600)};
445
+ --nb-c-layer-border-0: #{layers.level(layers.$dark, border, 0)};
446
+ --nb-c-layer-border-1: #{layers.level(layers.$dark, border, 1)};
447
+ --nb-c-layer-border-2: #{layers.level(layers.$dark, border, 2)};
448
+ --nb-c-layer-border-3: #{layers.level(layers.$dark, border, 3)};
442
449
 
443
- --nb-c-layer-hover-0: #{colors.get-color('nouveau-gray', 800)};
444
- --nb-c-layer-hover-1: #{colors.get-color('nouveau-gray', 750)};
445
- --nb-c-layer-hover-2: #{colors.get-color('nouveau-gray', 700)};
446
- --nb-c-layer-hover-3: #{colors.get-color('nouveau-gray', 650)};
450
+ --nb-c-layer-hover-0: #{layers.level(layers.$dark, hover, 0)};
451
+ --nb-c-layer-hover-1: #{layers.level(layers.$dark, hover, 1)};
452
+ --nb-c-layer-hover-2: #{layers.level(layers.$dark, hover, 2)};
453
+ --nb-c-layer-hover-3: #{layers.level(layers.$dark, hover, 3)};
447
454
 
448
455
  // Surface tokens — override to dark values
449
456
  --nb-c-surface: var(--nb-c-layer-1);
@@ -454,17 +461,21 @@
454
461
  --nb-c-document: #{colors.get-color('plain-white', 50)};
455
462
 
456
463
  // Text
457
- --nb-c-text: #{colors.get-color('plain-white', 150)}; // primary text: ~#d9d9d9
458
- --nb-c-text-muted: #{colors.get-color('nouveau-gray', 400)};
459
- --nb-c-text-subtle: #{colors.get-color('nouveau-gray', 500)};
464
+ --nb-c-text: #{layers.token(layers.$dark, text)};
465
+ --nb-c-text-a11y: #{colors.color-contrast(layers.token(layers.$dark, text))};
466
+ --nb-c-text-muted: #{layers.token(layers.$dark, text-muted)};
467
+ --nb-c-text-muted-a11y: #{colors.color-contrast(
468
+ layers.token(layers.$dark, text-muted)
469
+ )};
470
+ --nb-c-text-subtle: #{layers.token(layers.$dark, text-subtle)};
460
471
 
461
472
  // Background tiers — deepest layer for page background
462
- --nb-c-bg: #{colors.get-color('nouveau-gray', 900)}; // page bg: ~#17191c (close to VitePress's #1b1b1f)
463
- --nb-c-bg-soft: #{colors.get-color('nouveau-gray', 850)}; // elevated bg: ~#242527
473
+ --nb-c-bg: var(--nb-c-layer-0);
474
+ --nb-c-bg-soft: var(--nb-c-layer-1);
464
475
 
465
476
  // Form fields
466
- --nb-c-field-bg: #{colors.get-color('plain-black', 800)}; // input bg: ~#333333
467
- --nb-c-field-border: #{colors.get-color('nouveau-gray', 600)};
477
+ --nb-c-field-bg: #{layers.token(layers.$dark, field-bg)};
478
+ --nb-c-field-border: #{layers.token(layers.$dark, field-border)};
468
479
 
469
480
  // Discrete
470
481
  --nb-c-discrete: #{colors.get-color('nouveau-gray', 700)};
@@ -0,0 +1,142 @@
1
+ @use 'sass:map';
2
+
3
+ /// Layer ramps.
4
+ ///
5
+ /// The four visual depth levels, defined as explicit values rather than derived
6
+ /// from the tint ramp. Depth separation is a measured property: each step has to
7
+ /// clear a perceptual distance from its neighbour, and every text colour has to
8
+ /// clear AAA on every surface it can land on, including hover surfaces. A
9
+ /// lightness-stepped tint chain cannot express that, and it also emitted
10
+ /// fractional `rgb(229.5, ...)` channels.
11
+ ///
12
+ /// Verify any change here with `node scripts/audit-contrast.mjs`, which measures
13
+ /// these against the published Carbon Design System token values in
14
+ /// `scripts/fixtures/carbon-tokens.json`.
15
+ ///
16
+ /// @group @nubisco/layers
17
+
18
+ /// Light ramp.
19
+ ///
20
+ /// Alternates the way Carbon's light themes do, because a light theme should
21
+ /// stay light as surfaces stack: CIE L* 94.05 ground, 98.22 panel, 91.95 nested
22
+ /// section, 96.14 popover. The deepest surface is still a bright L* 96, so
23
+ /// nesting never turns the page muddy.
24
+ ///
25
+ /// Two deliberate improvements on the reference. Carbon's g10 sets layer-02
26
+ /// byte-identical to its background and layer-03 to layer-01, so past one level
27
+ /// of nesting the fill carries no depth information at all. Here all four
28
+ /// surfaces are distinct, worst pair 2.08 dL*. And no surface goes above
29
+ /// L* 98.5: a fill pinned against the L*100 ceiling under dark text is the
30
+ /// light-theme equivalent of a near-black ground, and glare over long sessions
31
+ /// is set by the brightest surface, not the darkest.
32
+ ///
33
+ /// Because the fill alternates it cannot carry depth order on its own, so the
34
+ /// borders do: 2.15 / 2.60 / 3.05 / 3.50 against their own surface, escalating
35
+ /// with depth, capped well short of reading as a wireframe.
36
+ ///
37
+ /// Hover darkens on every layer, 5.7 to 8.0 dL*, and every hover value sits at
38
+ /// least 1.76 dL* from every surface value, so a hovered row never reads as
39
+ /// another layer's resting state.
40
+ ///
41
+ /// @type Map
42
+ $light: (
43
+ surface: (
44
+ 0: #edeeef,
45
+ 1: #f9fafb,
46
+ 2: #e7e8e9,
47
+ 3: #f3f4f5,
48
+ ),
49
+ border: (
50
+ 0: #a3a4a5,
51
+ 1: #9c9d9e,
52
+ 2: #838485,
53
+ 3: #818283,
54
+ ),
55
+ hover: (
56
+ 0: #dcddde,
57
+ 1: #e2e3e4,
58
+ 2: #d7d8d9,
59
+ 3: #e1e2e3,
60
+ ),
61
+ text: #242526,
62
+ text-muted: #38393a,
63
+ text-subtle: #545556,
64
+ field-bg: #cacbcc,
65
+ field-border: #646566,
66
+ );
67
+
68
+ /// Dark ramp.
69
+ ///
70
+ /// CIE L* 4.63 / 11.69 / 18.87 / 26.15, adjacent steps 7.06 / 7.18 / 7.27,
71
+ /// non-decreasing so deeper nesting separates more, matching the discipline of
72
+ /// Carbon's g100 ramp rather than our previous flat 6.70 / 5.20 / 5.58.
73
+ ///
74
+ /// Hover REVERSES at layer 3: levels 0 to 2 lighten by about 5.1 dL*, level 3
75
+ /// darkens by 5.42. There is no headroom above layer 3, and a hover surface
76
+ /// lighter than it would force muted text toward white, collapsing the gap
77
+ /// between the two text tiers. Carbon does the same thing at the top of its own
78
+ /// g90 ramp (layer-03 #6f6f6f hovers to #5e5e5e). Direction is not the
79
+ /// requirement, magnitude is.
80
+ ///
81
+ /// layer-0 sits low deliberately. Raising it drags the whole ramp up, which
82
+ /// raises layer 3, which forces muted text brighter to hold 7:1 there, which
83
+ /// forces primary brighter still to keep the tiers apart, which makes glare
84
+ /// worse rather than better. Halation is set at the top of the ramp, not the
85
+ /// bottom: primary text on the page ground measures 15.96:1 here, against
86
+ /// 17.63:1 for a near-black ground and 18.06:1 for a raised one.
87
+ ///
88
+ /// @type Map
89
+ $dark: (
90
+ surface: (
91
+ 0: #0f1011,
92
+ 1: #1e1f20,
93
+ 2: #2d2e2f,
94
+ 3: #3d3e3f,
95
+ ),
96
+ border: (
97
+ 0: #434445,
98
+ 1: #4d4e4f,
99
+ 2: #595a5b,
100
+ 3: #696a6b,
101
+ ),
102
+ hover: (
103
+ 0: #1a1b1c,
104
+ 1: #292a2b,
105
+ 2: #38393a,
106
+ 3: #313233,
107
+ ),
108
+ text: #eaebec,
109
+ text-muted: #d0d1d2,
110
+ text-subtle: #a7a8aa,
111
+ field-bg: #141516,
112
+ field-border: #626364,
113
+ );
114
+
115
+ /// The third text tier, `text-subtle`, is held to WCAG AA (4.5:1) rather than
116
+ /// AAA, on every one of the nine painted surfaces in its theme. AAA is not
117
+ /// reachable for a third tier: muted already sits at 9.43:1 on the darkest light
118
+ /// surface, so a tier lighter than muted and still above 7:1 has almost no room
119
+ /// to exist and would not read as a separate tier. Carbon makes the same call
120
+ /// with its own helper text. Use `text-subtle` only for genuinely non-essential
121
+ /// text, and never for anything a person must read to operate the interface.
122
+ ///
123
+ /// Returns one value from a ramp.
124
+ ///
125
+ /// @param {Map} $ramp - `$light` or `$dark`
126
+ /// @param {String} $role - surface, border or hover
127
+ /// @param {Number} $level - 0 through 3
128
+ /// @access public
129
+ /// @group @nubisco/layers
130
+ @function level($ramp, $role, $level) {
131
+ @return map.get(map.get($ramp, $role), $level);
132
+ }
133
+
134
+ /// Returns a scalar token from a ramp.
135
+ ///
136
+ /// @param {Map} $ramp - `$light` or `$dark`
137
+ /// @param {String} $key - text, text-muted, field-bg or field-border
138
+ /// @access public
139
+ /// @group @nubisco/layers
140
+ @function token($ramp, $key) {
141
+ @return map.get($ramp, $key);
142
+ }