@gitlab/ui 32.59.0 → 32.63.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "32.59.0",
3
+ "version": "32.63.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -90,15 +90,15 @@
90
90
  "@rollup/plugin-commonjs": "^11.1.0",
91
91
  "@rollup/plugin-node-resolve": "^7.1.3",
92
92
  "@rollup/plugin-replace": "^2.3.2",
93
- "@storybook/addon-a11y": "6.4.10",
94
- "@storybook/addon-docs": "6.4.10",
95
- "@storybook/addon-essentials": "6.4.10",
93
+ "@storybook/addon-a11y": "6.4.13",
94
+ "@storybook/addon-docs": "6.4.13",
95
+ "@storybook/addon-essentials": "6.4.13",
96
96
  "@storybook/addon-knobs": "6.4.0",
97
- "@storybook/addon-storyshots": "6.4.10",
98
- "@storybook/addon-storyshots-puppeteer": "6.4.10",
99
- "@storybook/addon-viewport": "6.4.10",
100
- "@storybook/theming": "6.4.10",
101
- "@storybook/vue": "6.4.10",
97
+ "@storybook/addon-storyshots": "6.4.13",
98
+ "@storybook/addon-storyshots-puppeteer": "6.4.13",
99
+ "@storybook/addon-viewport": "6.4.13",
100
+ "@storybook/theming": "6.4.13",
101
+ "@storybook/vue": "6.4.13",
102
102
  "@vue/test-utils": "1.3.0",
103
103
  "autoprefixer": "^9.7.6",
104
104
  "babel-jest": "^26.6.3",
@@ -240,6 +240,7 @@ export const glLineHeight24 = '1.5rem'
240
240
  export const glLineHeight28 = '1.75rem'
241
241
  export const glLineHeight32 = '2rem'
242
242
  export const glLineHeight36 = '2.25rem'
243
+ export const glLineHeight42 = '2.625rem'
243
244
  export const glLineHeight44 = '2.75rem'
244
245
  export const glLineHeight52 = '3.25rem'
245
246
  export const glFontSize = '0.875rem'
@@ -1249,6 +1249,11 @@
1249
1249
  "value": "px-to-rem(36px)",
1250
1250
  "compiledValue": "2.25rem"
1251
1251
  },
