@gitlab/ui 78.2.3 → 78.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 +19 -0
- package/dist/components/base/breadcrumb/breadcrumb.js +103 -29
- package/dist/components/base/new_dropdowns/disclosure/disclosure_dropdown.js +1 -1
- package/dist/components/experimental/duo/chat/duo_chat.js +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/tokens/css/tokens.css +1 -1
- package/dist/tokens/css/tokens.dark.css +1 -1
- package/dist/tokens/js/tokens.dark.js +1 -1
- package/dist/tokens/js/tokens.js +1 -1
- package/dist/tokens/scss/_tokens.dark.scss +1 -1
- package/dist/tokens/scss/_tokens.scss +1 -1
- package/package.json +5 -5
- package/src/components/base/breadcrumb/breadcrumb.scss +3 -6
- package/src/components/base/breadcrumb/breadcrumb.spec.js +74 -42
- package/src/components/base/breadcrumb/breadcrumb.stories.js +18 -1
- package/src/components/base/breadcrumb/breadcrumb.vue +133 -63
- package/src/components/base/drawer/drawer.stories.js +9 -4
- package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue +1 -1
- package/src/components/experimental/duo/chat/duo_chat.spec.js +11 -0
- package/src/components/experimental/duo/chat/duo_chat.vue +1 -1
- package/translations.json +1 -0
package/dist/tokens/js/tokens.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "78.
|
|
3
|
+
"version": "78.4.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -93,11 +93,11 @@
|
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@arkweid/lefthook": "0.7.7",
|
|
96
|
-
"@babel/core": "^7.24.
|
|
96
|
+
"@babel/core": "^7.24.3",
|
|
97
97
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
|
98
98
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
|
99
|
-
"@babel/preset-env": "^7.24.
|
|
100
|
-
"@babel/preset-react": "^7.
|
|
99
|
+
"@babel/preset-env": "^7.24.3",
|
|
100
|
+
"@babel/preset-react": "^7.24.1",
|
|
101
101
|
"@cypress/grep": "^4.0.1",
|
|
102
102
|
"@gitlab/eslint-plugin": "19.4.0",
|
|
103
103
|
"@gitlab/fonts": "^1.3.0",
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"sass-true": "^6.1.0",
|
|
172
172
|
"start-server-and-test": "^1.10.6",
|
|
173
173
|
"storybook": "^7.6.17",
|
|
174
|
-
"storybook-dark-mode": "4.0.
|
|
174
|
+
"storybook-dark-mode": "4.0.1",
|
|
175
175
|
"style-dictionary": "^3.8.0",
|
|
176
176
|
"stylelint": "15.10.2",
|
|
177
177
|
"tailwind-config-viewer": "1.7.3",
|
|
@@ -11,15 +11,15 @@ $breadcrumb-max-width: $grid-size * 16;
|
|
|
11
11
|
@include gl-align-items-center;
|
|
12
12
|
@include gl-line-height-normal;
|
|
13
13
|
@include gl-m-0;
|
|
14
|
-
@include
|
|
15
|
-
|
|
16
|
-
}
|
|
14
|
+
@include gl-flex-nowrap;
|
|
15
|
+
@include gl-max-w-full;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
// bootstrap overrides
|
|
20
19
|
.gl-breadcrumb-item {
|
|
21
20
|
@include gl-font-sm;
|
|
22
21
|
@include gl-line-height-normal;
|
|
22
|
+
@include gl-flex-shrink-0;
|
|
23
23
|
|
|
24
24
|
&:not(:last-child)::after {
|
|
25
25
|
@include gl-text-gray-200;
|
|
@@ -29,9 +29,6 @@ $breadcrumb-max-width: $grid-size * 16;
|
|
|
29
29
|
|
|
30
30
|
> a {
|
|
31
31
|
@include gl-text-gray-700;
|
|
32
|
-
@include media-breakpoint-down(xs) {
|
|
33
|
-
@include str-truncated($breadcrumb-max-width);
|
|
34
|
-
}
|
|
35
32
|
|
|
36
33
|
&:active,
|
|
37
34
|
&:focus,
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { nextTick } from 'vue';
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
3
2
|
import avatarPath1 from '../../../../static/img/avatar.png';
|
|
4
3
|
import avatarPath3 from '../../../../static/img/avatar_1.png';
|
|
5
|
-
import
|
|
4
|
+
import GlDisclosureDropdown from '../new_dropdowns/disclosure/disclosure_dropdown.vue';
|
|
5
|
+
import GlDisclosureDropdownItem from '../new_dropdowns/disclosure/disclosure_dropdown_item.vue';
|
|
6
|
+
import GlBreadcrumb from './breadcrumb.vue';
|
|
6
7
|
import GlBreadcrumbItem from './breadcrumb_item.vue';
|
|
7
8
|
|
|
8
9
|
describe('Breadcrumb component', () => {
|
|
9
10
|
let wrapper;
|
|
10
11
|
|
|
11
12
|
const items = [
|
|
12
|
-
{
|
|
13
|
+
{
|
|
14
|
+
text: 'first_breadcrumb',
|
|
15
|
+
href: 'https://gitlab.com',
|
|
16
|
+
avatarPath: avatarPath1,
|
|
17
|
+
},
|
|
13
18
|
{
|
|
14
19
|
text: 'second_breadcrumb',
|
|
15
20
|
to: 'to_value',
|
|
@@ -21,41 +26,44 @@ describe('Breadcrumb component', () => {
|
|
|
21
26
|
},
|
|
22
27
|
];
|
|
23
28
|
|
|
24
|
-
const extraItems = [
|
|
25
|
-
{ text: 'fourth_breadcrumb', href: 'https://gitlab.com' },
|
|
26
|
-
{
|
|
27
|
-
text: 'fifth_breadcrumb',
|
|
28
|
-
to: 'to_value',
|
|
29
|
-
},
|
|
30
|
-
];
|
|
31
|
-
|
|
32
29
|
const findAllAvatars = () => wrapper.findAll('[data-testid="avatar"]');
|
|
33
30
|
const findBreadcrumbItems = () => wrapper.findAllComponents(GlBreadcrumbItem);
|
|
34
|
-
const
|
|
31
|
+
const findOverflowDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
|
|
35
32
|
|
|
36
33
|
const findVisibleBreadcrumbItems = () =>
|
|
37
34
|
findBreadcrumbItems().wrappers.filter((item) => item.isVisible());
|
|
38
|
-
const findHiddenBreadcrumbItems = () =>
|
|
39
|
-
findBreadcrumbItems().wrappers.filter((item) => !item.isVisible());
|
|
40
35
|
|
|
41
36
|
const createComponent = (propsData = { items }) => {
|
|
42
|
-
wrapper =
|
|
37
|
+
wrapper = mount(GlBreadcrumb, {
|
|
43
38
|
propsData,
|
|
44
39
|
stubs: {
|
|
45
40
|
GlBreadcrumbItem,
|
|
41
|
+
GlDisclosureDropdown,
|
|
46
42
|
},
|
|
47
43
|
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const mockWrapperWidth = (widthInPx) => {
|
|
47
|
+
wrapper.element.style.width = `${widthInPx}px`;
|
|
48
|
+
|
|
49
|
+
Object.defineProperty(wrapper.element, 'clientWidth', {
|
|
50
|
+
get: () => widthInPx,
|
|
51
|
+
configurable: true,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
const mockWideWrapperWidth = () => {
|
|
56
|
+
mockWrapperWidth(1000);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const mockSmallWrapperWidth = () => {
|
|
60
|
+
mockWrapperWidth(1);
|
|
54
61
|
};
|
|
55
62
|
|
|
56
63
|
describe('items', () => {
|
|
57
|
-
it('has one breadcrumb-item for each item in the items props', () => {
|
|
64
|
+
it('has one breadcrumb-item for each item in the items props', async () => {
|
|
58
65
|
createComponent();
|
|
66
|
+
await wrapper.vm.$nextTick();
|
|
59
67
|
|
|
60
68
|
expect(findBreadcrumbItems()).toHaveLength(items.length);
|
|
61
69
|
});
|
|
@@ -75,9 +83,36 @@ describe('Breadcrumb component', () => {
|
|
|
75
83
|
});
|
|
76
84
|
});
|
|
77
85
|
|
|
86
|
+
describe('showMoreLabel', () => {
|
|
87
|
+
describe('when provided', () => {
|
|
88
|
+
beforeEach(async () => {
|
|
89
|
+
createComponent({ items, showMoreLabel: 'More...' });
|
|
90
|
+
mockSmallWrapperWidth();
|
|
91
|
+
await wrapper.vm.$nextTick();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('uses prop', () => {
|
|
95
|
+
expect(findOverflowDropdown().props('toggleText')).toBe('More...');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('when not provided', () => {
|
|
100
|
+
beforeEach(async () => {
|
|
101
|
+
createComponent();
|
|
102
|
+
mockSmallWrapperWidth();
|
|
103
|
+
await wrapper.vm.$nextTick();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('uses default', () => {
|
|
107
|
+
expect(findOverflowDropdown().props('toggleText')).toBe('Show more breadcrumbs');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
78
112
|
describe('avatars', () => {
|
|
79
|
-
it('renders 2 avatars when 2 avatarPaths are passed', () => {
|
|
113
|
+
it('renders 2 avatars when 2 avatarPaths are passed', async () => {
|
|
80
114
|
createComponent();
|
|
115
|
+
await wrapper.vm.$nextTick();
|
|
81
116
|
|
|
82
117
|
expect(findAllAvatars()).toHaveLength(2);
|
|
83
118
|
});
|
|
@@ -86,6 +121,7 @@ describe('Breadcrumb component', () => {
|
|
|
86
121
|
describe('bindings', () => {
|
|
87
122
|
beforeEach(() => {
|
|
88
123
|
createComponent();
|
|
124
|
+
mockWideWrapperWidth();
|
|
89
125
|
});
|
|
90
126
|
|
|
91
127
|
it('first breadcrumb has text, href && ariaCurrent=`false` bound', () => {
|
|
@@ -114,12 +150,14 @@ describe('Breadcrumb component', () => {
|
|
|
114
150
|
});
|
|
115
151
|
|
|
116
152
|
describe('collapsible', () => {
|
|
117
|
-
describe(`when
|
|
153
|
+
describe(`when there is enough room to fit all items`, () => {
|
|
118
154
|
beforeEach(() => {
|
|
119
155
|
createComponent();
|
|
156
|
+
mockWideWrapperWidth();
|
|
120
157
|
});
|
|
158
|
+
|
|
121
159
|
it('should not display collapsed list expander', () => {
|
|
122
|
-
expect(
|
|
160
|
+
expect(findOverflowDropdown().exists()).toBe(false);
|
|
123
161
|
});
|
|
124
162
|
|
|
125
163
|
it('should display all items visible', () => {
|
|
@@ -127,27 +165,21 @@ describe('Breadcrumb component', () => {
|
|
|
127
165
|
});
|
|
128
166
|
});
|
|
129
167
|
|
|
130
|
-
describe(`when
|
|
131
|
-
beforeEach(() => {
|
|
132
|
-
createComponent(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
expect(findCollapsedListExpander().exists()).toBe(true);
|
|
168
|
+
describe(`when there is NOT enough room to fit all items`, () => {
|
|
169
|
+
beforeEach(async () => {
|
|
170
|
+
createComponent();
|
|
171
|
+
mockSmallWrapperWidth();
|
|
172
|
+
await wrapper.vm.$nextTick();
|
|
136
173
|
});
|
|
137
174
|
|
|
138
|
-
it('should display
|
|
139
|
-
|
|
140
|
-
expect(findVisibleBreadcrumbItems()).toHaveLength(alwaysVisibleNum);
|
|
141
|
-
expect(findHiddenBreadcrumbItems()).toHaveLength(
|
|
142
|
-
items.length + extraItems.length - alwaysVisibleNum
|
|
143
|
-
);
|
|
175
|
+
it('should display overflow dropdown', () => {
|
|
176
|
+
expect(findOverflowDropdown().exists()).toBe(true);
|
|
144
177
|
});
|
|
145
178
|
|
|
146
|
-
it('
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
expect(
|
|
150
|
-
expect(findVisibleBreadcrumbItems()).toHaveLength(items.length + extraItems.length);
|
|
179
|
+
it('moves the overflowing items into the dropdown', () => {
|
|
180
|
+
const fittingItems = findBreadcrumbItems().length;
|
|
181
|
+
const overflowingItems = wrapper.findAllComponents(GlDisclosureDropdownItem).length;
|
|
182
|
+
expect(fittingItems + overflowingItems).toEqual(items.length);
|
|
151
183
|
});
|
|
152
184
|
});
|
|
153
185
|
});
|
|
@@ -10,6 +10,15 @@ const template = `
|
|
|
10
10
|
/>
|
|
11
11
|
`;
|
|
12
12
|
|
|
13
|
+
const collapsedTemplate = `
|
|
14
|
+
<div style="max-width: 300px">
|
|
15
|
+
<gl-breadcrumb
|
|
16
|
+
:items="items"
|
|
17
|
+
:aria-label="ariaLabel"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
`;
|
|
21
|
+
|
|
13
22
|
const defaultItems = [
|
|
14
23
|
{
|
|
15
24
|
text: 'First item',
|
|
@@ -45,6 +54,14 @@ const Template = (args, { argTypes }) => ({
|
|
|
45
54
|
export const Default = Template.bind({});
|
|
46
55
|
Default.args = generateProps();
|
|
47
56
|
|
|
57
|
+
const CollapsedTemplate = (args, { argTypes }) => ({
|
|
58
|
+
components: {
|
|
59
|
+
GlBreadcrumb,
|
|
60
|
+
},
|
|
61
|
+
props: Object.keys(argTypes),
|
|
62
|
+
template: collapsedTemplate,
|
|
63
|
+
});
|
|
64
|
+
|
|
48
65
|
export default {
|
|
49
66
|
title: 'base/breadcrumb',
|
|
50
67
|
component: GlBreadcrumb,
|
|
@@ -77,5 +94,5 @@ const extraItems = [
|
|
|
77
94
|
},
|
|
78
95
|
];
|
|
79
96
|
|
|
80
|
-
export const CollapsedItems =
|
|
97
|
+
export const CollapsedItems = CollapsedTemplate.bind({});
|
|
81
98
|
CollapsedItems.args = generateProps({ items: [...defaultItems, ...extraItems] });
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
<!-- eslint-disable vue/multi-word-component-names -->
|
|
2
2
|
<script>
|
|
3
3
|
import { BBreadcrumb } from 'bootstrap-vue';
|
|
4
|
-
import
|
|
4
|
+
import debounce from 'lodash/debounce';
|
|
5
|
+
import { translate } from '../../../utils/i18n';
|
|
5
6
|
import GlAvatar from '../avatar/avatar.vue';
|
|
7
|
+
import GlDisclosureDropdown from '../new_dropdowns/disclosure/disclosure_dropdown.vue';
|
|
6
8
|
import { GlTooltipDirective } from '../../../directives/tooltip';
|
|
7
9
|
import GlBreadcrumbItem from './breadcrumb_item.vue';
|
|
8
10
|
|
|
9
|
-
export const COLLAPSE_AT_SIZE = 4;
|
|
10
|
-
|
|
11
11
|
export default {
|
|
12
12
|
name: 'GlBreadcrumb',
|
|
13
13
|
components: {
|
|
14
14
|
BBreadcrumb,
|
|
15
|
-
GlButton,
|
|
16
15
|
GlBreadcrumbItem,
|
|
17
16
|
GlAvatar,
|
|
17
|
+
GlDisclosureDropdown,
|
|
18
18
|
},
|
|
19
19
|
directives: {
|
|
20
20
|
GlTooltip: GlTooltipDirective,
|
|
@@ -40,47 +40,118 @@ export default {
|
|
|
40
40
|
required: false,
|
|
41
41
|
default: 'Breadcrumb',
|
|
42
42
|
},
|
|
43
|
+
/**
|
|
44
|
+
* The label for the collapsed dropdown toggle. Screen-reader only.
|
|
45
|
+
*/
|
|
46
|
+
showMoreLabel: {
|
|
47
|
+
type: String,
|
|
48
|
+
required: false,
|
|
49
|
+
default: () => translate('GlBreadcrumb.showMoreLabel', 'Show more breadcrumbs'),
|
|
50
|
+
},
|
|
43
51
|
},
|
|
44
52
|
data() {
|
|
45
53
|
return {
|
|
46
|
-
|
|
54
|
+
fittingItems: [...this.items], // array of items that fit on the screen
|
|
55
|
+
overflowingItems: [], // array of items that didn't fit and were put in a dropdown instead
|
|
56
|
+
totalBreadcrumbsWidth: 0, // the total width of all breadcrumb items combined
|
|
57
|
+
widthPerItem: [], // array with the indivudal widths of each breadcrumb item
|
|
58
|
+
resizeDone: false, // to apply some CSS only during/after resizing
|
|
47
59
|
};
|
|
48
60
|
},
|
|
49
61
|
computed: {
|
|
50
|
-
breadcrumbsSize() {
|
|
51
|
-
return this.items.length;
|
|
52
|
-
},
|
|
53
62
|
hasCollapsible() {
|
|
54
|
-
return this.
|
|
63
|
+
return this.overflowingItems.length > 0;
|
|
55
64
|
},
|
|
56
|
-
|
|
57
|
-
return
|
|
65
|
+
breadcrumbStyle() {
|
|
66
|
+
return this.resizeDone ? {} : { opacity: 0 };
|
|
58
67
|
},
|
|
68
|
+
itemStyle() {
|
|
69
|
+
/**
|
|
70
|
+
* If the last/only item, which is always visible, has a very long title,
|
|
71
|
+
* it could overflow the breadcrumb component. This CSS makes sure it
|
|
72
|
+
* shows an ellipsis instead.
|
|
73
|
+
* But this CSS cannot be active while we do the size calculation, as that
|
|
74
|
+
* would then not take the real unshrunk width of that item into account.
|
|
75
|
+
*/
|
|
76
|
+
if (this.resizeDone && this.fittingItems.length === 1) {
|
|
77
|
+
return {
|
|
78
|
+
'flex-shrink': 1,
|
|
79
|
+
'text-overflow': 'ellipsis',
|
|
80
|
+
'overflow-x': 'hidden',
|
|
81
|
+
'text-wrap': 'nowrap',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {};
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
watch: {
|
|
88
|
+
items: {
|
|
89
|
+
handler: 'measureAndMakeBreadcrumbsFit',
|
|
90
|
+
deep: true,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
created() {
|
|
94
|
+
this.debounceMakeBreadcrumbsFit = debounce(this.makeBreadcrumbsFit, 25);
|
|
95
|
+
},
|
|
96
|
+
mounted() {
|
|
97
|
+
window.addEventListener('resize', this.debounceMakeBreadcrumbsFit);
|
|
98
|
+
this.measureAndMakeBreadcrumbsFit();
|
|
99
|
+
},
|
|
100
|
+
beforeDestroy() {
|
|
101
|
+
window.removeEventListener('resize', this.debounceMakeBreadcrumbsFit);
|
|
59
102
|
},
|
|
60
103
|
methods: {
|
|
61
|
-
|
|
62
|
-
|
|
104
|
+
resetItems() {
|
|
105
|
+
this.fittingItems = [...this.items];
|
|
106
|
+
this.overflowingItems = [];
|
|
63
107
|
},
|
|
64
|
-
|
|
65
|
-
|
|
108
|
+
async measureAndMakeBreadcrumbsFit() {
|
|
109
|
+
this.resizeDone = false;
|
|
110
|
+
this.resetItems();
|
|
111
|
+
|
|
112
|
+
// Wait for DOM update so all items get rendered and can be measured.
|
|
113
|
+
await this.$nextTick();
|
|
114
|
+
|
|
115
|
+
this.totalBreadcrumbsWidth = 0;
|
|
116
|
+
this.$refs.breadcrumbs.forEach((b, index) => {
|
|
117
|
+
const width = b.$el.clientWidth;
|
|
118
|
+
this.totalBreadcrumbsWidth += width;
|
|
119
|
+
this.widthPerItem[index] = width;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
this.makeBreadcrumbsFit();
|
|
66
123
|
},
|
|
67
|
-
|
|
68
|
-
this.
|
|
124
|
+
makeBreadcrumbsFit() {
|
|
125
|
+
this.resizeDone = false;
|
|
126
|
+
this.resetItems();
|
|
127
|
+
|
|
128
|
+
const containerWidth = this.$el.clientWidth;
|
|
129
|
+
const buttonWidth = 50; // px
|
|
130
|
+
|
|
131
|
+
if (this.totalBreadcrumbsWidth + buttonWidth > containerWidth) {
|
|
132
|
+
// Not all breadcrumb items fit. Start moving items over to the dropdown.
|
|
133
|
+
const startSlicingAt = 0;
|
|
69
134
|
|
|
70
|
-
|
|
71
|
-
this
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
135
|
+
// The last item will never be moved into the dropdown.
|
|
136
|
+
const stopSlicingAt = this.items.length - 1;
|
|
137
|
+
|
|
138
|
+
let widthNeeded = this.totalBreadcrumbsWidth;
|
|
139
|
+
for (let index = startSlicingAt; index < stopSlicingAt; index += 1) {
|
|
140
|
+
// Move one breadcrumb item into the dropdown
|
|
141
|
+
this.overflowingItems.push(this.fittingItems[startSlicingAt]);
|
|
142
|
+
this.fittingItems.splice(startSlicingAt, 1);
|
|
143
|
+
|
|
144
|
+
widthNeeded -= this.widthPerItem[index];
|
|
145
|
+
|
|
146
|
+
// Does it fit now? Then stop.
|
|
147
|
+
if (widthNeeded + buttonWidth < containerWidth) break;
|
|
148
|
+
}
|
|
75
149
|
}
|
|
150
|
+
|
|
151
|
+
this.resizeDone = true;
|
|
76
152
|
},
|
|
77
|
-
|
|
78
|
-
return index ===
|
|
79
|
-
},
|
|
80
|
-
isItemCollapsed(index) {
|
|
81
|
-
return (
|
|
82
|
-
this.hasCollapsible && this.isListCollapsed && !this.nonCollapsibleIndices.includes(index)
|
|
83
|
-
);
|
|
153
|
+
isLastItem(index) {
|
|
154
|
+
return index === this.fittingItems.length - 1;
|
|
84
155
|
},
|
|
85
156
|
getAriaCurrentAttr(index) {
|
|
86
157
|
return this.isLastItem(index) ? 'page' : false;
|
|
@@ -89,42 +160,41 @@ export default {
|
|
|
89
160
|
};
|
|
90
161
|
</script>
|
|
91
162
|
<template>
|
|
92
|
-
<nav class="gl-breadcrumbs" :aria-label="ariaLabel">
|
|
163
|
+
<nav class="gl-breadcrumbs" :aria-label="ariaLabel" :style="breadcrumbStyle">
|
|
93
164
|
<b-breadcrumb class="gl-breadcrumb-list" v-bind="$attrs" v-on="$listeners">
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
aria-hidden="true"
|
|
108
|
-
class="gl-breadcrumb-avatar-tile gl-border gl-mr-2 gl-rounded-base!"
|
|
109
|
-
shape="rect"
|
|
110
|
-
data-testid="avatar"
|
|
111
|
-
/><span>{{ item.text }}</span>
|
|
112
|
-
</gl-breadcrumb-item>
|
|
165
|
+
<li v-if="hasCollapsible" class="gl-breadcrumb-item">
|
|
166
|
+
<gl-disclosure-dropdown
|
|
167
|
+
:items="overflowingItems"
|
|
168
|
+
:toggle-text="showMoreLabel"
|
|
169
|
+
fluid-width
|
|
170
|
+
text-sr-only
|
|
171
|
+
no-caret
|
|
172
|
+
icon="ellipsis_h"
|
|
173
|
+
size="small"
|
|
174
|
+
style="height: 16px"
|
|
175
|
+
placement="left"
|
|
176
|
+
/>
|
|
177
|
+
</li>
|
|
113
178
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
179
|
+
<!-- eslint-disable-next-line vue/valid-v-for (for @vue/compat) -->
|
|
180
|
+
<gl-breadcrumb-item
|
|
181
|
+
v-for="(item, index) in fittingItems"
|
|
182
|
+
ref="breadcrumbs"
|
|
183
|
+
:text="item.text"
|
|
184
|
+
:href="item.href"
|
|
185
|
+
:style="itemStyle"
|
|
186
|
+
:to="item.to"
|
|
187
|
+
:aria-current="getAriaCurrentAttr(index)"
|
|
188
|
+
><gl-avatar
|
|
189
|
+
v-if="item.avatarPath"
|
|
190
|
+
:src="item.avatarPath"
|
|
191
|
+
:size="16"
|
|
192
|
+
aria-hidden="true"
|
|
193
|
+
class="gl-breadcrumb-avatar-tile gl-border gl-mr-2 gl-rounded-base!"
|
|
194
|
+
shape="rect"
|
|
195
|
+
data-testid="avatar"
|
|
196
|
+
/><span>{{ item.text }}</span>
|
|
197
|
+
</gl-breadcrumb-item>
|
|
128
198
|
</b-breadcrumb>
|
|
129
199
|
</nav>
|
|
130
200
|
</template>
|
|
@@ -62,14 +62,16 @@ const drawerContent = generateDrawerContent([
|
|
|
62
62
|
|
|
63
63
|
const drawerContentShortList = generateDrawerContent(['One', 'Two', 'Three']);
|
|
64
64
|
|
|
65
|
-
const createSidebarTemplate = (content) => `
|
|
65
|
+
const createSidebarTemplate = (content, { extraBindings = {} } = {}) => `
|
|
66
66
|
<gl-drawer
|
|
67
67
|
:open="open"
|
|
68
68
|
:header-height="headerHeight"
|
|
69
69
|
:header-sticky="headerSticky"
|
|
70
70
|
:z-index="zIndex"
|
|
71
71
|
:variant="variant"
|
|
72
|
-
|
|
72
|
+
${Object.entries(extraBindings)
|
|
73
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
74
|
+
.join('\n')}
|
|
73
75
|
@close="close">${content}</gl-drawer>
|
|
74
76
|
`;
|
|
75
77
|
|
|
@@ -124,10 +126,13 @@ export const Default = (_args, { viewMode }) => ({
|
|
|
124
126
|
template: `
|
|
125
127
|
<div :data-opened-count="timesOpened">
|
|
126
128
|
<gl-button @click="toggle">Toggle Drawer</gl-button>
|
|
127
|
-
${createSidebarTemplate(
|
|
129
|
+
${createSidebarTemplate(
|
|
130
|
+
`
|
|
128
131
|
<template #title>List Settings</template>
|
|
129
132
|
${drawerContent}
|
|
130
|
-
|
|
133
|
+
`,
|
|
134
|
+
{ extraBindings: { '@opened': 'opened' } }
|
|
135
|
+
)}
|
|
131
136
|
</div>`,
|
|
132
137
|
});
|
|
133
138
|
Default.args = generateProps();
|
|
@@ -404,7 +404,7 @@ export default {
|
|
|
404
404
|
<template v-if="isItem(item)">
|
|
405
405
|
<!-- eslint-disable-next-line vue/valid-v-for -->
|
|
406
406
|
<gl-disclosure-dropdown-item :key="uniqueItemId()" :item="item" @action="handleAction">
|
|
407
|
-
<template #list-item>
|
|
407
|
+
<template v-if="'list-item' in $scopedSlots" #list-item>
|
|
408
408
|
<!-- @slot Custom template of the disclosure dropdown item -->
|
|
409
409
|
<slot name="list-item" :item="item"></slot>
|
|
410
410
|
</template>
|
|
@@ -307,6 +307,17 @@ describe('GlDuoChat', () => {
|
|
|
307
307
|
describe('submit', () => {
|
|
308
308
|
const ENTER = 'Enter';
|
|
309
309
|
|
|
310
|
+
it('trims the prompt', () => {
|
|
311
|
+
const question = ' foo bar ';
|
|
312
|
+
const expectedPrompt = 'foo bar';
|
|
313
|
+
createComponent({
|
|
314
|
+
propsData: { isChatAvailable: true, messages: [] },
|
|
315
|
+
});
|
|
316
|
+
setPromptInput(question);
|
|
317
|
+
clickSubmit();
|
|
318
|
+
expect(wrapper.emitted('send-chat-prompt')).toEqual([[expectedPrompt]]);
|
|
319
|
+
});
|
|
320
|
+
|
|
310
321
|
it.each`
|
|
311
322
|
trigger | event | action | expectEmitted
|
|
312
323
|
${() => clickSubmit()} | ${'Submit button click'} | ${'submit'} | ${[[promptStr]]}
|
package/translations.json
CHANGED
|
@@ -4,5 +4,6 @@
|
|
|
4
4
|
"GlSorting.sortDescending": "Sort direction: descending",
|
|
5
5
|
"GlSearchBoxByType.clearButtonTitle": "Clear",
|
|
6
6
|
"GlSearchBoxByType.input.placeholder": "Search",
|
|
7
|
+
"GlBreadcrumb.showMoreLabel": "Show more breadcrumbs",
|
|
7
8
|
"GlCollapsibleListbox.srOnlyResultsLabel": "Results count"
|
|
8
9
|
}
|