@gitlab/ui 37.7.0 → 37.9.1
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 +21 -0
- package/dist/components/base/filtered_search/filtered_search.js +13 -2
- package/dist/components/base/modal/modal.documentation.js +1 -1
- package/dist/components/base/tabs/tabs/tabs.documentation.js +1 -1
- package/dist/components/utilities/truncate/truncate.js +2 -2
- package/package.json +1 -1
- package/src/components/base/filtered_search/filtered_search.spec.js +2 -2
- package/src/components/base/filtered_search/filtered_search.vue +12 -4
- package/src/components/base/modal/modal.md +5 -5
- package/src/components/base/modal/modal.spec.js +2 -2
- package/src/components/base/tabs/tabs/tabs.md +5 -5
- package/src/components/utilities/truncate/truncate.spec.js +49 -14
- package/src/components/utilities/truncate/truncate.stories.js +59 -1
- package/src/components/utilities/truncate/truncate.vue +21 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [37.9.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v37.9.0...v37.9.1) (2022-03-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **GlFilteredSearch:** Keep active state on backspace on first token ([bd241df](https://gitlab.com/gitlab-org/gitlab-ui/commit/bd241dfbbd87064c10a67f0a0857b54542362605))
|
|
7
|
+
|
|
8
|
+
# [37.9.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v37.8.0...v37.9.0) (2022-03-15)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **GlTruncate:** add slots to surround the text ([4a79750](https://gitlab.com/gitlab-org/gitlab-ui/commit/4a797503c85ac755e7dd4df1b2d46e95f3102e04))
|
|
14
|
+
|
|
15
|
+
# [37.8.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v37.7.0...v37.8.0) (2022-03-15)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **GlFilteredSearch:** Allow to pass token dynamically ([247ea01](https://gitlab.com/gitlab-org/gitlab-ui/commit/247ea011487a1ab0087d994cdb994c9c553565eb))
|
|
21
|
+
|
|
1
22
|
# [37.7.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v37.6.0...v37.7.0) (2022-03-14)
|
|
2
23
|
|
|
3
24
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _isEqual from 'lodash/isEqual';
|
|
1
2
|
import _cloneDeep from 'lodash/cloneDeep';
|
|
2
3
|
import PortalVue from 'portal-vue';
|
|
3
4
|
import Vue from 'vue';
|
|
@@ -150,6 +151,16 @@ var script = {
|
|
|
150
151
|
this.$emit('input', this.tokens);
|
|
151
152
|
},
|
|
152
153
|
|
|
154
|
+
deep: true,
|
|
155
|
+
immediate: true
|
|
156
|
+
},
|
|
157
|
+
value: {
|
|
158
|
+
handler(newValue, oldValue) {
|
|
159
|
+
if (newValue.length && !_isEqual(newValue, oldValue)) {
|
|
160
|
+
this.applyNewValue(_cloneDeep(newValue));
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
|
|
153
164
|
deep: true,
|
|
154
165
|
immediate: true
|
|
155
166
|
}
|
|
@@ -230,8 +241,8 @@ var script = {
|
|
|
230
241
|
// possible, make sure no token is active.
|
|
231
242
|
|
|
232
243
|
if (intent === INTENT_ACTIVATE_PREVIOUS) {
|
|
233
|
-
// If there is a previous token, activate it; else,
|
|
234
|
-
this.activeTokenIdx = idx
|
|
244
|
+
// If there is a previous token, activate it; else, activate the first token
|
|
245
|
+
this.activeTokenIdx = Math.max(idx - 1, 0);
|
|
235
246
|
} else if (idx < this.activeTokenIdx) {
|
|
236
247
|
// Preserve the active token's active status (it shifted down one index)
|
|
237
248
|
this.activeTokenIdx -= 1;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var description = "Modals are used to reveal critical information, show information without losing context, or when the\nsystem requires a user response. Modals can also fragment a complex workflow into simpler steps and\nshould serve a single purpose dedicated to completing the user’s task.\n\n## VModel\n\nYou can use the `v-model` directive to control the modal’s visibility. The `v-model`\ndirective interfaces with the `visible` property and the `@change` event.\n\n## Deprecation Warning\n\nWe are deprecating the `modal-ok` and `modal-cancel` slots. We are also changing the way the\n`modal-footer` slot content is populated. This is in order to align this component with the design\nsystem.\n\nThe `modal-footer` slot should only be populated via props: `action-primary`, `action-secondary` and\n`action-cancel`. These props allow you to handle how a primary, secondary and cancel button will\nbehave in the modals footer. The props receive an object as such:\n\n```js\n{\n text: 'Save Changes',\n attributes:
|
|
1
|
+
var description = "Modals are used to reveal critical information, show information without losing context, or when the\nsystem requires a user response. Modals can also fragment a complex workflow into simpler steps and\nshould serve a single purpose dedicated to completing the user’s task.\n\n## VModel\n\nYou can use the `v-model` directive to control the modal’s visibility. The `v-model`\ndirective interfaces with the `visible` property and the `@change` event.\n\n## Deprecation Warning\n\nWe are deprecating the `modal-ok` and `modal-cancel` slots. We are also changing the way the\n`modal-footer` slot content is populated. This is in order to align this component with the design\nsystem.\n\nThe `modal-footer` slot should only be populated via props: `action-primary`, `action-secondary` and\n`action-cancel`. These props allow you to handle how a primary, secondary and cancel button will\nbehave in the modals footer. The props receive an object as such:\n\n```js\n{\n text: 'Save Changes',\n attributes: {\n variant: 'confirm',\n disabled: this.someState,\n class: 'some-class',\n ...\n }\n}\n```\n";
|
|
2
2
|
|
|
3
3
|
var modal_documentation = {
|
|
4
4
|
followsDesignSystem: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var description = "Tabs are used to divide content into meaningful, related sections. Tabs allow users to focus on one\nspecific view at a time while maintaining sight of all the relevant content options available. Each\ntab, when active, will reveal it’s own unique content.\n\n## Using the component Vue\n\n~~~html\n<gl-tabs>\n <gl-tab title=\"Tab 1\">\n Tab panel 1\n </gl-tab>\n <gl-tab title=\"Tab 2\">\n Tab panel 2\n </gl-tab>\n</gl-tabs>\n~~~\n\n## Using the component HTML\n\n~~~html\n<div class=\"tabs gl-tabs\">\n <ul role=\"tablist\" class=\"nav gl-tabs-nav\">\n <li role=\"presentation\" class=\"nav-item\">\n <a\n role=\"tab\"\n target=\"_self\"\n href=\"#\"\n class=\"nav-link gl-tab-nav-item gl-tab-nav-item-active gl-tab-nav-item-active-indigo\"\n >Tab 1</a>\n </li>\n <li role=\"presentation\" class=\"nav-item\">\n <a role=\"tab\" target=\"_self\" href=\"#\" class=\"nav-link gl-tab-nav-item\">Tab 2</a>\n </li>\n </ul>\n <div class=\"tab-content gl-tab-content\">\n <div role=\"tabpanel\" class=\"tab-pane gl-tab-content active\">Tab panel 1</div>\n <div role=\"tabpanel\" class=\"tab-pane gl-tab-content\">Tab panel 2</div>\n </div>\n</div>\n~~~\n\n## Adding Action Buttons to the Tabs\n\nTabs start and end slot can be populated via props: `action-primary`, `action-secondary` and\n`action-tertiary`. These props allow you to handle how a primary, secondary and tertiary button will\nbehave and look. The props receive an object as such:\n\n~~~js\n{\n text: 'Save Changes',\n attributes:
|
|
1
|
+
var description = "Tabs are used to divide content into meaningful, related sections. Tabs allow users to focus on one\nspecific view at a time while maintaining sight of all the relevant content options available. Each\ntab, when active, will reveal it’s own unique content.\n\n## Using the component Vue\n\n~~~html\n<gl-tabs>\n <gl-tab title=\"Tab 1\">\n Tab panel 1\n </gl-tab>\n <gl-tab title=\"Tab 2\">\n Tab panel 2\n </gl-tab>\n</gl-tabs>\n~~~\n\n## Using the component HTML\n\n~~~html\n<div class=\"tabs gl-tabs\">\n <ul role=\"tablist\" class=\"nav gl-tabs-nav\">\n <li role=\"presentation\" class=\"nav-item\">\n <a\n role=\"tab\"\n target=\"_self\"\n href=\"#\"\n class=\"nav-link gl-tab-nav-item gl-tab-nav-item-active gl-tab-nav-item-active-indigo\"\n >Tab 1</a>\n </li>\n <li role=\"presentation\" class=\"nav-item\">\n <a role=\"tab\" target=\"_self\" href=\"#\" class=\"nav-link gl-tab-nav-item\">Tab 2</a>\n </li>\n </ul>\n <div class=\"tab-content gl-tab-content\">\n <div role=\"tabpanel\" class=\"tab-pane gl-tab-content active\">Tab panel 1</div>\n <div role=\"tabpanel\" class=\"tab-pane gl-tab-content\">Tab panel 2</div>\n </div>\n</div>\n~~~\n\n## Adding Action Buttons to the Tabs\n\nTabs start and end slot can be populated via props: `action-primary`, `action-secondary` and\n`action-tertiary`. These props allow you to handle how a primary, secondary and tertiary button will\nbehave and look. The props receive an object as such:\n\n~~~js\n{\n text: 'Save Changes',\n attributes: {\n variant: 'info',\n disabled: this.someState,\n class: 'some-class',\n ...\n }\n}\n~~~\n\n## Scrollable tab buttons\n\nBy default, `GlTab` will wrap tab buttons when they overflow the container. To\nenable horizontally scrolling for the tab buttons, use the `GlScrollableTabs`\ncomponent. This is a separate Vue component because of some limitations:\n\n- The action props (e.g., `action-primary`) are not respected in `GlScrollableTabs`. At the\n moment, BootstrapVue does not provide a reliable way for us to achieve this desired combination.\n\n`GlScrollableTabs` composes `GlTabs` and passes through every listener, slot, or prop (with the above\nexceptions).\n\n~~~html\n<gl-scrollable-tabs>\n <gl-tab v-for=\"tab in tabs\" :key=\"tab.key\" :title=\"tab.title\"> {{ tab.content }} </gl-tab>\n</gl-scrollable-tabs>\n~~~\n";
|
|
2
2
|
|
|
3
3
|
var tabs_documentation = {
|
|
4
4
|
description,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { GlTooltipDirective } from '../../../directives/tooltip';
|
|
2
1
|
import { GlResizeObserverDirective } from '../../../directives/resize_observer/resize_observer';
|
|
2
|
+
import { GlTooltipDirective } from '../../../directives/tooltip';
|
|
3
3
|
import { POSITION } from './constants';
|
|
4
4
|
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
|
|
5
5
|
|
|
@@ -84,7 +84,7 @@ var script = {
|
|
|
84
84
|
const __vue_script__ = script;
|
|
85
85
|
|
|
86
86
|
/* template */
|
|
87
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.position === _vm.$options.POSITION.START)?_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"title":_vm.text}},[_c('span',{ref:"text",staticClass:"gl-truncate-start gl-text-overflow-ellipsis!"},[_vm._v(""+_vm._s(_vm.text)+"")])]):(_vm.position === _vm.$options.POSITION.MIDDLE)?_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"title":_vm.text}},[_c('span',{ref:"text",staticClass:"gl-truncate-end"},[_vm._v(_vm._s(_vm.first))]),_c('span',{staticClass:"gl-truncate-start"},[_vm._v(""+_vm._s(_vm.last)+"")])]):_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"data-testid":"truncate-end-container","title":_vm.text}},[_c('span',{ref:"text",staticClass:"gl-truncate-end"},[_vm._v(_vm._s(_vm.text))])])};
|
|
87
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.position === _vm.$options.POSITION.START)?_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"title":_vm.text}},[(_vm.$scopedSlots['before-text'])?_c('span',{staticClass:"gl-pr-3"},[_vm._t("before-text")],2):_vm._e(),_vm._v(" "),_c('span',{ref:"text",staticClass:"gl-truncate-start gl-text-overflow-ellipsis!"},[_vm._v(""+_vm._s(_vm.text)+"")]),_vm._v(" "),(_vm.$scopedSlots['after-text'])?_c('span',{staticClass:"gl-pl-3"},[_vm._t("after-text")],2):_vm._e()]):(_vm.position === _vm.$options.POSITION.MIDDLE)?_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"title":_vm.text}},[(_vm.$scopedSlots['before-text'])?_c('span',{staticClass:"gl-pr-3"},[_vm._t("before-text")],2):_vm._e(),_vm._v(" "),_c('span',{ref:"text",staticClass:"gl-truncate-end",attrs:{"data-testid":"text-beginning"}},[_vm._v(_vm._s(_vm.first))]),_c('span',{staticClass:"gl-truncate-start",attrs:{"data-testid":"text-ending"}},[_vm._v(""+_vm._s(_vm.last)+"")]),_vm._v(" "),(_vm.$scopedSlots['after-text'])?_c('span',{staticClass:"gl-pl-3"},[_vm._t("after-text")],2):_vm._e()]):_c('span',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip",value:({ disabled: _vm.isTooltipDisabled }),expression:"{ disabled: isTooltipDisabled }"},{name:"gl-resize-observer",rawName:"v-gl-resize-observer:[withTooltip]",value:(_vm.checkTruncationState),expression:"checkTruncationState",arg:_vm.withTooltip}],staticClass:"gl-truncate",attrs:{"data-testid":"truncate-end-container","title":_vm.text}},[(_vm.$scopedSlots['before-text'])?_c('span',{staticClass:"gl-pr-3"},[_vm._t("before-text")],2):_vm._e(),_vm._v(" "),_c('span',{ref:"text",staticClass:"gl-truncate-end"},[_vm._v(_vm._s(_vm.text))]),_vm._v(" "),(_vm.$scopedSlots['after-text'])?_c('span',{staticClass:"gl-pl-3"},[_vm._t("after-text")],2):_vm._e()])};
|
|
88
88
|
var __vue_staticRenderFns__ = [];
|
|
89
89
|
|
|
90
90
|
/* style */
|
package/package.json
CHANGED
|
@@ -211,7 +211,7 @@ describe('Filtered search', () => {
|
|
|
211
211
|
expect(wrapper.findComponent(FakeToken).props('active')).toBe(true);
|
|
212
212
|
});
|
|
213
213
|
|
|
214
|
-
it('
|
|
214
|
+
it('keeps first token active on first token destruction with backspace', async () => {
|
|
215
215
|
createComponent({
|
|
216
216
|
value: ['foo', { type: 'faketoken', value: '' }],
|
|
217
217
|
});
|
|
@@ -228,7 +228,7 @@ describe('Filtered search', () => {
|
|
|
228
228
|
|
|
229
229
|
await nextTick();
|
|
230
230
|
|
|
231
|
-
expect(wrapper.findComponent(FakeToken).props('active')).toBe(
|
|
231
|
+
expect(wrapper.findComponent(FakeToken).props('active')).toBe(true);
|
|
232
232
|
});
|
|
233
233
|
|
|
234
234
|
it('keeps active token active if later one destroyed', async () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { cloneDeep } from 'lodash';
|
|
2
|
+
import { cloneDeep, isEqual } from 'lodash';
|
|
3
3
|
import PortalVue from 'portal-vue';
|
|
4
4
|
import Vue from 'vue';
|
|
5
5
|
import { GlTooltipDirective } from '../../../directives/tooltip';
|
|
@@ -146,13 +146,21 @@ export default {
|
|
|
146
146
|
deep: true,
|
|
147
147
|
immediate: true,
|
|
148
148
|
},
|
|
149
|
+
value: {
|
|
150
|
+
handler(newValue, oldValue) {
|
|
151
|
+
if (newValue.length && !isEqual(newValue, oldValue)) {
|
|
152
|
+
this.applyNewValue(cloneDeep(newValue));
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
deep: true,
|
|
156
|
+
immediate: true,
|
|
157
|
+
},
|
|
149
158
|
},
|
|
150
159
|
mounted() {
|
|
151
160
|
if (this.value.length) {
|
|
152
161
|
this.applyNewValue(cloneDeep(this.value));
|
|
153
162
|
}
|
|
154
163
|
},
|
|
155
|
-
|
|
156
164
|
methods: {
|
|
157
165
|
applyNewValue(newValue) {
|
|
158
166
|
this.tokens = needDenormalization(newValue) ? denormalizeTokens(newValue) : newValue;
|
|
@@ -214,8 +222,8 @@ export default {
|
|
|
214
222
|
// active state for the token that was active at the time. If that's not
|
|
215
223
|
// possible, make sure no token is active.
|
|
216
224
|
if (intent === INTENT_ACTIVATE_PREVIOUS) {
|
|
217
|
-
// If there is a previous token, activate it; else,
|
|
218
|
-
this.activeTokenIdx = idx
|
|
225
|
+
// If there is a previous token, activate it; else, activate the first token
|
|
226
|
+
this.activeTokenIdx = Math.max(idx - 1, 0);
|
|
219
227
|
} else if (idx < this.activeTokenIdx) {
|
|
220
228
|
// Preserve the active token's active status (it shifted down one index)
|
|
221
229
|
this.activeTokenIdx -= 1;
|
|
@@ -20,11 +20,11 @@ behave in the modals footer. The props receive an object as such:
|
|
|
20
20
|
```js
|
|
21
21
|
{
|
|
22
22
|
text: 'Save Changes',
|
|
23
|
-
attributes:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
attributes: {
|
|
24
|
+
variant: 'confirm',
|
|
25
|
+
disabled: this.someState,
|
|
26
|
+
class: 'some-class',
|
|
27
27
|
...
|
|
28
|
-
|
|
28
|
+
}
|
|
29
29
|
}
|
|
30
30
|
```
|
|
@@ -107,14 +107,14 @@ describe('Modal component', () => {
|
|
|
107
107
|
const props = {
|
|
108
108
|
actionPrimary: {
|
|
109
109
|
text: 'Primary',
|
|
110
|
-
attributes:
|
|
110
|
+
attributes: { variant: 'info' },
|
|
111
111
|
},
|
|
112
112
|
actionSecondary: {
|
|
113
113
|
text: 'Secondary',
|
|
114
114
|
},
|
|
115
115
|
actionCancel: {
|
|
116
116
|
text: 'Cancel',
|
|
117
|
-
attributes:
|
|
117
|
+
attributes: { variant: 'danger' },
|
|
118
118
|
},
|
|
119
119
|
};
|
|
120
120
|
|
|
@@ -48,12 +48,12 @@ behave and look. The props receive an object as such:
|
|
|
48
48
|
~~~js
|
|
49
49
|
{
|
|
50
50
|
text: 'Save Changes',
|
|
51
|
-
attributes:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
attributes: {
|
|
52
|
+
variant: 'info',
|
|
53
|
+
disabled: this.someState,
|
|
54
|
+
class: 'some-class',
|
|
55
55
|
...
|
|
56
|
-
|
|
56
|
+
}
|
|
57
57
|
}
|
|
58
58
|
~~~
|
|
59
59
|
|
|
@@ -16,10 +16,12 @@ describe('Truncate component', () => {
|
|
|
16
16
|
text: 'ee/app/assets/javascripts/vue_shared/src/utils_reports/components/utils/index.js',
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
|
|
20
|
+
|
|
21
|
+
const createComponent = (props, slots = {}) => {
|
|
20
22
|
wrapper = shallowMount(
|
|
21
23
|
{ extends: Truncate, directives: { GlTooltip: createMockDirective('gl-tooltip') } },
|
|
22
|
-
{ propsData: { ...defaultProps, ...props } }
|
|
24
|
+
{ propsData: { ...defaultProps, ...props }, slots }
|
|
23
25
|
);
|
|
24
26
|
};
|
|
25
27
|
|
|
@@ -55,12 +57,13 @@ describe('Truncate component', () => {
|
|
|
55
57
|
createComponent({ position: 'start' });
|
|
56
58
|
});
|
|
57
59
|
|
|
58
|
-
it('should have the truncate-start class', () => {
|
|
59
|
-
|
|
60
|
+
it('should have the truncate-start class on the text span', () => {
|
|
61
|
+
const textSpan = wrapper.findComponent({ ref: 'text' });
|
|
62
|
+
expect(textSpan.classes('gl-truncate-start')).toBe(true);
|
|
60
63
|
});
|
|
61
64
|
|
|
62
65
|
it('should have the special char surrounded', () => {
|
|
63
|
-
const spanTag = wrapper.
|
|
66
|
+
const spanTag = wrapper.findComponent({ ref: 'text' }).text();
|
|
64
67
|
|
|
65
68
|
expect(spanTag.charAt(0)).toBe('\u200E');
|
|
66
69
|
expect(spanTag.charAt(spanTag.length - 1)).toBe('\u200E');
|
|
@@ -68,27 +71,27 @@ describe('Truncate component', () => {
|
|
|
68
71
|
});
|
|
69
72
|
|
|
70
73
|
describe('middle truncation', () => {
|
|
71
|
-
let
|
|
72
|
-
let
|
|
74
|
+
let firstTextSpan;
|
|
75
|
+
let secondTextSpan;
|
|
73
76
|
|
|
74
77
|
beforeEach(() => {
|
|
75
78
|
createComponent({ position: 'middle' });
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
firstTextSpan = findByTestId('text-beginning');
|
|
80
|
+
secondTextSpan = findByTestId('text-ending');
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
it('should have appropriate classes applied', () => {
|
|
81
|
-
expect(
|
|
82
|
-
expect(
|
|
84
|
+
expect(firstTextSpan.classes('gl-truncate-end')).toBe(true);
|
|
85
|
+
expect(secondTextSpan.classes('gl-truncate-start')).toBe(true);
|
|
83
86
|
});
|
|
84
87
|
|
|
85
88
|
it('should have the spans positioned correctly', () => {
|
|
86
|
-
expect(
|
|
87
|
-
expect(
|
|
89
|
+
expect(firstTextSpan.text()).toBe('ee/app/assets/javascripts/vue_shared/src');
|
|
90
|
+
expect(secondTextSpan.text()).toBe('/utils_reports/components/utils/index.js');
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
it('last part should have the special char surrounded', () => {
|
|
91
|
-
const lastPart =
|
|
94
|
+
const lastPart = secondTextSpan.text();
|
|
92
95
|
|
|
93
96
|
expect(lastPart.charAt(0)).toBe('\u200E');
|
|
94
97
|
expect(lastPart.charAt(lastPart.length - 1)).toBe('\u200E');
|
|
@@ -108,4 +111,36 @@ describe('Truncate component', () => {
|
|
|
108
111
|
expect(wrapper.find('.gl-truncate-end').exists()).toBe(true);
|
|
109
112
|
});
|
|
110
113
|
});
|
|
114
|
+
|
|
115
|
+
describe('slots', () => {
|
|
116
|
+
const beforeText = 'Before Text Slot';
|
|
117
|
+
const afterText = 'After Text Slot';
|
|
118
|
+
const slotOptions = {
|
|
119
|
+
'before-text': {
|
|
120
|
+
slot: `<span id="before-text">${beforeText}</span>`,
|
|
121
|
+
text: beforeText,
|
|
122
|
+
},
|
|
123
|
+
'after-text': {
|
|
124
|
+
slot: `<span id="after-text">${afterText}</span>`,
|
|
125
|
+
text: afterText,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
it.each`
|
|
130
|
+
position | slotType
|
|
131
|
+
${'start'} | ${'before-text'}
|
|
132
|
+
${'start'} | ${'after-text'}
|
|
133
|
+
${'middle'} | ${'before-text'}
|
|
134
|
+
${'middle'} | ${'after-text'}
|
|
135
|
+
${'end'} | ${'before-text'}
|
|
136
|
+
${'end'} | ${'after-text'}
|
|
137
|
+
`('$position truncation $slot slot', ({ position, slotType }) => {
|
|
138
|
+
const slot = { [slotType]: slotOptions[slotType].slot };
|
|
139
|
+
|
|
140
|
+
createComponent({ position }, slot);
|
|
141
|
+
|
|
142
|
+
const slotComponent = wrapper.find(`#${slotType}`);
|
|
143
|
+
expect(slotComponent.text()).toBe(slotOptions[slotType].text);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
111
146
|
});
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import { GlTruncate } from '../../../index';
|
|
1
|
+
import { GlIcon, GlTruncate } from '../../../index';
|
|
2
2
|
import { POSITION } from './constants';
|
|
3
3
|
import readme from './truncate.md';
|
|
4
4
|
|
|
5
5
|
const template = '<gl-truncate :text="text" :position="position" :with-tooltip="withTooltip" />';
|
|
6
|
+
const beforeTextTemplate = `
|
|
7
|
+
<gl-truncate :text="text" :position="position" :with-tooltip="withTooltip">
|
|
8
|
+
<template #before-text><gl-icon name="container-image" /></template>
|
|
9
|
+
</gl-truncate>`;
|
|
10
|
+
const afterTextTemplate = `
|
|
11
|
+
<gl-truncate :text="text" :position="position" :with-tooltip="withTooltip">
|
|
12
|
+
<template #after-text><gl-icon name="abuse" /></template>
|
|
13
|
+
</gl-truncate>`;
|
|
14
|
+
const bothSlotsTemplate = `
|
|
15
|
+
<gl-truncate :text="text" :position="position" :with-tooltip="withTooltip">
|
|
16
|
+
<template #before-text><gl-icon name="rocket" /></template>
|
|
17
|
+
<template #after-text><gl-icon name="requirements" /></template>
|
|
18
|
+
</gl-truncate>`;
|
|
6
19
|
|
|
7
20
|
const generateProps = ({
|
|
8
21
|
text = 'src/thisIs/AVeryLongFilePath/that/needs/to/be/smartly/truncated/from/the/middle/so/we/dont/lose/important/information/here.vue',
|
|
@@ -24,6 +37,51 @@ const Template = (args, { argTypes }) => ({
|
|
|
24
37
|
export const Default = Template.bind({});
|
|
25
38
|
Default.args = generateProps();
|
|
26
39
|
|
|
40
|
+
const BeforeTextTemplate = (args, { argTypes }) => ({
|
|
41
|
+
components: { GlIcon, GlTruncate },
|
|
42
|
+
props: Object.keys(argTypes),
|
|
43
|
+
template: beforeTextTemplate,
|
|
44
|
+
});
|
|
45
|
+
export const BeforeText = BeforeTextTemplate.bind({});
|
|
46
|
+
BeforeText.args = generateProps({ position: 'start' });
|
|
47
|
+
BeforeText.parameters = {
|
|
48
|
+
docs: {
|
|
49
|
+
source: {
|
|
50
|
+
code: beforeTextTemplate,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const AfterTextTemplate = (args, { argTypes }) => ({
|
|
56
|
+
components: { GlIcon, GlTruncate },
|
|
57
|
+
props: Object.keys(argTypes),
|
|
58
|
+
template: afterTextTemplate,
|
|
59
|
+
});
|
|
60
|
+
export const AfterText = AfterTextTemplate.bind({});
|
|
61
|
+
AfterText.args = generateProps({ position: 'end' });
|
|
62
|
+
AfterText.parameters = {
|
|
63
|
+
docs: {
|
|
64
|
+
source: {
|
|
65
|
+
code: afterTextTemplate,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const BothSlotsTemplate = (args, { argTypes }) => ({
|
|
71
|
+
components: { GlIcon, GlTruncate },
|
|
72
|
+
props: Object.keys(argTypes),
|
|
73
|
+
template: bothSlotsTemplate,
|
|
74
|
+
});
|
|
75
|
+
export const BothSlots = BothSlotsTemplate.bind({});
|
|
76
|
+
BothSlots.args = generateProps();
|
|
77
|
+
BothSlots.parameters = {
|
|
78
|
+
docs: {
|
|
79
|
+
source: {
|
|
80
|
+
code: bothSlotsTemplate,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
27
85
|
export default {
|
|
28
86
|
title: 'utilities/truncate',
|
|
29
87
|
component: GlTruncate,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { GlTooltipDirective } from '../../../directives/tooltip';
|
|
3
2
|
import { GlResizeObserverDirective } from '../../../directives/resize_observer/resize_observer';
|
|
3
|
+
import { GlTooltipDirective } from '../../../directives/tooltip';
|
|
4
4
|
import { POSITION } from './constants';
|
|
5
5
|
|
|
6
6
|
export default {
|
|
@@ -81,9 +81,15 @@ export default {
|
|
|
81
81
|
class="gl-truncate"
|
|
82
82
|
:title="text"
|
|
83
83
|
>
|
|
84
|
+
<span v-if="$scopedSlots['before-text']" class="gl-pr-3">
|
|
85
|
+
<slot name="before-text"></slot>
|
|
86
|
+
</span>
|
|
84
87
|
<span ref="text" class="gl-truncate-start gl-text-overflow-ellipsis!"
|
|
85
88
|
>‎{{ text }}‎</span
|
|
86
89
|
>
|
|
90
|
+
<span v-if="$scopedSlots['after-text']" class="gl-pl-3">
|
|
91
|
+
<slot name="after-text"></slot>
|
|
92
|
+
</span>
|
|
87
93
|
</span>
|
|
88
94
|
|
|
89
95
|
<!-- MIDDLE -->
|
|
@@ -94,8 +100,14 @@ export default {
|
|
|
94
100
|
class="gl-truncate"
|
|
95
101
|
:title="text"
|
|
96
102
|
>
|
|
97
|
-
<span
|
|
98
|
-
|
|
103
|
+
<span v-if="$scopedSlots['before-text']" class="gl-pr-3">
|
|
104
|
+
<slot name="before-text"></slot>
|
|
105
|
+
</span>
|
|
106
|
+
<span ref="text" class="gl-truncate-end" data-testid="text-beginning">{{ first }}</span
|
|
107
|
+
><span class="gl-truncate-start" data-testid="text-ending">‎{{ last }}‎</span>
|
|
108
|
+
<span v-if="$scopedSlots['after-text']" class="gl-pl-3">
|
|
109
|
+
<slot name="after-text"></slot>
|
|
110
|
+
</span>
|
|
99
111
|
</span>
|
|
100
112
|
|
|
101
113
|
<!-- END -->
|
|
@@ -107,6 +119,12 @@ export default {
|
|
|
107
119
|
data-testid="truncate-end-container"
|
|
108
120
|
:title="text"
|
|
109
121
|
>
|
|
122
|
+
<span v-if="$scopedSlots['before-text']" class="gl-pr-3">
|
|
123
|
+
<slot name="before-text"></slot>
|
|
124
|
+
</span>
|
|
110
125
|
<span ref="text" class="gl-truncate-end">{{ text }}</span>
|
|
126
|
+
<span v-if="$scopedSlots['after-text']" class="gl-pl-3">
|
|
127
|
+
<slot name="after-text"></slot>
|
|
128
|
+
</span>
|
|
111
129
|
</span>
|
|
112
130
|
</template>
|