1252
+ {
1253
+ "name": "$gl-line-height-42",
1254
+ "value": "px-to-rem(42px)",
1255
+ "compiledValue": "2.625rem"
1256
+ },
1252
1257
  {
1253
1258
  "name": "$gl-line-height-44",
1254
1259
  "value": "px-to-rem(44px)",
@@ -1,6 +1,7 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
2
  import { BBreadcrumbItem } from 'bootstrap-vue';
3
- import Breadcrumb from './breadcrumb.vue';
3
+ import Breadcrumb, { COLLAPSE_AT_SIZE } from './breadcrumb.vue';
4
+ import { createMockDirective } from '~helpers/vue_mock_directive';
4
5
 
5
6
  describe('Broadcast message component', () => {
6
7
  let wrapper;
@@ -14,10 +15,24 @@ describe('Broadcast message component', () => {
14
15
  { text: 'third_breadcrumb', href: 'http://about.gitlab.com' },
15
16
  ];
16
17
 
18
+ const extraItems = [
19
+ { text: 'fourth_breadcrumb', href: 'http://gitlab.com' },
20
+ {
21
+ text: 'fifth_breadcrumb',
22
+ to: 'to_value',
23
+ },
24
+ ];
25
+
17
26
  const findAvatarSlot = () => wrapper.find('[data-testid="avatar-slot"]');
18
27
  const findSeparatorSlot = () => wrapper.find('[data-testid="separator-slot"]');
19
28
  const findBreadcrumbItems = () => wrapper.findAllComponents(BBreadcrumbItem);
20
29
  const findAllSeparators = () => wrapper.findAll('[data-testid="separator"]');
30
+ const findCollapsedListExpander = () => wrapper.find('[data-testid="collapsed-expander"]');
31
+ const findExpanderSeparator = () => wrapper.find('[data-testid="expander-separator"]');
32
+
33
+ const findVisibleBreadcrumbItems = () =>
34
+ wrapper.findAll('.gl-breadcrumb-item:not(.gl-display-none)');
35
+ const findHiddenBreadcrumbItems = () => wrapper.findAll('.gl-breadcrumb-item.gl-display-none');
21
36
 
22
37
  const createComponent = (propsData = { items }) => {
23
38
  wrapper = shallowMount(Breadcrumb, {
@@ -26,7 +41,14 @@ describe('Broadcast message component', () => {
26
41
  avatar: '<div data-testid="avatar-slot"></div>',
27
42
  separator: '<div data-testid="separator-slot"></div>',
28
43
  },
44
+ directives: { GlTooltip: createMockDirective('gl-tooltip') },
29
45
  });
46
+
47
+ wrapper.vm.$refs.firstItem = [
48
+ {
49
+ querySelector: () => ({ focus: jest.fn() }),
50
+ },
51
+ ];
30
52
  };
31
53
 
32
54
  describe('slots', () => {
@@ -82,4 +104,45 @@ describe('Broadcast message component', () => {
82
104
  });
83
105
  });
84
106
  });
107
+
108
+ describe('collapsible', () => {
109
+ describe(`when breadcrumbs list size is NOT larger than ${COLLAPSE_AT_SIZE}`, () => {
110
+ beforeEach(() => {
111
+ createComponent();
112
+ });
113
+ it('should not display collapsed list expander && separator', () => {
114
+ expect(findCollapsedListExpander().exists()).toBe(false);
115
+ expect(findExpanderSeparator().exists()).toBe(false);
116
+ });
117
+
118
+ it('should display all items visible', () => {
119
+ expect(findVisibleBreadcrumbItems()).toHaveLength(items.length);
120
+ });
121
+ });
122
+
123
+ describe(`when breadcrumbs list size is larger than ${COLLAPSE_AT_SIZE}`, () => {
124
+ beforeEach(() => {
125
+ createComponent({ items: [...items, ...extraItems] });
126
+ });
127
+ it('should display collapsed list expander && separator', () => {
128
+ expect(findCollapsedListExpander().exists()).toBe(true);
129
+ expect(findExpanderSeparator().exists()).toBe(true);
130
+ });
131
+
132
+ it('should display only first && 2 last items and the rest as hidden', () => {
133
+ const alwaysVisibleNum = 3;
134
+ expect(findVisibleBreadcrumbItems()).toHaveLength(alwaysVisibleNum);
135
+ expect(findHiddenBreadcrumbItems()).toHaveLength(
136
+ items.length + extraItems.length - alwaysVisibleNum
137
+ );
138
+ });
139
+
140
+ it('should expand the list on expander click', async () => {
141
+ findCollapsedListExpander().vm.$emit('click');
142
+ await wrapper.vm.$nextTick();
143
+ expect(findHiddenBreadcrumbItems()).toHaveLength(0);
144
+ expect(findVisibleBreadcrumbItems()).toHaveLength(items.length + extraItems.length);
145
+ });
146
+ });
147
+ });
85
148
  });
@@ -60,3 +60,25 @@ export default {
60
60
  },
61
61
  },
62
62
  };
63
+
64
+ const extraItems = [
65
+ {
66
+ text: 'Fifth Item',
67
+ href: '#',
68
+ },
69
+ {
70
+ text: 'Sixth Item',
71
+ href: '#',
72
+ },
73
+ {
74
+ text: 'Seventh Item',
75
+ href: '#',
76
+ },
77
+ {
78
+ text: 'Eighth Item',
79
+ href: '#',
80
+ },
81
+ ];
82
+
83
+ export const CollapsedItems = Template.bind({});
84
+ CollapsedItems.args = generateProps({ items: [...defaultItems, ...extraItems] });
@@ -1,12 +1,20 @@
1
1
  <script>
2
2
  import { BBreadcrumb, BBreadcrumbItem } from 'bootstrap-vue';
3
3
  import GlIcon from '../icon/icon.vue';
4
+ import GlButton from '../button/button.vue';
5
+ import { GlTooltipDirective } from '../../../directives/tooltip';
6
+
7
+ export const COLLAPSE_AT_SIZE = 4;
4
8
 
5
9
  export default {
6
10
  components: {
7
11
  BBreadcrumb,
8
12
  BBreadcrumbItem,
9
13
  GlIcon,
14
+ GlButton,
15
+ },
16
+ directives: {
17
+ GlTooltip: GlTooltipDirective,
10
18
  },
11
19
  inheritAttrs: false,
12
20
  props: {
@@ -25,9 +33,45 @@ export default {
25
33
  },
26
34
  },
27
35
  },
