@gitlab/ui 137.1.2 → 137.2.1

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 (44) hide show
  1. package/dist/components/charts/chart/chart.js +81 -20
  2. package/dist/components/charts/gauge/gauge.js +3 -13
  3. package/dist/components/charts/heatmap/heatmap.js +33 -13
  4. package/dist/components/charts/legend/legend.js +13 -1
  5. package/dist/index.css +2 -2
  6. package/dist/index.css.map +1 -1
  7. package/dist/tokens/build/js/tokens.dark.js +196 -191
  8. package/dist/tokens/build/js/tokens.js +218 -213
  9. package/dist/tokens/color_mode.js +17 -0
  10. package/dist/utils/charts/color_mode.js +95 -0
  11. package/dist/utils/charts/css_values.js +105 -0
  12. package/dist/utils/charts/theme.js +72 -7
  13. package/dist/utils/constants.js +5 -5
  14. package/package.json +6 -6
  15. package/src/components/base/alert/alert.scss +13 -48
  16. package/src/components/charts/chart/chart.vue +68 -22
  17. package/src/components/charts/gauge/gauge.vue +1 -12
  18. package/src/components/charts/heatmap/heatmap.vue +34 -10
  19. package/src/components/charts/legend/legend.vue +13 -1
  20. package/src/scss/themes.scss +23 -5
  21. package/src/tokens/build/css/tokens.css +20 -15
  22. package/src/tokens/build/css/tokens.dark.css +32 -27
  23. package/src/tokens/build/docs/tokens-tailwind-docs.dark.json +199 -199
  24. package/src/tokens/build/docs/tokens-tailwind-docs.json +198 -198
  25. package/src/tokens/build/figma/constants.dark.json +33 -33
  26. package/src/tokens/build/figma/constants.json +33 -33
  27. package/src/tokens/build/figma/mode.dark.json +66 -16
  28. package/src/tokens/build/figma/mode.json +54 -4
  29. package/src/tokens/build/js/tokens.dark.js +195 -190
  30. package/src/tokens/build/js/tokens.js +217 -212
  31. package/src/tokens/build/json/tokens.dark.json +378 -218
  32. package/src/tokens/build/json/tokens.json +388 -228
  33. package/src/tokens/build/scss/_tokens.dark.scss +32 -27
  34. package/src/tokens/build/scss/_tokens.scss +20 -15
  35. package/src/tokens/build/scss/_tokens_custom_properties.scss +5 -0
  36. package/src/tokens/color_mode.js +16 -0
  37. package/src/tokens/constant/color.neutral.tokens.json +33 -33
  38. package/src/tokens/contextual/alert.tokens.json +80 -35
  39. package/src/tokens/semantic/action.tokens.json +3 -3
  40. package/src/tokens/semantic/feedback.tokens.json +6 -6
  41. package/src/utils/charts/color_mode.js +83 -0
  42. package/src/utils/charts/css_values.js +59 -0
  43. package/src/utils/charts/theme.js +72 -5
  44. package/src/utils/constants.js +5 -5
@@ -30,10 +30,7 @@
30
30
  },
31
31
  "background": {
32
32
  "color": {
33
- "$value": {
34
- "default": "{feedback.neutral.background.color}",
35
- "dark": "{background.color.subtle}"
36
- },
33
+ "$value": "{feedback.neutral.background.color}",
37
34
  "$type": "color",
38
35
  "$description": "Used for the background color of a neutral alert.",
39
36
  "$extensions": {
@@ -45,6 +42,16 @@
45
42
  }
46
43
  },
47
44
  "border": {
45
+ "color": {
46
+ "$value": "{feedback.neutral.border.color}",
47
+ "$type": "color",
48
+ "$description": "Used for the border color of a neutral alert.",
49
+ "$extensions": {
50
+ "com.figma.scopes": [
51
+ "STROKE_COLOR"
52
+ ]
53
+ }
54
+ },
48
55
  "top": {
49
56
  "color": {
50
57
  "$value": {
@@ -52,13 +59,14 @@
52
59
  "dark": "{color.neutral.400}"
53
60
  },
54
61
  "$type": "color",
55
- "$description": "Used for the border center color of a neutral alert.",
62
+ "$description": "Used for the border color of a neutral alert.",
56
63
  "$extensions": {
57
64
  "com.figma.scopes": [
58
65
  "STROKE_COLOR"
59
66
  ]
60
67
  }
61
- }
68
+ },
69
+ "$deprecated": true
62
70
  },
63
71
  "bottom": {
64
72
  "color": {
@@ -70,7 +78,8 @@
70
78
  "STROKE_COLOR"
71
79
  ]
72
80
  }
73
- }
81
+ },
82
+ "$deprecated": true
74
83
  }
