@gitlab/ui 113.6.0 → 114.0.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "113.6.0",
3
+ "version": "114.0.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -7,13 +7,11 @@ button variant.
7
7
 
8
8
  A button link is a link that is styled to look like a button, semantically speaking it's a `<a>` tag
9
9
  with the necessary classes added to make it look like a button, it shares the same functionality as
10
- [`<gl-link>`]
10
+ [<gl-link>](?path=/docs/base-link--docs)
11
11
 
12
12
  > Note: Setting a `target` attribute without a `href` attribute, will not create any side effects.
13
13
  > Without the presence of a `href` attribute, this component will render a `<button>`.
14
14
 
15
- [`<gl-link>`]: ./?path=/story/base-link--default-link
16
-
17
15
  ## Icon-only button
18
16
 
19
17
  Icon-only buttons must have an accessible name.
@@ -23,6 +21,66 @@ You can provide one with the `aria-label` attribute, which is read out by screen
23
21
  <gl-button icon="close" aria-label="Close" />
24
22
  ```
25
23
 
24
+ ## Type
25
+
26
+ You can specify the button's type by setting the prop `type` to `button`, `submit` or `reset`.
27
+ The default type is `button`.
28
+
29
+ Note the `type` prop has no effect when either `href` or `to` props are set.
30
+
31
+ ## Sizing
32
+
33
+ Specify `small` or `medium` via the `size` prop. Defaults to `medium`.
34
+
35
+ ```html
36
+ <gl-button size="small">Small Button</gl-button>
37
+ <gl-button>Default Button (medium)</gl-button>
38
+ <gl-button size="medium">Medium Button</gl-button>
39
+ ```
40
+
41
+ ## Categories
42
+
43
+ Use the `category` prop to set the button category to `primary`, `secondary`, or `tertiary`.
44
+ Defaults to `primary`.
45
+
46
+ ## Variants
47
+
48
+ Use the `variant` prop to set the button variant to `default`, `confirm`, `danger`, `dashed`, or `link`.
49
+ Defaults to `default`.
50
+
51
+ ## Block level buttons
52
+
53
+ Create block level buttons, those that span the full width of a parent, by setting the `block`
54
+ prop.
55
+
56
+ ```html
57
+ <gl-button block>Block Level Button</gl-button>
58
+ ```
59
+
60
+ ## Disabled state
61
+
62
+ Set the `disabled` prop to disable button default functionality. `disabled` also works with buttons
63
+ rendered as `<a>` elements and `<router-link>` (i.e. with the `href` or `to` prop set).
64
+
65
+ ```html
66
+ <gl-button disabled>Disabled</gl-button>
67
+ ```
68
+
69
+ ## Router link support
70
+
71
+ Refer to the [Router support](?path=/docs/base-link--docs#router-links) reference docs for
72
+ the various supported `<router-link>` related props.
73
+
74
+ ## Accessibility
75
+
76
+ When the `href` prop is set to `'#'`, `<gl-button>` will render a link (`<a>`) element with attribute
77
+ `role="button"` set and appropriate keydown listeners (<kbd>Enter</kbd> and <kbd>Space</kbd>) so
78
+ that the link acts like a native HTML `<button>` for screen reader and keyboard-only users. When
79
+ disabled, the `aria-disabled="true"` attribute will be set on the `<a>` element.
80
+
81
+ When the `href` is set to any other value (or the `to` prop is used), `role="button"` will not be
82
+ added, nor will the keyboard event listeners be enabled.
83
+
26
84
  ## Label button
27
85
 
28
86
  A label button renders a non-interactive `span` styled as a button. This can be especially useful
@@ -1,79 +1,219 @@
1
1
  <!-- eslint-disable vue/multi-word-component-names -->
2
2
  <script>
3
- import { BButton } from '../../../vendor/bootstrap-vue/src/components/button/button';
3
+ import GlLink from '../link/link.vue';
4
4
  import {
5
5
  buttonCategoryOptions,
6
6
  buttonVariantOptions,
7
7
  buttonSizeOptions,
8
+ linkVariantUnstyled,
8
9
  } from '../../../utils/constants';
9
- import { logWarning } from '../../../utils/utils';
10
+ import { logWarning, stopEvent } from '../../../utils/utils';
10
11
  import { isSlotEmpty } from '../../../utils/is_slot_empty';
11
12
  import { SafeLinkMixin } from '../../mixins/safe_link_mixin';
13
+ import { isEvent } from '../../../vendor/bootstrap-vue/src/utils/inspect';
12
14
  import GlIcon from '../icon/icon.vue';
13
15
  import GlLoadingIcon from '../loading_icon/loading_icon.vue';
16
+ import { ENTER, SPACE } from '../new_dropdowns/constants';
14
17
 
15
18
  export default {
16
19
  name: 'GlButton',
17
20
  components: {
18
- BButton,
19
21
  GlIcon,
20
22
  GlLoadingIcon,
21
23
  },
22
24
  mixins: [SafeLinkMixin],
23
25
  props: {
26
+ /**
27
+ * Set the category of the button.
28
+ */
24
29
  category: {
25
30
  type: String,
26
31
  required: false,
27
32
  default: buttonCategoryOptions.primary,
28
33
  validator: (value) => Object.keys(buttonCategoryOptions).includes(value),
29
34
  },
35
+ /**
36
+ * Set the variant of the button.
37
+ */
30
38
  variant: {
31
39
  type: String,
32
40
  required: false,
33
41
  default: buttonVariantOptions.default,
34
42
  validator: (value) => Object.keys(buttonVariantOptions).includes(value),
35
43
  },
44
+ /**
45
+ * Specify the size of the button. Options are `small` and `medium`.
46
+ */
36
47
  size: {
37
48
  type: String,
38
49
  required: false,
39
50
  default: 'medium',
40
51
  validator: (value) => Object.keys(buttonSizeOptions).includes(value),
41
52
  },
53
+ /**
54
+ * Style the button as selected.
55
+ */
42
56
  selected: {
43
57
  type: Boolean,
44
58
  required: false,
45
59
  default: false,
46
60
  },
61
+ /**
62
+ * Specify an icon to render in the button.
63
+ */
47
64
  icon: {
48
65
  type: String,
49
66
  required: false,
50
67
  default: '',
51
68
  },
69
+ /**
70
+ * Render a non-interactive label button, a `span` styled as a button.
71
+ */
52
72
  label: {
53
73
  type: Boolean,
54
74
  required: false,
55
75
  default: false,
56
76
  },
77
+ /**
78
+ * Set the loading state of the button.
79
+ */
57
80
  loading: {
58
81
  type: Boolean,
59
82
  required: false,
60
83
  default: false,
61
84
  },
85
+ /**
86
+ * CSS classes to add to the button text.
87
+ */
62
88
  buttonTextClasses: {
63
89
  type: String,
64
90
  required: false,
65
91
  default: '',
66
92
  },
93
+ /**
94
+ * Renders a 100% width button (expands to the width of its parent container).
95
+ */
67
96
  block: {
68
97
  type: Boolean,
69
98
  required: false,
70
99
  default: false,
71
100
  },
101
+ /**
102
+ * Specify the HTML tag to render instead of the default tag.
103
+ */
104
+ tag: {
105
+ type: String,
106
+ required: false,
107
+ default: 'button',
108
+ },
109
+ /**
110
+ * The value to set the button's `type` attribute to. Can be one of `button`, `submit`, or `reset`.
111
+ */
112
+ type: {
113
+ type: String,
114
+ required: false,
115
+ default: 'button',
116
+ validator: (value) => ['button', 'submit', 'reset'].includes(value),
117
+ },
118
+ /**
119
+ * Disables the component's functionality and places it in a disabled state.
120
+ */
72
121
  disabled: {
73
122
  type: Boolean,
74
123
  required: false,
75
124
  default: false,
76
125
  },
126
+ /**
127
+ * Denotes the target URL of the link for standard links.
128
+ */
129
+ href: {
130
+ type: String,
131
+ required: false,
132
+ default: undefined,
133
+ },
134
+ /**
135
+ * Skips sanitization of href if true. This should be used sparingly.
136
+ * Consult security team before setting to true.
137
+ */
138
+ isUnsafeLink: {
139
+ type: Boolean,
140
+ required: false,
141
+ default: false,
142
+ },
143
+ /**
144
+ * Sets the 'rel' attribute on the rendered link.
145
+ */
146
+ rel: {
147
+ type: String,
148
+ required: false,
149
+ default: null,
150
+ },
151
+ /**
152
+ * Sets the 'target' attribute on the rendered link.
153
+ */
154
+ target: {
155
+ type: String,
156
+ required: false,
157
+ default: null,
158
+ },
159
+ /**
160
+ * Places the component in the active state with active styling
161
+ */
162
+ active: {
163
+ type: Boolean,
164
+ required: false,
165
+ default: false,
166
+ },
167
+ /**
168
+ * <router-link> prop: Denotes the target route of the link.
169
+ * When clicked, the value of the to prop will be passed to `router.push()` internally,
170
+ * so the value can be either a string or a Location descriptor object.
171
+ */
172
+ to: {
173
+ type: [Object, String],
174
+ required: false,
175
+ default: undefined,
176
+ },
177
+ /**
178
+ * <router-link> prop: Configure the active CSS class applied when the link is active.
179
+ */
180
+ activeClass: {
181
+ type: String,
182
+ required: false,
183
+ default: undefined,
184
+ },
185
+ /**
186
+ * <router-link> prop: Configure the active CSS class applied when the link is active with exact match.
187
+ */
188
+ exactActiveClass: {
189
+ type: String,
190
+ required: false,
191
+ default: undefined,
192
+ },
193
+ /**
194
+ * <router-link> prop: Setting the replace prop will call `router.replace()` instead of `router.push()`
195
+ * when clicked, so the navigation will not leave a history record.
196
+ */
197
+ replace: {
198
+ type: Boolean,
199
+ required: false,
200
+ default: false,
201
+ },
202
+ /**
203
+ * <nuxt-link> prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport,
204
+ * Nuxt.js will automatically prefetch the code splitted page. Setting `prefetch` to `true` or `false` will overwrite the default value of `router.prefetchLinks`
205
+ */
206
+ prefetch: {
207
+ type: Boolean,
208
+ required: false,
209
+ // Must be `null` to fall back to the value defined in the
210
+ // `nuxt.config.js` configuration file for `router.prefetchLinks`
211
+ // We convert `null` to `undefined`, so that Nuxt.js will use the
212
+ // compiled default
213
+ // Vue treats `undefined` as default of `false` for Boolean props,
214
+ // so we must set it as `null` here to be a true tri-state prop
215
+ default: null,
216
+ },
77
217
  },
78
218
  computed: {
79
219
  hasIcon() {
@@ -86,7 +226,7 @@ export default {
86
226
  return this.disabled || this.loading;
87
227
  },
88
228
  buttonClasses() {
89
- const classes = ['gl-button'];
229
+ const classes = ['btn', 'gl-button', `btn-${this.variant}`, `btn-${this.buttonSize}`];
90
230
 
91
231
  if (this.category !== buttonCategoryOptions.primary) {
92
232
  classes.push(`btn-${this.variant}-${this.category}`);
@@ -96,10 +236,12 @@ export default {
96
236
  'btn-icon': this.hasIconOnly,
97
237
  'button-ellipsis-horizontal': this.hasIconOnly && this.icon === 'ellipsis_h',
98
238
  selected: this.selected,
239
+ 'btn-block': this.displayBlock,
240
+ disabled: this.disabled,
99
241
  });
100
242
 
101
243
  if (this.label) {
102
- classes.push('btn', 'btn-label', `btn-${this.buttonSize}`);
244
+ classes.push('btn', 'btn-label');
103
245
  }
104
246
 
105
247
  return classes;
@@ -110,6 +252,83 @@ export default {
110
252
  displayBlock() {
111
253
  return !this.label && this.block;
112
254
  },
255
+ isLink() {
256
+ return this.href || this.to;
257
+ },
258
+ isHashLink() {
259
+ return this.isLink && this.href === '#';
260
+ },
261
+ isButton() {
262
+ return this.componentIs === 'button';
263
+ },
264
+ isNonStandardTag() {
265
+ if (this.label) {
266
+ return false;
267
+ }
268
+
269
+ return !this.isLink && !this.isButton;
270
+ },
271
+ tabindex() {
272
+ // When disabled remove links and non-standard tags from tab order
273
+ if (this.disabled) {
274
+ return this.isLink || this.isNonStandardTag ? '-1' : this.$attrs.tabindex;
275
+ }
276
+
277
+ // Add hash links and non-standard tags to tab order
278
+ return this.isNonStandardTag || this.isHashLink ? '0' : this.$attrs.tabindex;
279
+ },
280
+ computedPropsAndAttributes() {
281
+ const base = {
282
+ // Type only used for "real" buttons
283
+ type: this.isButton ? this.type : null,
284
+ // Disabled only set on "real" buttons
285
+ disabled: this.isButton ? this.isButtonDisabled : null,
286
+ // We add a role of button when the tag is not a link or button or when link has `href` of `#`
287
+ role: this.isNonStandardTag || this.isHashLink ? 'button' : this.$attrs?.role,
288
+ // We set the `aria-disabled` state for non-standard tags
289
+ ...(this.isNonStandardTag ? { 'aria-disabled': String(this.disabled) } : {}),
290
+ tabindex: this.tabindex,
291
+ };
292
+
293
+ if (this.isLink) {
294
+ return {
295
+ ...this.$attrs,
296
+ ...base,
297
+ variant: linkVariantUnstyled,
298
+ disabled: this.disabled,
299
+ href: this.href,
300
+ isUnsafeLink: this.isUnsafeLink,
301
+ rel: this.rel,
302
+ target: this.target,
303
+ active: this.active,
304
+ to: this.to,
305
+ activeClass: this.activeClass,
306
+ exactActiveClass: this.exactActiveClass,
307
+ replace: this.replace,
308
+ prefetch: this.prefetch,
309
+ };
310
+ }
311
+
312
+ return { ...this.$attrs, ...base };
313
+ },
314
+ computedListeners() {
315
+ return {
316
+ click: this.onClick,
317
+ keydown: this.onKeydown,
318
+ ...this.$listeners,
319
+ };
320
+ },
321
+ componentIs() {
322
+ if (this.label) {
323
+ return 'span';
324
+ }
325
+
326
+ if (this.isLink) {
327
+ return GlLink;
328
+ }
329
+
330
+ return this.tag;
331
+ },
113
332
  },