36
+ data() {
37
+ return {
38
+ isListCollapsed: true,
39
+ };
40
+ },
41
+ computed: {
42
+ breadcrumbsSize() {
43
+ return this.items.length;
44
+ },
45
+ hasCollapsible() {
46
+ return this.breadcrumbsSize > COLLAPSE_AT_SIZE;
47
+ },
48
+ nonCollapsibleIndices() {
49
+ return [0, this.breadcrumbsSize - 1, this.breadcrumbsSize - 2];
50
+ },
51
+ },
28
52
  methods: {
29
- isLastItem(items, index) {
30
- return index === items.length - 1;
53
+ isFirstItem(index) {
54
+ return index === 0;
55
+ },
56
+ isLastItem(index) {
57
+ return index === this.breadcrumbsSize - 1;
58
+ },
59
+ expandBreadcrumbs() {
60
+ this.isListCollapsed = false;
61
+ try {
62
+ this.$refs.firstItem[0].querySelector('a').focus();
63
+ } catch (e) {
64
+ /* eslint-disable-next-line no-console */
65
+ console.error(`Failed to set focus on the last breadcrumb item.`);
66
+ }
67
+ },
68
+ showCollapsedBreadcrumbsExpander(index) {
69
+ return index === 0 && this.hasCollapsible && this.isListCollapsed;
70
+ },
71
+ isItemCollapsed(index) {
72
+ return (
73
+ this.hasCollapsible && this.isListCollapsed && !this.nonCollapsibleIndices.includes(index)
74
+ );
31
75
  },
32
76
  },
33
77
  };
@@ -40,14 +84,16 @@ export default {
40
84
  <template v-for="(item, index) in items">
41
85
  <b-breadcrumb-item
42
86
  :key="index"
87
+ :ref="isFirstItem(index) ? 'firstItem' : null"
43
88
  class="gl-breadcrumb-item"
44
89
  :text="item.text"
45
90
  :href="item.href"
46
91
  :to="item.to"
92
+ :class="{ 'gl-display-none': isItemCollapsed(index) }"
47
93
  >
48
94
  <span>{{ item.text }}</span>
49
95
  <span
50
- v-if="!isLastItem(items, index)"
96
+ v-if="!isLastItem(index)"
51
97
  :key="`${index} ${item.text}`"
52
98
  class="gl-breadcrumb-separator"
53
99
  data-testid="separator"
@@ -58,6 +104,28 @@ export default {
58
104
  </slot>
59
105
  </span>
60
106
  </b-breadcrumb-item>
107
+
108
+ <template v-if="showCollapsedBreadcrumbsExpander(index)">
109
+ <!-- eslint-disable-next-line vue/valid-v-for -->
110
+ <gl-button
111
+ v-gl-tooltip.hover="'Show all breadcrumbs'"
112
+ aria-label="Show all breadcrumbs"
113
+ data-testid="collapsed-expander"
114
+ icon="ellipsis_h"
115
+ category="primary"
116
+ @click="expandBreadcrumbs"
117
+ />
118
+ <!-- eslint-disable-next-line vue/require-v-for-key -->
119
+ <span
120
+ key="expander"
121
+ class="gl-display-inline-flex gl-text-gray-500"
122
+ data-testid="expander-separator"
123
+ >
124
+ <slot name="separator">
125
+ <gl-icon name="chevron-right" />
126
+ </slot>
127
+ </span>
128
+ </template>
61
129
  </template>
62
130
  </b-breadcrumb>
63
131
  </nav>
@@ -2940,6 +2940,18 @@
2940
2940
  }
2941
2941
  }
2942
2942
 
2943
+ .gl-md-flex-direction-column {
2944
+ @include gl-media-breakpoint-up(md) {
2945
+ @include gl-flex-direction-column;
2946
+ }
2947
+ }
2948
+
2949
+ .gl-md-flex-direction-column\! {
2950
+ @include gl-media-breakpoint-up(md) {
2951
+ @include gl-flex-direction-column;
2952
+ }
2953
+ }
2954
+
2943
2955
  .gl-xs-flex-direction-column {
2944
2956
  @include gl-media-breakpoint-down(sm) {
2945
2957
  flex-direction: column;
@@ -5586,6 +5598,16 @@
5586
5598
  margin-bottom: $gl-spacing-scale-3 !important;
5587
5599
  }
5588
5600
  }
