@gitlab/ui 41.9.1 → 42.0.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.
@@ -80,7 +80,6 @@ const buttonVariantOptions = {
80
80
  confirm: 'confirm',
81
81
  info: 'info (deprecated)',
82
82
  success: 'success (deprecated)',
83
- warning: 'warning (deprecated)',
84
83
  danger: 'danger',
85
84
  dashed: 'dashed',
86
85
  link: 'link',
@@ -103,7 +102,6 @@ const dropdownVariantOptions = {
103
102
  confirm: 'confirm',
104
103
  info: 'info (deprecated)',
105
104
  success: 'success (deprecated)',
106
- warning: 'warning (deprecated)',
107
105
  danger: 'danger',
108
106
  link: 'link'
109
107
  };
@@ -224,7 +222,8 @@ const keyboard = {
224
222
  right: 'Right',
225
223
  arrowRight: 'ArrowRight',
226
224
  home: 'Home',
227
- end: 'End'
225
+ end: 'End',
226
+ tab: 'Tab'
228
227
  };
229
228
  const truncateOptions = POSITION;
230
229
  const formInputSizes = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "41.9.1",
3
+ "version": "42.0.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -82,8 +82,8 @@
82
82
  "@babel/core": "^7.10.2",
83
83
  "@babel/preset-env": "^7.10.2",
84
84
  "@gitlab/eslint-plugin": "12.3.0",
85
- "@gitlab/stylelint-config": "4.0.0",
86
- "@gitlab/svgs": "2.19.0",
85
+ "@gitlab/stylelint-config": "4.1.0",
86
+ "@gitlab/svgs": "2.20.0",
87
87
  "@rollup/plugin-commonjs": "^11.1.0",
88
88
  "@rollup/plugin-node-resolve": "^7.1.3",
89
89
  "@rollup/plugin-replace": "^2.3.2",
@@ -122,7 +122,7 @@
122
122
  "npm-run-all": "^4.1.5",
123
123
  "pikaday": "^1.8.0",
124
124
  "plop": "^2.5.4",
125
- "postcss": "8.4.12",
125
+ "postcss": "8.4.14",
126
126
  "postcss-loader": "^3.0.0",
127
127
  "postcss-scss": "4.0.4",
128
128
  "prettier": "2.6.2",
@@ -140,7 +140,7 @@
140
140
  "sass-true": "^5.0.0",
141
141
  "start-server-and-test": "^1.10.6",
142
142
  "storybook-dark-mode": "^1.0.8",
143
- "stylelint": "14.3.0",
143
+ "stylelint": "14.9.1",
144
144
  "stylelint-config-prettier": "9.0.3",
145
145
  "stylelint-prettier": "2.0.0",
146
146
  "vue": "2.6.11",
@@ -135,8 +135,7 @@
135
135
  &.btn-confirm,
136
136
  &.btn-info,
137
137
  &.btn-success,
138
- &.btn-danger,
139
- &.btn-warning {
138
+ &.btn-danger {
140
139
  @include gl-text-contrast-light;
141
140
  }
142
141
 
@@ -251,60 +250,6 @@
251
250
  }
252
251
  }
253
252
 
