@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
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The classes that decide which set of token values applies to an element.
3
+ *
4
+ * Dark mode is applied either to the whole document, by putting `gl-dark` on the root, or to a
5
+ * subtree, by putting `gl-dark-scope` on any ancestor. `gl-light-scope` flips a subtree back.
6
+ *
7
+ * These are the single source of truth: `bin/build_tokens.mjs` builds the CSS selectors from them,
8
+ * so the stylesheets and any JavaScript that has to reason about the colour mode cannot drift
9
+ * apart. Changing a name here changes both.
10
+ */
11
+ const DARK_ROOT_CLASS = 'gl-dark';
12
+ const DARK_SCOPE_CLASS = 'gl-dark-scope';
13
+ const LIGHT_SCOPE_CLASS = 'gl-light-scope';
14
+ const LIGHT_SELECTOR = `:root, .${LIGHT_SCOPE_CLASS}`;
15
+ const DARK_SELECTOR = `:root.${DARK_ROOT_CLASS}, .${DARK_SCOPE_CLASS}`;
16
+
17
+ export { DARK_ROOT_CLASS, DARK_SCOPE_CLASS, DARK_SELECTOR, LIGHT_SCOPE_CLASS, LIGHT_SELECTOR };
@@ -0,0 +1,95 @@
1
+ import { LIGHT_SCOPE_CLASS, DARK_SCOPE_CLASS, DARK_ROOT_CLASS } from '../../tokens/color_mode';
2
+
3
+ const SCOPE_CLASSES = [DARK_SCOPE_CLASS, LIGHT_SCOPE_CLASS, DARK_ROOT_CLASS];
4
+ const SCOPE_SELECTOR = SCOPE_CLASSES.map(scopeClass => `.${scopeClass}`).join(', ');
5
+
6
+ /**
7
+ * Whether the given element sits in dark mode.
8
+ *
9
+ * Colour mode is not a document-wide fact. Tokens are declared for the root dark class and for a
10
+ * dark scope class, so any subtree can be flipped independently, and a light scope can flip one
11
+ * back inside a dark document. The nearest scope wins, which is what `closest` gives us.
12
+ *
13
+ * This lives under `charts` rather than in the general utilities because charts are the only thing
14
+ * in the package that needs it. Everywhere else, colour follows tokens and CSS re-resolves them on
15
+ * its own. Charts cannot always do that: ECharts parses colours numerically for its visual maps,
16
+ * and canvas cannot read custom properties at all, so those values have to be chosen in JavaScript
17
+ * and re-chosen when the mode changes.
18
+ */
19
+ const isDarkMode = element => {
20
+ var _element$closest;
21
+ const scope = element === null || element === void 0 ? void 0 : (_element$closest = element.closest) === null || _element$closest === void 0 ? void 0 : _element$closest.call(element, SCOPE_SELECTOR);
22
+ return Boolean(scope) && !scope.classList.contains(LIGHT_SCOPE_CLASS);
23
+ };
24
+
25
+ // Keyed by subscription so that each subscribed element's last known mode is tracked separately.
26
+ const subscriptions = new Map();
27
+ let observer = null;
28
+
29
+ // Most class mutations (hover states, dropdowns opening and closing) have nothing to do with the
30
+ // colour mode. Only mutations that added or removed a scope class can change it, so anything else
31
+ // can be dismissed with this cheap check instead of re-checking every subscriber.
32
+ const touchesColorMode = _ref => {
33
+ let target = _ref.target,
34
+ oldValue = _ref.oldValue;
35
+ return SCOPE_CLASSES.some(scopeClass => {
36
+ var _target$classList;
37
+ return ((_target$classList = target.classList) === null || _target$classList === void 0 ? void 0 : _target$classList.contains(scopeClass)) || (oldValue === null || oldValue === void 0 ? void 0 : oldValue.includes(scopeClass));
38
+ });
39
+ };
40
+ const notify = () => {
41
+ subscriptions.forEach((_ref2, key) => {
42
+ let element = _ref2.element,
43
+ subscriber = _ref2.subscriber,
44
+ mode = _ref2.mode;
45
+ const current = isDarkMode(element);
46
+ if (current !== mode) {
47
+ subscriptions.set(key, {
48
+ element,
49
+ subscriber,
50
+ mode: current
51
+ });
52
+ subscriber(current);
53
+ }
54
+ });
55
+ };
56
+
57
+ /**
58
+ * Calls back when the colour mode of `element` changes, and returns a function that unsubscribes.
59
+ *
60
+ * Most colour can be left to CSS, which re-resolves custom properties on its own. This exists for
61
+ * the places that cannot: colours ECharts parses numerically, such as the interpolated stops of a
62
+ * continuous `visualMap`, and colours handed to a canvas context. Both need a concrete value picked
63
+ * in JavaScript, so both need telling when the mode flips.
64
+ *
65
+ * Because a scope class can live on any ancestor, this watches the whole document for class
66
+ * changes, then re-checks each subscribed element and only calls back when its own mode changed.
67
+ */
68
+ const onColorModeChange = (element, subscriber) => {
69
+ if (!subscriptions.size && typeof MutationObserver !== 'undefined') {
70
+ observer = new MutationObserver(mutations => {
71
+ if (mutations.some(mutation => touchesColorMode(mutation))) notify();
72
+ });
73
+ observer.observe(document.documentElement, {
74
+ attributeFilter: ['class'],
75
+ attributeOldValue: true,
76
+ subtree: true
77
+ });
78
+ }
79
+ const key = Symbol('colorModeSubscription');
80
+ subscriptions.set(key, {
81
+ element,
82
+ subscriber,
83
+ mode: isDarkMode(element)
84
+ });
85
+ return () => {
86
+ subscriptions.delete(key);
87
+ if (!subscriptions.size) {
88
+ var _observer;
89
+ (_observer = observer) === null || _observer === void 0 ? void 0 : _observer.disconnect();
90
+ observer = null;
91
+ }
92
+ };
93
+ };
94
+
95
+ export { isDarkMode, onColorModeChange };
@@ -0,0 +1,105 @@
1
+ import { mapValues } from 'lodash-es';
2
+
3
+ function _arrayLikeToArray(r, a) {
4
+ (null == a || a > r.length) && (a = r.length);
5
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
6
+ return n;
7
+ }
8
+ function _arrayWithHoles(r) {
9
+ if (Array.isArray(r)) return r;
10
+ }
11
+ function _iterableToArrayLimit(r, l) {
12
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
13
+ if (null != t) {
14
+ var e,
15
+ n,
16
+ i,
17
+ u,
18
+ a = [],
19
+ f = !0,
20
+ o = !1;
21
+ try {
22
+ if (i = (t = t.call(r)).next, 0 === l) {
23
+ if (Object(t) !== t) return;
24
+ f = !1;
25
+ } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
26
+ } catch (r) {
27
+ o = !0, n = r;
28
+ } finally {
29
+ try {
30
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
31
+ } finally {
32
+ if (o) throw n;
33
+ }
34
+ }
35
+ return a;
36
+ }
37
+ }
38
+ function _nonIterableRest() {
39
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
40
+ }
41
+ function _slicedToArray(r, e) {
42
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
43
+ }
44
+ function _unsupportedIterableToArray(r, a) {
45
+ if (r) {
46
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
47
+ var t = {}.toString.call(r).slice(8, -1);
48
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
49
+ }
50
+ }
51
+
52
+ const CSS_VAR = /^var\(\s*(--[\w-]+)\s*(?:,\s*([\s\S]*?)\s*)?\)$/;
53
+ const resolveString = (value, styles) => {
54
+ if (value.toLowerCase() === 'currentcolor') {
55
+ return styles.color;
56
+ }
57
+ const match = CSS_VAR.exec(value);
58
+ if (!match) {
59
+ return value;
60
+ }
61
+ const _match = _slicedToArray(match, 3),
62
+ name = _match[1],
63
+ fallback = _match[2];
64
+ const resolved = styles.getPropertyValue(name).trim();
65
+ if (resolved) {
66
+ return resolved;
67
+ }
68
+ return fallback ? resolveString(fallback, styles) : value;
69
+ };
70
+
71
+ /**
72
+ * Deeply replaces `var(--token)` and `currentcolor` strings with the values they resolve to for the
73
+ * given element.
74
+ *
75
+ * The SVG renderer needs none of this: it emits both forms into the DOM, where the browser resolves
76
+ * them and keeps them live across a colour-mode change. Canvas has no such context. `fillStyle` is
77
+ * a plain string setter, so an unparseable `var(--token)` is dropped on the floor and leaves the
78
+ * previous fill in place, while `currentcolor` has no element to inherit from and is defined to
79
+ * resolve to opaque black. Either way a canvas-rendered chart silently loses its themed colours.
80
+ *
81
+ * Resolution is relative to `element` rather than the document root so that `.gl-dark-scope`
82
+ * ancestors are honoured.
83
+ *
84
+ * Values are resolved once, so callers are responsible for resolving again when the colour mode
85
+ * changes.
86
+ */
87
+ const resolveCssValues = (input, element) => {
88
+ const styles = window.getComputedStyle(element);
89
+ const walk = value => {
90
+ if (typeof value === 'string') {
91
+ return resolveString(value, styles);
92
+ }
93
+ if (Array.isArray(value)) {
94
+ return value.map(item => walk(item));
95
+ }
96
+ // Themes carry functions (formatters) which must pass through untouched.
97
+ if (value !== null && typeof value === 'object') {
98
+ return mapValues(value, walk);
99
+ }
100
+ return value;
101
+ };
102
+ return walk(input);
103
+ };
104
+
105
+ export { resolveCssValues };
@@ -1,10 +1,35 @@
1
- import { GL_COLOR_NEUTRAL_100, GL_COLOR_DATA_BLUE_200, GL_COLOR_DATA_BLUE_400, GL_COLOR_DATA_BLUE_600, GL_COLOR_DATA_BLUE_800, GL_COLOR_NEUTRAL_900, GL_COLOR_NEUTRAL_500, GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_BLUE_900, GL_COLOR_DATA_ORANGE_500, GL_COLOR_DATA_ORANGE_600, GL_COLOR_DATA_AQUA_500, GL_COLOR_DATA_GREEN_600, GL_COLOR_DATA_MAGENTA_500, GL_COLOR_DATA_BLUE_700, GL_COLOR_DATA_ORANGE_800, GL_COLOR_DATA_AQUA_700, GL_COLOR_DATA_GREEN_800, GL_COLOR_DATA_MAGENTA_700, GL_COLOR_DATA_ORANGE_950, GL_COLOR_DATA_AQUA_900, GL_COLOR_DATA_GREEN_950, GL_COLOR_DATA_MAGENTA_900, GL_COLOR_DATA_ORANGE_700, GL_COLOR_DATA_AQUA_600, GL_COLOR_DATA_GREEN_700, GL_COLOR_DATA_MAGENTA_600, GL_COLOR_DATA_ORANGE_900, GL_COLOR_DATA_AQUA_800, GL_COLOR_DATA_GREEN_900, GL_COLOR_DATA_MAGENTA_800, GL_COLOR_DATA_BLUE_950, GL_COLOR_DATA_AQUA_950, GL_COLOR_DATA_GREEN_500, GL_COLOR_DATA_MAGENTA_950, GL_COLOR_DATA_ORANGE_400, GL_COLOR_DATA_GREEN_400, GL_COLOR_DATA_BLUE_300, GL_COLOR_DATA_ORANGE_200, GL_COLOR_DATA_AQUA_300, GL_COLOR_DATA_GREEN_200, GL_COLOR_DATA_MAGENTA_300, GL_COLOR_DATA_BLUE_100, GL_COLOR_DATA_ORANGE_50, GL_COLOR_DATA_AQUA_100, GL_COLOR_DATA_GREEN_50, GL_COLOR_DATA_MAGENTA_100, GL_COLOR_DATA_ORANGE_300, GL_COLOR_DATA_AQUA_400, GL_COLOR_DATA_GREEN_300, GL_COLOR_DATA_MAGENTA_400, GL_COLOR_DATA_ORANGE_100, GL_COLOR_DATA_AQUA_200, GL_COLOR_DATA_GREEN_100, GL_COLOR_DATA_MAGENTA_200, GL_COLOR_DATA_BLUE_50, GL_COLOR_DATA_AQUA_50, GL_COLOR_DATA_MAGENTA_50, GL_COLOR_NEUTRAL_50 } from '../../tokens/build/js/tokens';
1
+ import { GL_FONT_FAMILY_REGULAR, GL_COLOR_NEUTRAL_100, GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_BLUE_600, GL_COLOR_DATA_BLUE_800, GL_COLOR_DATA_BLUE_950, GL_COLOR_NEUTRAL_800, GL_COLOR_DATA_BLUE_400, GL_COLOR_DATA_BLUE_200, GL_COLOR_NEUTRAL_0, GL_COLOR_NEUTRAL_950, GL_COLOR_DATA_BLUE_900, GL_COLOR_DATA_ORANGE_500, GL_COLOR_DATA_ORANGE_600, GL_COLOR_DATA_AQUA_500, GL_COLOR_DATA_GREEN_600, GL_COLOR_DATA_MAGENTA_500, GL_COLOR_DATA_BLUE_700, GL_COLOR_DATA_ORANGE_800, GL_COLOR_DATA_AQUA_700, GL_COLOR_DATA_GREEN_800, GL_COLOR_DATA_MAGENTA_700, GL_COLOR_DATA_ORANGE_950, GL_COLOR_DATA_AQUA_900, GL_COLOR_DATA_GREEN_950, GL_COLOR_DATA_MAGENTA_900, GL_COLOR_DATA_ORANGE_700, GL_COLOR_DATA_AQUA_600, GL_COLOR_DATA_GREEN_700, GL_COLOR_DATA_MAGENTA_600, GL_COLOR_DATA_ORANGE_900, GL_COLOR_DATA_AQUA_800, GL_COLOR_DATA_GREEN_900, GL_COLOR_DATA_MAGENTA_800, GL_COLOR_DATA_AQUA_950, GL_COLOR_DATA_GREEN_500, GL_COLOR_DATA_MAGENTA_950, GL_COLOR_DATA_ORANGE_400, GL_COLOR_DATA_GREEN_400, GL_COLOR_DATA_BLUE_300, GL_COLOR_DATA_ORANGE_200, GL_COLOR_DATA_AQUA_300, GL_COLOR_DATA_GREEN_200, GL_COLOR_DATA_MAGENTA_300, GL_COLOR_DATA_BLUE_100, GL_COLOR_DATA_ORANGE_50, GL_COLOR_DATA_AQUA_100, GL_COLOR_DATA_GREEN_50, GL_COLOR_DATA_MAGENTA_100, GL_COLOR_DATA_ORANGE_300, GL_COLOR_DATA_AQUA_400, GL_COLOR_DATA_GREEN_300, GL_COLOR_DATA_MAGENTA_400, GL_COLOR_DATA_ORANGE_100, GL_COLOR_DATA_AQUA_200, GL_COLOR_DATA_GREEN_100, GL_COLOR_DATA_MAGENTA_200, GL_COLOR_DATA_BLUE_50, GL_COLOR_DATA_AQUA_50, GL_COLOR_DATA_MAGENTA_50, GL_COLOR_NEUTRAL_50 } from '../../tokens/build/js/tokens';
2
2
  import { scrollHandleSvgPath, marqueeSelectionSvgPath, redoSvgPath, clearAllSvgPath, downloadSvgPath } from '../svgs/svg_paths';
