@gitlab/ui 62.4.0 → 62.5.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.
@@ -44,21 +44,38 @@ function hexToRgba(hex) {
44
44
  const [r, g, b] = rgbFromHex(hex);
45
45
  return `rgba(${r}, ${g}, ${b}, ${opacity})`;
46
46
  }
47
+ function toSrgb(value) {
48
+ const normalized = value / 255;
49
+ return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
50
+ }
51
+ function relativeLuminance(rgb) {
52
+ // WCAG 2.1 formula: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
53
+ // -
54
+ // WCAG 3.0 will use APAC
55
+ // Using APAC would be the ultimate goal, but was dismissed by engineering as of now
56
+ // See https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/3418#note_1370107090
57
+ return 0.2126 * toSrgb(rgb[0]) + 0.7152 * toSrgb(rgb[1]) + 0.0722 * toSrgb(rgb[2]);
58
+ }
47
59
  function colorFromBackground(backgroundColor) {
48
- let r;
49
- let g;
50
- let b;
60
+ let color;
61
+ const lightColor = rgbFromHex('#FFFFFF');
62
+ const darkColor = rgbFromHex('#1f1e24');
51
63
  if (backgroundColor.startsWith('#')) {
52
- [r, g, b] = rgbFromHex(backgroundColor);
64
+ color = rgbFromHex(backgroundColor);
53
65
  } else if (backgroundColor.startsWith('rgba(')) {
54
- [r, g, b] = rgbFromString(backgroundColor, 5);
66
+ color = rgbFromString(backgroundColor, 5);
55
67
  } else if (backgroundColor.startsWith('rgb(')) {
56
- [r, g, b] = rgbFromString(backgroundColor, 4);
68
+ color = rgbFromString(backgroundColor, 4);
57
69
  }
58
- if (r + g + b <= 500) {
59
- return labelColorOptions.light;
60
- }
61
- return labelColorOptions.dark;
70
+ const luminance = relativeLuminance(color);
71
+ const lightLuminance = relativeLuminance(lightColor);
72
+ const darkLuminance = relativeLuminance(darkColor);
73
+ const contrastLight = (lightLuminance + 0.05) / (luminance + 0.05);
74
+ const contrastDark = (luminance + 0.05) / (darkLuminance + 0.05);
75
+
76
+ // Using a threshold contrast of 2.4 instead of 3
77
+ // as this will solve weird color combinations in the mid tones
78
+ return contrastLight >= 2.4 || contrastLight > contrastDark ? labelColorOptions.light : labelColorOptions.dark;
62
79
  }
