@gitlab/ui 37.4.2 → 37.5.2

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/components/base/accordion/accordion_item.js +14 -1
  3. package/dist/components/base/datepicker/datepicker.documentation.js +2 -47
  4. package/dist/components/base/datepicker/datepicker.js +37 -1
  5. package/dist/components/base/dropdown/dropdown.js +2 -2
  6. package/dist/components/base/filtered_search/filtered_search.js +24 -8
  7. package/dist/components/base/filtered_search/filtered_search_term.js +10 -1
  8. package/dist/components/base/filtered_search/filtered_search_utils.js +2 -1
  9. package/dist/index.css +1 -1
  10. package/dist/index.css.map +1 -1
  11. package/documentation/documented_stories.js +1 -0
  12. package/package.json +2 -2
  13. package/src/components/base/accordion/accordion_item.spec.js +12 -0
  14. package/src/components/base/accordion/accordion_item.stories.js +4 -2
  15. package/src/components/base/accordion/accordion_item.vue +12 -1
  16. package/src/components/base/datepicker/datepicker.documentation.js +0 -59
  17. package/src/components/base/datepicker/datepicker.md +0 -6
  18. package/src/components/base/datepicker/datepicker.stories.js +97 -71
  19. package/src/components/base/datepicker/datepicker.vue +36 -1
  20. package/src/components/base/dropdown/dropdown.spec.js +29 -0
  21. package/src/components/base/dropdown/dropdown.stories.js +4 -1
  22. package/src/components/base/dropdown/dropdown.vue +5 -3
  23. package/src/components/base/dropdown/dropdown_section_header.scss +1 -0
  24. package/src/components/base/filtered_search/filtered_search.spec.js +106 -12
  25. package/src/components/base/filtered_search/filtered_search.stories.js +3 -0
  26. package/src/components/base/filtered_search/filtered_search.vue +21 -6
  27. package/src/components/base/filtered_search/filtered_search_term.spec.js +14 -9
  28. package/src/components/base/filtered_search/filtered_search_term.vue +7 -1
  29. package/src/components/base/filtered_search/filtered_search_utils.js +2 -0
  30. package/dist/components/base/datepicker/examples/datepicker.basic.example.js +0 -48
  31. package/dist/components/base/datepicker/examples/datepicker.custom_input.example.js +0 -48
  32. package/dist/components/base/datepicker/examples/datepicker.disabled.example.js +0 -48
  33. package/dist/components/base/datepicker/examples/datepicker.open_on_focus.example.js +0 -48
  34. package/dist/components/base/datepicker/examples/datepicker.with_clear_button.example.js +0 -48
  35. package/dist/components/base/datepicker/examples/index.js +0 -32
  36. package/src/components/base/datepicker/examples/datepicker.basic.example.vue +0 -12
  37. package/src/components/base/datepicker/examples/datepicker.custom_input.example.vue +0 -21
  38. package/src/components/base/datepicker/examples/datepicker.disabled.example.vue +0 -12
  39. package/src/components/base/datepicker/examples/datepicker.open_on_focus.example.vue +0 -12
  40. package/src/components/base/datepicker/examples/datepicker.with_clear_button.example.vue +0 -12
  41. package/src/components/base/datepicker/examples/index.js +0 -38
@@ -77,6 +77,7 @@ export const setupStorybookReadme = () =>
77
77
  'GlSorting',
78
78
  'GlSortingItem',
79
79
  'GlIcon',
80
+ 'GlDatepicker',
80
81
  'GlSafeLinkDirective',
81
82
  'GlDashboardSkeleton',
82
83
  'GlToggle',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "37.4.2",
3
+ "version": "37.5.2",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -83,7 +83,7 @@
83
83
  "@arkweid/lefthook": "^0.7.6",
84
84
  "@babel/core": "^7.10.2",
85
85
  "@babel/preset-env": "^7.10.2",
86
- "@gitlab/eslint-plugin": "10.0.2",
86
+ "@gitlab/eslint-plugin": "11.0.0",
87
87
  "@gitlab/stylelint-config": "4.0.0",
