@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.
- package/dist/components/charts/chart/chart.js +81 -20
- package/dist/components/charts/gauge/gauge.js +3 -13
- package/dist/components/charts/heatmap/heatmap.js +33 -13
- package/dist/components/charts/legend/legend.js +13 -1
- package/dist/index.css +2 -2
- package/dist/index.css.map +1 -1
- package/dist/tokens/build/js/tokens.dark.js +196 -191
- package/dist/tokens/build/js/tokens.js +218 -213
- package/dist/tokens/color_mode.js +17 -0
- package/dist/utils/charts/color_mode.js +95 -0
- package/dist/utils/charts/css_values.js +105 -0
- package/dist/utils/charts/theme.js +72 -7
- package/dist/utils/constants.js +5 -5
- package/package.json +6 -6
- package/src/components/base/alert/alert.scss +13 -48
- package/src/components/charts/chart/chart.vue +68 -22
- package/src/components/charts/gauge/gauge.vue +1 -12
- package/src/components/charts/heatmap/heatmap.vue +34 -10
- package/src/components/charts/legend/legend.vue +13 -1
- package/src/scss/themes.scss +23 -5
- package/src/tokens/build/css/tokens.css +20 -15
- package/src/tokens/build/css/tokens.dark.css +32 -27
- package/src/tokens/build/docs/tokens-tailwind-docs.dark.json +199 -199
- package/src/tokens/build/docs/tokens-tailwind-docs.json +198 -198
- package/src/tokens/build/figma/constants.dark.json +33 -33
- package/src/tokens/build/figma/constants.json +33 -33
- package/src/tokens/build/figma/mode.dark.json +66 -16
- package/src/tokens/build/figma/mode.json +54 -4
- package/src/tokens/build/js/tokens.dark.js +195 -190
- package/src/tokens/build/js/tokens.js +217 -212
- package/src/tokens/build/json/tokens.dark.json +378 -218
- package/src/tokens/build/json/tokens.json +388 -228
- package/src/tokens/build/scss/_tokens.dark.scss +32 -27
- package/src/tokens/build/scss/_tokens.scss +20 -15
- package/src/tokens/build/scss/_tokens_custom_properties.scss +5 -0
- package/src/tokens/color_mode.js +16 -0
- package/src/tokens/constant/color.neutral.tokens.json +33 -33
- package/src/tokens/contextual/alert.tokens.json +80 -35
- package/src/tokens/semantic/action.tokens.json +3 -3
- package/src/tokens/semantic/feedback.tokens.json +6 -6
- package/src/utils/charts/color_mode.js +83 -0
- package/src/utils/charts/css_values.js +59 -0
- package/src/utils/charts/theme.js +72 -5
- 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 {
|
|
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
|
-
|
|
7
|
-
|
|
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,
|
|
265
|
+
export { colorFromDarkPalette, colorFromDefaultPalette, colorPaletteDark, colorPaletteDefault, createTheme, defaultFontFamily, gaugeSafeHues, gaugeWarningHue, heatmapHues, heatmapHuesDark, heatmapSurface, heatmapSurfaceDark, themeName };
|
package/dist/utils/constants.js
CHANGED
|
@@ -337,23 +337,23 @@ const themes = [{
|
|
|
337
337
|
value: 'default',
|
|
338
338
|
title: 'Default'
|
|
339
339
|
}, {
|
|
340
|
-
class: '
|
|
340
|
+
class: 'ui-indigo',
|
|
341
341
|
value: 'indigo',
|
|
342
342
|
title: 'Indigo'
|
|
343
343
|
}, {
|
|
344
|
-
class: '
|
|
344
|
+
class: 'ui-blue',
|
|
345
345
|
value: 'blue',
|
|
346
346
|
title: 'Blue'
|
|
347
347
|
}, {
|
|
348
|
-
class: '
|
|
348
|
+
class: 'ui-green',
|
|
349
349
|
value: 'green',
|
|
350
350
|
title: 'Green'
|
|
351
351
|
}, {
|
|
352
|
-
class: '
|
|
352
|
+
class: 'ui-red',
|
|
353
353
|
value: 'red',
|
|
354
354
|
title: 'Red'
|
|
355
355
|
}, {
|
|
356
|
-
class: '
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
60
|
+
height: $gl-line-height-24;
|
|
91
61
|
|
|
92
62
|
.gl-alert-has-title & {
|
|
93
|
-
top:
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
/**
|