3
+ import { defaultFontSize } from './config';
3
4
 
4
5
  const GL_BORDER_RADIUS_BASE = '0.25rem';
5
6
  const themeName = 'gitlab';
6
- const heatmapHues = [GL_COLOR_NEUTRAL_100, GL_COLOR_DATA_BLUE_200, GL_COLOR_DATA_BLUE_400, GL_COLOR_DATA_BLUE_600, GL_COLOR_DATA_BLUE_800];
7
- const gaugeNeutralHues = [GL_COLOR_NEUTRAL_900, GL_COLOR_NEUTRAL_500];
7
+
8
+ /**
9
+ * Sequential scales step away from the surface so that higher values gain contrast against it:
10
+ * darker on a light surface, lighter on a dark one. The lowest step is neutral rather than blue, so
11
+ * that "no data" reads as absence instead of as the bottom of the data range.
12
+ *
13
+ * https://design.gitlab.com/data-visualization/color#sequential-data
14
+ */
15
+ const heatmapHues = [GL_COLOR_NEUTRAL_100, GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_BLUE_600, GL_COLOR_DATA_BLUE_800, GL_COLOR_DATA_BLUE_950];
16
+ const heatmapHuesDark = [GL_COLOR_NEUTRAL_800, GL_COLOR_DATA_BLUE_600, GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_BLUE_400, GL_COLOR_DATA_BLUE_200];
17
+
18
+ /**
19
+ * The plate behind the cells, and the separators between them. Separators match the surface so that
20
+ * neighbouring cells, which are only guaranteed to contrast with the surface and not with each
21
+ * other, stay distinguishable.
22
+ *
23
+ * https://design.gitlab.com/data-visualization/color#visual-separators
24
+ */
25
+ const heatmapSurface = {
26
+ plate: GL_COLOR_NEUTRAL_100,
27
+ separator: GL_COLOR_NEUTRAL_0
28
+ };
29
+ const heatmapSurfaceDark = {
30
+ plate: GL_COLOR_NEUTRAL_800,
31
+ separator: GL_COLOR_NEUTRAL_950
32
+ };
8
33
  const gaugeSafeHues = [GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_BLUE_900];
