@gitlab/ui 63.1.3 → 63.2.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/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.1.3",
3
+ "version": "63.2.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -27,6 +27,7 @@
27
27
  "copy-fonts": "make copy-fonts",
28
28
  "build-scss-variables": "make scss_to_js/scss_variables.js",
29
29
  "clean": "rm -r dist storybook scss_to_js/scss_variables.* src/scss/utilities.scss",
30
+ "cy:edge": "cypress run --browser edge",
30
31
  "cy:run": "cypress run --browser firefox",
31
32
  "start": "yarn storybook",
32
33
  "storybook": "yarn storybook-prep && storybook dev --ci --host localhost --port 9001 -c .storybook",
@@ -89,7 +90,7 @@
89
90
  "@gitlab/eslint-plugin": "19.0.0",
90
91
  "@gitlab/fonts": "^1.2.0",
91
92
  "@gitlab/stylelint-config": "4.1.0",
92
- "@gitlab/svgs": "3.47.0",
93
+ "@gitlab/svgs": "3.48.0",
93
94
  "@rollup/plugin-commonjs": "^11.1.0",
94
95
  "@rollup/plugin-node-resolve": "^7.1.3",
95
96
  "@rollup/plugin-replace": "^2.3.2",
@@ -118,7 +119,7 @@
118
119
  "bootstrap": "4.6.2",
119
120
  "cypress": "12.12.0",
120
121
  "emoji-regex": "^10.0.0",
121
- "eslint": "8.40.0",
122
+ "eslint": "8.41.0",
122
123
  "eslint-import-resolver-jest": "3.0.2",
123
124
  "eslint-plugin-cypress": "2.13.3",
124
125
  "eslint-plugin-storybook": "0.6.12",
@@ -152,7 +153,7 @@
152
153
  "sass-loader": "^10.2.0",
153
154
  "sass-true": "^6.1.0",
154
155
  "start-server-and-test": "^1.10.6",
155
- "storybook": "7.0.7",
156
+ "storybook": "7.0.12",
156
157
  "storybook-dark-mode": "3.0.0",
157
158
  "stylelint": "14.9.1",
158
159
  "stylelint-config-prettier": "9.0.4",
@@ -269,8 +269,8 @@ export default {
269
269
  },
270
270
  },
271
271
  beforeDestroy() {
272
- this.chart.getDom().removeEventListener('mousemove', this.debouncedShowHideTooltip);
273
- this.chart.getDom().removeEventListener('mouseout', this.debouncedShowHideTooltip);
272
+ this.chart.getZr().off('mousemove', this.debouncedShowHideTooltip);
273
+ this.chart.getZr().off('mouseout', this.debouncedShowHideTooltip);
274
274
 
275
275
  this.chart.off('mouseout', this.hideAnnotationsTooltip);
276
276
  this.chart.off('mouseover', this.onChartMouseOver);
@@ -293,8 +293,8 @@ export default {
293
293
  // when the mouse is hovered over the parent container
294
294
  // of echarts' svg element. This works only for data points
295
295
  // and not markPoints.
296
- chart.getDom().addEventListener('mousemove', this.debouncedShowHideTooltip);
297
- chart.getDom().addEventListener('mouseout', this.debouncedShowHideTooltip);
296
+ chart.getZr().on('mousemove', this.debouncedShowHideTooltip);
297
+ chart.getZr().on('mouseout', this.debouncedShowHideTooltip);
298
298
 
299
299
  // eCharts inbuild mouse events
300
300
  // https://echarts.apache.org/en/api.html#events.Mouse%20events
@@ -314,7 +314,7 @@ export default {
314
314
  this.chart = chart;
315
315
  this.$emit('created', chart);
316
316
  },
317
- showHideTooltip(mouseEvent) {
317
+ showHideTooltip({ event: mouseEvent }) {
318
318
  this.showDataTooltip = this.chart.containPixel('grid', [mouseEvent.zrX, mouseEvent.zrY]);
319
319
 
320
320
  this.dataTooltipPosition = {
@@ -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/issues.svg';
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,5 @@
1
+ export const STATES = {
2
+ INITIAL: 'initial',
3
+ TRUNCATED: 'truncated',
4
+ EXTENDED: 'extended',
5
+ };
@@ -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';
@@ -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';