5601
+ .gl-xs-mb-4 {
5602
+ @include gl-media-breakpoint-down(sm) {
5603
+ margin-bottom: $gl-spacing-scale-4;
5604
+ }
5605
+ }
5606
+ .gl-xs-mb-4\! {
5607
+ @include gl-media-breakpoint-down(sm) {
5608
+ margin-bottom: $gl-spacing-scale-4 !important;
5609
+ }
5610
+ }
5589
5611
  .gl-sm-ml-3 {
5590
5612
  @include gl-media-breakpoint-up(sm) {
5591
5613
  margin-left: $gl-spacing-scale-3;
@@ -6296,6 +6318,16 @@
6296
6318
  padding-left: $gl-spacing-scale-5 !important;
6297
6319
  }
6298
6320
  }
6321
+ .gl-md-pl-7 {
6322
+ @include gl-media-breakpoint-up(md) {
6323
+ padding-left: $gl-spacing-scale-7;
6324
+ }
6325
+ }
6326
+ .gl-md-pl-7\! {
6327
+ @include gl-media-breakpoint-up(md) {
6328
+ padding-left: $gl-spacing-scale-7 !important;
6329
+ }
6330
+ }
6299
6331
  .gl-lg-pt-0 {
6300
6332
  @include gl-media-breakpoint-up(lg) {
6301
6333
  padding-top: 0;
@@ -7287,6 +7319,14 @@
7287
7319
  line-height: $gl-line-height-36 !important;
7288
7320
  }
7289
7321
 
7322
+ .gl-line-height-42 {
7323
+ line-height: $gl-line-height-42;
7324
+ }
7325
+
7326
+ .gl-line-height-42\! {
7327
+ line-height: $gl-line-height-42 !important;
7328
+ }
7329
+
7290
7330
  .gl-line-height-52 {
7291
7331
  line-height: $gl-line-height-52;
7292
7332
  }
@@ -96,6 +96,12 @@
96
96
  }
97
97
  }
98
98
 
99
+ @mixin gl-md-flex-direction-column {
100
+ @include gl-media-breakpoint-up(md) {
101
+ @include gl-flex-direction-column;
102
+ }
103
+ }
104
+
99
105
  @mixin gl-xs-flex-direction-column {
100
106
  @include gl-media-breakpoint-down(sm) {
101
107
  flex-direction: column;
@@ -761,6 +761,12 @@
761
761
  }
762
762
  }
763
763
 
764
+ @mixin gl-xs-mb-4 {
765
+ @include gl-media-breakpoint-down(sm) {
766
+ @include gl-mb-4;
767
+ }
768
+ }
769
+
764
770
  @mixin gl-sm-ml-3 {
765
771
  @include gl-media-breakpoint-up(sm) {
766
772
  @include gl-ml-3;
@@ -1189,6 +1195,12 @@
1189
1195
  }
1190
1196
  }
1191
1197
 
1198
+ @mixin gl-md-pl-7 {
1199
+ @include gl-media-breakpoint-up(md) {
1200
+ @include gl-pl-7;
1201
+ }
1202
+ }
1203
+
1192
1204
  @mixin gl-lg-pt-0 {
1193
1205
  @include gl-media-breakpoint-up(lg) {
1194
1206
  @include gl-pt-0;
@@ -177,6 +177,10 @@
177
177
  line-height: $gl-line-height-36;
178
178
  }
179
179
 
180
+ @mixin gl-line-height-42 {
181
+ line-height: $gl-line-height-42;
182
+ }
183
+
180
184
  @mixin gl-line-height-52 {
181
185
  line-height: $gl-line-height-52;
182
186
  }
@@ -316,6 +316,7 @@ $gl-line-height-24: px-to-rem(24px);
316
316
  $gl-line-height-28: px-to-rem(28px);
317
317
  $gl-line-height-32: px-to-rem(32px);
318
318
  $gl-line-height-36: px-to-rem(36px);
319
+ $gl-line-height-42: px-to-rem(42px);
319
320
  $gl-line-height-44: px-to-rem(44px);
320
321
  $gl-line-height-52: px-to-rem(52px);
321
322