9
34
  const gaugeWarningHue = GL_COLOR_DATA_ORANGE_500;
10
35
 
@@ -18,12 +43,32 @@ const colorPaletteDefault = [GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_ORANGE_600, G
18
43
  const colorFromDefaultPalette = index => colorPaletteDefault[index % colorPaletteDefault.length];
19
44
  const colorPaletteDark = [GL_COLOR_DATA_BLUE_500, GL_COLOR_DATA_ORANGE_400, GL_COLOR_DATA_AQUA_500, GL_COLOR_DATA_GREEN_400, GL_COLOR_DATA_MAGENTA_500, GL_COLOR_DATA_BLUE_300, GL_COLOR_DATA_ORANGE_200, GL_COLOR_DATA_AQUA_300, GL_COLOR_DATA_GREEN_200, GL_COLOR_DATA_MAGENTA_300, GL_COLOR_DATA_BLUE_100, GL_COLOR_DATA_ORANGE_50, GL_COLOR_DATA_AQUA_100, GL_COLOR_DATA_GREEN_50, GL_COLOR_DATA_MAGENTA_100, GL_COLOR_DATA_BLUE_400, GL_COLOR_DATA_ORANGE_300, GL_COLOR_DATA_AQUA_400, GL_COLOR_DATA_GREEN_300, GL_COLOR_DATA_MAGENTA_400, GL_COLOR_DATA_BLUE_200, GL_COLOR_DATA_ORANGE_100, GL_COLOR_DATA_AQUA_200, GL_COLOR_DATA_GREEN_100, GL_COLOR_DATA_MAGENTA_200, GL_COLOR_DATA_BLUE_50, GL_COLOR_DATA_ORANGE_500, GL_COLOR_DATA_AQUA_50, GL_COLOR_DATA_GREEN_500, GL_COLOR_DATA_MAGENTA_50];
20
45
  const colorFromDarkPalette = index => colorPaletteDark[index % colorPaletteDark.length];
46
+
47
+ // ECharts lays out text by assigning a CSS font shorthand to a canvas context and calling
48
+ // `measureText`, even under the SVG renderer. Canvas does not substitute CSS custom properties,
49
+ // and an unparseable shorthand is rejected wholesale, leaving text measured at the canvas default
50
+ // of 10px sans-serif. So the chart font stack must be concrete: drop the leading
51
+ // `var(--default-regular-font, 'GitLab Sans')` consumer override, whose own fallback is the
52
+ // 'GitLab Sans' that follows it.
53
+ const defaultFontFamily = GL_FONT_FAMILY_REGULAR.filter(family => !family.startsWith('var(')).join(', ');
54
+
55
+ /**
56
+ * Chart font sizes are concrete pixel numbers rather than the rem-based font.size tokens they
57
+ * mirror. zrender measures text on a detached canvas, which always resolves 1rem as 16px however
58
+ * the document root is set, so rem sizes render scaled while being laid out at the wrong width.
59
+ *
60
+ * `defaultFontSize` (12, font.size.100) is set per component in the theme below, because ECharts
61
+ * pins fontSize in its own component defaults (axis labels at 12, title at 18, gauge detail at 30)
62
+ * and those shadow `textStyle`.
63
+ */
64
+
21
65
  const axes = {
22
66
  axisLabel: {
23
67
  margin: 8,
24
68
  show: true,
25
69
  color: 'var(--gl-chart-axis-text-color)',
26
- hideOverlap: true
70
+ hideOverlap: true,
71
+ fontSize: defaultFontSize
27
72
  },
28
73
  axisLine: {
29
74
  show: false
@@ -46,7 +91,8 @@ const axes = {
46
91
  },
47
92
  nameGap: 30,
48
93
  nameTextStyle: {
49
- fontWeight: 'bold'
94
+ fontWeight: 'bold',
95
+ fontSize: defaultFontSize
50
96
  },
51
97
  splitLine: {
52
98
  lineStyle: {
@@ -64,7 +110,26 @@ const createTheme = function () {
64
110
  color: colorPaletteDefault,
65
111
  backgroundColor: 'transparent',
66
112
  textStyle: {
67
- color: 'var(--gl-text-color-default)'
113
+ color: 'var(--gl-text-color-default)',
114
+ fontFamily: defaultFontFamily,
115
+ fontSize: defaultFontSize
116
+ },
117
+ title: {
118
+ textStyle: {
119
+ fontSize: 18 // font.size.500-fixed, 1.125rem
120
+ }
121
+ },
122
+ gauge: {
123
+ axisLabel: {
124
+ fontSize: 14,
125
+ // font.size.300, 0.875rem, the default body size
126
+ color: 'var(--gl-chart-axis-text-color)'
127
+ },
128
+ detail: {
129
+ fontSize: 30,
130
+ fontWeight: 'bold',
131
+ color: 'var(--gl-text-color-default)'
132
+ }
68
133
  },
69
134
  markLine: {
70
135
  silent: true,
@@ -197,4 +262,4 @@ const createTheme = function () {
197
262
  };
198
263
  };
199
264
 
200
- export { colorFromDarkPalette, colorFromDefaultPalette, colorPaletteDark, colorPaletteDefault, createTheme, gaugeNeutralHues, gaugeSafeHues, gaugeWarningHue, heatmapHues, themeName };
265
+ export { colorFromDarkPalette, colorFromDefaultPalette, colorPaletteDark, colorPaletteDefault, createTheme, defaultFontFamily, gaugeSafeHues, gaugeWarningHue, heatmapHues, heatmapHuesDark, heatmapSurface, heatmapSurfaceDark, themeName };
@@ -337,23 +337,23 @@ const themes = [{
337
337
  value: 'default',
338
338
  title: 'Default'
339
339
  }, {
340
- class: 'tint-neutral-indigo',
340
+ class: 'ui-indigo',
341
341
  value: 'indigo',
342
342
  title: 'Indigo'
343
343
  }, {
344
- class: 'tint-neutral-blue',
344
+ class: 'ui-blue',
345
345
  value: 'blue',
346
346
  title: 'Blue'
347
347
  }, {
348
- class: 'tint-neutral-green',
348
+ class: 'ui-green',
349
349
  value: 'green',
350
350
  title: 'Green'
351
351
  }, {
352
- class: 'tint-neutral-red',
352
+ class: 'ui-red',
353
353
  value: 'red',
354
354
  title: 'Red'
355
355
  }, {
356
- class: 'tint-neutral-gray',
356
+ class: 'ui-gray',
357
357
  value: 'gray',
358
358
  title: 'Gray'
359
359
  }];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "137.1.2",
3
+ "version": "137.2.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -93,7 +93,7 @@
93
93
  "chokidar": "^4.0.3",
94
94
  "sane": "^5.0.1",
95
95
  "jackspeak": "2.3.6",
96
- "postcss": "8.5.26",
96
+ "postcss": "8.5.28",
97
97
  "json5": "2.2.3",
98
98
  "rollup-plugin-vue/@vue/component-compiler/postcss-modules-sync/generic-names/loader-utils": "3.3.1"
99
99
  },
@@ -109,7 +109,7 @@
109
109
  "@gitlab/fonts": "^1.3.1",
110
110
  "@gitlab/hybrid-vue": "npm:@vue/compat@3.5.34",
111
111
  "@gitlab/svgs": "*",
112
- "@jest/test-sequencer": "30.4.1",
112
+ "@jest/test-sequencer": "30.5.1",
113
113
  "@rollup/plugin-commonjs": "^28.0.9",
114
114
  "@rollup/plugin-node-resolve": "^16.0.3",
115
115
  "@rollup/plugin-replace": "^6.0.3",
@@ -137,7 +137,7 @@
137
137
  "@yarnpkg/lockfile": "^1.1.0",
138
138
  "acorn": "^8.18.0",
139
139
  "acorn-walk": "^8.3.5",
140
- "autoprefixer": "10.5.4",
140
+ "autoprefixer": "10.5.5",
141
141
  "axe-playwright": "^2.2.2",
142
142
  "babel-loader": "^9.2.1",
143
143
  "cypress": "15.21.1",
@@ -158,7 +158,7 @@
158
158
  "pikaday": "^1.8.0",
159
159
  "playwright": "^1.62.1",
160
160
  "playwright-core": "^1.62.1",
161
- "postcss": "8.5.26",
161
+ "postcss": "8.5.28",
162
162
  "postcss-loader": "8.2.1",
163
163
  "postcss-scss": "4.0.9",
164
164
  "react": "18.3.1",
@@ -175,7 +175,7 @@
175
175
  "start-server-and-test": "^2.1.5",
176
176
  "storybook": "^7.6.24",
177
177
  "storybook-dark-mode": "4.0.2",
178
- "style-dictionary": "^5.5.2",
178
+ "style-dictionary": "^5.5.3",
179
179
  "style-loader": "^4",
180
180
  "tailwindcss": "3.4.19",
181
181
  "vue": "2.7.16",
@@ -7,28 +7,11 @@
7
7
  */
8
8
  // stylelint-disable length-zero-no-unit
9
9
  $gl-alert-padding-x: var(--gl-alert-padding-x, 0px);
10
- $gl-alert-top-border-size: 2px;
11
-
12
- @mixin gl-alert-variant(
13
- $variant,
14
- $background-color,
15
- $title-color,
16
- $border-top-color,
17
- $border-bottom-color,
18
- $icon-color
19
- ) {
10
+
11
+ @mixin gl-alert-variant($variant, $background-color, $title-color, $border-color, $icon-color) {
20
12
  .gl-alert-#{$variant} {
21
13
  background-color: $background-color;
22
- border-bottom: 1px solid $border-bottom-color;
23
-
24
- &::before {
25
- background: linear-gradient(
26
- to right,
27
- transparent 0%,
28
- $border-top-color 50%,
29
- transparent 100%
30
- );
31
- }
14
+ border-color: $border-color;
32
15
 
33
16
  .gl-alert-title {
34
17
  color: $title-color;
@@ -48,20 +31,7 @@ $gl-alert-top-border-size: 2px;
48
31
  border-radius: var(--gl-alert-border-radius);
49
32
  padding-inline: calc(#{$gl-spacing-scale-9} + #{$gl-alert-padding-x});
50
33
  position: relative;
51
-
52
- @media (forced-colors: active) {
53
- border: 1px solid transparent;
54
- }
55
-
56
- &::before {
57
- content: "";
58
- display: block;
59
- width: 100%;
60
- height: $gl-alert-top-border-size;
61
- position: absolute;
62
- top: -1px; // To accumilate for the border width
63
- left: 0;
64
- }
34
+ border: 1px solid;
65
35
 
66
36
  &-not-dismissible {
67
37
  padding-right: calc(#{$gl-spacing-scale-5} + #{$gl-alert-padding-x});
@@ -83,14 +53,14 @@ $gl-alert-top-border-size: 2px;
83
53
 
84
54
  .gl-alert-icon-container {
85
55
  position: absolute;
86
- top: $gl-spacing-scale-5;
56
+ top: #{$gl-spacing-scale-4 + $gl-spacing-scale-1};
87
57
  left: calc(#{$gl-spacing-scale-5} + #{$gl-alert-padding-x});
88
58
  @apply gl-flex;
89
59
  @apply gl-items-center;
90
- height: $gl-line-height-20;
60
+ height: $gl-line-height-24;
91
61
 
92
62
  .gl-alert-has-title & {
93
- top: calc(#{$gl-spacing-scale-4} + #{$gl-alert-top-border-size});
63
+ top: $gl-spacing-scale-4;
94
64
  }
95
65
  }
96
66
 
@@ -119,7 +89,7 @@ $gl-alert-top-border-size: 2px;
119
89
  // This is the replacement for .gl-alert-dismiss
120
90
  .gl-dismiss-btn {
121
91
  position: absolute;
122
- top: calc(#{$gl-spacing-scale-4} + #{$gl-alert-top-border-size});
92
+ top: $gl-spacing-scale-4;
123
93
  right: calc(#{$gl-spacing-scale-4} + #{$gl-alert-padding-x});
124
94
 
125
95
  .gl-alert-has-title & {
@@ -133,8 +103,7 @@ $gl-alert-top-border-size: 2px;
133
103
  $variant: danger,
134
104
  $background-color: var(--gl-alert-danger-background-color),
135
105
  $title-color: var(--gl-alert-danger-title-color),
136
- $border-top-color: var(--gl-alert-danger-border-top-color),
137
- $border-bottom-color: var(--gl-alert-danger-border-bottom-color),
106
+ $border-color: var(--gl-alert-danger-border-color),
138
107
  $icon-color: var(--gl-feedback-danger-icon-color)
139
108
  );
140
109
 
@@ -142,8 +111,7 @@ $gl-alert-top-border-size: 2px;
142
111
  $variant: warning,
143
112
  $background-color: var(--gl-alert-warning-background-color),
144
113
  $title-color: var(--gl-alert-warning-title-color),
145
- $border-top-color: var(--gl-alert-warning-border-top-color),
146
- $border-bottom-color: var(--gl-alert-warning-border-bottom-color),
114
+ $border-color: var(--gl-alert-warning-border-color),
147
115
  $icon-color: var(--gl-feedback-warning-icon-color)
148
116
  );
149
117
 
@@ -151,8 +119,7 @@ $gl-alert-top-border-size: 2px;
151
119
  $variant: tip,
152
120
  $background-color: var(--gl-alert-neutral-background-color),
153
121
  $title-color: var(--gl-alert-neutral-title-color),
154
- $border-top-color: var(--gl-alert-neutral-border-top-color),
155
- $border-bottom-color: var(--gl-alert-neutral-border-bottom-color),
122
+ $border-color: var(--gl-alert-neutral-border-color),
156
123
  $icon-color: var(--gl-feedback-neutral-icon-color)
157
124
  );
158
125
 
@@ -160,8 +127,7 @@ $gl-alert-top-border-size: 2px;
160
127
  $variant: info,
161
128
  $background-color: var(--gl-alert-info-background-color),
162
129
  $title-color: var(--gl-alert-info-title-color),
163
- $border-top-color: var(--gl-alert-info-border-top-color),
164
- $border-bottom-color: var(--gl-alert-info-border-bottom-color),
130
+ $border-color: var(--gl-alert-info-border-color),
165
131
  $icon-color: var(--gl-feedback-info-icon-color)
166
132
  );
167
133
 
@@ -169,7 +135,6 @@ $gl-alert-top-border-size: 2px;
169
135
  $variant: success,
170
136
  $background-color: var(--gl-alert-success-background-color),
171
137
  $title-color: var(--gl-alert-success-title-color),
172
- $border-top-color: var(--gl-alert-success-border-top-color),
173
- $border-bottom-color: var(--gl-alert-success-border-bottom-color),
138
+ $border-color: var(--gl-alert-success-border-color),
174
139
  $icon-color: var(--gl-feedback-success-icon-color)
175
140
  );
@@ -8,6 +8,8 @@ import {
8
8
  toolboxHeight,
9
9
  } from '../../../utils/charts/config';
10
10
  import { createTheme, themeName } from '../../../utils/charts/theme';
11
+ import { resolveCssValues } from '../../../utils/charts/css_values';
12
+ import { onColorModeChange } from '../../../utils/charts/color_mode';
11
13
  import { GlResizeObserverDirective } from '../../../directives/resize_observer/resize_observer';
12
14
  import { debounceByAnimationFrame } from '../../../utils/utils';
13
15
 
@@ -122,35 +124,79 @@ export default {
122
124
  async mounted() {
123
125
  await this.$nextTick();
124
126
 
125
- const chart = echarts.init(this.$refs.chart, themeName, {
126
- renderer: this.renderer,
127
- width: defaultWidth,
128
- height: defaultHeight,
129
- });
130
- // FIXME: temporary workaround to ensure compatibility with @vue/compat
131
- // eslint-disable-next-line no-underscore-dangle
132
- chart.__v_skip = true;
133
- this.chart = chart;
134
-
135
- if (this.groupId.length) {
136
- this.chart.group = this.groupId;
137
- echarts.connect(this.groupId);
138
- }
139
-
140
- this.chart.on('click', this.handleClick);
141
- /**
142
- * Emitted after calling `echarts.init`
143
- */
144
- this.$emit('created', this.chart);
145
- this.draw();
146
- this.setChartSize();
127
+ this.initChart();
128
+ this.observeColorMode();
147
129
  },
148
130
  beforeDestroy() {
149
131
  if (!this.chart) return;
150
132
 
151
133
  this.chart.off('click', this.handleClick);
134
+ this.unsubscribeColorMode?.();
152
135
  },
153
136
  methods: {
137
+ initChart() {
138
+ const chart = echarts.init(this.$refs.chart, this.theme(), {
139
+ renderer: this.renderer,
140
+ width: defaultWidth,
141
+ height: defaultHeight,
142
+ });
143
+ // FIXME: temporary workaround to ensure compatibility with @vue/compat
144
+ // eslint-disable-next-line no-underscore-dangle
145
+ chart.__v_skip = true;
146
+ this.chart = chart;
147
+
148
+ if (this.groupId.length) {
149
+ this.chart.group = this.groupId;
150
+ echarts.connect(this.groupId);
151
+ }
152
+
153
+ this.chart.on('click', this.handleClick);
154
+ /**
155
+ * Emitted after calling `echarts.init`
156
+ */
157
+ this.$emit('created', this.chart);
158
+ this.draw();
159
+ this.setChartSize();
160
+ },
161
+ /**
162
+ * The SVG renderer consumes the registered theme as-is, leaving its `var(--token)` colours for
163
+ * the browser to resolve. Canvas cannot, so it gets a copy resolved against this element.
164
+ */
165
+ theme() {
166
+ if (this.renderer !== 'canvas') {
167
+ return themeName;
168
+ }
169
+
170
+ return resolveCssValues(createTheme(this.options), this.$refs.chart);
171
+ },
172
+ /**
173
+ * Rebuilding the chart discards its interaction state, so the legend selection and zoom range
174
+ * are carried over. Only these stateful fields are copied: the full option from getOption()
175
+ * holds colours resolved against the old colour mode.
176
+ */
177
+ preservedState() {
178
+ const { legend = [], dataZoom = [] } = this.chart.getOption();
179
+ return {
180
+ legend: legend.map(({ selected }) => ({ selected })),
181
+ dataZoom: dataZoom.map(({ start, end }) => ({ start, end })),
182
+ };
183
+ },
184
+ /**
185
+ * Colours resolved for canvas are frozen at init, so the chart has to be rebuilt when the
186
+ * colour mode changes. SVG re-resolves on its own and needs no subscription.
187
+ */
188
+ observeColorMode() {
189
+ if (this.renderer !== 'canvas') {
190
+ return;
191
+ }
192
+
193
+ this.unsubscribeColorMode = onColorModeChange(this.$refs.chart, () => {
194
+ const state = this.preservedState();
195
+ this.chart.dispose();
196
+ this.initChart();
197
+ this.chart.setOption(state);
198
+ });
199
+ },
154
200
  draw() {
155
201
  this.chart.setOption(this.modifiedOptions);
156
202
  /**