@gitlab/ui 39.3.2 → 39.4.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 +7 -0
- 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/utils/utils.js +24 -1
- package/package.json +2 -1
- package/src/components/base/dropdown/dropdown.scss +10 -3
- package/src/components/base/dropdown/dropdown_item.scss +1 -0
- 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/index.js +4 -0
- package/src/utils/utils.js +18 -0
- package/src/utils/utils.spec.js +41 -1
package/dist/index.js
CHANGED
|
@@ -44,6 +44,8 @@ export { default as GlDropdownSectionHeader } from './components/base/dropdown/d
|
|
|
44
44
|
export { default as GlDropdownDivider } from './components/base/dropdown/dropdown_divider';
|
|
45
45
|
export { default as GlDropdownText } from './components/base/dropdown/dropdown_text';
|
|
46
46
|
export { default as GlDropdown } from './components/base/dropdown/dropdown';
|
|
47
|
+
export { default as GlListbox } from './components/base/new_dropdowns/listbox/listbox';
|
|
48
|
+
export { default as GlListboxItem } from './components/base/new_dropdowns/listbox/listbox_item';
|
|
47
49
|
export { default as GlPath } from './components/base/path/path';
|
|
48
50
|
export { default as GlTable } from './components/base/table/table';
|
|
49
51
|
export { default as GlBreadcrumb } from './components/base/breadcrumb/breadcrumb';
|
package/dist/utils/utils.js
CHANGED
|
@@ -119,5 +119,28 @@ function logWarning() {
|
|
|
119
119
|
console.warn(message); // eslint-disable-line no-console
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Stop default event handling and propagation
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
function stopEvent(event) {
|
|
127
|
+
let {
|
|
128
|
+
preventDefault = true,
|
|
129
|
+
stopPropagation = true,
|
|
130
|
+
stopImmediatePropagation = false
|
|
131
|
+
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
132
|
+
|
|
133
|
+
if (preventDefault) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (stopPropagation) {
|
|
138
|
+
event.stopPropagation();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (stopImmediatePropagation) {
|
|
142
|
+
event.stopImmediatePropagation();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
122
145
|
|
|
123
|
-
export { colorFromBackground, debounceByAnimationFrame, focusFirstFocusableElement, hexToRgba, isDev, isElementFocusable, logWarning, rgbFromHex, rgbFromString, throttle, uid };
|
|
146
|
+
export { colorFromBackground, debounceByAnimationFrame, focusFirstFocusableElement, hexToRgba, isDev, isElementFocusable, logWarning, rgbFromHex, rgbFromString, stopEvent, throttle, uid };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "39.
|
|
3
|
+
"version": "39.4.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"generate:component": "plop"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
+
"@popperjs/core": "^2.11.2",
|
|
58
59
|
"bootstrap-vue": "2.20.1",
|
|
59
60
|
"dompurify": "^2.3.6",
|
|
60
61
|
"echarts": "^5.2.1",
|
|
@@ -128,8 +128,14 @@
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
.gl-dropdown-toggle
|
|
132
|
-
|
|
131
|
+
.gl-dropdown-toggle {
|
|
132
|
+
&.btn-block {
|
|
133
|
+
@include gl-justify-content-space-between;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.gl-button-text {
|
|
137
|
+
@include gl-display-inline-flex;
|
|
138
|
+
}
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
.gl-new-dropdown-button-text {
|
|
@@ -173,7 +179,8 @@
|
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
.dropdown-icon-only {
|
|
176
|
-
.dropdown-icon
|
|
182
|
+
.dropdown-icon,
|
|
183
|
+
.gl-button-icon.gl-button-icon {
|
|
177
184
|
@include gl-mr-0;
|
|
178
185
|
}
|
|
179
186
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import { GL_DROPDOWN_HIDDEN, GL_DROPDOWN_SHOWN, POPPER_CONFIG } from '../constants';
|
|
4
|
+
import GlBaseDropdown from './base_dropdown.vue';
|
|
5
|
+
|
|
6
|
+
const destroyPopper = jest.fn();
|
|
7
|
+
const updatePopper = jest.fn();
|
|
8
|
+
const mockCreatePopper = jest.fn().mockImplementation(() => ({
|
|
9
|
+
destroy: destroyPopper,
|
|
10
|
+
update: updatePopper,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('@popperjs/core', () => ({
|
|
14
|
+
createPopper: (...args) => mockCreatePopper(...args),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const DEFAULT_BTN_TOGGLE_CLASSES = [
|
|
18
|
+
'btn',
|
|
19
|
+
'btn-default',
|
|
20
|
+
'btn-md',
|
|
21
|
+
'gl-button',
|
|
22
|
+
'dropdown-toggle',
|
|
23
|
+
'gl-dropdown-toggle',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
describe('base dropdown', () => {
|
|
27
|
+
let wrapper;
|
|
28
|
+
|
|
29
|
+
const buildWrapper = (propsData, slots = {}) => {
|
|
30
|
+
wrapper = mount(GlBaseDropdown, {
|
|
31
|
+
propsData: {
|
|
32
|
+
toggleId: 'dropdown-toggle-btn-1',
|
|
33
|
+
...propsData,
|
|
34
|
+
},
|
|
35
|
+
slots,
|
|
36
|
+
attachTo: document.body,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const findDropdownToggle = () => wrapper.find('.btn.gl-dropdown-toggle');
|
|
45
|
+
const findDropdownMenu = () => wrapper.find('.dropdown-menu');
|
|
46
|
+
|
|
47
|
+
describe('popper.js instance', () => {
|
|
48
|
+
it('should initialize popper.js instance with toggle and menu elements and config for left-aligned menu', async () => {
|
|
49
|
+
await buildWrapper();
|
|
50
|
+
expect(mockCreatePopper).toHaveBeenCalledWith(
|
|
51
|
+
findDropdownToggle().element,
|
|
52
|
+
findDropdownMenu().element,
|
|
53
|
+
{ ...POPPER_CONFIG, placement: 'bottom-start' }
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should initialize popper.js instance with toggle and menu elements and config for right-aligned menu', async () => {
|
|
58
|
+
await buildWrapper({ right: true });
|
|
59
|
+
expect(mockCreatePopper).toHaveBeenCalledWith(
|
|
60
|
+
findDropdownToggle().element,
|
|
61
|
+
findDropdownMenu().element,
|
|
62
|
+
{ ...POPPER_CONFIG, placement: 'bottom-end' }
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should update popper instance when component is updated', async () => {
|
|
67
|
+
await buildWrapper();
|
|
68
|
+
await findDropdownToggle().trigger('click');
|
|
69
|
+
await wrapper.setProps({ category: 'tertiary' });
|
|
70
|
+
expect(updatePopper).toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should destroy popper instance when component is destroyed', async () => {
|
|
74
|
+
await buildWrapper();
|
|
75
|
+
wrapper.destroy();
|
|
76
|
+
expect(destroyPopper).toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('renders content to the default slot', () => {
|
|
81
|
+
const defaultContent = 'Some content here';
|
|
82
|
+
const slots = { default: defaultContent };
|
|
83
|
+
|
|
84
|
+
it('renders the content', () => {
|
|
85
|
+
buildWrapper({}, slots);
|
|
86
|
+
expect(wrapper.find('.gl-new-dropdown-inner').html()).toContain(defaultContent);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe.each`
|
|
91
|
+
props | toggleClasses
|
|
92
|
+
${{}} | ${[]}
|
|
93
|
+
${{ toggleText: 'toggleText' }} | ${[]}
|
|
94
|
+
${{ toggleText: 'toggleText', icon: 'close' }} | ${['dropdown-icon-text']}
|
|
95
|
+
${{ icon: 'close' }} | ${['dropdown-icon-only']}
|
|
96
|
+
${{ icon: 'close', toggleText: 'toggleText', textSrOnly: true }} | ${['dropdown-icon-only']}
|
|
97
|
+
${{ icon: 'close', textSrOnly: true }} | ${['dropdown-icon-only']}
|
|
98
|
+
${{ toggleText: 'toggleText', noCaret: true }} | ${['dropdown-toggle-no-caret']}
|
|
99
|
+
`('dropdown with props $props', ({ props, toggleClasses }) => {
|
|
100
|
+
beforeEach(async () => {
|
|
101
|
+
buildWrapper(props);
|
|
102
|
+
|
|
103
|
+
await nextTick();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it(`sets toggle button classes to '${toggleClasses}'`, () => {
|
|
107
|
+
const classes = findDropdownToggle().classes().sort();
|
|
108
|
+
|
|
109
|
+
expect(classes).toEqual([...DEFAULT_BTN_TOGGLE_CLASSES, ...toggleClasses].sort());
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe.each`
|
|
114
|
+
toggleClass | expectedClasses | type
|
|
115
|
+
${'my-class'} | ${[...DEFAULT_BTN_TOGGLE_CLASSES, 'my-class']} | ${'string'}
|
|
116
|
+
${{ 'my-class': true }} | ${[...DEFAULT_BTN_TOGGLE_CLASSES, 'my-class']} | ${'object'}
|
|
117
|
+
${['cls-1', 'cls-2']} | ${[...DEFAULT_BTN_TOGGLE_CLASSES, 'cls-1', 'cls-2']} | ${'array'}
|
|
118
|
+
${null} | ${[...DEFAULT_BTN_TOGGLE_CLASSES]} | ${'null'}
|
|
119
|
+
`('with toggle classes', ({ toggleClass, expectedClasses, type }) => {
|
|
120
|
+
beforeEach(async () => {
|
|
121
|
+
buildWrapper({ toggleClass });
|
|
122
|
+
|
|
123
|
+
await nextTick();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it(`class is inherited from toggle class of type ${type}`, () => {
|
|
127
|
+
expect(findDropdownToggle().classes().sort()).toEqual(
|
|
128
|
+
expect.arrayContaining(expectedClasses.sort())
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('toggle visibility', () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
buildWrapper();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should toggle menu visibility on toggle button click ', async () => {
|
|
139
|
+
const toggle = findDropdownToggle();
|
|
140
|
+
const menu = findDropdownMenu();
|
|
141
|
+
|
|
142
|
+
// open menu clicking toggle btn
|
|
143
|
+
await toggle.trigger('click');
|
|
144
|
+
expect(menu.classes('show')).toBe(true);
|
|
145
|
+
expect(toggle.attributes('aria-expanded')).toBe('true');
|
|
146
|
+
expect(wrapper.emitted(GL_DROPDOWN_SHOWN).length).toBe(1);
|
|
147
|
+
|
|
148
|
+
// close menu clicking toggle btn again
|
|
149
|
+
await toggle.trigger('click');
|
|
150
|
+
expect(menu.classes('show')).toBe(false);
|
|
151
|
+
expect(toggle.attributes('aria-expanded')).toBeUndefined();
|
|
152
|
+
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN).length).toBe(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should close the menu when Escape is pressed inside menu and focus toggle', async () => {
|
|
156
|
+
const toggle = findDropdownToggle();
|
|
157
|
+
const menu = findDropdownMenu();
|
|
158
|
+
|
|
159
|
+
// open menu clicking toggle btn
|
|
160
|
+
await toggle.trigger('click');
|
|
161
|
+
expect(menu.classes('show')).toBe(true);
|
|
162
|
+
|
|
163
|
+
// close menu pressing ESC on it
|
|
164
|
+
await menu.trigger('keydown.esc');
|
|
165
|
+
expect(menu.classes('show')).toBe(false);
|
|
166
|
+
expect(toggle.attributes('aria-expanded')).toBeUndefined();
|
|
167
|
+
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN).length).toBe(1);
|
|
168
|
+
expect(toggle.element).toHaveFocus();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { createPopper } from '@popperjs/core';
|
|
3
|
+
import {
|
|
4
|
+
buttonCategoryOptions,
|
|
5
|
+
buttonSizeOptions,
|
|
6
|
+
dropdownVariantOptions,
|
|
7
|
+
} from '../../../../utils/constants';
|
|
8
|
+
import { POPPER_CONFIG, GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN } from '../constants';
|
|
9
|
+
|
|
10
|
+
import GlButton from '../../button/button.vue';
|
|
11
|
+
import GlIcon from '../../icon/icon.vue';
|
|
12
|
+
import { OutsideDirective } from '../../../../directives/outside/outside';
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
components: {
|
|
16
|
+
GlButton,
|
|
17
|
+
GlIcon,
|
|
18
|
+
},
|
|
19
|
+
directives: { Outside: OutsideDirective },
|
|
20
|
+
props: {
|
|
21
|
+
toggleText: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: false,
|
|
24
|
+
default: '',
|
|
25
|
+
},
|
|
26
|
+
textSrOnly: {
|
|
27
|
+
type: Boolean,
|
|
28
|
+
required: false,
|
|
29
|
+
default: false,
|
|
30
|
+
},
|
|
31
|
+
category: {
|
|
32
|
+
type: String,
|
|
33
|
+
required: false,
|
|
34
|
+
default: buttonCategoryOptions.primary,
|
|
35
|
+
validator: (value) => Object.keys(buttonCategoryOptions).includes(value),
|
|
36
|
+
},
|
|
37
|
+
variant: {
|
|
38
|
+
type: String,
|
|
39
|
+
required: false,
|
|
40
|
+
default: dropdownVariantOptions.default,
|
|
41
|
+
validator: (value) => Object.keys(dropdownVariantOptions).includes(value),
|
|
42
|
+
},
|
|
43
|
+
size: {
|
|
44
|
+
type: String,
|
|
45
|
+
required: false,
|
|
46
|
+
default: buttonSizeOptions.medium,
|
|
47
|
+
validator: (value) => Object.keys(buttonSizeOptions).includes(value),
|
|
48
|
+
},
|
|
49
|
+
icon: {
|
|
50
|
+
type: String,
|
|
51
|
+
required: false,
|
|
52
|
+
default: '',
|
|
53
|
+
},
|
|
54
|
+
disabled: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
required: false,
|
|
57
|
+
default: false,
|
|
58
|
+
},
|
|
59
|
+
loading: {
|
|
60
|
+
type: Boolean,
|
|
61
|
+
required: false,
|
|
62
|
+
default: false,
|
|
63
|
+
},
|
|
64
|
+
toggleClass: {
|
|
65
|
+
type: [String, Array, Object],
|
|
66
|
+
required: false,
|
|
67
|
+
default: null,
|
|
68
|
+
},
|
|
69
|
+
noCaret: {
|
|
70
|
+
type: Boolean,
|
|
71
|
+
required: false,
|
|
72
|
+
default: false,
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Right align dropdown menu with respect to the toggle button
|
|
76
|
+
*/
|
|
77
|
+
right: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
required: false,
|
|
80
|
+
default: false,
|
|
81
|
+
},
|
|
82
|
+
// ARIA props
|
|
83
|
+
ariaHaspopup: {
|
|
84
|
+
type: [String, Boolean],
|
|
85
|
+
required: false,
|
|
86
|
+
default: false,
|
|
87
|
+
validator: (value) => {
|
|
88
|
+
return ['menu', 'listbox', 'tree', 'grid', 'dialog', true, false].includes(value);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* Id that will be referenced by `aria-labelledby` attribute of the dropdown content`
|
|
93
|
+
*/
|
|
94
|
+
toggleId: {
|
|
95
|
+
type: String,
|
|
96
|
+
required: true,
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* The `aria-labelledby` attribute value for the toggle `button`
|
|
100
|
+
*/
|
|
101
|
+
ariaLabelledby: {
|
|
102
|
+
type: String,
|
|
103
|
+
required: false,
|
|
104
|
+
default: null,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
data() {
|
|
108
|
+
return {
|
|
109
|
+
visible: false,
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
computed: {
|
|
113
|
+
isIconOnly() {
|
|
114
|
+
return Boolean(this.icon && (!this.toggleText?.length || this.textSrOnly));
|
|
115
|
+
},
|
|
116
|
+
isIconWithText() {
|
|
117
|
+
return Boolean(this.icon && this.toggleText?.length && !this.textSrOnly);
|
|
118
|
+
},
|
|
119
|
+
toggleButtonClasses() {
|
|
120
|
+
return [
|
|
121
|
+
this.toggleClass,
|
|
122
|
+
{
|
|
123
|
+
'gl-dropdown-toggle': true,
|
|
124
|
+
'dropdown-toggle': true,
|
|
125
|
+
'dropdown-icon-only': this.isIconOnly,
|
|
126
|
+
'dropdown-icon-text': this.isIconWithText,
|
|
127
|
+
'dropdown-toggle-no-caret': this.noCaret,
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
},
|
|
131
|
+
toggleLabelledBy() {
|
|
132
|
+
return this.ariaLabelledby ? `${this.ariaLabelledby} ${this.toggleId}` : this.toggleId;
|
|
133
|
+
},
|
|
134
|
+
popperConfig() {
|
|
135
|
+
return {
|
|
136
|
+
placement: this.right ? 'bottom-end' : 'bottom-start',
|
|
137
|
+
...POPPER_CONFIG,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
updated() {
|
|
142
|
+
if (this.visible) {
|
|
143
|
+
this.popper?.update();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
mounted() {
|
|
147
|
+
this.$nextTick(() => {
|
|
148
|
+
this.popper = createPopper(this.$refs.toggle.$el, this.$refs.content, this.popperConfig);
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
beforeDestroy() {
|
|
152
|
+
this.popper.destroy();
|
|
153
|
+
},
|
|
154
|
+
methods: {
|
|
155
|
+
toggle() {
|
|
156
|
+
this.visible = !this.visible;
|
|
157
|
+
|
|
158
|
+
if (this.visible) {
|
|
159
|
+
this.popper.update();
|
|
160
|
+
this.$emit(GL_DROPDOWN_SHOWN);
|
|
161
|
+
} else {
|
|
162
|
+
this.$emit(GL_DROPDOWN_HIDDEN);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
close() {
|
|
166
|
+
if (!this.visible) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.toggle();
|
|
170
|
+
},
|
|
171
|
+
closeAndFocus() {
|
|
172
|
+
if (!this.visible) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.toggle();
|
|
176
|
+
this.focusToggle();
|
|
177
|
+
},
|
|
178
|
+
focusToggle() {
|
|
179
|
+
this.$refs.toggle.$el.focus();
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<template>
|
|
186
|
+
<div v-outside="close" class="gl-new-dropdown dropdown btn-group">
|
|
187
|
+
<gl-button
|
|
188
|
+
:id="toggleId"
|
|
189
|
+
ref="toggle"
|
|
190
|
+
data-testid="base-dropdown-toggle"
|
|
191
|
+
:icon="icon"
|
|
192
|
+
:category="category"
|
|
193
|
+
:variant="variant"
|
|
194
|
+
:size="size"
|
|
195
|
+
:disabled="disabled"
|
|
196
|
+
:loading="loading"
|
|
197
|
+
:class="toggleButtonClasses"
|
|
198
|
+
:aria-haspopup="ariaHaspopup"
|
|
199
|
+
:aria-expanded="visible"
|
|
200
|
+
:aria-labelledby="toggleLabelledBy"
|
|
201
|
+
@click="toggle"
|
|
202
|
+
>
|
|
203
|
+
<span class="gl-new-dropdown-button-text" :class="{ 'gl-sr-only': textSrOnly }">
|
|
204
|
+
{{ toggleText }}
|
|
205
|
+
</span>
|
|
206
|
+
<gl-icon v-if="!noCaret" class="gl-button-icon dropdown-chevron" name="chevron-down" />
|
|
207
|
+
</gl-button>
|
|
208
|
+
|
|
209
|
+
<div
|
|
210
|
+
ref="content"
|
|
211
|
+
data-testid="base-dropdown-menu"
|
|
212
|
+
class="dropdown-menu"
|
|
213
|
+
:class="{ show: visible }"
|
|
214
|
+
@keydown.esc.stop.prevent="closeAndFocus"
|
|
215
|
+
>
|
|
216
|
+
<div class="gl-new-dropdown-inner">
|
|
217
|
+
<slot></slot>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const POPPER_CONFIG = {
|
|
2
|
+
modifiers: [
|
|
3
|
+
{
|
|
4
|
+
name: 'offset',
|
|
5
|
+
options: {
|
|
6
|
+
offset: [0, 4],
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// base dropdown events
|
|
13
|
+
export const GL_DROPDOWN_SHOWN = 'shown';
|
|
14
|
+
export const GL_DROPDOWN_HIDDEN = 'hidden';
|
|
15
|
+
|
|
16
|
+
// KEY Codes
|
|
17
|
+
export const HOME = 'Home';
|
|
18
|
+
export const END = 'End';
|
|
19
|
+
export const ARROW_UP = 'ArrowUp';
|
|
20
|
+
export const ARROW_DOWN = 'ArrowDown';
|
|
21
|
+
export const ENTER = 'Enter';
|
|
22
|
+
export const SPACE = 'Space';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
A listbox dropdown is a button that toggles a panel containing a list of options.
|
|
2
|
+
Listbox supports single and multi-selection.
|
|
3
|
+
|
|
4
|
+
**Single-select:** By default, selecting an option will update the toggle label with the choice.
|
|
5
|
+
But the custom toggle text can be provided.
|
|
6
|
+
When option is selected, the dropdown will be closed and focus set on the toggle button.
|
|
7
|
+
|
|
8
|
+
**Multi-select:** Selecting an option will not update the toggle, but it can be customized
|
|
9
|
+
providing `toggleText` property. Also, selecting or deselecting an item won't close the dropdown.
|
|
10
|
+
|
|
11
|
+
### Icon-only listbox
|
|
12
|
+
|
|
13
|
+
Icon-only listboxes must have an accessible name.
|
|
14
|
+
You can provide this with the combination of `toggleText` and `textSrOnly` props.
|
|
15
|
+
For single-select listboxes `toggleText` will be set to the selected item's `text` property value
|
|
16
|
+
by default.
|
|
17
|
+
|
|
18
|
+
Optionally, you can use `no-caret` to remove the caret and `category="tertiary"` to remove the border.
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<gl-listbox
|
|
22
|
+
icon="ellipsis_v"
|
|
23
|
+
toggle-text="More options"
|
|
24
|
+
text-sr-only
|
|
25
|
+
category="tertiary"
|
|
26
|
+
no-caret
|
|
27
|
+
>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Opening the listbox
|
|
31
|
+
|
|
32
|
+
Listbox will open on toggle button click (if it was previously closed).
|
|
33
|
+
On open, `GlListbox` will emit the `shown` event.
|
|
34
|
+
|
|
35
|
+
### Closing the listbox
|
|
36
|
+
|
|
37
|
+
The listbox is closed by any of the following:
|
|
38
|
+
|
|
39
|
+
- pressing <kbd>Esc</kbd>
|
|
40
|
+
- clicking anywhere outside the component
|
|
41
|
+
- selecting an option in single-select mode
|
|
42
|
+
|
|
43
|
+
After closing, `GlListbox` emits a `hidden` event.
|
|
44
|
+
|
|
45
|
+
### Selecting items
|
|
46
|
+
|
|
47
|
+
Set the `v-model` on the listbox to have 2-way data binding for the selected items in the listbox.
|
|
48
|
+
Alternatively, you can set `selected` property to the array of selected items
|
|
49
|
+
`value` properties (for multi-select) or to the selected item `value` property for a single-select.
|
|
50
|
+
On selection the listbox will emit the `select` event with the selected values.
|
|
51
|
+
|
|
52
|
+
### Setting listbox options
|
|
53
|
+
|
|
54
|
+
Provide the list of options for the listbox - each item in the array should have `value` property.
|
|
55
|
+
It is used as a primary key.
|
|
56
|
+
To render the default listbox item template, the item should also have `text` property.
|
|
57
|
+
If you want to use custom template for rendering the listbox item, use the `list-item` template.
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<gl-listbox :items="items">
|
|
61
|
+
<template #list-item="{ item }">
|
|
62
|
+
<span class="gl-display-flex gl-align-items-center">
|
|
63
|
+
<gl-avatar :size="32" class-="gl-mr-3"/>
|
|
64
|
+
<span class="gl-display-flex gl-flex-direction-column">
|
|
65
|
+
<span class="gl-font-weight-bold gl-white-space-nowrap">{{ item.text }}</span>
|
|
66
|
+
<span class="gl-text-gray-400"> {{ item.secondaryText }}</span>
|
|
67
|
+
</span>
|
|
68
|
+
</span>
|
|
69
|
+
</template>
|
|
70
|
+
</gl-litsbox>
|
|
71
|
+
```
|