75
84
  }
76
85
  },
@@ -79,7 +88,7 @@
79
88
  "color": {
80
89
  "$value": {
81
90
  "default": "{text.color.heading}",
82
- "dark": "{color.blue.300}"
91
+ "dark": "{color.blue.200}"
83
92
  },
84
93
  "$type": "color",
85
94
  "$description": "Used for the title color of an info alert.",
@@ -92,10 +101,7 @@
92
101
  },
93
102
  "background": {
94
103
  "color": {
95
- "$value": {
96
- "default": "{feedback.info.background.color}",
97
- "dark": "{background.color.subtle}"
98
- },
104
+ "$value": "{feedback.info.background.color}",
99
105
  "$type": "color",
100
106
  "$description": "Used for the background color of an info alert.",
101
107
  "$extensions": {
@@ -107,6 +113,16 @@
107
113
  }
108
114
  },
109
115
  "border": {
116
+ "color": {
117
+ "$value": "{feedback.info.border.color}",
118
+ "$type": "color",
119
+ "$description": "Used for the border color of an info alert.",
120
+ "$extensions": {
121
+ "com.figma.scopes": [
122
+ "STROKE_COLOR"
123
+ ]
124
+ }
125
+ },
110
126
  "top": {
111
127
  "color": {
112
128
  "$value": {
@@ -120,14 +136,16 @@
120
136
  "STROKE_COLOR"
121
137
  ]
122
138
  }
123
- }
139
+ },
140
+ "$deprecated": true
124
141
  },
125
142
  "bottom": {
126
143
  "color": {
127
144
  "$value": "{color.alpha.0}",
128
145
  "$type": "color",
129
146
  "$description": "Used for the border bottom color of an info alert."
130
- }
147
+ },
148
+ "$deprecated": true
131
149
  }
132
150
  }
133
151
  },
@@ -136,7 +154,7 @@
136
154
  "color": {
137
155
  "$value": {
138
156
  "default": "{text.color.heading}",
139
- "dark": "{color.green.300}"
157
+ "dark": "{color.green.200}"
140
158
  },
141
159
  "$type": "color",
142
160
  "$description": "Used for the title color of a success alert.",
@@ -149,10 +167,7 @@
149
167
  },
150
168
  "background": {
151
169
  "color": {
152
- "$value": {
153
- "default": "{feedback.success.background.color}",
154
- "dark": "{background.color.subtle}"
155
- },
170
+ "$value": "{feedback.success.background.color}",
156
171
  "$type": "color",
157
172
  "$description": "Used for the background color of a success alert.",
158
173
  "$extensions": {
@@ -164,6 +179,16 @@
164
179
  }
165
180
  },
166
181
  "border": {
182
+ "color": {
183
+ "$value": "{feedback.success.border.color}",
184
+ "$type": "color",
185
+ "$description": "Used for the border color of a success alert.",
186
+ "$extensions": {
187
+ "com.figma.scopes": [
188
+ "STROKE_COLOR"
189
+ ]
190
+ }
191
+ },
167
192
  "top": {
168
193
  "color": {
169
194
  "$value": {
@@ -177,7 +202,8 @@
177
202
  "STROKE_COLOR"
178
203
  ]
179
204
  }
180
- }
205
+ },
206
+ "$deprecated": true
181
207
  },
182
208
  "bottom": {
183
209
  "color": {
@@ -189,7 +215,8 @@
189
215
  "STROKE_COLOR"
190
216
  ]
191
217
  }
192
- }
218
+ },
219
+ "$deprecated": true
193
220
  }
194
221
  }
195
222
  },