114
333
  mounted() {
115
334
  // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
@@ -120,20 +339,36 @@ export default {
120
339
  );
121
340
  }
122
341
  },
342
+ methods: {
343
+ onKeydown(event) {
344
+ // Skip if disabled
345
+ // Add SPACE keydown handler for link has `href` of `#`
346
+ // Add ENTER handler for non-standard tags
347
+ if (!this.disabled && (this.isNonStandardTag || this.isHashLink)) {
348
+ const { code } = event;
349
+
350
+ if (code === SPACE || (code === ENTER && this.isNonStandardTag)) {
351
+ const target = event.currentTarget || event.target;
352
+ stopEvent(event, { propagation: false });
353
+ target.click();
354
+ }
355
+ }
356
+ },
357
+ onClick(event) {
358
+ if (this.disabled && isEvent(event)) {
359
+ stopEvent(event);
360
+ }
361
+ },
362
+ },
123
363
  };
124
364
  </script>
125
365
  <template>
126
366
  <component
127
- :is="label ? 'span' : 'b-button'"
128
- v-bind="$attrs"
367
+ :is="componentIs"
368
+ v-bind="computedPropsAndAttributes"
129
369
  v-safe-link:[safeLinkConfig]
130
- :block="displayBlock"
131
- :target="target"
132
- :variant="variant"
133
- :size="buttonSize"
134
- :disabled="isButtonDisabled"
135
370
  :class="buttonClasses"
