@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,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
|
+
```
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
|
|
4
|
+
import {
|
|
5
|
+
GL_DROPDOWN_SHOWN,
|
|
6
|
+
GL_DROPDOWN_HIDDEN,
|
|
7
|
+
ARROW_DOWN,
|
|
8
|
+
ARROW_UP,
|
|
9
|
+
HOME,
|
|
10
|
+
END,
|
|
11
|
+
} from '../constants';
|
|
12
|
+
import GlListbox, { ITEM_SELECTOR } from './listbox.vue';
|
|
13
|
+
import GlListboxItem from './listbox_item.vue';
|
|
14
|
+
|
|
15
|
+
const mockItems = [
|
|
16
|
+
{
|
|
17
|
+
value: 'eng',
|
|
18
|
+
text: 'Engineering',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: 'sales',
|
|
22
|
+
text: 'Sales',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: 'marketing',
|
|
26
|
+
text: 'Marketing',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
describe('GlListbox', () => {
|
|
31
|
+
let wrapper;
|
|
32
|
+
|
|
33
|
+
const buildWrapper = (propsData, slots = {}) => {
|
|
34
|
+
wrapper = mount(GlListbox, {
|
|
35
|
+
propsData,
|
|
36
|
+
slots,
|
|
37
|
+
attachTo: document.body,
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const findBaseDropdown = () => wrapper.findComponent(GlBaseDropdown);
|
|
42
|
+
const findListContainer = () => wrapper.find('[role="listbox"]');
|
|
43
|
+
const findListboxItems = () => wrapper.findAllComponents(GlListboxItem);
|
|
44
|
+
const findListItem = (index) => findListboxItems().at(index).find(ITEM_SELECTOR);
|
|
45
|
+
|
|
46
|
+
describe('toggle text', () => {
|
|
47
|
+
describe.each`
|
|
48
|
+
toggleText | multiple | selected | expectedToggleText
|
|
49
|
+
${'Toggle caption'} | ${true} | ${[mockItems[0].value]} | ${'Toggle caption'}
|
|
50
|
+
${''} | ${true} | ${[mockItems[0]].value} | ${''}
|
|
51
|
+
${''} | ${false} | ${mockItems[0].value} | ${mockItems[0].text}
|
|
52
|
+
${''} | ${false} | ${''} | ${''}
|
|
53
|
+
`('when listbox', ({ toggleText, multiple, selected, expectedToggleText }) => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
buildWrapper({ items: mockItems, toggleText, multiple, selected });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it(`is ${multiple ? 'multi' : 'single'}-select, toggleText is ${
|
|
59
|
+
toggleText.length ? '' : 'not '
|
|
60
|
+
}provided and ${selected ? 'has' : 'does not have'} selected`, () => {
|
|
61
|
+
expect(findBaseDropdown().props('toggleText')).toBe(expectedToggleText);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('ARIA attributes', () => {
|
|
67
|
+
it('should provide `toggleId` to the base dropdown and reference it in`aria-labelledby` attribute of the list container` ', async () => {
|
|
68
|
+
await buildWrapper();
|
|
69
|
+
expect(findBaseDropdown().props('toggleId')).toBe(
|
|
70
|
+
findListContainer().attributes('aria-labelledby')
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('selecting items', () => {
|
|
76
|
+
describe('multi-select', () => {
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
buildWrapper({
|
|
79
|
+
multiple: true,
|
|
80
|
+
selected: [mockItems[1].value, mockItems[2].value],
|
|
81
|
+
items: mockItems,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should render items as selected when `selected` provided ', () => {
|
|
86
|
+
expect(findListboxItems().at(1).props('isSelected')).toBe(true);
|
|
87
|
+
expect(findListboxItems().at(2).props('isSelected')).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should deselect previously selected', async () => {
|
|
91
|
+
findListboxItems().at(1).vm.$emit('select', false);
|
|
92
|
+
await nextTick();
|
|
93
|
+
expect(wrapper.emitted('select')[0][0]).toEqual([mockItems[2].value]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should add to selection', async () => {
|
|
97
|
+
findListboxItems().at(0).vm.$emit('select', true);
|
|
98
|
+
await nextTick();
|
|
99
|
+
expect(wrapper.emitted('select')[0][0]).toEqual(
|
|
100
|
+
expect.arrayContaining(mockItems.map(({ value }) => value))
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('single-select', () => {
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
buildWrapper({ selected: mockItems[1].value, items: mockItems });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should throw an error when array of selections is provided', () => {
|
|
111
|
+
expect(() => {
|
|
112
|
+
buildWrapper({
|
|
113
|
+
selected: [mockItems[1].value, mockItems[2].value],
|
|
114
|
+
items: mockItems,
|
|
115
|
+
});
|
|
116
|
+
}).toThrowError('To allow multi-selection, please, set "multiple" property to "true"');
|
|
117
|
+
expect(wrapper).toHaveLoggedVueErrors();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should render item as selected when `selected` provided ', () => {
|
|
121
|
+
expect(findListboxItems().at(1).props('isSelected')).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should deselect previously selected and select a new item', async () => {
|
|
125
|
+
findListboxItems().at(2).vm.$emit('select', true);
|
|
126
|
+
await nextTick();
|
|
127
|
+
expect(wrapper.emitted('select')[0][0]).toEqual(mockItems[2].value);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('onShow', () => {
|
|
133
|
+
beforeEach(async () => {
|
|
134
|
+
buildWrapper({
|
|
135
|
+
multiple: true,
|
|
136
|
+
items: mockItems,
|
|
137
|
+
selected: [mockItems[2].value, mockItems[1].value],
|
|
138
|
+
});
|
|
139
|
+
findBaseDropdown().vm.$emit(GL_DROPDOWN_SHOWN);
|
|
140
|
+
await nextTick();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should re-emit the event', () => {
|
|
144
|
+
expect(wrapper.emitted(GL_DROPDOWN_SHOWN)).toHaveLength(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should focus the first selected item', () => {
|
|
148
|
+
expect(findListboxItems().at(1).find(ITEM_SELECTOR).element).toHaveFocus();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('onHide', () => {
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
buildWrapper();
|
|
155
|
+
findBaseDropdown().vm.$emit(GL_DROPDOWN_HIDDEN);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should re-emit the event', () => {
|
|
159
|
+
expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('navigating the items', () => {
|
|
164
|
+
let firstItem;
|
|
165
|
+
let secondItem;
|
|
166
|
+
let thirdItem;
|
|
167
|
+
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
buildWrapper({ items: mockItems });
|
|
170
|
+
findBaseDropdown().vm.$emit(GL_DROPDOWN_SHOWN);
|
|
171
|
+
firstItem = findListItem(0);
|
|
172
|
+
secondItem = findListItem(1);
|
|
173
|
+
thirdItem = findListItem(2);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should move the focus down the list of items on `arrow down` and stop on the last item', async () => {
|
|
177
|
+
expect(firstItem.element).toHaveFocus();
|
|
178
|
+
await firstItem.trigger('keydown', { code: ARROW_DOWN });
|
|
179
|
+
expect(secondItem.element).toHaveFocus();
|
|
180
|
+
await secondItem.trigger('keydown', { code: ARROW_DOWN });
|
|
181
|
+
expect(thirdItem.element).toHaveFocus();
|
|
182
|
+
await thirdItem.trigger('keydown', { code: ARROW_DOWN });
|
|
183
|
+
expect(thirdItem.element).toHaveFocus();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should move the focus up the list of items on `arrow up` and stop on the first item', async () => {
|
|
187
|
+
await firstItem.trigger('keydown', { code: ARROW_DOWN });
|
|
188
|
+
await secondItem.trigger('keydown', { code: ARROW_DOWN });
|
|
189
|
+
expect(thirdItem.element).toHaveFocus();
|
|
190
|
+
await thirdItem.trigger('keydown', { code: ARROW_UP });
|
|
191
|
+
expect(secondItem.element).toHaveFocus();
|
|
192
|
+
await secondItem.trigger('keydown', { code: ARROW_UP });
|
|
193
|
+
expect(firstItem.element).toHaveFocus();
|
|
194
|
+
await firstItem.trigger('keydown', { code: ARROW_UP });
|
|
195
|
+
expect(firstItem.element).toHaveFocus();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should move focus to the last item on `END` keydown', async () => {
|
|
199
|
+
expect(firstItem.element).toHaveFocus();
|
|
200
|
+
await firstItem.trigger('keydown', { code: END });
|
|
201
|
+
expect(thirdItem.element).toHaveFocus();
|
|
202
|
+
await thirdItem.trigger('keydown', { code: END });
|
|
203
|
+
expect(thirdItem.element).toHaveFocus();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should move focus to the first item on `HOME` keydown', async () => {
|
|
207
|
+
await firstItem.trigger('keydown', { code: ARROW_DOWN });
|
|
208
|
+
await secondItem.trigger('keydown', { code: ARROW_DOWN });
|
|
209
|
+
expect(thirdItem.element).toHaveFocus();
|
|
210
|
+
await thirdItem.trigger('keydown', { code: HOME });
|
|
211
|
+
expect(firstItem.element).toHaveFocus();
|
|
212
|
+
await thirdItem.trigger('keydown', { code: HOME });
|
|
213
|
+
expect(firstItem.element).toHaveFocus();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('when the header slot content is provided', () => {
|
|
218
|
+
const headerContent = 'Header Content';
|
|
219
|
+
const slots = { header: headerContent };
|
|
220
|
+
|
|
221
|
+
it('renders it', () => {
|
|
222
|
+
buildWrapper({}, slots);
|
|
223
|
+
expect(wrapper.text()).toContain(headerContent);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('when the footer slot content is provided', () => {
|
|
228
|
+
const footerContent = 'Footer Content';
|
|
229
|
+
const slots = { footer: footerContent };
|
|
230
|
+
|
|
231
|
+
it('renders it', () => {
|
|
232
|
+
buildWrapper({}, slots);
|
|
233
|
+
expect(wrapper.text()).toContain(footerContent);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buttonCategoryOptions,
|
|
3
|
+
buttonSizeOptions,
|
|
4
|
+
buttonVariantOptions,
|
|
5
|
+
} from '../../../../utils/constants';
|
|
6
|
+
import {
|
|
7
|
+
GlIcon,
|
|
8
|
+
GlListbox,
|
|
9
|
+
GlSearchBoxByType,
|
|
10
|
+
GlButtonGroup,
|
|
11
|
+
GlButton,
|
|
12
|
+
GlAvatar,
|
|
13
|
+
} from '../../../../index';
|
|
14
|
+
import { makeContainer } from '../../../../utils/story_decorators/container';
|
|
15
|
+
import readme from './listbox.md';
|
|
16
|
+
|
|
17
|
+
const defaultValue = (prop) => GlListbox.props[prop].default;
|
|
18
|
+
|
|
19
|
+
const defaultItems = [
|
|
20
|
+
{
|
|
21
|
+
value: 'prod',
|
|
22
|
+
text: 'Product',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: 'ppl',
|
|
26
|
+
text: 'People',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: 'fin',
|
|
30
|
+
text: 'Finance',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: 'leg',
|
|
34
|
+
text: 'Legal',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: 'eng',
|
|
38
|
+
text: 'Engineering',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
value: 'sales',
|
|
42
|
+
text: 'Sales',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
value: 'marketing',
|
|
46
|
+
text: 'Marketing',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
value: 'acc',
|
|
50
|
+
text: 'Accounting',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
value: 'hr',
|
|
54
|
+
text: 'Human Resource Management',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
value: 'rnd',
|
|
58
|
+
text: 'Research and Development',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
value: 'cust',
|
|
62
|
+
text: 'Customer Service',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
value: 'sup',
|
|
66
|
+
text: 'Support',
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
const generateProps = ({
|
|
70
|
+
items = defaultItems,
|
|
71
|
+
category = defaultValue('category'),
|
|
72
|
+
variant = defaultValue('variant'),
|
|
73
|
+
size = defaultValue('size'),
|
|
74
|
+
disabled = defaultValue('disabled'),
|
|
75
|
+
loading = defaultValue('loading'),
|
|
76
|
+
noCaret = defaultValue('noCaret'),
|
|
77
|
+
right = defaultValue('right'),
|
|
78
|
+
toggleText,
|
|
79
|
+
textSrOnly = defaultValue('textSrOnly'),
|
|
80
|
+
icon = '',
|
|
81
|
+
multiple = defaultValue('multiple'),
|
|
82
|
+
ariaLabelledby,
|
|
83
|
+
} = {}) => ({
|
|
84
|
+
items,
|
|
85
|
+
category,
|
|
86
|
+
variant,
|
|
87
|
+
size,
|
|
88
|
+
disabled,
|
|
89
|
+
loading,
|
|
90
|
+
noCaret,
|
|
91
|
+
right,
|
|
92
|
+
toggleText,
|
|
93
|
+
textSrOnly,
|
|
94
|
+
icon,
|
|
95
|
+
multiple,
|
|
96
|
+
ariaLabelledby,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function openListbox(component) {
|
|
100
|
+
component.$nextTick(() => component.$el.querySelector('.dropdown-toggle').click());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const template = (content, label = '') => `
|
|
104
|
+
<div>
|
|
105
|
+
${label}
|
|
106
|
+
<br/>
|
|
107
|
+
<gl-listbox
|
|
108
|
+
v-model="selected"
|
|
109
|
+
:items="items"
|
|
110
|
+
:category="category"
|
|
111
|
+
:variant="variant"
|
|
112
|
+
:size="size"
|
|
113
|
+
:disabled="disabled"
|
|
114
|
+
:loading="loading"
|
|
115
|
+
:no-caret="noCaret"
|
|
116
|
+
:right="right"
|
|
117
|
+
:toggle-text="toggleText"
|
|
118
|
+
:text-sr-only="textSrOnly"
|
|
119
|
+
:icon="icon"
|
|
120
|
+
:multiple="multiple"
|
|
121
|
+
:aria-labelledby="ariaLabelledby"
|
|
122
|
+
>
|
|
123
|
+
${content}
|
|
124
|
+
</gl-listbox>
|
|
125
|
+
</div>
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
export const Default = (args, { argTypes }) => ({
|
|
129
|
+
props: Object.keys(argTypes),
|
|
130
|
+
components: {
|
|
131
|
+
GlListbox,
|
|
132
|
+
},
|
|
133
|
+
data() {
|
|
134
|
+
return {
|
|
135
|
+
selected: defaultItems[1].value,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
mounted() {
|
|
139
|
+
openListbox(this);
|
|
140
|
+
},
|
|
141
|
+
template: template('', `<span class="gl-my-0" id="listbox-label">Select a department</span>`),
|
|
142
|
+
});
|
|
143
|
+
Default.args = generateProps({ ariaLabelledby: 'listbox-label' });
|
|
144
|
+
Default.decorators = [makeContainer({ height: '150px' })];
|
|
145
|
+
|
|
146
|
+
export const HeaderAndFooter = (args, { argTypes }) => ({
|
|
147
|
+
props: Object.keys(argTypes),
|
|
148
|
+
components: {
|
|
149
|
+
GlListbox,
|
|
150
|
+
GlSearchBoxByType,
|
|
151
|
+
GlButtonGroup,
|
|
152
|
+
GlButton,
|
|
153
|
+
},
|
|
154
|
+
data() {
|
|
155
|
+
return {
|
|
156
|
+
selected: [],
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
mounted() {
|
|
160
|
+
openListbox(this);
|
|
161
|
+
},
|
|
162
|
+
methods: {
|
|
163
|
+
selectItem(index) {
|
|
164
|
+
this.selected.push(defaultItems[index].value);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
template: template(`<template #header>
|
|
168
|
+
<gl-search-box-by-type/>
|
|
169
|
+
</template>
|
|
170
|
+
<template #footer>
|
|
171
|
+
<div class="gl-border-t-solid gl-border-t-1 gl-border-t-gray-100 gl-display-flex gl-justify-content-center gl-p-3">
|
|
172
|
+
<gl-button-group :vertical="false">
|
|
173
|
+
<gl-button @click="selectItem(0)">1st</gl-button>
|
|
174
|
+
<gl-button @click="selectItem(1)">2nd</gl-button>
|
|
175
|
+
<gl-button @click="selectItem(2)">3rd</gl-button>
|
|
176
|
+
</gl-button-group>
|
|
177
|
+
</div>
|
|
178
|
+
</template>`),
|
|
179
|
+
});
|
|
180
|
+
HeaderAndFooter.args = generateProps({ toggleText: 'Header and Footer', multiple: true });
|
|
181
|
+
HeaderAndFooter.decorators = [makeContainer({ height: '220px' })];
|
|
182
|
+
|
|
183
|
+
export const CustomListItem = (args, { argTypes }) => ({
|
|
184
|
+
props: Object.keys(argTypes),
|
|
185
|
+
data() {
|
|
186
|
+
return {
|
|
187
|
+
selected: ['mikegreiling'],
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
components: {
|
|
191
|
+
GlListbox,
|
|
192
|
+
GlIcon,
|
|
193
|
+
GlAvatar,
|
|
194
|
+
},
|
|
195
|
+
mounted() {
|
|
196
|
+
openListbox(this);
|
|
197
|
+
},
|
|
198
|
+
computed: {
|
|
199
|
+
headerText() {
|
|
200
|
+
return this.selected.length !== 1
|
|
201
|
+
? `${this.selected.length} assignees`
|
|
202
|
+
: this.items.find(({ value }) => value === this.selected[0]).text;
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
template: `
|
|
206
|
+
<gl-listbox
|
|
207
|
+
v-model="selected"
|
|
208
|
+
:items="items"
|
|
209
|
+
:category="category"
|
|
210
|
+
:variant="variant"
|
|
211
|
+
:size="size"
|
|
212
|
+
:disabled="disabled"
|
|
213
|
+
:loading="loading"
|
|
214
|
+
:no-caret="noCaret"
|
|
215
|
+
:right="right"
|
|
216
|
+
:toggle-text="headerText"
|
|
217
|
+
:text-sr-only="textSrOnly"
|
|
218
|
+
:icon="icon"
|
|
219
|
+
:multiple="multiple"
|
|
220
|
+
:aria-labelledby="ariaLabelledby"
|
|
221
|
+
>
|
|
222
|
+
<template #list-item="{ item }">
|
|
223
|
+
<span class="gl-display-flex gl-align-items-center">
|
|
224
|
+
<gl-avatar :size="32" class-="gl-mr-3"/>
|
|
225
|
+
<span class="gl-display-flex gl-flex-direction-column">
|
|
226
|
+
<span class="gl-font-weight-bold gl-white-space-nowrap">{{ item.text }}</span>
|
|
227
|
+
<span class="gl-text-gray-400"> {{ item.secondaryText }}</span>
|
|
228
|
+
</span>
|
|
229
|
+
</span>
|
|
230
|
+
</template>
|
|
231
|
+
</gl-listbox>
|
|
232
|
+
`,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
CustomListItem.args = generateProps({
|
|
236
|
+
items: [
|
|
237
|
+
{ value: 'mikegreiling', text: 'Mike Greiling', secondaryText: '@mikegreiling', icon: 'foo' },
|
|
238
|
+
{ value: 'ohoral', text: 'Olena Horal-Koretska', secondaryText: '@ohoral', icon: 'bar' },
|
|
239
|
+
{ value: 'markian', text: 'Mark Florian', secondaryText: '@markian', icon: 'bin' },
|
|
240
|
+
],
|
|
241
|
+
multiple: true,
|
|
242
|
+
});
|
|
243
|
+
CustomListItem.decorators = [makeContainer({ height: '200px' })];
|
|
244
|
+
|
|
245
|
+
export default {
|
|
246
|
+
title: 'base/new-dropdowns/listbox',
|
|
247
|
+
component: GlListbox,
|
|
248
|
+
parameters: {
|
|
249
|
+
knobs: { disable: true },
|
|
250
|
+
docs: {
|
|
251
|
+
description: {
|
|
252
|
+
component: readme,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
argTypes: {
|
|
257
|
+
category: {
|
|
258
|
+
control: {
|
|
259
|
+
type: 'select',
|
|
260
|
+
options: buttonCategoryOptions,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
variant: {
|
|
264
|
+
control: {
|
|
265
|
+
type: 'select',
|
|
266
|
+
options: buttonVariantOptions,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
size: {
|
|
270
|
+
control: {
|
|
271
|
+
type: 'select',
|
|
272
|
+
options: buttonSizeOptions,
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
};
|