@@ -198,7 +225,7 @@
198
225
  "color": {
199
226
  "$value": {
200
227
  "default": "{text.color.heading}",
201
- "dark": "{color.orange.300}"
228
+ "dark": "{color.orange.200}"
202
229
  },
203
230
  "$type": "color",
204
231
  "$description": "Used for the title color of a warning alert.",
@@ -211,10 +238,7 @@
211
238
  },
212
239
  "background": {
213
240
  "color": {
214
- "$value": {
215
- "default": "{feedback.warning.background.color}",
216
- "dark": "{background.color.subtle}"
217
- },
241
+ "$value": "{feedback.warning.background.color}",
218
242
  "$type": "color",
219
243
  "$description": "Used for the background color of a warning alert.",
220
244
  "$extensions": {
@@ -226,6 +250,16 @@
226
250
  }
227
251
  },
228
252
  "border": {
253
+ "color": {
254
+ "$value": "{feedback.warning.border.color}",
255
+ "$type": "color",
256
+ "$description": "Used for the border color of a warning alert.",
257
+ "$extensions": {
258
+ "com.figma.scopes": [
259
+ "STROKE_COLOR"
260
+ ]
261
+ }
262
+ },
229
263
  "top": {
230
264
  "color": {
231
265
  "$value": {
@@ -239,7 +273,8 @@
239
273
  "STROKE_COLOR"
240
274
  ]
241
275
  }
242
- }
276
+ },
277
+ "$deprecated": true
243
278
  },
244
279
  "bottom": {
245
280
  "color": {
@@ -251,7 +286,8 @@
251
286
  "STROKE_COLOR"
252
287
  ]
253
288
  }
254
- }
289
+ },
290
+ "$deprecated": true
255
291
  }
256
292
  }
257
293
  },
@@ -260,7 +296,7 @@
260
296
  "color": {
261
297
  "$value": {
262
298
  "default": "{text.color.heading}",
263
- "dark": "{color.red.300}"
299
+ "dark": "{color.red.200}"
264
300
  },
265
301
  "$type": "color",
266
302
  "$description": "Used for the title color of a danger alert.",
@@ -273,10 +309,7 @@
273
309
  },
274
310
  "background": {
275
311
  "color": {
276
- "$value": {
277
- "default": "{feedback.danger.background.color}",
278
- "dark": "{background.color.subtle}"
279
- },
312
+ "$value": "{feedback.danger.background.color}",
280
313
  "$type": "color",
281
314
  "$description": "Used for the background color of a danger alert.",
282
315
  "$extensions": {
@@ -288,6 +321,16 @@
288
321
  }
289
322
  },
290
323
  "border": {
324
+ "color": {
325
+ "$value": "{feedback.danger.border.color}",
326
+ "$type": "color",
327
+ "$description": "Used for the border color of a danger alert.",
328
+ "$extensions": {
329
+ "com.figma.scopes": [
330
+ "STROKE_COLOR"
331
+ ]
332
+ }
333
+ },
291
334
  "top": {
292
335
  "color": {
293
336
  "$value": {
@@ -301,7 +344,8 @@
301
344
  "STROKE_COLOR"
302
345
  ]
303
346
  }
304
- }
347
+ },
348
+ "$deprecated": true
305
349
  },
306
350
  "bottom": {
307
351
  "color": {
@@ -313,7 +357,8 @@
313
357
  "STROKE_COLOR"
314
358
  ]
315
359
  }
316
- }
360
+ },
361
+ "$deprecated": true
317
362
  }
318
363
  }
319
364
  }
