@gitlab/ui 39.3.1 → 39.5.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/CHANGELOG.md +26 -0
- package/dist/components/base/alert/alert.js +1 -1
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +240 -0
- package/dist/components/base/new_dropdowns/constants.js +20 -0
- package/dist/components/base/new_dropdowns/listbox/listbox.js +381 -0
- package/dist/components/base/new_dropdowns/listbox/listbox_item.js +77 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +2 -0
- package/dist/utility_classes.css +1 -1
- package/dist/utility_classes.css.map +1 -1
- package/dist/utils/utils.js +24 -1
- package/package.json +6 -5
- package/scss_to_js/scss_variables.js +1 -0
- package/scss_to_js/scss_variables.json +5 -0
- package/src/components/base/alert/alert.spec.js +3 -1
- package/src/components/base/alert/alert.vue +1 -1
- package/src/components/base/avatar_labeled/avatar_labeled.stories.js +2 -1
- package/src/components/base/avatar_link/avatar_link.stories.js +2 -3
- package/src/components/base/breadcrumb/breadcrumb.md +1 -1
- package/src/components/base/breadcrumb/breadcrumb.stories.js +2 -1
- package/src/components/base/broadcast_message/broadcast_message.scss +1 -1
- package/src/components/base/button/button.scss +1 -1
- package/src/components/base/dropdown/dropdown.scss +10 -3
- package/src/components/base/dropdown/dropdown_item.scss +1 -0
- package/src/components/base/link/link.stories.js +9 -7
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +171 -0
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +221 -0
- package/src/components/base/new_dropdowns/constants.js +22 -0
- package/src/components/base/new_dropdowns/listbox/listbox.md +71 -0
- package/src/components/base/new_dropdowns/listbox/listbox.spec.js +236 -0
- package/src/components/base/new_dropdowns/listbox/listbox.stories.js +276 -0
- package/src/components/base/new_dropdowns/listbox/listbox.vue +348 -0
- package/src/components/base/new_dropdowns/listbox/listbox_item.spec.js +104 -0
- package/src/components/base/new_dropdowns/listbox/listbox_item.vue +59 -0
- package/src/components/utilities/friendly_wrap/friendly_wrap.stories.js +10 -11
- package/src/components/utilities/sprintf/sprintf.stories.js +11 -9
- package/src/index.js +4 -0
- package/src/scss/utilities.scss +18 -0
- package/src/scss/utility-mixins/color.scss +4 -0
- package/src/scss/utility-mixins/composite.scss +20 -0
- package/src/scss/utility-mixins/index.scss +1 -0
- package/src/scss/variables.scss +1 -0
- package/src/utils/data_utils.js +2 -21
- package/src/utils/utils.js +18 -0
- package/src/utils/utils.spec.js +41 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { clamp, uniqueId } from 'lodash';
|
|
3
|
+
import { stopEvent } from '../../../../utils/utils';
|
|
4
|
+
import {
|
|
5
|
+
GL_DROPDOWN_SHOWN,
|
|
6
|
+
GL_DROPDOWN_HIDDEN,
|
|
7
|
+
HOME,
|
|
8
|
+
END,
|
|
9
|
+
ARROW_DOWN,
|
|
10
|
+
ARROW_UP,
|
|
11
|
+
} from '../constants';
|
|
12
|
+
import {
|
|
13
|
+
buttonCategoryOptions,
|
|
14
|
+
buttonSizeOptions,
|
|
15
|
+
dropdownVariantOptions,
|
|
16
|
+
} from '../../../../utils/constants';
|
|
17
|
+
import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
|
|
18
|
+
import GlListboxItem from './listbox_item.vue';
|
|
19
|
+
|
|
20
|
+
export const ITEM_SELECTOR = '[role="option"]';
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
events: {
|
|
24
|
+
GL_DROPDOWN_SHOWN,
|
|
25
|
+
GL_DROPDOWN_HIDDEN,
|
|
26
|
+
},
|
|
27
|
+
components: {
|
|
28
|
+
GlBaseDropdown,
|
|
29
|
+
GlListboxItem,
|
|
30
|
+
},
|
|
31
|
+
model: {
|
|
32
|
+
prop: 'selected',
|
|
33
|
+
event: 'select',
|
|
34
|
+
},
|
|
35
|
+
props: {
|
|
36
|
+
/**
|
|
37
|
+
* Items to display in the dropdown
|
|
38
|
+
*/
|
|
39
|
+
items: {
|
|
40
|
+
type: Array,
|
|
41
|
+
required: false,
|
|
42
|
+
default: () => [],
|
|
43
|
+
validator: (items) => {
|
|
44
|
+
return items.every(({ value }) => typeof value === 'string');
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* array of selected items values for multi-select and selected item value for single-select
|
|
49
|
+
*/
|
|
50
|
+
selected: {
|
|
51
|
+
type: [Array, String, Number],
|
|
52
|
+
required: false,
|
|
53
|
+
default: () => [],
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* Allow multi-selection
|
|
57
|
+
*/
|
|
58
|
+
multiple: {
|
|
59
|
+
type: Boolean,
|
|
60
|
+
required: false,
|
|
61
|
+
default: false,
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Toggle button text
|
|
65
|
+
*/
|
|
66
|
+
toggleText: {
|
|
67
|
+
type: String,
|
|
68
|
+
required: false,
|
|
69
|
+
default: '',
|
|
70
|
+
},
|
|
71
|
+
/**
|
|
72
|
+
* Toggle text to be read by screen readers only
|
|
73
|
+
*/
|
|
74
|
+
textSrOnly: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
required: false,
|
|
77
|
+
default: false,
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Styling option - dropdown's toggle category
|
|
81
|
+
*/
|
|
82
|
+
category: {
|
|
83
|
+
type: String,
|
|
84
|
+
required: false,
|
|
85
|
+
default: buttonCategoryOptions.primary,
|
|
86
|
+
validator: (value) => Object.keys(buttonCategoryOptions).includes(value),
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Styling option - dropdown's toggle variant
|
|
90
|
+
*/
|
|
91
|
+
variant: {
|
|
92
|
+
type: String,
|
|
93
|
+
required: false,
|
|
94
|
+
default: dropdownVariantOptions.default,
|
|
95
|
+
validator: (value) => Object.keys(dropdownVariantOptions).includes(value),
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* The size of the dropdown toggle
|
|
99
|
+
*/
|
|
100
|
+
size: {
|
|
101
|
+
type: String,
|
|
102
|
+
required: false,
|
|
103
|
+
default: buttonSizeOptions.medium,
|
|
104
|
+
validator: (value) => Object.keys(buttonSizeOptions).includes(value),
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Icon name that will be rendered in the toggle button
|
|
108
|
+
*/
|
|
109
|
+
icon: {
|
|
110
|
+
type: String,
|
|
111
|
+
required: false,
|
|
112
|
+
default: '',
|
|
113
|
+
},
|
|
114
|
+
/**
|
|
115
|
+
* Set to "true" to disable the dropdown
|
|
116
|
+
*/
|
|
117
|
+
disabled: {
|
|
118
|
+
type: Boolean,
|
|
119
|
+
required: false,
|
|
120
|
+
default: false,
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* Set to "true" when dropdown content (items) is loading
|
|
124
|
+
*/
|
|
125
|
+
loading: {
|
|
126
|
+
type: Boolean,
|
|
127
|
+
required: false,
|
|
128
|
+
default: false,
|
|
129
|
+
},
|
|
130
|
+
/**
|
|
131
|
+
* Additional CSS classes to customize toggle appearance
|
|
132
|
+
*/
|
|
133
|
+
toggleClass: {
|
|
134
|
+
type: [String, Array, Object],
|
|
135
|
+
required: false,
|
|
136
|
+
default: null,
|
|
137
|
+
},
|
|
138
|
+
/**
|
|
139
|
+
* Set to "true" to hide the caret
|
|
140
|
+
*/
|
|
141
|
+
noCaret: {
|
|
142
|
+
type: Boolean,
|
|
143
|
+
required: false,
|
|
144
|
+
default: false,
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* Right align listbox menu with respect to the toggle button
|
|
148
|
+
*/
|
|
149
|
+
right: {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
required: false,
|
|
152
|
+
default: false,
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* The `aria-labelledby` attribute value for the toggle button
|
|
156
|
+
*/
|
|
157
|
+
ariaLabelledby: {
|
|
158
|
+
type: String,
|
|
159
|
+
required: false,
|
|
160
|
+
default: null,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
data() {
|
|
164
|
+
return {
|
|
165
|
+
selectedValues: [],
|
|
166
|
+
toggleId: uniqueId('dropdown-toggle-btn-'),
|
|
167
|
+
nextFocusedItemIndex: null,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
computed: {
|
|
171
|
+
listboxToggleText() {
|
|
172
|
+
if (!this.toggleText) {
|
|
173
|
+
if (!this.multiple && this.selectedValues.length) {
|
|
174
|
+
return this.items.find(({ value }) => value === this.selectedValues[0])?.text;
|
|
175
|
+
}
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return this.toggleText;
|
|
180
|
+
},
|
|
181
|
+
selectedIndices() {
|
|
182
|
+
return this.selectedValues
|
|
183
|
+
.map((selected) => this.items.findIndex(({ value }) => value === selected))
|
|
184
|
+
.sort();
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
watch: {
|
|
188
|
+
selected: {
|
|
189
|
+
immediate: true,
|
|
190
|
+
handler(newSelected) {
|
|
191
|
+
if (Array.isArray(newSelected)) {
|
|
192
|
+
if (!this.multiple && newSelected.length) {
|
|
193
|
+
throw new Error('To allow multi-selection, please, set "multiple" property to "true"');
|
|
194
|
+
}
|
|
195
|
+
this.selectedValues = [...newSelected];
|
|
196
|
+
} else {
|
|
197
|
+
this.selectedValues = [newSelected];
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
methods: {
|
|
203
|
+
onShow() {
|
|
204
|
+
this.$nextTick(() => {
|
|
205
|
+
this.focusItem(this.selectedIndices[0] ?? 0, this.getFocusableListItemElements());
|
|
206
|
+
/**
|
|
207
|
+
* Emitted when dropdown is shown
|
|
208
|
+
*
|
|
209
|
+
* @event shown
|
|
210
|
+
*/
|
|
211
|
+
this.$emit(GL_DROPDOWN_SHOWN);
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
onHide() {
|
|
215
|
+
/**
|
|
216
|
+
* Emitted when dropdown is hidden
|
|
217
|
+
*
|
|
218
|
+
* @event hidden
|
|
219
|
+
*/
|
|
220
|
+
this.$emit(GL_DROPDOWN_HIDDEN);
|
|
221
|
+
this.nextFocusedItemIndex = null;
|
|
222
|
+
},
|
|
223
|
+
onKeydown(event) {
|
|
224
|
+
const { code } = event;
|
|
225
|
+
const elements = this.getFocusableListItemElements();
|
|
226
|
+
|
|
227
|
+
if (elements.length < 1) return;
|
|
228
|
+
|
|
229
|
+
let stop = true;
|
|
230
|
+
|
|
231
|
+
if (code === HOME) {
|
|
232
|
+
this.focusItem(0, elements);
|
|
233
|
+
} else if (code === END) {
|
|
234
|
+
this.focusItem(elements.length - 1, elements);
|
|
235
|
+
} else if (code === ARROW_UP) {
|
|
236
|
+
this.focusNextItem(event, elements, -1);
|
|
237
|
+
} else if (code === ARROW_DOWN) {
|
|
238
|
+
this.focusNextItem(event, elements, 1);
|
|
239
|
+
} else {
|
|
240
|
+
stop = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (stop) {
|
|
244
|
+
stopEvent(event);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
getFocusableListItemElements() {
|
|
248
|
+
const items = this.$refs.list.querySelectorAll(ITEM_SELECTOR);
|
|
249
|
+
return Array.from(items);
|
|
250
|
+
},
|
|
251
|
+
focusNextItem(event, elements, offset) {
|
|
252
|
+
const { target } = event;
|
|
253
|
+
const currentIndex = elements.indexOf(target);
|
|
254
|
+
const nextIndex = clamp(currentIndex + offset, 0, elements.length - 1);
|
|
255
|
+
|
|
256
|
+
this.focusItem(nextIndex, elements);
|
|
257
|
+
},
|
|
258
|
+
focusItem(index, elements) {
|
|
259
|
+
this.nextFocusedItemIndex = index;
|
|
260
|
+
|
|
261
|
+
this.$nextTick(() => {
|
|
262
|
+
elements[index]?.focus();
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
onSelect({ value }, isSelected) {
|
|
266
|
+
if (this.multiple) {
|
|
267
|
+
this.onMultiSelect(value, isSelected);
|
|
268
|
+
} else {
|
|
269
|
+
this.onSingleSelect(value, isSelected);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
isSelected(item) {
|
|
273
|
+
return this.selectedValues.some((value) => value === item.value);
|
|
274
|
+
},
|
|
275
|
+
onSingleSelect(value, isSelected) {
|
|
276
|
+
if (isSelected) {
|
|
277
|
+
/**
|
|
278
|
+
* Emitted when selection is changed
|
|
279
|
+
*
|
|
280
|
+
* @event select
|
|
281
|
+
*/
|
|
282
|
+
this.$emit('select', value);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.$refs.baseDropdown.closeAndFocus();
|
|
286
|
+
},
|
|
287
|
+
onMultiSelect(value, isSelected) {
|
|
288
|
+
if (isSelected) {
|
|
289
|
+
this.$emit('select', [...this.selectedValues, value]);
|
|
290
|
+
} else {
|
|
291
|
+
this.$emit(
|
|
292
|
+
'select',
|
|
293
|
+
this.selectedValues.filter((selectedValue) => selectedValue !== value)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
</script>
|
|
300
|
+
|
|
301
|
+
<template>
|
|
302
|
+
<gl-base-dropdown
|
|
303
|
+
ref="baseDropdown"
|
|
304
|
+
aria-haspopup="listbox"
|
|
305
|
+
:aria-labelledby="ariaLabelledby"
|
|
306
|
+
:toggle-id="toggleId"
|
|
307
|
+
:toggle-text="listboxToggleText"
|
|
308
|
+
:toggle-class="toggleClass"
|
|
309
|
+
:text-sr-only="textSrOnly"
|
|
310
|
+
:category="category"
|
|
311
|
+
:variant="variant"
|
|
312
|
+
:size="size"
|
|
313
|
+
:icon="icon"
|
|
314
|
+
:disabled="disabled"
|
|
315
|
+
:loading="loading"
|
|
316
|
+
:no-caret="noCaret"
|
|
317
|
+
:right="right"
|
|
318
|
+
@[$options.events.GL_DROPDOWN_SHOWN]="onShow"
|
|
319
|
+
@[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
|
|
320
|
+
>
|
|
321
|
+
<!-- @slot Content to display in dropdown header -->
|
|
322
|
+
<slot name="header"></slot>
|
|
323
|
+
|
|
324
|
+
<ul
|
|
325
|
+
ref="list"
|
|
326
|
+
:aria-labelledby="toggleId"
|
|
327
|
+
role="listbox"
|
|
328
|
+
class="gl-new-dropdown-contents gl-list-style-none gl-pl-0 gl-mb-0"
|
|
329
|
+
tabindex="-1"
|
|
330
|
+
@keydown="onKeydown"
|
|
331
|
+
>
|
|
332
|
+
<gl-listbox-item
|
|
333
|
+
v-for="(item, index) in items"
|
|
334
|
+
:key="item.value"
|
|
335
|
+
:is-selected="isSelected(item)"
|
|
336
|
+
:is-focused="nextFocusedItemIndex === index"
|
|
337
|
+
@select="onSelect(item, $event)"
|
|
338
|
+
>
|
|
339
|
+
<!-- @slot Custom template of the listbox item -->
|
|
340
|
+
<slot name="list-item" :item="item">
|
|
341
|
+
{{ item.text }}
|
|
342
|
+
</slot>
|
|
343
|
+
</gl-listbox-item>
|
|
344
|
+
</ul>
|
|
345
|
+
<!-- @slot Content to display in dropdown footer -->
|
|
346
|
+
<slot name="footer"></slot>
|
|
347
|
+
</gl-base-dropdown>
|
|
348
|
+
</template>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import GLIcon from '../../icon/icon.vue';
|
|
3
|
+
|
|
4
|
+
import { ENTER, SPACE } from '../constants';
|
|
5
|
+
import GlListboxItem from './listbox_item.vue';
|
|
6
|
+
|
|
7
|
+
describe('GlListboxItem', () => {
|
|
8
|
+
let wrapper;
|
|
9
|
+
|
|
10
|
+
const buildWrapper = (propsData, slots = {}) => {
|
|
11
|
+
wrapper = mount(GlListboxItem, {
|
|
12
|
+
propsData,
|
|
13
|
+
slots,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
const findItem = () => wrapper.find('[role="option"]');
|
|
17
|
+
const findCheckIcon = () => findItem().findComponent(GLIcon);
|
|
18
|
+
|
|
19
|
+
describe('toggleSelection', () => {
|
|
20
|
+
describe('when selected ', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
buildWrapper({ isSelected: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should set `aria-selected` attribute on the list item to `true`', () => {
|
|
26
|
+
expect(findItem().attributes('aria-selected')).toBe('true');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should display check icon', () => {
|
|
30
|
+
expect(findCheckIcon().classes()).not.toContain('gl-visibility-hidden');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should emit the `select` event when clicked', async () => {
|
|
34
|
+
await findItem().trigger('click');
|
|
35
|
+
expect(wrapper.emitted('select')[0][0]).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should emit the `select` event on `ENTER` keydown event', async () => {
|
|
39
|
+
await findItem().trigger('click');
|
|
40
|
+
findItem().trigger('keydown', { code: SPACE });
|
|
41
|
+
expect(wrapper.emitted('select')[0][0]).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should emit the `select` event on `SPACE` keydown event', async () => {
|
|
45
|
+
await findItem().trigger('click');
|
|
46
|
+
findItem().trigger('keydown', { code: SPACE });
|
|
47
|
+
expect(wrapper.emitted('select')[0][0]).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('when not selected ', () => {
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
buildWrapper({ isSelected: false });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should set `aria-selected` attribute on the list item to `false`', () => {
|
|
57
|
+
expect(findItem().attributes('aria-selected')).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should not display check icon', () => {
|
|
61
|
+
expect(findCheckIcon().classes()).toContain('gl-visibility-hidden');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should emit the `select` event when clicked', async () => {
|
|
65
|
+
await findItem().trigger('click');
|
|
66
|
+
expect(wrapper.emitted('select')[0][0]).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should emit the `select` event on `ENTER` keydown event', async () => {
|
|
70
|
+
await findItem().trigger('click');
|
|
71
|
+
findItem().trigger('keydown', { code: ENTER });
|
|
72
|
+
expect(wrapper.emitted('select')[0][0]).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should emit the `select` event on `SPACE` keydown event', async () => {
|
|
76
|
+
await findItem().trigger('click');
|
|
77
|
+
findItem().trigger('keydown', { code: SPACE });
|
|
78
|
+
expect(wrapper.emitted('select')[0][0]).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('tabindex', () => {
|
|
84
|
+
it('should set tabindex to `-1` when item is not focused', () => {
|
|
85
|
+
buildWrapper({ isFocused: false });
|
|
86
|
+
expect(wrapper.attributes('tabindex')).toBe('-1');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should set tabindex to `0` when item is focused', () => {
|
|
90
|
+
buildWrapper({ isFocused: true });
|
|
91
|
+
expect(wrapper.attributes('tabindex')).toBe('0');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('when default slot content provided', () => {
|
|
96
|
+
const content = 'This is an item';
|
|
97
|
+
const slots = { default: content };
|
|
98
|
+
|
|
99
|
+
it('renders it', () => {
|
|
100
|
+
buildWrapper({}, slots);
|
|
101
|
+
expect(wrapper.text()).toContain(content);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import GlIcon from '../../icon/icon.vue';
|
|
3
|
+
import { ENTER, SPACE } from '../constants';
|
|
4
|
+
import { stopEvent } from '../../../../utils/utils';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
components: {
|
|
8
|
+
GlIcon,
|
|
9
|
+
},
|
|
10
|
+
props: {
|
|
11
|
+
isSelected: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
required: false,
|
|
15
|
+
},
|
|
16
|
+
isFocused: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
default: false,
|
|
19
|
+
required: false,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
methods: {
|
|
23
|
+
toggleSelection() {
|
|
24
|
+
this.$emit('select', !this.isSelected);
|
|
25
|
+
},
|
|
26
|
+
onKeydown(event) {
|
|
27
|
+
const { code } = event;
|
|
28
|
+
|
|
29
|
+
if (code === ENTER || code === SPACE) {
|
|
30
|
+
stopEvent(event);
|
|
31
|
+
this.toggleSelection();
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<li
|
|
40
|
+
class="gl-new-dropdown-item"
|
|
41
|
+
role="option"
|
|
42
|
+
:tabindex="isFocused ? 0 : -1"
|
|
43
|
+
:aria-selected="isSelected"
|
|
44
|
+
@click="toggleSelection"
|
|
45
|
+
@keydown="onKeydown"
|
|
46
|
+
>
|
|
47
|
+
<span class="dropdown-item">
|
|
48
|
+
<gl-icon
|
|
49
|
+
name="mobile-issue-close"
|
|
50
|
+
data-testid="dropdown-item-checkbox"
|
|
51
|
+
class="gl-mt-3 gl-align-self-start"
|
|
52
|
+
:class="['gl-new-dropdown-item-check-icon', { 'gl-visibility-hidden': !isSelected }]"
|
|
53
|
+
/>
|
|
54
|
+
<span class="gl-new-dropdown-item-text-wrapper">
|
|
55
|
+
<slot></slot>
|
|
56
|
+
</span>
|
|
57
|
+
</span>
|
|
58
|
+
</li>
|
|
59
|
+
</template>
|
|
@@ -12,26 +12,26 @@ const generateProps = ({ text = '', symbols = defaultValue('symbols')() } = {})
|
|
|
12
12
|
symbols,
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
const makeStory =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const makeStory =
|
|
16
|
+
(options = {}) =>
|
|
17
|
+
(args, { argTypes }) => ({
|
|
18
|
+
components,
|
|
19
|
+
props: Object.keys(argTypes),
|
|
20
|
+
...options,
|
|
21
|
+
});
|
|
20
22
|
|
|
21
23
|
export const Default = makeStory({
|
|
22
24
|
template: `<gl-friendly-wrap :text="text" :symbols="symbols" />`,
|
|
23
25
|
});
|
|
24
26
|
Default.args = generateProps({
|
|
25
|
-
text:
|
|
26
|
-
'/lorem/ipsum/dolor/sit/amet/consectetur/adipiscing/elit/Aenean/tincidunt/urna/ac/tellus/cursus/laoreet/aenean/blandit/erat/vel/est/maximus/porta/Sed/id/nunc/non/sapien/cursus/ullamcorper',
|
|
27
|
+
text: '/lorem/ipsum/dolor/sit/amet/consectetur/adipiscing/elit/Aenean/tincidunt/urna/ac/tellus/cursus/laoreet/aenean/blandit/erat/vel/est/maximus/porta/Sed/id/nunc/non/sapien/cursus/ullamcorper',
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
export const BreakWord = makeStory({
|
|
30
31
|
template: `<gl-friendly-wrap :text="text" :symbols="symbols" />`,
|
|
31
32
|
});
|
|
32
33
|
BreakWord.args = generateProps({
|
|
33
|
-
text:
|
|
34
|
-
'LoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitamet',
|
|
34
|
+
text: 'LoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitamet',
|
|
35
35
|
symbols: ['dolor'],
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -39,8 +39,7 @@ export const MultipleSymbols = makeStory({
|
|
|
39
39
|
template: `<gl-friendly-wrap :text="text" :symbols="symbols" />`,
|
|
40
40
|
});
|
|
41
41
|
MultipleSymbols.args = generateProps({
|
|
42
|
-
text:
|
|
43
|
-
'LoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitamet',
|
|
42
|
+
text: 'LoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitamet',
|
|
44
43
|
symbols: ['e', 'o'],
|
|
45
44
|
});
|
|
46
45
|
|
|
@@ -6,15 +6,17 @@ const generateProps = ({ message = 'Written by %{author}', placeholders } = {})
|
|
|
6
6
|
placeholders,
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
const makeStory =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
const makeStory =
|
|
10
|
+
(options) =>
|
|
11
|
+
(args, { argTypes }) => ({
|
|
12
|
+
components: {
|
|
13
|
+
GlSprintf,
|
|
14
|
+
GlButton,
|
|
15
|
+
GlLink,
|
|
16
|
+
},
|
|
17
|
+
props: Object.keys(argTypes),
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
18
20
|
|
|
19
21
|
export const SentenceWithLink = makeStory({
|
|
20
22
|
template: `
|
package/src/index.js
CHANGED
|
@@ -54,6 +54,10 @@ export { default as GlDropdownSectionHeader } from './components/base/dropdown/d
|
|
|
54
54
|
export { default as GlDropdownDivider } from './components/base/dropdown/dropdown_divider.vue';
|
|
55
55
|
export { default as GlDropdownText } from './components/base/dropdown/dropdown_text.vue';
|
|
56
56
|
export { default as GlDropdown } from './components/base/dropdown/dropdown.vue';
|
|
57
|
+
// new components aiming to replace GlDropdown - start
|
|
58
|
+
export { default as GlListbox } from './components/base/new_dropdowns/listbox/listbox.vue';
|
|
59
|
+
export { default as GlListboxItem } from './components/base/new_dropdowns/listbox/listbox_item.vue';
|
|
60
|
+
// new components aiming to replace GlDropdown - end
|
|
57
61
|
export { default as GlPath } from './components/base/path/path.vue';
|
|
58
62
|
export { default as GlTable } from './components/base/table/table.vue';
|
|
59
63
|
export { default as GlBreadcrumb } from './components/base/breadcrumb/breadcrumb.vue';
|
package/src/scss/utilities.scss
CHANGED
|
@@ -2061,6 +2061,14 @@
|
|
|
2061
2061
|
color: $white !important;
|
|
2062
2062
|
}
|
|
2063
2063
|
|
|
2064
|
+
.gl-text-contrast-light {
|
|
2065
|
+
color: $white-contrast;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
.gl-text-contrast-light\! {
|
|
2069
|
+
color: $white-contrast !important;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2064
2072
|
.gl-text-body {
|
|
2065
2073
|
color: $body-color;
|
|
2066
2074
|
}
|
|
@@ -2476,6 +2484,16 @@
|
|
|
2476
2484
|
filter: invert(0.8) hue-rotate(180deg) !important;
|
|
2477
2485
|
}
|
|
2478
2486
|
}
|
|
2487
|
+
.gl--flex-center{
|
|
2488
|
+
@include gl-display-flex;
|
|
2489
|
+
@include gl-align-items-center;
|
|
2490
|
+
@include gl-justify-content-center
|
|
2491
|
+
}
|
|
2492
|
+
.gl--flex-center\!{
|
|
2493
|
+
@include gl-display-flex;
|
|
2494
|
+
@include gl-align-items-center;
|
|
2495
|
+
@include gl-justify-content-center
|
|
2496
|
+
}
|
|
2479
2497
|
.gl-content-empty {
|
|
2480
2498
|
content: ''
|
|
2481
2499
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Composite utilities
|
|
3
|
+
//
|
|
4
|
+
// naming convention: gl--{name}
|
|
5
|
+
// purpose: This is for composite classes that provide significant readability and maintainability profit.
|
|
6
|
+
//
|
|
7
|
+
// PLEASE NOTE: When considering to abstract a set of utility classes, please prefer, in order:
|
|
8
|
+
// 1. Not abstracting
|
|
9
|
+
// 2. Create a component
|
|
10
|
+
// 3. Create a constant scoped within a specific module
|
|
11
|
+
// 4. Create a composite class if there are many preexisting occurrences with a clear design responsibility
|
|
12
|
+
//
|
|
13
|
+
// notes:
|
|
14
|
+
// - Composite classes should be very short and not have any nested rules.
|
|
15
|
+
//
|
|
16
|
+
@mixin gl--flex-center {
|
|
17
|
+
@include gl-display-flex;
|
|
18
|
+
@include gl-align-items-center;
|
|
19
|
+
@include gl-justify-content-center;
|
|
20
|
+
}
|
package/src/scss/variables.scss
CHANGED