@gitlab/ui 55.2.0 → 55.3.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": "55.2.0",
3
+ "version": "55.3.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -87,18 +87,18 @@
87
87
  "@gitlab/eslint-plugin": "18.1.0",
88
88
  "@gitlab/fonts": "^1.2.0",
89
89
  "@gitlab/stylelint-config": "4.1.0",
90
- "@gitlab/svgs": "3.19.0",
90
+ "@gitlab/svgs": "3.20.0",
91
91
  "@rollup/plugin-commonjs": "^11.1.0",
92
92
  "@rollup/plugin-node-resolve": "^7.1.3",
93
93
  "@rollup/plugin-replace": "^2.3.2",
94
- "@storybook/addon-a11y": "6.5.15",
95
- "@storybook/addon-docs": "6.5.15",
96
- "@storybook/addon-essentials": "6.5.15",
97
- "@storybook/addon-storyshots": "6.5.15",
98
- "@storybook/addon-storyshots-puppeteer": "6.5.15",
99
- "@storybook/addon-viewport": "6.5.15",
100
- "@storybook/theming": "6.5.15",
101
- "@storybook/vue": "6.5.15",
94
+ "@storybook/addon-a11y": "6.5.16",
95
+ "@storybook/addon-docs": "6.5.16",
96
+ "@storybook/addon-essentials": "6.5.16",
97
+ "@storybook/addon-storyshots": "6.5.16",
98
+ "@storybook/addon-storyshots-puppeteer": "6.5.16",
99
+ "@storybook/addon-viewport": "6.5.16",
100
+ "@storybook/theming": "6.5.16",
101
+ "@storybook/vue": "6.5.16",
102
102
  "@vue/compat": "^3.2.40",
103
103
  "@vue/compiler-sfc": "^3.2.40",
104
104
  "@vue/test-utils": "1.3.0",
@@ -123,9 +123,9 @@
123
123
  "glob": "^7.2.0",
124
124
  "identity-obj-proxy": "^3.0.0",
125
125
  "inquirer-select-directory": "^1.2.0",
126
- "jest": "^29.4.1",
127
- "jest-circus": "29.4.1",
128
- "jest-environment-jsdom": "29.4.1",
126
+ "jest": "^29.4.2",
127
+ "jest-circus": "29.4.2",
128
+ "jest-environment-jsdom": "29.4.2",
129
129
  "markdownlint-cli": "^0.29.0",
130
130
  "mockdate": "^2.0.5",
131
131
  "npm-run-all": "^4.1.5",
@@ -8,12 +8,13 @@
8
8
  @include gl-pt-0;
9
9
  @include gl-pb-3;
10
10
 
11
- > .form-text {
11
+ > .label-description {
12
12
  @include gl-mt-3;
13
13
  }
14
14
  }
15
15
 