254
- &.btn-warning {
255
- @include gl-bg-orange-500;
256
- @include gl-inset-border-1-orange-600;
257
-
258
- &:hover {
259
- @include gl-tmp-button-hover($orange-800, $orange-600);
260
- }
261
-
262
- &:focus {
263
- @include gl-focus($color: $orange-800);
264
- @include gl-bg-orange-600;
265
- }
266
-
267
- &:active,
268
- &.active {
269
- @include gl-focus($color: $orange-900);
270
- @include gl-bg-orange-700;
271
-
272
- &:focus {
273
- @include gl-focus($color: $orange-900);
274
- }
275
- }
276
- }
277
-
278
- &.btn-warning-secondary,
279
- &.btn-warning-tertiary {
280
- @include gl-bg-transparent;
281
- @include gl-text-orange-500;
282
- @include gl-font-weight-normal;
283
- @include gl-inset-border-1-orange-500;
284
-
285
- &:hover {
286
- @include gl-text-orange-700;
287
- @include gl-tmp-button-hover($orange-700, $orange-50);
288
- }
289
-
290
- &:focus {
291
- @include gl-text-orange-700;
292
- @include gl-focus($color: $orange-700);
293
- @include gl-bg-orange-50;
294
- }
295
-
296
- &:active,
297
- &.active {
298
- @include gl-text-orange-900;
299
- @include gl-focus($color: $orange-900);
300
- @include gl-bg-orange-100;
301
-
302
- &:focus {
303
- @include gl-focus($color: $orange-900);
304
- }
305
- }
306
- }
307
-
308
253
  &.btn-danger {
309
254
  @include gl-bg-red-500;
310
255
  @include gl-inset-border-1-red-600;
@@ -364,8 +309,7 @@
364
309
  &.btn-confirm,
365
310
  &.btn-info,
366
311
  &.btn-success,
367
- &.btn-danger,
368
- &.btn-warning {
312
+ &.btn-danger {
369
313
  &-secondary {
370
314
  @include gl-bg-white;
371
315
  }
@@ -376,8 +320,7 @@
376
320
  &.btn-confirm,
377
321
  &.btn-info,
378
322
  &.btn-success,
379
- &.btn-danger,
380
- &.btn-warning {
323
+ &.btn-danger {
381
324
  &-tertiary {
382
325
  @include gl-shadow-none;
383
326
  mix-blend-mode: multiply;
@@ -99,9 +99,6 @@ describe('button component', () => {
99
99
  ${'success'} | ${'primary'} | ${'btn-success'}
100
100
  ${'success'} | ${'secondary'} | ${'btn-success-secondary'}
101
101
  ${'success'} | ${'tertiary'} | ${'btn-success-tertiary'}
102
- ${'warning'} | ${'primary'} | ${'btn-warning'}
103
- ${'warning'} | ${'secondary'} | ${'btn-warning-secondary'}
104
- ${'warning'} | ${'tertiary'} | ${'btn-warning-tertiary'}
105
102
  ${'danger'} | ${'primary'} | ${'btn-danger'}
106
103
  ${'danger'} | ${'secondary'} | ${'btn-danger-secondary'}
107
104
  ${'danger'} | ${'tertiary'} | ${'btn-danger-tertiary'}
@@ -108,8 +108,7 @@
108
108
  &.btn-confirm,
109
109
  &.btn-info,
110
110
  &.btn-success,
111
- &.btn-danger,
112
- &.btn-warning {
111
+ &.btn-danger {
113
112
  margin-left: 1px;
114
113
  }
115
114
 
@@ -62,6 +62,15 @@ describe('GlTokenContainer', () => {
62
62
  });
63
63
  });
64
64
 
65
+ describe('viewOnly', () => {
66
+ it('passes viewOnly prop to tokens correctly', () => {
67
+ createComponent({ propsData: { viewOnly: true } });
68
+ const tokenWrappers = wrapper.findAllComponents(GlToken);
69
+
70
+ expect(tokenWrappers.wrappers.every((token) => token.props('viewOnly'))).toBe(true);
71
+ });
72
+ });
73
+
65
74
  describe('state', () => {
66
75
  describe('when `state` is `false`', () => {
67
76
  it('adds `aria-invalid="true"` attribute`', () => {
@@ -247,6 +256,17 @@ describe('GlTokenContainer', () => {
247
256
  const expectedFocusedToken = findTokenByName(tokens[2].name);
248
257
  expect(expectedFocusedToken.element).toHaveFocus();
249
258
  });
259
+
260
+ it('emits the `cancel-focus` event when tab key is pressed', async () => {
261
+ createComponent({});
262
+
263
+ const focusedToken = findTokenByName(tokens[0].name);
264
+
265
+ await focusedToken.trigger('focus');
266
+ await focusedToken.trigger('keydown', { key: keyboard.tab });
267
+
268
+ expect(wrapper.emitted('cancel-focus')).toEqual([[]]);
269
+ });
250
270
  });
251
271
  });
252
272
  });
@@ -21,6 +21,11 @@ export default {
21
21
  type: Function,
22
22
  required: true,
23
23
  },
24
+ viewOnly: {
25
+ type: Boolean,
26
+ required: false,
27
+ default: false,
28
+ },
24
29
  },
25
30
  data() {
26
31
  return {
@@ -90,6 +95,9 @@ export default {
90
95
  this.focusedTokenIndex = null;
91
96
  this.$emit('cancel-focus');
92
97
  },
98
+ handleTab() {
99
+ this.$emit('cancel-focus');
100
+ },
93
101
  // Only called when a token is focused by a click/tap
94
102
  handleTokenFocus(index) {
95
103
  this.focusedTokenIndex = index;
@@ -127,7 +135,7 @@ export default {
127
135
  @keydown.end="handleEnd"
128
136
  @keydown.delete="handleDelete"
129
137
  @keydown.esc="handleEscape"
130
- @keydown.prevent
138
+ @keydown.tab.prevent="handleTab"
131
139
  >
132
140
  <div
133
141
  v-for="(token, index) in tokens"
@@ -143,6 +151,7 @@ export default {
143
151
  class="gl-cursor-default"
144
152
  :class="token.class"
145
153
  :style="token.style"
154
+ :view-only="viewOnly"
146
155
  @close="handleClose(token)"
147
156
  >
148
157
  <slot name="token-content" :token="token">
@@ -46,6 +46,8 @@ describe('GlTokenSelector', () => {
46
46
  },
47
47
  ];
48
48
 
49
+ const placeholderText = 'Test placeholder';
50
+
49
51
  let wrapper;
50
52
 
51
53
  const defaultProps = { selectedTokens: [] };
@@ -141,6 +143,22 @@ describe('GlTokenSelector', () => {
141
143
  });
142
144
  });
143
145
 
146
+ describe('viewOnly', () => {
147
+ beforeEach(() => {
148
+ createComponent({ propsData: { viewOnly: true } });
149
+ });
150
+
151
+ it('passes `viewOnly` prop to GlTokenContainer', () => {
152
+ expect(wrapper.findComponent(GlTokenContainer).props('viewOnly')).toBe(true);
153
+ });
154
+
155
+ it('disables input field if viewOnly is true', () => {
156
+ findTextInput().trigger('focus');
157
+
158
+ expect(findTextInput().attributes('disabled')).toBe('disabled');
159
+ });
160
+ });
161
+
144
162
  describe('containerClass', () => {
145
163
  it('renders passed CSS classes', () => {
146
164
  createComponent({
@@ -302,6 +320,48 @@ describe('GlTokenSelector', () => {
302
320
 
303
321
  expect(wrapper.findComponent(component).vm.$scopedSlots).toHaveProperty(slot);
304
322
  });
323
+
324
+ it('renders empty-placeholder slot if tokens list is empty and input is not focused', () => {
325
+ createComponent({
326
+ propsData: {
327
+ selectedTokens: [],
328
+ },
329
+ slots: {
330
+ 'empty-placeholder': placeholderText,
331
+ },
332
+ });
333
+
334
+ expect(wrapper.text()).toContain(placeholderText);
335
+ });
336
+
337
+ it('does not render empty-placeholder slot if token list is not empty', () => {
338
+ createComponent({
339
+ propsData: {
340
+ selectedTokens: tokens,
341
+ },
342
+ slots: {
343
+ 'empty-placeholder': placeholderText,
344
+ },
345
+ });
346
+
347
+ expect(wrapper.text()).not.toContain(placeholderText);
348
+ });
349
+
350
+ it('hides empty-placeholder slot if input is focused', async () => {
351
+ createComponent({
352
+ propsData: {
353
+ selectedTokens: [],
354
+ },
355
+ slots: {
356
+ 'empty-placeholder': placeholderText,
357
+ },
358
+ });
359
+
360
+ expect(wrapper.text()).toContain(placeholderText);
361
+
362
+ await findTextInput().trigger('focus');
363
+ expect(wrapper.text()).not.toContain(placeholderText);
364
+ });
305
365
  });
306
366
 
307
367
  describe('text input events', () => {
@@ -113,6 +113,14 @@ export default {
113
113
  validator: tokensValidator,
114
114
  required: true,
115
115
  },
116
+ /**
117
+ * Controls the `view-only` mode for the tokens
118
+ */
119
+ viewOnly: {
120
+ type: Boolean,
121
+ required: false,
122
+ default: false,
123
+ },
116
124
  },
117
125
  data() {
118
126
  return {
@@ -165,6 +173,9 @@ export default {
165
173
  ? 'is-valid gl-inset-border-1-gray-400!'
166
174
  : 'is-invalid gl-inset-border-1-red-500!';
167
175
  },
176
+ showEmptyPlaceholder() {
177
+ return this.selectedTokens.length === 0 && !this.inputFocused;
178
+ },
168
179
  },
169
180
  watch: {
170
181
  inputText(newValue, oldValue) {
@@ -354,14 +365,18 @@ export default {
354
365
  <div>
355
366
  <div
356
367
  ref="container"
357
- class="gl-token-selector gl-form-input form-control form-control-plaintext gl-cursor-text! gl-py-2! gl-px-3!"
368
+ class="gl-token-selector gl-form-input gl-display-flex gl-align-items-center form-control form-control-plaintext gl-cursor-text! gl-py-2! gl-px-3!"
358
369
  :class="[inputFocused ? 'gl-token-selector-focus-glow' : '', containerClass, stateClass]"
359
370
  @click="handleContainerClick"
360
371
  >
372
+ <!-- @slot Optional content to display a placeholder when tokens list is empty
373
+ and user doesn't edit tokens -->
374
+ <slot v-if="showEmptyPlaceholder" name="empty-placeholder"></slot>
361
375
  <gl-token-container
362
376
  :tokens="selectedTokens"
363
377
  :state="state"
364
378
  :register-focus-on-token="registerFocusOnToken"
379
+ :view-only="viewOnly"
365
380
  @token-remove="removeToken"
366
381
  @cancel-focus="cancelTokenFocus"
367
382
  >
@@ -383,6 +398,7 @@ export default {
383
398
  :autocomplete="autocomplete"
384
399
  :aria-labelledby="ariaLabelledby"
385
400
  :placeholder="placeholder"
401
+ :disabled="viewOnly"
386
402
  v-bind="textInputAttrs"
387
403
  @input="inputText = $event.target.value"
388
404
  @focus="handleFocus"
@@ -97,7 +97,6 @@ export const buttonVariantOptions = {
97
97
  confirm: 'confirm',
98
98
  info: 'info (deprecated)',
99
99
  success: 'success (deprecated)',
100
- warning: 'warning (deprecated)',
101
100
  danger: 'danger',
102
101
  dashed: 'dashed',
103
102
  link: 'link',
@@ -121,7 +120,6 @@ export const dropdownVariantOptions = {
121
120
  confirm: 'confirm',
122
121
  info: 'info (deprecated)',
123
122
  success: 'success (deprecated)',
124
- warning: 'warning (deprecated)',
125
123
  danger: 'danger',
126
124
  link: 'link',
127
125
  };
@@ -266,6 +264,7 @@ export const keyboard = {
266
264
  arrowRight: 'ArrowRight',
267
265
  home: 'Home',
268
266
  end: 'End',
267
+ tab: 'Tab',
269
268
  };
270
269
 
271
270
  export const truncateOptions = POSITION;