63
80
  function uid() {
64
81
  return Math.random().toString(36).substring(2);
@@ -156,4 +173,4 @@ function filterVisible(els) {
156
173
  return (els || []).filter(isVisible);
157
174
  }
158
175
 
159
- export { colorFromBackground, debounceByAnimationFrame, filterVisible, focusFirstFocusableElement, hexToRgba, isDev, isElementFocusable, isElementTabbable, logWarning, rgbFromHex, rgbFromString, stopEvent, throttle, uid };
176
+ export { colorFromBackground, debounceByAnimationFrame, filterVisible, focusFirstFocusableElement, hexToRgba, isDev, isElementFocusable, isElementTabbable, logWarning, relativeLuminance, rgbFromHex, rgbFromString, stopEvent, throttle, toSrgb, uid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "62.4.0",
3
+ "version": "62.5.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -29,13 +29,13 @@
29
29
  "clean": "rm -r dist storybook scss_to_js/scss_variables.* src/scss/utilities.scss",
30
30
  "cy:run": "cypress run --browser firefox",
31
31
  "start": "yarn storybook",
32
- "storybook": "yarn storybook-prep && node ./bin/run_storybook.js --ci --host localhost --port 9001 -c .storybook",
33
- "storybook-vue3": "yarn storybook-prep && VUE_VERSION=3 node ./bin/run_storybook.js --ci --host localhost --port 9001 -c .storybook",
32
+ "storybook": "yarn storybook-prep && storybook dev --ci --host localhost --port 9001 -c .storybook",
33
+ "storybook-vue3": "yarn storybook-prep && VUE_VERSION=3 storybook dev --ci --host localhost --port 9001 -c .storybook",
34
34
  "storybook-prep": "run-s generate-utilities build-scss-variables copy-fonts",
35
- "storybook-static": "yarn storybook-prep && build-storybook -c .storybook -o storybook",
35
+ "storybook-static": "yarn storybook-prep && storybook build -c .storybook -o storybook",
36
36
  "pretest:unit": "yarn build-scss-variables",
37
37
  "test": "run-s test:unit test:visual",
38
- "test:integration": "NODE_ENV=test start-server-and-test start http://localhost:9001 cy:run",
38
+ "test:integration": "NODE_ENV=test start-server-and-test start http://localhost:9001/iframe.html cy:run",
39
39
  "test:unit": "NODE_ENV=test jest --testPathIgnorePatterns storyshots.spec.js",
40
40
  "test:unit:watch": "yarn test:unit --watch",
41
41
  "test:unit:debug": "NODE_ENV=test node --inspect node_modules/.bin/jest --testPathIgnorePatterns storyshot.spec.js --watch --runInBand",
@@ -45,7 +45,7 @@
45
45
  "test:visual": "./bin/run-visual-tests.sh 'jest ./tests/storyshots.spec.js'",
46
46
  "test:visual:minimal": "node ./bin/run_minimal_visual_tests.js",
47
47
  "test:visual:update": "./bin/run-visual-tests.sh 'JEST_IMAGE_SNAPSHOT_TRACK_OBSOLETE=1 jest ./tests/storyshots.spec.js --updateSnapshot'",
48
- "test:visual:internal": "NODE_ENV=test IS_VISUAL_TEST=true start-test http-get://localhost:9001",
48
+ "test:visual:internal": "NODE_ENV=test IS_VISUAL_TEST=true start-test http-get://localhost:9001/iframe.html",
49
49
  "prettier": "prettier --check '**/*.{js,vue}'",
50
50
  "prettier:fix": "prettier --write '**/*.{js,vue}'",
51
51
  "eslint": "eslint --max-warnings 0 --ext .js,.vue .",
@@ -78,8 +78,6 @@
78
78
  "vue": "^2.6.10"
79
79
  },
80
80
  "resolutions": {
81
- "@storybook/vue/webpack": "^5.9.0",
82
- "@storybook/vue3/vue-loader": "^17.0.0",
83
81
  "chokidar": "^3.5.2",
84
82
  "sane": "^5.0.1"
85
83
  },
@@ -87,24 +85,26 @@
87
85
  "@arkweid/lefthook": "0.7.7",
88
86
  "@babel/core": "^7.21.4",
89
87
  "@babel/preset-env": "^7.21.4",
90
- "@gitlab/eslint-plugin": "18.3.2",
88
+ "@babel/preset-react": "^7.18.6",
89
+ "@gitlab/eslint-plugin": "19.0.0",
91
90
  "@gitlab/fonts": "^1.2.0",
92
91
  "@gitlab/stylelint-config": "4.1.0",
93
- "@gitlab/svgs": "3.40.0",
92
+ "@gitlab/svgs": "3.42.0",
94
93
  "@rollup/plugin-commonjs": "^11.1.0",
95
94
  "@rollup/plugin-node-resolve": "^7.1.3",
96
95
  "@rollup/plugin-replace": "^2.3.2",
97
- "@storybook/addon-a11y": "6.5.16",
98
- "@storybook/addon-docs": "6.5.16",
99
- "@storybook/addon-essentials": "6.5.16",
100
- "@storybook/addon-storyshots": "6.5.16",
101
- "@storybook/addon-storyshots-puppeteer": "6.5.16",
102
- "@storybook/addon-viewport": "6.5.16",
103
- "@storybook/builder-webpack5": "^6.5.16",
104
- "@storybook/manager-webpack5": "^6.5.16",
105
- "@storybook/theming": "6.5.16",
106
- "@storybook/vue": "6.5.16",
107
- "@storybook/vue3": "6.5.16",
96
+ "@storybook/addon-a11y": "7.0.7",
97
+ "@storybook/addon-docs": "7.0.7",
98
+ "@storybook/addon-essentials": "7.0.7",
99
+ "@storybook/addon-storyshots": "7.0.7",
100
+ "@storybook/addon-storyshots-puppeteer": "7.0.7",
101
+ "@storybook/addon-viewport": "7.0.7",
102
+ "@storybook/builder-webpack5": "7.0.7",
103
+ "@storybook/theming": "7.0.7",
104
+ "@storybook/vue": "7.0.7",
105
+ "@storybook/vue-webpack5": "7.0.7",
106
+ "@storybook/vue3": "7.0.7",
107
+ "@storybook/vue3-webpack5": "7.0.7",
108
108
  "@vue/compat": "^3.2.40",
109
109
  "@vue/compiler-sfc": "^3.2.40",
110
110
  "@vue/test-utils": "1.3.0",
@@ -115,15 +115,13 @@
115
115
  "babel-jest": "29.0.1",
116
116
  "babel-loader": "^8.0.5",
117
117
  "babel-plugin-require-context-hook": "^1.0.0",
118
- "babel-preset-vue": "^2.0.2",
119
118
  "bootstrap": "4.6.2",
120
- "cypress": "^11.2.0",
119
+ "cypress": "12.10.0",
121
120
  "emoji-regex": "^10.0.0",
122
121
  "eslint": "8.38.0",
123
122
  "eslint-import-resolver-jest": "3.0.2",
124
123
  "eslint-plugin-cypress": "2.13.2",
125
124
  "eslint-plugin-storybook": "0.6.11",
126
- "file-loader": "^4.2.0",
127
125
  "glob": "^7.2.0",
128
126
  "identity-obj-proxy": "^3.0.0",
129
127
  "inquirer-select-directory": "^1.2.0",
@@ -142,6 +140,8 @@
142
140
  "prettier": "2.6.2",
143
141
  "puppeteer": "15.5.0",
144
142
  "raw-loader": "^0.5.1",
143
+ "react": "^18.2.0",
144
+ "react-dom": "^18.2.0",
145
145
  "rollup": "^2.53.1",
146
146
  "rollup-plugin-babel": "^4.4.0",
147
147
  "rollup-plugin-postcss": "^3.1.1",
@@ -153,7 +153,8 @@
153
153
  "sass-loader": "^10.2.0",
154
154
  "sass-true": "^6.1.0",
155
155
  "start-server-and-test": "^1.10.6",
156
- "storybook-dark-mode": "^1.0.8",
156
+ "storybook": "7.0.7",
157
+ "storybook-dark-mode": "3.0.0",
157
158
  "stylelint": "14.9.1",
158
159
  "stylelint-config-prettier": "9.0.4",
159
160
  "stylelint-prettier": "2.0.0",
@@ -130,6 +130,7 @@ export default {
130
130
  :alt="alt"
131
131
  :class="['gl-avatar', { 'gl-avatar-circle': isCircle }, sizeClasses]"
132
132
  @error="handleLoadError"
133
+ v-on="$listeners"
133
134
  />
134
135
  <div
135
136
  v-else
@@ -139,6 +140,7 @@ export default {
139
140
  sizeClasses,
140
141
  identiconBackgroundClass,
141
142
  ]"
143
+ v-on="$listeners"
142
144
  >
143
145
  {{ identiconText }}
144
146
  </div>
@@ -14,6 +14,7 @@
14
14
  &-label {
15
15
  @include gl-font-weight-bold;
16
16
  @include gl-p-1;
17
+ @include gl-text-gray-900;
17
18
  }
18
19
 
19
20
  &-sublabel {
@@ -63,13 +63,26 @@ describe('avatar labeled', () => {
63
63
  buildWrapper({ label, subLabel, labelLink: 'http://label', subLabelLink: 'http://subLabel' });
64
64
  });
65
65
 
66
- it('displays the avatar label link', () => {
67
- const labelLink = wrapper.findAllComponents(GlLink).at(0);
68
- expect(labelLink.text()).toBe(label);
69
- expect(labelLink.attributes('href')).toBe('http://label');
66
+ describe('with label link', () => {
67
+ const findLabelLink = () => wrapper.findAllComponents(GlLink).at(0);
68
+
69
+ it('displays the label link', () => {
70
+ const labelLink = findLabelLink();
71
+ expect(labelLink.text()).toBe(label);
72
+ expect(labelLink.attributes('href')).toBe('http://label');
73
+ });
74
+
75
+ it('navigates to label link when avatar is clicked', () => {
76
+ const labelLink = findLabelLink();
77
+ const labelLinkClickSpy = jest.spyOn(labelLink.element, 'click');
78
+
79
+ wrapper.findComponent(Avatar).vm.$emit('click');
80
+
81
+ expect(labelLinkClickSpy).toHaveBeenCalled();
82
+ });
70
83
  });
71
84
 
72
- it('displays the avatar sub-label link', () => {
85
+ it('displays the sub-label link', () => {
73
86
  const subLabelLink = wrapper.findAllComponents(GlLink).at(1);
74
87
  expect(subLabelLink.text()).toBe(subLabel);
75
88
  expect(subLabelLink.attributes('href')).toBe('http://subLabel');
@@ -1,6 +1,7 @@
1
1
  import Vue from 'vue';
2
2
  import GlBadge from '../badge/badge.vue';
3
3
  import GlButton from '../button/button.vue';
4
+ import GlIcon from '../icon/icon.vue';
4
5
  import { GlTooltipDirective } from '../../../directives/tooltip';
5
6
  import { avatarSizeOptions, avatarShapeOptions, tooltipPlacements } from '../../../utils/constants';
6
7
  import avatarPath from '../../../../static/img/avatar.png';
@@ -17,12 +18,14 @@ const generateProps = ({
17
18
  size = 32,
18
19
  shape = 'circle',
19
20
  src = avatarPath,
21
+ labelLink,
20
22
  } = {}) => ({
21
23
  label,
22
24
  subLabel,
23
25
  size,
24
26
  shape,
25
27
  src,
28
+ labelLink,
26
29
  });
27
30
 
28
31
  const generateTooltipProps = ({ tooltipText = 'Avatar tooltip', placement = 'top' } = {}) => ({
@@ -111,6 +114,38 @@ export const WithDefaultSlot = (args, { argTypes }) => ({
111
114
  });
112
115
  WithDefaultSlot.args = generateProps({ size: 64 });
113
116
 
117
+ export const WithLinks = (args, { argTypes }) => ({
118
+ components: { GlAvatarLabeled, GlBadge, GlIcon },
119
+ props: Object.keys(argTypes),
120
+ template: `
121
+ <gl-avatar-labeled
122
+ :shape="shape"
123
+ :size="size"
124
+ :label-link="labelLink"
125
+ :src="src"
126
+ :label="label"
127
+ >
128
+ <template #meta>
129
+ <gl-icon
130
+ v-gl-tooltip="'Public - The project can be accessed without any authentication.'"
131
+ name="earth"
132
+ class="gl-text-secondary gl-ml-2"
133
+ />
134
+ </template>
135
+ <div class="gl-max-w-75">
136
+ <p class="gl-mb-0 gl-mt-2 gl-font-sm">GitLab is an open source end-to-end software development platform with built-in version control, issue tracking, code review, CI/CD, and more. Self-host GitLab on your own servers, in a container, or on a cloud provider.</p>
137
+ </div>
138
+ </gl-avatar-labeled>
139
+ `,
140
+ });
141
+ WithLinks.args = generateProps({
142
+ size: 48,
143
+ shape: 'rect',
144
+ label: 'GitLab.org / GitLab',
145
+ subLabel: '',
146
+ labelLink: 'https://gitlab.com/gitlab-org/gitlab',
147
+ });
148
+
114
149
  export default {
115
150
  title: 'base/avatar/labeled',
116
151
  component: GlAvatarLabeled,
@@ -36,17 +36,37 @@ export default {
36
36
  hasSubLabelLink() {
37
37
  return Boolean(this.subLabelLink);
38
38
  },
39
+ avatarListeners() {
40
+ if (this.hasLabelLink) {
41
+ return {
42
+ ...this.$listeners,
43
+ click: this.onAvatarClick,
44
+ };
45
+ }
46
+
47
+ return this.$listeners;
48
+ },
49
+ avatarCssClasses() {
50
+ return {
51
+ 'gl-cursor-pointer': this.hasLabelLink,
52
+ };
53
+ },
54
+ },
55
+ methods: {
56
+ onAvatarClick() {
57
+ this.$refs.labelLink.$el.click();
58
+ },
39
59
  },
40
60
  };
41
61
  </script>
42
62
  <template>
43
63
  <div class="gl-avatar-labeled">
44
- <gl-avatar v-bind="$attrs" alt v-on="$listeners" />
64
+ <gl-avatar v-bind="$attrs" :class="avatarCssClasses" alt v-on="avatarListeners" />
45
65
  <div class="gl-avatar-labeled-labels gl-text-left!">
46
66
  <div
47
67
  class="gl-display-flex gl-flex-wrap gl-align-items-center gl-text-left! gl-mx-n1 gl-my-n1"
48
68
  >
49
- <gl-link v-if="hasLabelLink" :href="labelLink" class="gl-avatar-link">
69
+ <gl-link v-if="hasLabelLink" ref="labelLink" :href="labelLink" class="gl-avatar-link">
50
70
  <span class="gl-avatar-labeled-label">{{ label }}</span>
51
71
  </gl-link>
52
72
  <span v-else class="gl-avatar-labeled-label">{{ label }}</span>
@@ -4,9 +4,7 @@ indicate an issue’s status, a member’s role, or if a branch is protected.
4
4
 
5
5
  ## Usage
6
6
 
7
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
8
7
  ```html
9
-
10
8
  <gl-badge>Hello, world!</gl-badge>
11
9
  ```
12
10
 
@@ -19,9 +19,7 @@ with the necessary classes added to make it look like a button, it shares the sa
19
19
  Icon-only buttons must have an accessible name.
20
20
  You can provide one with the `aria-label` attribute, which is read out by screen readers.
21
21
 
22
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
23
22
  ```html
24
-
25
23
  <gl-button icon="close" aria-label="Close" />
26
24
  ```
27
25
 
@@ -71,8 +71,6 @@ const availableTokens = [
71
71
  Pass the list of tokens to the search component. Optionally, you can use `v-model` to receive
72
72
  realtime updates:
73
73
 
74
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
75
74
  ```html
76
-
77
75
  <gl-filtered-search :available-tokens="tokens" v-model="value" terms-as-tokens />
78
76
  ```
@@ -10,9 +10,7 @@ is to `v-bind` to the
10
10
  [`PageInfo`](https://docs.gitlab.com/ee/api/graphql/reference/#pageinfo) type
11
11
  returned by the endpoint:
12
12
 
13
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
14
13
  ```html
15
-
16
14
  <gl-keyset-pagination v-bind="pageInfo" />
17
15
  ```
18
16
 
@@ -27,9 +25,7 @@ can't be translated.
27
25
 
28
26
  Example:
29
27
 
30
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
31
28
  ```html
32
-
33
29
  <gl-keyset-pagination v-bind="pageInfo" :prev-text="__('Prev')" :next-text="__('Next')" />
34
30
  ```
35
31
 
@@ -40,9 +40,7 @@ selector to the root element that contains the markdown-generated HTML.
40
40
 
41
41
  Set the `compact` property to true in `GlMarkdown` to apply the compact markdown styles.
42
42
 
43
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
44
43
  ```html
45
-
46
44
  <gl-markdown compact></gl-compact>
47
45
  ```
48
46
 
@@ -4,9 +4,7 @@ to make sure this is the right dropdown component for you.
4
4
 
5
5
  ### Basic usage
6
6
 
7
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
8
7
  ```html
9
-
10
8
  <gl-disclosure-dropdown-dropdown toggle-text="Actions" :items="items" />
11
9
  ```
12
10
 
@@ -21,17 +21,13 @@ wbr {
21
21
  By default, `GlFriendlyWrap` wraps text with slashes (`/`) as the break-symbol, which is especially
22
22
  useful when displaying paths or URLs:
23
23
 
24
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
25
24
  ```html
26
-
27
25
  <gl-friendly-wrap text="/some/file/path" />
28
26
  ```
29
27
 
30
28
  The code above renders to the following HTML:
31
29
 
32
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
33
30
  ```html
34
-
35
31
  <span class="text-break">/<wbr>some/<wbr>file/<wbr>path</span>
36
32
  ```
37
33
 
@@ -48,9 +44,7 @@ Multiple custom break-symbols can be defined via the `GlFriendlyWrap` prop:
48
44
 
49
45
  Which renders to:
50
46
 
51
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
52
47
  ```html
53
-
54
48
  <span class="text-break">some;<wbr>text-<wbr>that.<wbr>needs;<wbr>to-<wbr>be.<wbr>wrapped</span>
55
49
  ```
56
50
 
@@ -67,8 +61,6 @@ Symbols can be words too:
67
61
 
68
62
  Which renders to:
69
63
 
70
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
71
64
  ```html
72
-
73
65
  <span class="text-break">it goes on and<wbr> on and<wbr> on and<wbr> on</span>
74
66
  ```
@@ -34,9 +34,7 @@ consistent formatting.
34
34
 
35
35
  This renders to the following HTML:
36
36
 
37
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
38
37
  ```html
39
-
40
38
  <span><span>Item 1</span>, <span>Item 2</span>, <span>Item 3</span></span>
41
39
  ```
42
40
 
@@ -54,9 +52,7 @@ A custom separator can be defined via the `separator` prop:
54
52
 
55
53
  This renders to the following HTML:
56
54
 
57
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
58
55
  ```html
59
-
60
56
  <span><span>Item 1</span>/<span>Item 2</span>/<span>Item 3</span></span>
61
57
  ```
62
58
 
@@ -74,9 +70,7 @@ A custom last separator can be defined via the `lastSeparator` prop:
74
70
 
75
71
  This renders to the following HTML:
76
72
 
77
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
78
73
  ```html
79
-
80
74
  <span><span>Item 1</span>, <span>Item 2</span>, and <span>Item 3</span></span>
81
75
  ```
82
76
 
@@ -91,8 +85,6 @@ A custom last separator used on two items will only place `lastSeparator` betwee
91
85
 
92
86
  This renders to the following HTML:
93
87
 
94
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
95
88
  ```html
96
-
97
89
  <span><span>Item 1</span> and <span>Item 2</span></span>
98
90
  ```
@@ -130,9 +130,7 @@ export default {
130
130
 
131
131
  The example above renders to this HTML:
132
132
 
133
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
134
133
  ```html
135
-
136
134
  <div>Written by <span>Some author</span></div>
137
135
  ```
138
136
 
@@ -4,9 +4,7 @@ The `GlTruncate` component lets you truncate the long texts with ellipsis.
4
4
 
5
5
  ## Usage
6
6
 
7
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
8
7
  ```html
9
-
10
8
  <gl-truncate :text="text" :position="position" />
11
9
  ```
12
10
 
@@ -46,9 +46,7 @@ const config = { ALLOWED_TAGS: ['b'] };
46
46
  const config = { ALLOWED_TAGS: [] };
47
47
  ```
48
48
 
49
- <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
50
49
  ```html
51
-
52
50
  <div v-safe-html:[config]="rawHtml"></div>
53
51
  ```
54
52
 
@@ -49,23 +49,45 @@ export function hexToRgba(hex, opacity = 1) {
49
49
  return `rgba(${r}, ${g}, ${b}, ${opacity})`;
50
50
  }
51
51
 
52
+ export function toSrgb(value) {
53
+ const normalized = value / 255;
54
+ return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
55
+ }
56
+
57
+ export function relativeLuminance(rgb) {
58
+ // WCAG 2.1 formula: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
59
+ // -
60
+ // WCAG 3.0 will use APAC
61
+ // Using APAC would be the ultimate goal, but was dismissed by engineering as of now
62
+ // See https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/3418#note_1370107090
63
+ return 0.2126 * toSrgb(rgb[0]) + 0.7152 * toSrgb(rgb[1]) + 0.0722 * toSrgb(rgb[2]);
64
+ }
65
+
52
66
  export function colorFromBackground(backgroundColor) {
53
- let r;
54
- let g;
55
- let b;
67
+ let color;
68
+ const lightColor = rgbFromHex('#FFFFFF');
69
+ const darkColor = rgbFromHex('#1f1e24');
56
70
 
57
71
  if (backgroundColor.startsWith('#')) {
58
- [r, g, b] = rgbFromHex(backgroundColor);
72
+ color = rgbFromHex(backgroundColor);
59
73
  } else if (backgroundColor.startsWith('rgba(')) {
60
- [r, g, b] = rgbFromString(backgroundColor, 5);
74
+ color = rgbFromString(backgroundColor, 5);
61
75
  } else if (backgroundColor.startsWith('rgb(')) {
62
- [r, g, b] = rgbFromString(backgroundColor, 4);
76
+ color = rgbFromString(backgroundColor, 4);
63
77
  }
64
78
 
65
- if (r + g + b <= 500) {
66
- return labelColorOptions.light;
67
- }
68
- return labelColorOptions.dark;
79
+ const luminance = relativeLuminance(color);
80
+ const lightLuminance = relativeLuminance(lightColor);
81
+ const darkLuminance = relativeLuminance(darkColor);
82
+
83
+ const contrastLight = (lightLuminance + 0.05) / (luminance + 0.05);
84
+ const contrastDark = (luminance + 0.05) / (darkLuminance + 0.05);
85
+
86
+ // Using a threshold contrast of 2.4 instead of 3
87
+ // as this will solve weird color combinations in the mid tones
88
+ return contrastLight >= 2.4 || contrastLight > contrastDark
89
+ ? labelColorOptions.light
90
+ : labelColorOptions.dark;
69
91
  }
70
92
 
71
93
  export function uid() {