@gitlab/ui 63.1.3 → 63.2.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/utilities/truncate_text/constants.js +7 -0
- package/dist/components/utilities/truncate_text/truncate_text.js +136 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +4 -4
- package/src/components/regions/empty_state/empty_state.stories.js +1 -1
- package/src/components/utilities/truncate_text/constants.js +5 -0
- package/src/components/utilities/truncate_text/truncate_text.md +26 -0
- package/src/components/utilities/truncate_text/truncate_text.scss +14 -0
- package/src/components/utilities/truncate_text/truncate_text.spec.js +136 -0
- package/src/components/utilities/truncate_text/truncate_text.stories.js +43 -0
- package/src/components/utilities/truncate_text/truncate_text.vue +110 -0
- package/src/index.js +1 -0
- package/src/scss/components.scss +1 -0
package/dist/index.js
CHANGED
|
@@ -92,6 +92,7 @@ export { default as GlFriendlyWrap } from './components/utilities/friendly_wrap/
|
|
|
92
92
|
export { default as GlIntersperse } from './components/utilities/intersperse/intersperse';
|
|
93
93
|
export { default as GlSprintf } from './components/utilities/sprintf/sprintf';
|
|
94
94
|
export { default as GlTruncate } from './components/utilities/truncate/truncate';
|
|
95
|
+
export { default as GlTruncateText } from './components/utilities/truncate_text/truncate_text';
|
|
95
96
|
export { GlModalDirective } from './directives/modal';
|
|
96
97
|
export { GlTooltipDirective } from './directives/tooltip';
|
|
97
98
|
export { GlResizeObserverDirective } from './directives/resize_observer/resize_observer';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "63.
|
|
3
|
+
"version": "63.2.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@gitlab/eslint-plugin": "19.0.0",
|
|
90
90
|
"@gitlab/fonts": "^1.2.0",
|
|
91
91
|
"@gitlab/stylelint-config": "4.1.0",
|
|
92
|
-
"@gitlab/svgs": "3.
|
|
92
|
+
"@gitlab/svgs": "3.48.0",
|
|
93
93
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
94
94
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
95
95
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"bootstrap": "4.6.2",
|
|
119
119
|
"cypress": "12.12.0",
|
|
120
120
|
"emoji-regex": "^10.0.0",
|
|
121
|
-
"eslint": "8.
|
|
121
|
+
"eslint": "8.41.0",
|
|
122
122
|
"eslint-import-resolver-jest": "3.0.2",
|
|
123
123
|
"eslint-plugin-cypress": "2.13.3",
|
|
124
124
|
"eslint-plugin-storybook": "0.6.12",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"sass-loader": "^10.2.0",
|
|
153
153
|
"sass-true": "^6.1.0",
|
|
154
154
|
"start-server-and-test": "^1.10.6",
|
|
155
|
-
"storybook": "7.0.
|
|
155
|
+
"storybook": "7.0.12",
|
|
156
156
|
"storybook-dark-mode": "3.0.0",
|
|
157
157
|
"stylelint": "14.9.1",
|
|
158
158
|
"stylelint-config-prettier": "9.0.4",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import securityDashboardEmptyStateSvg from '@gitlab/svgs/dist/illustrations/security-dashboard-empty-state.svg';
|
|
2
|
-
import issuesSvg from '@gitlab/svgs/dist/illustrations/
|
|
2
|
+
import issuesSvg from '@gitlab/svgs/dist/illustrations/rocket-launch-md.svg';
|
|
3
3
|
import GlButton from '../../base/button/button.vue';
|
|
4
4
|
import GlEmptyState from './empty_state.vue';
|
|
5
5
|
import readme from './empty_state.md';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
The `GlTruncateText` component lets you truncate a large text by number of lines.
|
|
2
|
+
The last line ends with an ellipsis if the text is truncated.
|
|
3
|
+
Truncation can be toggled by a 'Show more' / 'Show less' button.
|
|
4
|
+
The button will not be shown when no truncation is necessary.
|
|
5
|
+
There is a separate property to set the number of lines initially shown on small screens.
|
|
6
|
+
Use the `showMoreText` and `showLessText` properties to provide translated strings.
|
|
7
|
+
|
|
8
|
+
> **Tip:** Try resizing the side panel to see the truncated number of lines change.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<gl-truncate-text :show-more-text="__('Show more')" :show-less-text="__('Show less')" :lines="3" :mobile-lines="10">
|
|
14
|
+
{{ largeText }}
|
|
15
|
+
</gl-truncate-text>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage caveats
|
|
19
|
+
|
|
20
|
+
When the size of the window is large,
|
|
21
|
+
and the text is displayed on a number of lines greater than the value of the `lines` property,
|
|
22
|
+
but smaller than the value of the `mobileLines` property,
|
|
23
|
+
and the `Show more` button has been clicked to show the entire content of the text,
|
|
24
|
+
and the window is resized to a small size,
|
|
25
|
+
then instead of disappearing,
|
|
26
|
+
the `Show less` button will remain visible and will do nothing when clicked.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Note: although vendor-prefixes are used here, these properties are supported by all browsers.
|
|
3
|
+
See https://caniuse.com/css-line-clamp
|
|
4
|
+
**/
|
|
5
|
+
.gl-truncate-text {
|
|
6
|
+
// stylelint-disable-next-line value-no-vendor-prefix
|
|
7
|
+
display: -webkit-box;
|
|
8
|
+
-webkit-line-clamp: var(--mobile-lines);
|
|
9
|
+
-webkit-box-orient: vertical;
|
|
10
|
+
|
|
11
|
+
@include gl-media-breakpoint-up(sm) {
|
|
12
|
+
-webkit-line-clamp: var(--lines);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { createMockDirective, getBinding } from '~helpers/vue_mock_directive';
|
|
3
|
+
import GlButton from '../../base/button/button.vue';
|
|
4
|
+
import GlTruncateText from './truncate_text.vue';
|
|
5
|
+
|
|
6
|
+
describe('GlTruncateText', () => {
|
|
7
|
+
let wrapper;
|
|
8
|
+
|
|
9
|
+
const showMoreText = 'Show more text';
|
|
10
|
+
const showLessText = 'Show less text';
|
|
11
|
+
|
|
12
|
+
const findContent = () => wrapper.find('article');
|
|
13
|
+
const findContentEl = () => findContent().element;
|
|
14
|
+
const findButton = () => wrapper.findComponent(GlButton);
|
|
15
|
+
|
|
16
|
+
const createComponent = (propsData = { showMoreText, showLessText }) => {
|
|
17
|
+
wrapper = mount(GlTruncateText, {
|
|
18
|
+
propsData,
|
|
19
|
+
directives: {
|
|
20
|
+
GlResizeObserver: createMockDirective('gl-resize-observer'),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
createComponent();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('when mounted', () => {
|
|
30
|
+
it('the content has class `gl-truncate-text`', () => {
|
|
31
|
+
expect(findContentEl().classList).toContain('gl-truncate-text');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('the content has style variables for `lines` and `mobile-lines` with the correct values', () => {
|
|
35
|
+
const { style } = findContentEl();
|
|
36
|
+
|
|
37
|
+
expect(style).toContain('--lines');
|
|
38
|
+
expect(style.getPropertyValue('--lines')).toBe('3');
|
|
39
|
+
expect(style).toContain('--mobile-lines');
|
|
40
|
+
expect(style.getPropertyValue('--mobile-lines')).toBe('10');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('the button is not visible', () => {
|
|
44
|
+
expect(findButton().exists()).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe.each`
|
|
49
|
+
property | variable
|
|
50
|
+
${'lines'} | ${'--lines'}
|
|
51
|
+
${'mobileLines'} | ${'--mobile-lines'}
|
|
52
|
+
`('when mounted with a value for the $property property', ({ property, variable }) => {
|
|
53
|
+
const value = 4;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
createComponent({ [property]: value });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`the ${variable} style variable has the value of the passed ${property} property`, () => {
|
|
60
|
+
expect(findContentEl().style.getPropertyValue(variable)).toBe(value.toString());
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('when resizing and the scroll height is smaller than the offset height', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
getBinding(findContentEl(), 'gl-resize-observer').value({
|
|
67
|
+
target: { scrollHeight: 10, offsetHeight: 20 },
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('the button remains invisible', () => {
|
|
72
|
+
expect(findButton().exists()).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('the aria-expanded property is set to `true`', () => {
|
|
76
|
+
expect(findContent().attributes('aria-expanded')).toBe('true');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe.each`
|
|
81
|
+
scrollHeight | offsetHeight | lessOrMore | showMoreVisible | ariaExpanded
|
|
82
|
+
${10} | ${20} | ${'less'} | ${false} | ${'true'}
|
|
83
|
+
${20} | ${10} | ${'more'} | ${true} | ${'false'}
|
|
84
|
+
`(
|
|
85
|
+
'when scroll height is $lessOrMore than the offset height',
|
|
86
|
+
({ scrollHeight, offsetHeight, showMoreVisible, ariaExpanded }) => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
getBinding(findContentEl(), 'gl-resize-observer').value({
|
|
89
|
+
target: { scrollHeight, offsetHeight },
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it(`the show more button should ${showMoreVisible ? 'be' : 'not be'} visible`, () => {
|
|
94
|
+
expect(findButton().exists()).toBe(showMoreVisible);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('the content contains the "gl-truncate-text" class', () => {
|
|
98
|
+
expect(findContentEl().classList).toContain('gl-truncate-text');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it(`the "aria-expanded" property is set to ${ariaExpanded}`, () => {
|
|
102
|
+
expect(findContent().attributes('aria-expanded')).toBe(ariaExpanded);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
describe('when resizing and the scroll height is greater than the offset height', () => {
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
getBinding(findContentEl(), 'gl-resize-observer').value({
|
|
110
|
+
target: { scrollHeight: 20, offsetHeight: 10 },
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('the button text displays the value for the showMoreText property', () => {
|
|
115
|
+
expect(findButton().text()).toBe(showMoreText);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('clicking the button', () => {
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
findButton().trigger('click');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('removes the `gl-truncate-text` class on the content', () => {
|
|
124
|
+
expect(findContentEl().classList).not.toContain('gl-truncate-text');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('the button text displays the value for the showLessText property', () => {
|
|
128
|
+
expect(findButton().text()).toBe(showLessText);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('the aria-expanded property is set to `true`', () => {
|
|
132
|
+
expect(findContent().attributes('aria-expanded')).toBe('true');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import GlTruncateText from './truncate_text.vue';
|
|
2
|
+
import readme from './truncate_text.md';
|
|
3
|
+
|
|
4
|
+
const generateProps = ({
|
|
5
|
+
showMoreText = 'Show more',
|
|
6
|
+
showLessText = 'Show less',
|
|
7
|
+
lines = 3,
|
|
8
|
+
mobileLines = 10,
|
|
9
|
+
} = {}) => ({
|
|
10
|
+
showMoreText,
|
|
11
|
+
showLessText,
|
|
12
|
+
lines,
|
|
13
|
+
mobileLines,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const content = () => [...Array(15)].map((_, i) => `line ${i + 1}`).join('\n');
|
|
17
|
+
|
|
18
|
+
const template = `
|
|
19
|
+
<gl-truncate-text v-bind="$props">
|
|
20
|
+
<div class="gl-white-space-pre-line">${content()}</div>
|
|
21
|
+
</gl-truncate-text>`;
|
|
22
|
+
|
|
23
|
+
const Template = (args, { argTypes }) => ({
|
|
24
|
+
components: { GlTruncateText },
|
|
25
|
+
props: Object.keys(argTypes),
|
|
26
|
+
template,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const Default = Template.bind({});
|
|
30
|
+
Default.args = generateProps();
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
title: 'utilities/truncate-text',
|
|
34
|
+
component: GlTruncateText,
|
|
35
|
+
parameters: {
|
|
36
|
+
storyshots: { disable: true },
|
|
37
|
+
docs: {
|
|
38
|
+
description: {
|
|
39
|
+
component: readme,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { GlResizeObserverDirective } from '../../../directives/resize_observer/resize_observer';
|
|
3
|
+
import GlButton from '../../base/button/button.vue';
|
|
4
|
+
import { STATES } from './constants';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: 'GlTruncateText',
|
|
8
|
+
components: {
|
|
9
|
+
GlButton,
|
|
10
|
+
},
|
|
11
|
+
directives: {
|
|
12
|
+
GlResizeObserver: GlResizeObserverDirective,
|
|
13
|
+
},
|
|
14
|
+
props: {
|
|
15
|
+
/**
|
|
16
|
+
* The text for the 'Show more' button
|
|
17
|
+
*/
|
|
18
|
+
showMoreText: {
|
|
19
|
+
type: String,
|
|
20
|
+
required: false,
|
|
21
|
+
default: 'Show more',
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* The text for the 'Show less' button
|
|
25
|
+
*/
|
|
26
|
+
showLessText: {
|
|
27
|
+
type: String,
|
|
28
|
+
required: false,
|
|
29
|
+
default: 'Show less',
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* The number of lines that are initially visible on larger screens
|
|
33
|
+
*/
|
|
34
|
+
lines: {
|
|
35
|
+
type: Number,
|
|
36
|
+
required: false,
|
|
37
|
+
default: 3,
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* The number of lines that are initially visible on smaller screens
|
|
41
|
+
*/
|
|
42
|
+
mobileLines: {
|
|
43
|
+
type: Number,
|
|
44
|
+
required: false,
|
|
45
|
+
default: 10,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
data() {
|
|
49
|
+
return {
|
|
50
|
+
state: STATES.INITIAL,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
computed: {
|
|
54
|
+
showTruncationToggle() {
|
|
55
|
+
return this.isTruncated || this.isExtended;
|
|
56
|
+
},
|
|
57
|
+
truncationToggleText() {
|
|
58
|
+
return this.isTruncated ? this.showMoreText : this.showLessText;
|
|
59
|
+
},
|
|
60
|
+
cssVariables() {
|
|
61
|
+
return { '--lines': this.lines, '--mobile-lines': this.mobileLines };
|
|
62
|
+
},
|
|
63
|
+
truncationClasses() {
|
|
64
|
+
return this.isExtended ? null : 'gl-truncate-text gl-overflow-hidden';
|
|
65
|
+
},
|
|
66
|
+
ariaExpanded() {
|
|
67
|
+
return (!this.isTruncated).toString();
|
|
68
|
+
},
|
|
69
|
+
isTruncated() {
|
|
70
|
+
return this.state === STATES.TRUNCATED;
|
|
71
|
+
},
|
|
72
|
+
isExtended() {
|
|
73
|
+
return this.state === STATES.EXTENDED;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
methods: {
|
|
77
|
+
onResize({ target }) {
|
|
78
|
+
if (target.scrollHeight > target.offsetHeight) {
|
|
79
|
+
this.state = STATES.TRUNCATED;
|
|
80
|
+
} else if (this.isTruncated) {
|
|
81
|
+
this.state = STATES.INITIAL;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
toggleTruncation() {
|
|
85
|
+
if (this.isTruncated) {
|
|
86
|
+
this.state = STATES.EXTENDED;
|
|
87
|
+
} else if (this.isExtended) {
|
|
88
|
+
this.state = STATES.TRUNCATED;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<template>
|
|
96
|
+
<section>
|
|
97
|
+
<article
|
|
98
|
+
v-gl-resize-observer="onResize"
|
|
99
|
+
:class="truncationClasses"
|
|
100
|
+
:style="cssVariables"
|
|
101
|
+
:aria-expanded="ariaExpanded"
|
|
102
|
+
>
|
|
103
|
+
<!-- @slot Text content -->
|
|
104
|
+
<slot></slot>
|
|
105
|
+
</article>
|
|
106
|
+
<gl-button v-if="showTruncationToggle" variant="link" @click="toggleTruncation">{{
|
|
107
|
+
truncationToggleText
|
|
108
|
+
}}</gl-button>
|
|
109
|
+
</section>
|
|
110
|
+
</template>
|
package/src/index.js
CHANGED
|
@@ -103,6 +103,7 @@ export { default as GlFriendlyWrap } from './components/utilities/friendly_wrap/
|
|
|
103
103
|
export { default as GlIntersperse } from './components/utilities/intersperse/intersperse.vue';
|
|
104
104
|
export { default as GlSprintf } from './components/utilities/sprintf/sprintf.vue';
|
|
105
105
|
export { default as GlTruncate } from './components/utilities/truncate/truncate.vue';
|
|
106
|
+
export { default as GlTruncateText } from './components/utilities/truncate_text/truncate_text.vue';
|
|
106
107
|
|
|
107
108
|
// Directives
|
|
108
109
|
export { GlModalDirective } from './directives/modal';
|
package/src/scss/components.scss
CHANGED
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
@import '../components/charts/tooltip/tooltip';
|
|
76
76
|
@import '../components/shared_components/charts/tooltip_default_format';
|
|
77
77
|
@import '../components/utilities/truncate/truncate';
|
|
78
|
+
@import '../components/utilities/truncate_text/truncate_text';
|
|
78
79
|
@import '../components/base/new_dropdowns/dropdown';
|
|
79
80
|
@import '../components/base/new_dropdowns/dropdown_item';
|
|
80
81
|
@import '../components/base/new_dropdowns/listbox/listbox';
|