136
- v-on="$listeners"
371
+ v-on="computedListeners"
137
372
  >
138
373
  <gl-loading-icon v-if="loading" inline class="gl-button-icon gl-button-loading-indicator" />
139
374
  <gl-icon v-if="hasIcon && !(hasIconOnly && loading)" class="gl-button-icon" :name="icon" />
@@ -20,6 +20,16 @@ caption {
20
20
  @apply gl-text-subtle;
21
21
  }
22
22
 
23
+ kbd {
24
+ @apply gl-inline-block gl-text-default gl-bg-subtle gl-border gl-border-b-strong gl-leading-1;
25
+ font-size: 87.5%;
26
+ padding: 3px 5px;
27
+ vertical-align: unset;
28
+ border-image: none;
29
+ border-radius: 3px;
30
+ box-shadow: inset 0 -1px 0 var(--gl-border-color-strong);
31
+ }
32
+
23
33
  .table {
24
34
  @apply gl-w-full gl-mb-3 gl-text-default gl-bg-transparent;
25
35
 
@@ -10,23 +10,6 @@ code {
10
10
  }
11
11
  }
12
12
 
13
- // User input typically entered via keyboard
14
- kbd {
15
- padding: $kbd-padding-y $kbd-padding-x;
16
- @include font-size($kbd-font-size);
17
- color: $kbd-color;
18
- background-color: $kbd-bg;
19
- @include border-radius($border-radius-sm);
20
- @include box-shadow($kbd-box-shadow);
21
-
22
- kbd {
23
- padding: 0;
24
- @include font-size(100%);
25
- font-weight: $nested-kbd-font-weight;
26
- @include box-shadow(none);
27
- }
28
- }
29
-
30
13
  // Blocks of code
31
14
  pre {
32
15
  display: block;
@@ -339,7 +339,6 @@ $mark-padding: .2em !default;
339
339
 
340
340
  $dt-font-weight: $font-weight-bold !default;
341
341
 
342
- $kbd-box-shadow: inset 0 -.1rem 0 rgba($black, .25) !default;
343
342
  $nested-kbd-font-weight: $font-weight-bold !default;
344
343
 
345
344
  $list-inline-padding: .5rem !default;
@@ -1129,12 +1128,6 @@ $close-text-shadow: 0 1px 0 $white !default;
1129
1128
  $code-font-size: 87.5% !default;
1130
1129
  $code-color: $pink !default;
1131
1130
 
1132
- $kbd-padding-y: .2rem !default;
1133
- $kbd-padding-x: .4rem !default;
1134
- $kbd-font-size: $code-font-size !default;
1135
- $kbd-color: $white !default;
1136
- $kbd-bg: $gray-900 !default;
1137
-
1138
1131
  $pre-color: $gray-900 !default;
1139
1132
  $pre-scrollable-max-height: 340px !default;
1140
1133