@@ -542,7 +542,7 @@
542
542
  "color": {
543
543
  "default": {
544
544
  "$value": {
545
- "default": "{color.red.500}",
545
+ "default": "{color.red.600}",
546
546
  "dark": "{color.red.200}"
547
547
  },
548
548
  "$type": "color",
@@ -556,7 +556,7 @@
556
556
  },
557
557
  "hover": {
558
558
  "$value": {
559
- "default": "{color.red.700}",
559
+ "default": "{color.red.800}",
560
560
  "dark": "{color.red.100}"
561
561
  },
562
562
  "$type": "color",
@@ -581,7 +581,7 @@
581
581
  },
582
582
  "active": {
583
583
  "$value": {
584
- "default": "{color.red.900}",
584
+ "default": "{color.red.950}",
585
585
  "dark": "{color.red.100}"
586
586
  },
587
587
  "$type": "color",
@@ -130,7 +130,7 @@
130
130
  "color": {
131
131
  "$value": {
132
132
  "default": "{color.neutral.600}",
133
- "dark": "{color.neutral.300}"
133
+ "dark": "{color.neutral.200}"
134
134
  },
135
135
  "$type": "color",
136
136
  "$description": "Used for the icon of a neutral feedback item when there isn't a specific meaning or urgency.",
@@ -194,7 +194,7 @@
194
194
  "color": {
195
195
  "$value": {
196
196
  "default": "{color.blue.600}",
197
- "dark": "{color.blue.300}"
197
+ "dark": "{color.blue.200}"
198
198
  },
199
199
  "$type": "color",
200
200
  "$description": "Used for the icon of an informational feedback item when highlighting new information or a change that doesn't require immediate action.",
@@ -258,7 +258,7 @@
258
258
  "color": {
259
259
  "$value": {
260
260
  "default": "{color.green.600}",
261
- "dark": "{color.green.300}"
261
+ "dark": "{color.green.200}"
262
262
  },
263
263
  "$type": "color",
264
264
  "$description": "Used for the icon of a success feedback item when confirming the successful completion of a user-initiated action.",
@@ -322,7 +322,7 @@
322
322
  "color": {
323
323
  "$value": {
324
324
  "default": "{color.orange.600}",
325
- "dark": "{color.orange.300}"
325
+ "dark": "{color.orange.200}"
326
326
  },
327
327
  "$type": "color",
328
328
  "$description": "Used for the icon of a warning feedback item when notifying about a potential issue or sensitive information.",
@@ -386,7 +386,7 @@
386
386
  "color": {
387
387
  "$value": {
388
388
  "default": "{color.red.600}",
389
- "dark": "{color.red.300}"
389
+ "dark": "{color.red.200}"
390
390
  },
391
391
  "$type": "color",
392
392
  "$description": "Used for the icon of a danger feedback item when notifying about a critical issue that has just occurred and requires immediate attention.",
@@ -450,7 +450,7 @@
450
450
  "color": {
451
451
  "$value": {
452
452
  "default": "{color.purple.600}",
453
- "dark": "{color.purple.300}"
453
+ "dark": "{color.purple.200}"
454
454
  },
455
455
  "$type": "color",
456
456
  "$description": "Used for the icon of a brand-related feedback item when highlighting information that pertains to GitLab.",
@@ -0,0 +1,83 @@
1
+ import { DARK_ROOT_CLASS, DARK_SCOPE_CLASS, LIGHT_SCOPE_CLASS } from '../../tokens/color_mode';
2
+
3
+ const SCOPE_CLASSES = [DARK_SCOPE_CLASS, LIGHT_SCOPE_CLASS, DARK_ROOT_CLASS];
4
+
5
+ const SCOPE_SELECTOR = SCOPE_CLASSES.map((scopeClass) => `.${scopeClass}`).join(', ');
6
+
7
+ /**
8
+ * Whether the given element sits in dark mode.
9
+ *
10
+ * Colour mode is not a document-wide fact. Tokens are declared for the root dark class and for a
11
+ * dark scope class, so any subtree can be flipped independently, and a light scope can flip one
12
+ * back inside a dark document. The nearest scope wins, which is what `closest` gives us.
13
+ *
14
+ * This lives under `charts` rather than in the general utilities because charts are the only thing
15
+ * in the package that needs it. Everywhere else, colour follows tokens and CSS re-resolves them on
16
+ * its own. Charts cannot always do that: ECharts parses colours numerically for its visual maps,
17
+ * and canvas cannot read custom properties at all, so those values have to be chosen in JavaScript
18
+ * and re-chosen when the mode changes.
19
+ */
20
+ export const isDarkMode = (element) => {
21
+ const scope = element?.closest?.(SCOPE_SELECTOR);
22
+
23
+ return Boolean(scope) && !scope.classList.contains(LIGHT_SCOPE_CLASS);
24
+ };
25
+
26
+ // Keyed by subscription so that each subscribed element's last known mode is tracked separately.
27
+ const subscriptions = new Map();
28
+ let observer = null;
29
+
30
+ // Most class mutations (hover states, dropdowns opening and closing) have nothing to do with the
31
+ // colour mode. Only mutations that added or removed a scope class can change it, so anything else
32
+ // can be dismissed with this cheap check instead of re-checking every subscriber.
33
+ const touchesColorMode = ({ target, oldValue }) =>
34
+ SCOPE_CLASSES.some(
35
+ (scopeClass) => target.classList?.contains(scopeClass) || oldValue?.includes(scopeClass),
36
+ );
37
+
38
+ const notify = () => {
39
+ subscriptions.forEach(({ element, subscriber, mode }, key) => {
40
+ const current = isDarkMode(element);
41
+
42
+ if (current !== mode) {
43
+ subscriptions.set(key, { element, subscriber, mode: current });
44
+ subscriber(current);
45
+ }
46
+ });
47
+ };
48
+
49
+ /**
50
+ * Calls back when the colour mode of `element` changes, and returns a function that unsubscribes.
51
+ *
52
+ * Most colour can be left to CSS, which re-resolves custom properties on its own. This exists for
53
+ * the places that cannot: colours ECharts parses numerically, such as the interpolated stops of a
54
+ * continuous `visualMap`, and colours handed to a canvas context. Both need a concrete value picked
55
+ * in JavaScript, so both need telling when the mode flips.
56
+ *
57
+ * Because a scope class can live on any ancestor, this watches the whole document for class
58
+ * changes, then re-checks each subscribed element and only calls back when its own mode changed.
59
+ */
60
+ export const onColorModeChange = (element, subscriber) => {
61
+ if (!subscriptions.size && typeof MutationObserver !== 'undefined') {
62
+ observer = new MutationObserver((mutations) => {
63
+ if (mutations.some((mutation) => touchesColorMode(mutation))) notify();
64
+ });
65
+ observer.observe(document.documentElement, {
66
+ attributeFilter: ['class'],
67
+ attributeOldValue: true,
68
+ subtree: true,
69
+ });
70
+ }
71
+
72
+ const key = Symbol('colorModeSubscription');
73
+ subscriptions.set(key, { element, subscriber, mode: isDarkMode(element) });
74
+
75
+ return () => {
76
+ subscriptions.delete(key);
77
+
78
+ if (!subscriptions.size) {
79
+ observer?.disconnect();
80
+ observer = null;
81
+ }
82
+ };
83
+ };
@@ -0,0 +1,59 @@
1
+ import { mapValues } from 'lodash-es';
2
+
3
+ const CSS_VAR = /^var\(\s*(--[\w-]+)\s*(?:,\s*([\s\S]*?)\s*)?\)$/;
4
+
5
+ const resolveString = (value, styles) => {
6
+ if (value.toLowerCase() === 'currentcolor') {
7
+ return styles.color;
8
+ }
9
+
10
+ const match = CSS_VAR.exec(value);
11
+ if (!match) {
12
+ return value;
13
+ }
14
+
15
+ const [, name, fallback] = match;
16
+ const resolved = styles.getPropertyValue(name).trim();
17
+
18
+ if (resolved) {
19
+ return resolved;
20
+ }
21
+
22
+ return fallback ? resolveString(fallback, styles) : value;
23
+ };
24
+
25
+ /**
26
+ * Deeply replaces `var(--token)` and `currentcolor` strings with the values they resolve to for the
27
+ * given element.
28
+ *
29
+ * The SVG renderer needs none of this: it emits both forms into the DOM, where the browser resolves
30
+ * them and keeps them live across a colour-mode change. Canvas has no such context. `fillStyle` is
31
+ * a plain string setter, so an unparseable `var(--token)` is dropped on the floor and leaves the
32
+ * previous fill in place, while `currentcolor` has no element to inherit from and is defined to
33
+ * resolve to opaque black. Either way a canvas-rendered chart silently loses its themed colours.
34
+ *
35
+ * Resolution is relative to `element` rather than the document root so that `.gl-dark-scope`
36
+ * ancestors are honoured.
37
+ *
38
+ * Values are resolved once, so callers are responsible for resolving again when the colour mode
39
+ * changes.
40
+ */
41
+ export const resolveCssValues = (input, element) => {
42
+ const styles = window.getComputedStyle(element);
43
+
44
+ const walk = (value) => {
45
+ if (typeof value === 'string') {
46
+ return resolveString(value, styles);
47
+ }
48
+ if (Array.isArray(value)) {
49
+ return value.map((item) => walk(item));
50
+ }
51
+ // Themes carry functions (formatters) which must pass through untouched.
52
+ if (value !== null && typeof value === 'object') {
53
+ return mapValues(value, walk);
54
+ }
55
+ return value;
56
+ };
57
+
58
+ return walk(input);
59
+ };
@@ -1,8 +1,9 @@
1
1
  import {
2
+ GL_COLOR_NEUTRAL_0,
2
3
  GL_COLOR_NEUTRAL_50,
3
4
  GL_COLOR_NEUTRAL_100,
4
- GL_COLOR_NEUTRAL_500,
5
- GL_COLOR_NEUTRAL_900,
5
+ GL_COLOR_NEUTRAL_800,
6
+ GL_COLOR_NEUTRAL_950,
6
7
  GL_COLOR_DATA_AQUA_50,
7
8
  GL_COLOR_DATA_AQUA_100,
8
9
  GL_COLOR_DATA_AQUA_200,
@@ -58,6 +59,7 @@ import {
58
59
  GL_COLOR_DATA_ORANGE_800,
59
60
  GL_COLOR_DATA_ORANGE_950,
60
61
  GL_COLOR_DATA_ORANGE_900,
62
+ GL_FONT_FAMILY_REGULAR,
61
63
  } from '../../tokens/build/js/tokens';
62
64
  import {
63
65
  scrollHandleSvgPath,
@@ -66,20 +68,45 @@ import {
66
68
  clearAllSvgPath,
67
69
  downloadSvgPath,
68
70
  } from '../svgs/svg_paths';
71
+ import { defaultFontSize } from './config';
69
72
 
70
73
  const GL_BORDER_RADIUS_BASE = '0.25rem';
71
74
 
72
75
  export const themeName = 'gitlab';
73
76
 
77
+ /**
78
+ * Sequential scales step away from the surface so that higher values gain contrast against it:
79
+ * darker on a light surface, lighter on a dark one. The lowest step is neutral rather than blue, so
80
+ * that "no data" reads as absence instead of as the bottom of the data range.
81
+ *
82
+ * https://design.gitlab.com/data-visualization/color#sequential-data
83
+ */
74
84
  export const heatmapHues = [
75
85
  GL_COLOR_NEUTRAL_100,
76
- GL_COLOR_DATA_BLUE_200,
77
- GL_COLOR_DATA_BLUE_400,
86
+ GL_COLOR_DATA_BLUE_500,
78
87
  GL_COLOR_DATA_BLUE_600,
79
88
  GL_COLOR_DATA_BLUE_800,
89
+ GL_COLOR_DATA_BLUE_950,
80
90
  ];
81
91
 
82
- export const gaugeNeutralHues = [GL_COLOR_NEUTRAL_900, GL_COLOR_NEUTRAL_500];
92
+ export const heatmapHuesDark = [
93
+ GL_COLOR_NEUTRAL_800,
94
+ GL_COLOR_DATA_BLUE_600,
95
+ GL_COLOR_DATA_BLUE_500,
96
+ GL_COLOR_DATA_BLUE_400,
97
+ GL_COLOR_DATA_BLUE_200,
98
+ ];
99
+
100
+ /**
101
+ * The plate behind the cells, and the separators between them. Separators match the surface so that
102
+ * neighbouring cells, which are only guaranteed to contrast with the surface and not with each
103
+ * other, stay distinguishable.
104
+ *
105
+ * https://design.gitlab.com/data-visualization/color#visual-separators
106
+ */
107
+ export const heatmapSurface = { plate: GL_COLOR_NEUTRAL_100, separator: GL_COLOR_NEUTRAL_0 };
108
+ export const heatmapSurfaceDark = { plate: GL_COLOR_NEUTRAL_800, separator: GL_COLOR_NEUTRAL_950 };
109
+
83
110
  export const gaugeSafeHues = [GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_BLUE_900];
84
111
  export const gaugeWarningHue = GL_COLOR_DATA_ORANGE_500;
85
112
 
@@ -159,12 +186,33 @@ export const colorPaletteDark = [
159
186
 
160
187
  export const colorFromDarkPalette = (index) => colorPaletteDark[index % colorPaletteDark.length];
161
188
 
189
+ // ECharts lays out text by assigning a CSS font shorthand to a canvas context and calling
190
+ // `measureText`, even under the SVG renderer. Canvas does not substitute CSS custom properties,
191
+ // and an unparseable shorthand is rejected wholesale, leaving text measured at the canvas default
192
+ // of 10px sans-serif. So the chart font stack must be concrete: drop the leading
193
+ // `var(--default-regular-font, 'GitLab Sans')` consumer override, whose own fallback is the
194
+ // 'GitLab Sans' that follows it.
195
+ export const defaultFontFamily = GL_FONT_FAMILY_REGULAR.filter(
196
+ (family) => !family.startsWith('var('),
197
+ ).join(', ');
198
+
199
+ /**
200
+ * Chart font sizes are concrete pixel numbers rather than the rem-based font.size tokens they
201
+ * mirror. zrender measures text on a detached canvas, which always resolves 1rem as 16px however
202
+ * the document root is set, so rem sizes render scaled while being laid out at the wrong width.
203
+ *
204
+ * `defaultFontSize` (12, font.size.100) is set per component in the theme below, because ECharts
205
+ * pins fontSize in its own component defaults (axis labels at 12, title at 18, gauge detail at 30)
206
+ * and those shadow `textStyle`.
207
+ */
208
+
162
209
  const axes = {
163
210
  axisLabel: {
164
211
  margin: 8,
165
212
  show: true,
166
213
  color: 'var(--gl-chart-axis-text-color)',
167
214
  hideOverlap: true,
215
+ fontSize: defaultFontSize,
168
216
  },
169
217
  axisLine: {
170
218
  show: false,
@@ -188,6 +236,7 @@ const axes = {
188
236
  nameGap: 30,
189
237
  nameTextStyle: {
190
238
  fontWeight: 'bold',
239
+ fontSize: defaultFontSize,
191
240
  },
192
241
  splitLine: {
193
242
  lineStyle: {
@@ -208,6 +257,24 @@ export const createTheme = (options = {}) => ({
208
257
  backgroundColor: 'transparent',
209
258
  textStyle: {
210
259
  color: 'var(--gl-text-color-default)',
260
+ fontFamily: defaultFontFamily,
261
+ fontSize: defaultFontSize,
262
+ },
263
+ title: {
264
+ textStyle: {
265
+ fontSize: 18, // font.size.500-fixed, 1.125rem
266
+ },
267
+ },
268
+ gauge: {
269
+ axisLabel: {
270
+ fontSize: 14, // font.size.300, 0.875rem, the default body size
271
+ color: 'var(--gl-chart-axis-text-color)',
272
+ },
273
+ detail: {
274
+ fontSize: 30,
275
+ fontWeight: 'bold',
276
+ color: 'var(--gl-text-color-default)',
277
+ },
211
278
  },
212
279
  markLine: {
213
280
  silent: true,
@@ -393,9 +393,9 @@ export const loadingIconVariants = {
393
393
 
394
394
  export const themes = [
395
395
  { class: null, value: 'default', title: 'Default' },
396
- { class: 'tint-neutral-indigo', value: 'indigo', title: 'Indigo' },
397
- { class: 'tint-neutral-blue', value: 'blue', title: 'Blue' },
398
- { class: 'tint-neutral-green', value: 'green', title: 'Green' },
399
- { class: 'tint-neutral-red', value: 'red', title: 'Red' },
400
- { class: 'tint-neutral-gray', value: 'gray', title: 'Gray' },
396
+ { class: 'ui-indigo', value: 'indigo', title: 'Indigo' },
397
+ { class: 'ui-blue', value: 'blue', title: 'Blue' },
398
+ { class: 'ui-green', value: 'green', title: 'Green' },
399
+ { class: 'ui-red', value: 'red', title: 'Red' },
400
+ { class: 'ui-gray', value: 'gray', title: 'Gray' },
401
401
  ];