16
- .optional-label {
16
+ .optional-label,
17
+ .label-description {
17
18
  @include gl-font-weight-normal;
18
19
  }
19
20
 
@@ -1,12 +1,10 @@
1
1
  <script>
2
2
  import { BFormGroup } from 'bootstrap-vue';
3
3
  import { isString, isArray, isPlainObject } from 'lodash';
4
- import GlFormText from '../form_text/form_text.vue';
5
4
 
6
5
  export default {
7
6
  components: {
8
7
  BFormGroup,
9
- GlFormText,
10
8
  },
11
9
  inheritAttrs: false,
12
10
  props: {
@@ -63,9 +61,9 @@ export default {
63
61
  optionalText
64
62
  }}</span>
65
63
  </slot>
66
- <gl-form-text v-if="hasLabelDescription" data-testid="label-description">
64
+ <div v-if="hasLabelDescription" data-testid="label-description" class="label-description">
67
65
  <slot name="label-description">{{ labelDescription }}</slot>
68
- </gl-form-text>
66
+ </div>
69
67
  </template>
70
68
 
71
69
  <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
@@ -25,42 +25,6 @@ import {
25
25
  mockGroupsCustomItem,
26
26
  } from './mock_data';
27
27
 
28
- const defaultValue = (prop) => GlDisclosureDropdown.props[prop].default;
29
-
30
- const generateProps = ({
31
- items = mockItems,
32
- category = defaultValue('category'),
33
- variant = defaultValue('variant'),
34
- size = defaultValue('size'),
35
- disabled = defaultValue('disabled'),
36
- loading = defaultValue('loading'),
37
- noCaret = defaultValue('noCaret'),
38
- placement = defaultValue('placement'),
39
- toggleId = defaultValue('toggleId')(),
40
- toggleText,
41
- textSrOnly = defaultValue('textSrOnly'),
42
- icon = '',
43
- toggleAriaLabelledBy,
44
- listAriaLabelledBy,
45
- startOpened = true,
46
- } = {}) => ({
47
- items,
48
- category,
49
- variant,
50
- size,
51
- disabled,
52
- loading,
53
- noCaret,
54
- placement,
55
- toggleId,
56
- toggleText,
57
- textSrOnly,
58
- icon,
59
- toggleAriaLabelledBy,
60
- listAriaLabelledBy,
61
- startOpened,
62
- });
63
-
64
28
  const makeBindings = (overrides = {}) =>
65
29
  Object.entries({
66
30
  ':items': 'items',
@@ -119,13 +83,14 @@ export const Default = (args, { argTypes }) => ({
119
83
  </div>
120
84
  `,
121
85
  });
122
- Default.args = generateProps({
86
+ Default.args = {
87
+ items: mockItems,
123
88
  icon: 'ellipsis_v',
124
89
  noCaret: true,
125
90
  toggleText: 'Disclosure',
126
91
  textSrOnly: true,
127
92
  toggleId: TOGGLE_ID,
128
- });
93
+ };
129
94
  Default.decorators = [makeContainer({ height: '200px' })];
130
95
 
131
96
  export const CustomListItem = (args, { argTypes }) => ({
@@ -162,11 +127,11 @@ export const CustomListItem = (args, { argTypes }) => ({
162
127
  ),
163
128
  });
164
129
 
165
- CustomListItem.args = generateProps({
130
+ CustomListItem.args = {
166
131
  items: mockItemsCustomItem,
167
132
  toggleText: 'Merge requests',
168
133
  placement: 'center',
169
- });
134
+ };
170
135
  CustomListItem.decorators = [makeContainer({ height: '200px' })];
171
136
 
172
137
  const makeGroupedExample = (changes) => {
@@ -190,7 +155,7 @@ const makeGroupedExample = (changes) => {
190
155
  ...changes,
191
156
  });
192
157
 
193
- story.args = generateProps({ items: mockGroups });
158
+ story.args = { items: mockGroups };
194
159
  story.decorators = [makeContainer({ height: '340px' })];
195
160
 
196
161
  return story;
@@ -199,12 +164,12 @@ const makeGroupedExample = (changes) => {
199
164
  export const Groups = makeGroupedExample({
200
165
  template: template(''),
201
166
  });
202
- Groups.args = generateProps({
167
+ Groups.args = {
203
168
  icon: 'plus-square',
204
169
  items: mockGroups,
205
170
  toggleText: 'Create new',
206
171
  textSrOnly: true,
207
- });
172
+ };
208
173
 
209
174
  export const CustomGroupsAndItems = (args, { argTypes }) => ({
210
175
  props: Object.keys(argTypes),
@@ -245,10 +210,10 @@ export const CustomGroupsAndItems = (args, { argTypes }) => ({
245
210
  ),
246
211
  });
247
212
 
248
- CustomGroupsAndItems.args = generateProps({
213
+ CustomGroupsAndItems.args = {
249
214
  items: mockGroupsCustomItem,
250
215
  toggleText: 'Merge requests',
251
- });
216
+ };
252
217
  CustomGroupsAndItems.decorators = [makeContainer({ height: '200px' })];
253
218
 
254
219
  export const CustomGroupsItemsAndToggle = makeGroupedExample({
@@ -313,12 +278,11 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
313
278
  },
314
279
  groups: mockProfileGroups,
315
280
  });
316
- CustomGroupsItemsAndToggle.args = generateProps({
281
+ CustomGroupsItemsAndToggle.args = {
317
282
  icon: 'plus-square',
318
283
  toggleText: 'User profile menu',
319
284
  textSrOnly: true,
320
- items: null,
321
- });
285
+ };
322
286
  CustomGroupsItemsAndToggle.decorators = [makeContainer({ height: '400px' })];
323
287
 
324
288
  export const MiscellaneousContent = (args, { argTypes }) => ({
@@ -338,12 +302,11 @@ export const MiscellaneousContent = (args, { argTypes }) => ({
338
302
  ),
339
303
  });
340
304
 
341
- MiscellaneousContent.args = generateProps({
305
+ MiscellaneousContent.args = {
342
306
  icon: 'doc-text',
343
307
  toggleText: 'Miscellaneous content',
344
308
  textSrOnly: true,
345
- items: null,
346
- });
309
+ };
347
310
  MiscellaneousContent.decorators = [makeContainer({ height: '200px' })];
348
311
 
349
312
  export default {
@@ -382,4 +345,7 @@ export default {
382
345
  },
383
346
  },
384
347
  },
348
+ args: {
349
+ startOpened: true,
350
+ },
385
351
  };
@@ -44,7 +44,7 @@
44
44
  }
45
45
 
46
46
  .gl-toggle-label,
47
- .gl-help-label {
47
+ .gl-description-label {
48
48
  @include gl-text-gray-500;
49
49
  }
50
50
  }
@@ -73,7 +73,6 @@
73
73
  }
74
74
 
75
75
  .gl-toggle-label {
76
- @include gl-mb-3;
77
76
  @include gl-font-weight-bold;
78
77
  }
79
78
 
@@ -8,6 +8,7 @@ describe('toggle', () => {
8
8
  let wrapper;
9
9
 
10
10
  const label = 'toggle label';
11
+ const descriptionText = 'description text';
11
12
  const helpText = 'help text';
12
13
 
13
14
  const createWrapper = (props = {}, options = {}) => {
@@ -21,6 +22,7 @@ describe('toggle', () => {
21
22
  };
22
23
 
23
24
  const findButton = () => wrapper.find('button');
25
+ const findDescriptionElement = () => wrapper.find('[data-testid="toggle-description"]');
24
26
  const findHelpElement = () => wrapper.find('[data-testid="toggle-help"]');
25
27
 
26
28
  it('has role=switch', () => {
@@ -85,6 +87,28 @@ describe('toggle', () => {
85
87
  });
86
88
  });
87
89
 
90
+ describe.each`
91
+ state | description | props | options
92
+ ${'with description'} | ${descriptionText} | ${{ description: descriptionText }} | ${undefined}
93
+ ${'with description in slot'} | ${descriptionText} | ${undefined} | ${{ slots: { description: descriptionText } }}
94
+ ${'without description'} | ${undefined} | ${undefined} | ${undefined}
95
+ ${'with description and labelPosition left'} | ${undefined} | ${{ desciption: descriptionText, labelPosition: toggleLabelPosition.left }} | ${undefined}
96
+ `('$state', ({ description, props, options }) => {
97
+ beforeEach(() => {
98
+ createWrapper(props, options);
99
+ });
100
+
101
+ if (description) {
102
+ it('shows description', () => {
103
+ expect(findDescriptionElement().text()).toBe(description);
104
+ });
105
+ } else {
106
+ it('does not show description', () => {
107
+ expect(findDescriptionElement().exists()).toBe(false);
108
+ });
109
+ }
110
+ });
111
+
88
112
  describe.each`
89
113
  state | help | props | options | getAriaDescribedBy
90
114
  ${'with help'} | ${helpText} | ${{ help: helpText }} | ${undefined} | ${() => findHelpElement().attributes('id')}
@@ -5,7 +5,9 @@ import readme from './toggle.md';
5
5
 
6
6
  const defaultValue = (prop) => GlToggle.props[prop].default;
7
7
 
8
- const longHelp = `This is a toggle component with a long help message.
8
+ const withDescription = 'A dark color theme that is easier on the eyes.';
9
+
10
+ const longHelp = `This is a toggle component with a long help message.
9
11
  You can notice how the text wraps when the width of the container
10
12
  is not enough to fix the entire text.`;
11
13
 
@@ -15,7 +17,8 @@ const generateProps = ({
15
17
  isLoading = defaultValue('isLoading'),
16
18
  label = 'Dark mode',
17
19
  labelId = 'dark-mode-toggle',
18
- help = 'Toggle dark mode for the website',
20
+ description = '',
21
+ help = 'Toggle dark mode for the website.',
19
22
  labelPosition = defaultValue('labelPosition'),
20
23
  } = {}) => ({
21
24
  value,
@@ -23,6 +26,7 @@ const generateProps = ({
23
26
  isLoading,
24
27
  label,
25
28
  labelId,
29
+ description,
26
30
  help,
27
31
  labelPosition,
28
32
  });
@@ -35,6 +39,7 @@ const Template = (args, { argTypes }) => ({
35
39
  <gl-toggle
36
40
  v-model="value"
37
41
  :disabled="disabled"
42
+ :description="description"
38
43
  :help="help"
39
44
  :label-id="labelId"
40
45
  :is-loading="isLoading"
@@ -47,11 +52,21 @@ const Template = (args, { argTypes }) => ({
47
52
  export const Default = Template.bind({});
48
53
  Default.args = generateProps();
49
54
 
55
+ export const WithDescription = Template.bind({});
56
+ WithDescription.args = generateProps({
57
+ description: withDescription,
58
+ });
59
+
50
60
  export const WithLongHelp = Template.bind({});
51
61
  WithLongHelp.args = generateProps({
52
62
  help: longHelp,
53
63
  });
54
64
 
65
+ export const LabelPositionLeft = Template.bind({});
66
+ LabelPositionLeft.args = generateProps({
67
+ labelPosition: 'left',
68
+ });
69
+
55
70
  export default {
56
71
  title: 'base/toggle',
57
72
  component: GlToggle,
@@ -71,6 +86,9 @@ export default {
71
86
  label: {
72
87
  control: 'text',
73
88
  },
89
+ description: {
90
+ control: 'text',
91
+ },
74
92
  help: {
75
93
  control: 'text',
76
94
  },
@@ -55,6 +55,14 @@ export default {
55
55
  type: String,
56
56
  required: true,
57
57
  },
58
+ /**
59
+ * The toggle's description.
60
+ */
61
+ description: {
62
+ type: String,
63
+ required: false,
64
+ default: undefined,
65
+ },
58
66
  /**
59
67
  * A help text to be shown below the toggle.
60
68
  */
@@ -81,10 +89,20 @@ export default {
81
89
  };
82
90
  },
83
91
  computed: {
92
+ shouldRenderDescription() {
93
+ // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
94
+ return Boolean(this.$scopedSlots.description || this.description) && this.isVerticalLayout;
95
+ },
84
96
  shouldRenderHelp() {
85
97
  // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
86
98
  return Boolean(this.$slots.help || this.help) && this.isVerticalLayout;
87
99
  },
100
+ toggleClasses() {
101
+ return [
102
+ { 'gl-sr-only': this.labelPosition === 'hidden' },
103
+ this.shouldRenderDescription ? 'gl-mb-2' : 'gl-mb-3',
104
+ ];
105
+ },
88
106
  icon() {
89
107
  return this.value ? 'mobile-issue-close' : 'close';
90
108
  },
@@ -132,13 +150,21 @@ export default {
132
150
  >
133
151
  <span
134
152
  :id="labelId"
135
- :class="{ 'gl-sr-only': labelPosition === 'hidden' }"
153
+ :class="toggleClasses"
136
154
  class="gl-toggle-label gl-flex-shrink-0"
137
155
  data-testid="toggle-label"
138
156
  >
139
157
  <!-- @slot The toggle's label. -->
140
158
  <slot name="label">{{ label }}</slot>
141
159
  </span>
160
+ <span
161
+ v-if="shouldRenderDescription"
162
+ class="gl-description-label gl-mb-3"
163
+ data-testid="toggle-description"
164
+ >
165
+ <!-- @slot A description text to be shown below the label. -->
166
+ <slot name="description">{{ description }}</slot>
167
+ </span>
142
168
  <input v-if="name" :name="name" :value="value" type="hidden" />
143
169
  <button
144
170
  role="switch"