88
88
  "@gitlab/svgs": "2.6.0",
89
89
  "@rollup/plugin-commonjs": "^11.1.0",
@@ -8,6 +8,7 @@ import GlAccordionItem from './accordion_item.vue';
8
8
  describe('GlAccordionItem', () => {
9
9
  let wrapper;
10
10
  const defaultTitle = 'Item 1';
11
+ const titleVisible = 'Item 1 visible';
11
12
  const defaultSlot = 'Hello';
12
13
 
13
14
  const createComponent = (props = {}, { defaultHeaderLevel = 3, accordionSetId = false } = {}) => {
@@ -38,6 +39,17 @@ describe('GlAccordionItem', () => {
38
39
  expect(findButton().find('span').text()).toBe(defaultTitle);
39
40
  });
40
41
 
42
+ it('renders alternative button text when the content is visible and the titleVisible property is set', async () => {
43
+ createComponent({ titleVisible });
44
+
45
+ expect(findButton().find('span').text()).toBe(defaultTitle);
46
+
47
+ await waitForAnimationFrame();
48
+ await findButton().trigger('click');
49
+
50
+ expect(findButton().find('span').text()).toBe(titleVisible);
51
+ });
52
+
41
53
  it('renders the appropriate header element', () => {
42
54
  createComponent({}, { defaultHeaderLevel: 3 });
43
55
 
@@ -2,7 +2,7 @@ import readme from './accordion_item.md';
2
2
  import GlAccordionItem from './accordion_item.vue';
3
3
 
4
4
  const template = `
5
- <gl-accordion-item :title="title" :visible="visible" :header-level="headerLevel">
5
+ <gl-accordion-item :title="title" :titleVisible="titleVisible" :visible="visible" :header-level="headerLevel">
6
6
  Lorem ipsum dolor sit amet consectetur adipisicing elit. Delectus, maiores.
7
7
  </gl-accordion-item>
8
8
  `;
@@ -13,10 +13,12 @@ const generateProps = ({
13
13
  visible = defaultValue('visible'),
14
14
  headerLevel = 3,
15
15
  title = 'Accordion Item Title',
16
+ titleVisible = undefined,
16
17
  } = {}) => ({
17
18
  visible,
18
19
  headerLevel,
19
20
  title,
21
+ titleVisible,
20
22
  });
21
23
 
22
24
  const Template = (args) => ({
@@ -32,7 +34,7 @@ const Template = (args) => ({
32
34
  });
33
35
 
34
36
  export const Default = Template.bind({});
35
- Default.args = generateProps();
37
+ Default.args = generateProps({ titleVisible: 'Accordion Item Title Expanded' });
36
38
 
37
39
  export const InitiallyExpanded = Template.bind({});
38
40
  InitiallyExpanded.args = generateProps({ visible: true, title: 'Item Content Initially Expanded' });
@@ -24,6 +24,14 @@ export default {
24
24
  required: true,
25
25
  },
26
26
  /*
27
+ Used to set the title of accordion link when the content is visible
28
+ */
29
+ titleVisible: {
30
+ type: String,
31
+ default: null,
32
+ required: false,
33
+ },
34
+ /*
27
35
  When set, it will ensure the accordion item is initially visible
28
36
  */
29
37
  visible: {
@@ -60,6 +68,9 @@ When set, it will ensure the accordion item is initially visible
60
68
  icon() {
61
69
  return this.isVisible ? 'chevron-down' : 'chevron-right';
62
70
  },
71
+ buttonTitle() {
72
+ return this.isVisible && this.titleVisible ? this.titleVisible : this.title;
73
+ },
63
74
  },
64
75
  };
65
76
  </script>
@@ -73,7 +84,7 @@ When set, it will ensure the accordion item is initially visible
73
84
  button-text-classes="gl-display-flex"
74
85
  :icon="icon"
75
86
  >
76
- {{ title }}
87
+ {{ buttonTitle }}
77
88
  </gl-button>
78
89
  </component>
79
90
  <b-collapse
@@ -1,64 +1,5 @@
1
1
  import description from './datepicker.md';
2
- import examples from './examples';
3
2
 
4
3
  export default {
5
4
  description,
6
- examples,
7
- propsInfo: {
8
- target: {
9
- additionalInfo:
10
- 'Selector of element that triggers the datepicker. Defaults to the calendar icon. Pass `null` to trigger on input focus.',
11
- },
12
- container: {
13
- additionalInfo:
14
- 'DOM node to render calendar into. Defaults to the datepicker container. Pass `null` to use Pikaday default.',
15
- },
16
- disableDayFn: {
17
- additionalInfo:
18
- 'Accepts a function that accepts a date as argument and returns true if the date is disabled.',
19
- },
20
- autocomplete: {
21
- additionalInfo:
22
- 'Defaults to `off` when datepicker opens on focus, otherwise defaults to `null`.',
23
- },
24
- defaultDate: {
25
- additionalInfo: 'Use this prop to set the initial date for the datepicker.',
26
- },
27
- },
28
- events: [
29
- {
30
- event: 'input',
31
- description: 'Emitted when a new date has been selected.',
32
- args: [
33
- {
34
- arg: 'date',
35
- description: 'The selected date',
36
- },
37
- ],
38
- },
39
- {
40
- event: 'close',
41
- description: 'Emitted when the datepicker is hidden.',
42
- },
43
- {
44
- event: 'open',
45
- description: 'Emitted when the datepicker becomes visible.',
46
- },
47
- {
48
- event: 'draw',
49
- description: 'Emitted when the datepicker draws a new month.',
50
- },
51
- {
52
- event: 'clear',
53
- description: 'Emitted when the clear button is clicked.',
54
- },
55
- ],
56
- slots: [
57
- {
58
- name: 'default',
59
- description:
60
- '(optional) Input to display and bind the datepicker to. Defaults to `<gl-form-input />`',
61
- scopedProps: `{ formattedDate: string }`,
62
- },
63
- ],
64
5
  };
@@ -1,9 +1,3 @@
1
- # Datepicker
2
-
3
- <!-- STORY -->
4
-
5
- ## Usage
6
-
7
1
  Date picker allows users to choose and input a date by manually typing the date into the input field
8
2
  or by using a calendar-like dropdown.
9
3
 
@@ -1,84 +1,110 @@
1
- import { withKnobs, date } from '@storybook/addon-knobs';
2
- import { documentedStoriesOf } from '../../../../documentation/documented_stories';
1
+ import { GlDatepicker } from '../../../index';
2
+ import { disableControls } from '../../../utils/stories_utils';
3
3
  import { useFakeDate } from '../../../utils/use_fake_date';
4
4
  import readme from './datepicker.md';
5
- import GlDatepicker from './datepicker.vue';
6
5
 
7
- const currentYear = 2020;
6
+ const defaults = {
7
+ components: { GlDatepicker },
8
+ mixins: [useFakeDate()],
9
+ };
8
10
 
11
+ const currentYear = 2020;
9
12
  const defaultDate = new Date(currentYear, 0, 15);
10
13
  const defaultMinDate = new Date(currentYear, 0, 1);
11
14
  const defaultMaxDate = new Date(currentYear, 2, 31);
12
15
 
13
- function dateTypeKnob(name, defaultValue) {
14
- return new Date(date(name, defaultValue));
15
- }
16
+ const generateProps = ({ minDate = defaultMinDate, maxDate = defaultMaxDate } = {}) => ({
17
+ minDate,
18
+ maxDate,
19
+ });
16
20
 
17
- function generateProps() {
18
- return {
19
- minDate: {
20
- type: Date,
21
- default: dateTypeKnob('minDate', defaultMinDate),
22
- },
23
- maxDate: {
24
- type: Date,
25
- default: dateTypeKnob('maxDate', defaultMaxDate),
26
- },
27
- };
28
- }
21
+ const wrap = (template) => `<div style="height: 280px">${template}</div>`;
29
22
 
30
- documentedStoriesOf('base/datepicker', readme)
31
- .addDecorator(withKnobs)
32
- .add('default', () => ({
33
- props: generateProps(),
34
- components: {
35
- GlDatepicker,
36
- },
37
- mixins: [useFakeDate()],
38
- data() {
39
- return {
40
- value: defaultDate,
41
- };
42
- },
43
- template: `
44
- <gl-datepicker :max-date="maxDate" :min-date="minDate" :start-opened="true" v-model="value">
45
- </gl-datepicker>
46
- `,
47
- }))
48
- .add('custom trigger', () => ({
49
- props: generateProps(),
50
- components: {
51
- GlDatepicker,
52
- },
53
- data() {
54
- return {
55
- value: null,
56
- };
23
+ export const Default = (_args, { argTypes }) => ({
24
+ ...defaults,
25
+ props: Object.keys(argTypes),
26
+ data() {
27
+ return {
28
+ pickerValue: defaultDate,
29
+ };
30
+ },
31
+ template: wrap(`
32
+ <gl-datepicker :max-date="maxDate" :min-date="minDate" :start-opened="true" v-model="pickerValue" />
33
+ `),
34
+ });
35
+ Default.args = generateProps();
36
+
37
+ export const CustomTrigger = (_args, { argTypes }) => ({
38
+ ...defaults,
39
+ props: Object.keys(argTypes),
40
+ data() {
41
+ return {
42
+ pickerValue: null,
43
+ };
44
+ },
45
+ template: wrap(`
46
+ <div class="dropdown">
47
+ <button type="button" class="dropdown-menu-toggle">
48
+ <span class="dropdown-toggle-text"> Start date: {{value}} </span>
49
+ </button>
50
+ </div>
51
+ <gl-datepicker v-model="pickerValue" target=".dropdown-menu-toggle" :container="null" />
52
+ `),
53
+ });
54
+ CustomTrigger.args = generateProps();
55
+
56
+ export const WithClearButton = (_args, { argTypes }) => ({
57
+ ...defaults,
58
+ props: Object.keys(argTypes),
59
+ data() {
60
+ return {
61
+ pickerValue: defaultDate,
62
+ };
63
+ },
64
+ template: wrap(`
65
+ <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="pickerValue" />
66
+ `),
67
+ });
68
+ WithClearButton.args = generateProps();
69
+
70
+ export default {
71
+ title: 'base/datepicker',
72
+ component: GlDatepicker,
73
+ parameters: {
74
+ docs: {
75
+ description: {
76
+ component: readme,
77
+ },
57
78
  },
58
- template: `
59
- <div>
60
- <div class="dropdown">
61
- <button type="button" class="dropdown-menu-toggle">
62
- <span class="dropdown-toggle-text"> Start date: {{value}} </span>
63
- </button>
64
- </div>
65
- <gl-datepicker v-model="value" target=".dropdown-menu-toggle" :container="null" />
66
- </div>
67
- `,
68
- }))
69
- .add('with clear button', () => ({
70
- props: generateProps(),
71
- components: {
72
- GlDatepicker,
79
+ },
80
+ argTypes: {
81
+ ...disableControls([
82
+ 'target',
83
+ 'container',
84
+ 'value',
85
+ 'startRange',
86
+ 'endRange',
87
+ 'disableDayFn',
88
+ 'firstDay',
89
+ 'arialLabel',
90
+ 'placeholder',
91
+ 'autocomplete',
92
+ 'disabled',
93
+ 'displayField',
94
+ 'startOpened',
95
+ 'defaultDate',
96
+ 'i18n',
97
+ 'theme',
98
+ 'showClearButton',
99
+ 'inputId',
100
+ 'inputLabel',
101
+ 'inputName',
102
+ ]),
103
+ minDate: {
104
+ control: { type: 'date' },
73
105
  },
74
- mixins: [useFakeDate()],
75
- data() {
76
- return {
77
- value: defaultDate,
78
- };
106
+ maxDate: {
107
+ control: { type: 'date' },
79
108
  },
80
- template: `
81
- <gl-datepicker showClearButton :max-date="maxDate" :min-date="minDate" v-model="value">
82
- </gl-datepicker>
83
- `,
84
- }));
109
+ },
110
+ };
@@ -45,11 +45,17 @@ export default {
45
45
  GlButton,
46
46
  },
47
47
  props: {
48
+ /**
49
+ * Selector of element that triggers the datepicker. Defaults to the calendar icon. Pass `null` to trigger on input focus.
50
+ */
48
51
  target: {
49
52
  type: String,
50
53
  required: false,
51
54
  default: '',
52
55
  },
56
+ /**
57
+ * DOM node to render calendar into. Defaults to the datepicker container. Pass `null` to use Pikaday default.
58
+ */
53
59
  container: {
54
60
  type: String,
55
61
  required: false,
@@ -80,6 +86,9 @@ export default {
80
86
  required: false,
81
87
  default: null,
82
88
  },
89
+ /**
90
+ * Accepts a function that accepts a date as argument and returns true if the date is disabled.
91
+ */
83
92
  disableDayFn: {
84
93
  type: Function,
85
94
  required: false,
@@ -100,6 +109,9 @@ export default {
100
109
  required: false,
101
110
  default: defaultDateFormat,
102
111
  },
112
+ /**
113
+ * Defaults to `off` when datepicker opens on focus, otherwise defaults to `null`.
114
+ */
103
115
  autocomplete: {
104
116
  type: String,
105
117
  required: false,
@@ -120,6 +132,9 @@ export default {
120
132
  required: false,
121
133
  default: false,
122
134
  },
135
+ /**
136
+ * Use this prop to set the initial date for the datepicker.
137
+ */
123
138
  defaultDate: {
124
139
  type: Date,
125
140
  required: false,
@@ -274,19 +289,35 @@ export default {
274
289
  },
275
290
  methods: {
276
291
  selected(date) {
292
+ /**
293
+ * Emitted when a new date has been selected.
294
+ * @property {Date} date The selected date
295
+ */
277
296
  this.$emit('input', date);
278
297
  },
279
298
  closed() {
299
+ /**
300
+ * Emitted when the datepicker is hidden.
301
+ */
280
302
  this.$emit('close');
281
303
  },
282
304
  opened() {
305
+ /**
306
+ * Emitted when the datepicker becomes visible.
307
+ */
283
308
  this.$emit('open');
284
309
  },
285
310
  cleared() {
286
311
  this.textInput = '';
312
+ /**
313
+ * Emitted when the clear button is clicked.
314
+ */
287
315
  this.$emit('clear');
288
316
  },
289
317
  draw() {
318
+ /**
319
+ * Emitted when the datepicker draws a new month.
320
+ */
290
321
  this.$emit('monthChange');
291
322
  },
292
323
  onKeydown() {
@@ -302,7 +333,11 @@ export default {
302
333
 
303
334
  <template>
304
335
  <div class="gl-datepicker d-inline-block">
305
- <div v-if="showDefaultField" class="position-relative">
336
+ <div v-if="showDefaultField" class="gl-relative">
337
+ <!--
338
+ @slot (optional) Input to display and bind the datepicker to. Defaults to `<gl-form-input />`
339
+ @binding {string} formattedDate
340
+ -->
306
341
  <slot :formatted-date="formattedDate">
307
342
  <gl-form-input
308
343
  :id="inputId"
@@ -336,6 +336,7 @@ describe('new dropdown', () => {
336
336
  const slots = { 'button-text': mockComponent };
337
337
  buildWrapper({ loading: true, icon: 'close' }, slots);
338
338
  expect(wrapper.findComponent(mockComponent).exists()).toBe(true);
339
+ expect(wrapper.text()).toBe('mock');
339
340
  expect(findLoadingIcon().exists()).toBe(true);
340
341
  expect(findIcon().exists()).toBe(true);
341
342
  expect(findCaret().exists()).toBe(true);
@@ -345,12 +346,40 @@ describe('new dropdown', () => {
345
346
  const slots = { 'button-content': mockComponent };
346
347
  buildWrapper({ loading: true, icon: 'close' }, slots);
347
348
  expect(wrapper.findComponent(mockComponent).exists()).toBe(true);
349
+ expect(wrapper.text()).toBe('mock');
348
350
  expect(findLoadingIcon().exists()).toBe(false);
349
351
  expect(findIcon().exists()).toBe(false);
350
352
  expect(findCaret().exists()).toBe(false);
351
353
  });
352
354
  });
353
355
 
356
+ describe('icon only dropdown', () => {
357
+ it('shows the icon and dropdown caret', () => {
358
+ buildWrapper({ icon: 'paper-airplane' });
359
+ expect(wrapper.text()).toBe('');
360
+ expect(findLoadingIcon().exists()).toBe(false);
361
+ expect(findIcon().exists()).toBe(true);
362
+ expect(findCaret().exists()).toBe(true);
363
+ });
364
+
365
+ it('shows text for screen readers', () => {
366
+ buildWrapper({ icon: 'paper-airplane', text: 'screenreader button text', textSrOnly: true });
367
+ expect(wrapper.text()).toBe('screenreader button text');
368
+ expect(wrapper.find('span').classes()).toContain('gl-sr-only');
369
+ expect(findLoadingIcon().exists()).toBe(false);
370
+ expect(findIcon().exists()).toBe(true);
371
+ expect(findCaret().exists()).toBe(true);
372
+ });
373
+
374
+ it('shows loading spinner instead of icon when loading', () => {
375
+ buildWrapper({ icon: 'paper-airplane', loading: true });
376
+ expect(wrapper.text()).toBe('');
377
+ expect(findLoadingIcon().exists()).toBe(true);
378
+ expect(findIcon().exists()).toBe(false);
379
+ expect(findCaret().exists()).toBe(true);
380
+ });
381
+ });
382
+
354
383
  describe('Clear all button', () => {
355
384
  it('is not visible by default', () => {
356
385
  buildWrapper({});
@@ -274,7 +274,10 @@ documentedStoriesOf('base/dropdown', readme)
274
274
  template: wrap`
275
275
  <gl-dropdown-section-header>Header title</gl-dropdown-section-header>
276
276
  <gl-dropdown-item>First item</gl-dropdown-item>
277
- <gl-dropdown-item>Second item</gl-dropdown-item>`,
277
+ <gl-dropdown-item>Second item</gl-dropdown-item>
278
+ <gl-dropdown-section-header>I am a really long header title which should wrap</gl-dropdown-section-header>
279
+ <gl-dropdown-item>Third item</gl-dropdown-item>
280
+ <gl-dropdown-item>Fourth item</gl-dropdown-item>`,
278
281
  mounted() {
279
282
  clickDropdown(this);
280
283
  },
@@ -151,7 +151,9 @@ export default {
151
151
  return !this.split;
152
152
  },
153
153
  isIconOnly() {
154
- return Boolean(this.icon && (!this.text?.length || this.textSrOnly));
154
+ return Boolean(
155
+ this.icon && (!this.text?.length || this.textSrOnly) && !this.hasSlotContents('button-text')
156
+ );
155
157
  },
156
158
  isIconWithText() {
157
159
  return Boolean(this.icon && this.text?.length && !this.textSrOnly);
@@ -282,8 +284,8 @@ export default {
282
284
  </div>
283
285
  <template #button-content>
284
286
  <slot name="button-content">
285
- <gl-loading-icon v-if="loading" class="gl-mr-2" />
286
- <gl-icon v-if="icon" class="dropdown-icon" :name="icon" />
287
+ <gl-loading-icon v-if="loading" :class="{ 'gl-mr-2': !isIconOnly }" />
288
+ <gl-icon v-if="icon && !(isIconOnly && loading)" class="dropdown-icon" :name="icon" />
287
289
  <span class="gl-new-dropdown-button-text" :class="{ 'gl-sr-only': textSrOnly }">
288
290
  <slot name="button-text">{{ buttonText }}</slot>
289
291
  </span>
@@ -6,6 +6,7 @@
6
6
  @include gl-font-weight-bold;
7
7
  @include gl-py-3;
8
8
  @include gl-px-4;
9
+ @include gl-white-space-normal;
9
10
  }
10
11
 
11
12
  /* Styling when the component is used on its own */