@gitlab/ui 78.19.0 → 79.1.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 22 Apr 2024 11:22:17 GMT
3
+ * Generated on Wed, 24 Apr 2024 11:54:45 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 22 Apr 2024 11:22:17 GMT
3
+ * Generated on Wed, 24 Apr 2024 11:54:45 GMT
4
4
  */
5
5
 
6
6
  :root.gl-dark {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 22 Apr 2024 11:22:17 GMT
3
+ * Generated on Wed, 24 Apr 2024 11:54:45 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#133a03";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 22 Apr 2024 11:22:17 GMT
3
+ * Generated on Wed, 24 Apr 2024 11:54:45 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#ddfab7";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Mon, 22 Apr 2024 11:22:17 GMT
3
+ // Generated on Wed, 24 Apr 2024 11:54:45 GMT
4
4
 
5
5
  $gl-text-tertiary: #737278 !default;
6
6
  $gl-text-secondary: #89888d !default;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Mon, 22 Apr 2024 11:22:17 GMT
3
+ // Generated on Wed, 24 Apr 2024 11:54:45 GMT
4
4
 
5
5
  $gl-text-tertiary: #89888d !default;
6
6
  $gl-text-secondary: #737278 !default;
@@ -5,12 +5,15 @@
5
5
  * @param {object} style The style attribute to apply to the container.
6
6
  * @return {function} The story decorator.
7
7
  */
8
- const makeContainer = style => Story => ({
9
- render(h) {
10
- return h('div', {
11
- style
12
- }, [h(Story())]);
13
- }
14
- });
8
+ const makeContainer = function (style) {
9
+ let tag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'div';
10
+ return Story => ({
11
+ render(h) {
12
+ return h(tag, {
13
+ style
14
+ }, [h(Story())]);
15
+ }
16
+ });
17
+ };
15
18
 
16
19
  export { makeContainer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "78.19.0",
3
+ "version": "79.1.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -7,4 +7,5 @@
7
7
  @include gl-text-decoration-none;
8
8
  @include gl-text-gray-900;
9
9
  @include gl-outline-0;
10
+ @include gl-focus($inset: true);
10
11
  }
@@ -4,6 +4,10 @@
4
4
  .gl-filtered-search-suggestion-list.dropdown-menu {
5
5
  @include gl-display-block;
6
6
  width: $gl-dropdown-width;
7
+ @include gl-p-2;
8
+ @include gl-border-gray-200;
9
+ @include gl-rounded-lg;
10
+ @include gl-shadow-md;
7
11
  }
8
12
 
9
13
  .gl-filtered-search-suggestion-list {
@@ -11,6 +15,20 @@
11
15
  @include gl-overflow-y-auto;
12
16
  max-height: $gl-max-dropdown-max-height;
13
17
 
18
+ .gl-filtered-search-suggestion,
19
+ .gl-filtered-search-suggestion > a,
20
+ .gl-filtered-search-suggestion > button {
21
+ border-radius: $gl-border-radius-base !important;
22
+ }
23
+
24
+ .gl-dropdown-divider {
25
+ @include gl-mx-n2;
26
+ }
27
+
28
+ .gl-spinner-container {
29
+ @include gl-my-2;
30
+ }
31
+
14
32
  .gl-filtered-search-suggestion {
15
33
  @include gl-p-0;
16
34
  }
@@ -1,8 +1,8 @@
1
+ import { disableControls } from '../../../../utils/stories_utils';
1
2
  import GlFormInput from '../form_input/form_input.vue';
2
3
  import GlFormTextarea from '../form_textarea/form_textarea.vue';
3
- import { disableControls } from '../../../../utils/stories_utils';
4
- import GlFormGroup from './form_group.vue';
5
4
  import readme from './form_group.md';
5
+ import GlFormGroup from './form_group.vue';
6
6
 
7
7
  const components = {
8
8
  GlFormGroup,
@@ -24,16 +24,24 @@ const generateProps = ({
24
24
  description,
25
25
  });
26
26
 
27
- const wrap = (template, bindings = '') => `
27
+ const makeBindings = (overrides = {}) =>
28
+ Object.entries({
29
+ ':id': "id + '_group'",
30
+ ':label-for': 'id',
31
+ ':label': 'label',
32
+ ':label-description': 'labelDescription',
33
+ ':optional': 'optional',
34
+ ':optional-text': 'optionalText',
35
+ ':description': 'description',
36
+ ...overrides,
37
+ })
38
+ .map(([key, value]) => `${key}="${value}"`)
39
+ .join(' ');
40
+
41
+ const wrap = (template, bindingsOverrides = {}) => `
28
42
  <gl-form-group
29
- :id="id + '_group'"
30
- :label="label"
31
- :label-description="labelDescription"
32
- :optional="optional"
33
- :optional-text="optionalText"
34
- :description="description"
35
- ${bindings}
36
- :label-for="id">
43
+ ${makeBindings(bindingsOverrides)}
44
+ >
37
45
  ${template}
38
46
  </gl-form-group>
39
47
  `;
@@ -78,14 +86,15 @@ export const WithValidations = (_args, { argTypes }) => ({
78
86
  components: { ...components, GlFormInput },
79
87
  template: `
80
88
  <div>
81
- ${wrap(
82
- '<gl-form-input :id="id" :state="false" />',
83
- 'invalid-feedback="This field is required."'
84
- )}
85
- ${wrap(
86
- '<gl-form-input :id="id" :state="true" value="Sidney Jones" />',
87
- 'valid-feedback="This field is valid."'
88
- )}
89
+ ${wrap('<gl-form-input :id="id + \'-name1\'" :state="false" />', {
90
+ 'invalid-feedback': 'This field is required.',
91
+ ':label-for': "id + '-name1'",
92
+ })}
93
+ ${wrap('<gl-form-input :id="id + \'-name2\'" :state="true" value="Sidney Jones" />', {
94
+ 'valid-feedback': 'This field is valid.',
95
+ ':id': "'group2'",
96
+ ':label-for': "id + '-name2'",
97
+ })}
89
98
  </div>
90
99
  `,
91
100
  });
@@ -1,6 +1,6 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import { nextTick } from 'vue';
3
- import { computePosition, autoUpdate, offset, autoPlacement } from '@floating-ui/dom';
3
+ import { computePosition, autoUpdate, offset, autoPlacement, shift } from '@floating-ui/dom';
4
4
  import {
5
5
  ARROW_DOWN,
6
6
  GL_DROPDOWN_FOCUS_CONTENT,
@@ -15,8 +15,9 @@ import GlBaseDropdown from './base_dropdown.vue';
15
15
 
16
16
  jest.mock('@floating-ui/dom');
17
17
  const mockStopAutoUpdate = jest.fn();
18
- offset.mockImplementation((options) => options);
19
- autoPlacement.mockImplementation((options) => options);
18
+ offset.mockImplementation((offsetOpts = {}) => ({ offsetOpts }));
19
+ autoPlacement.mockImplementation((autoPlacementOpts = {}) => ({ autoPlacementOpts }));
20
+ shift.mockImplementation((shiftOpts = {}) => ({ shiftOpts }));
20
21
 
21
22
  const DEFAULT_BTN_TOGGLE_CLASSES = [
22
23
  'btn',
@@ -29,7 +30,7 @@ const DEFAULT_BTN_TOGGLE_CLASSES = [
29
30
  describe('base dropdown', () => {
30
31
  let wrapper;
31
32
 
32
- const buildWrapper = (propsData, slots = {}, listeners = {}) => {
33
+ const buildWrapper = (propsData, { slots = {}, ...options } = {}) => {
33
34
  wrapper = mount(GlBaseDropdown, {
34
35
  propsData: {
35
36
  toggleId: 'dropdown-toggle-btn-1',
@@ -40,7 +41,7 @@ describe('base dropdown', () => {
40
41
  ...slots,
41
42
  },
42
43
  attachTo: document.body,
43
- listeners,
44
+ ...options,
44
45
  });
45
46
  };
46
47
 
@@ -99,6 +100,35 @@ describe('base dropdown', () => {
99
100
  autoUpdate.mockImplementation(jest.requireActual('@floating-ui/dom').autoUpdate);
100
101
  });
101
102
 
103
+ it('initializes Floating UI with a default boundary', async () => {
104
+ document.body.innerHTML = '<main><div></div></main>';
105
+
106
+ buildWrapper(undefined, {
107
+ attachTo: document.querySelector('main div'),
108
+ });
109
+ await findDefaultDropdownToggle().trigger('click');
110
+
111
+ expect(computePosition).toHaveBeenCalledWith(
112
+ findDefaultDropdownToggle().element,
113
+ findDropdownMenu().element,
114
+ {
115
+ placement: 'bottom-start',
116
+ strategy: 'absolute',
117
+ middleware: [
118
+ offset({ mainAxis: DEFAULT_OFFSET }),
119
+ autoPlacement({
120
+ alignment: 'start',
121
+ boundary: document.querySelector('main'),
122
+ allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
123
+ }),
124
+ shift(),
125
+ ],
126
+ }
127
+ );
128
+
129
+ document.body.innerHTML = '';
130
+ });
131
+
102
132
  it('initializes Floating UI with reference and floating elements and config for left-aligned menu', async () => {
103
133
  buildWrapper();
104
134
  await findDefaultDropdownToggle().trigger('click');
@@ -113,8 +143,10 @@ describe('base dropdown', () => {
113
143
  offset({ mainAxis: DEFAULT_OFFSET }),
114
144
  autoPlacement({
115
145
  alignment: 'start',
146
+ boundary: 'clippingAncestors',
116
147
  allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
117
148
  }),
149
+ shift(),
118
150
  ],
119
151
  }
120
152
  );
@@ -134,8 +166,10 @@ describe('base dropdown', () => {
134
166
  offset({ mainAxis: DEFAULT_OFFSET }),
135
167
  autoPlacement({
136
168
  alignment: undefined,
169
+ boundary: 'clippingAncestors',
137
170
  allowedPlacements: ['bottom', 'top'],
138
171
  }),
172
+ shift(),
139
173
  ],
140
174
  }
141
175
  );
@@ -155,8 +189,10 @@ describe('base dropdown', () => {
155
189
  offset({ mainAxis: DEFAULT_OFFSET }),
156
190
  autoPlacement({
157
191
  alignment: 'end',
192
+ boundary: 'clippingAncestors',
158
193
  allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
159
194
  }),
195
+ shift(),
160
196
  ],
161
197
  }
162
198
  );
@@ -176,8 +212,10 @@ describe('base dropdown', () => {
176
212
  offset({ mainAxis: DEFAULT_OFFSET }),
177
213
  autoPlacement({
178
214
  alignment: 'start',
215
+ boundary: 'clippingAncestors',
179
216
  allowedPlacements: ['right-start', 'right-end', 'left-start', 'left-end'],
180
217
  }),
218
+ shift(),
181
219
  ],
182
220
  }
183
221
  );
@@ -197,7 +235,7 @@ describe('base dropdown', () => {
197
235
  {
198
236
  placement: 'bottom-end',
199
237
  strategy: 'absolute',
200
- middleware: [offset(customOffset), autoPlacement(expect.any(Object))],
238
+ middleware: [offset(customOffset), autoPlacement(expect.any(Object)), shift()],
201
239
  }
202
240
  );
203
241
  });
@@ -245,7 +283,7 @@ describe('base dropdown', () => {
245
283
  const slots = { default: defaultContent };
246
284
 
247
285
  it('renders the content', () => {
248
- buildWrapper({}, slots);
286
+ buildWrapper({}, { slots });
249
287
  expect(wrapper.find('.gl-new-dropdown-inner').html()).toContain(defaultContent);
250
288
  });
251
289
  });
@@ -409,10 +447,12 @@ describe('base dropdown', () => {
409
447
 
410
448
  beforeEach(() => {
411
449
  event = undefined;
412
- buildWrapper(undefined, undefined, {
413
- [GL_DROPDOWN_BEFORE_CLOSE]({ originalEvent, preventDefault }) {
414
- event = originalEvent;
415
- preventDefault();
450
+ buildWrapper(undefined, {
451
+ listeners: {
452
+ [GL_DROPDOWN_BEFORE_CLOSE]({ originalEvent, preventDefault }) {
453
+ event = originalEvent;
454
+ preventDefault();
455
+ },
416
456
  },
417
457
  });
418
458
  });
@@ -467,7 +507,7 @@ describe('base dropdown', () => {
467
507
 
468
508
  beforeEach(() => {
469
509
  const slots = { toggle: toggleContent };
470
- buildWrapper({}, slots);
510
+ buildWrapper({}, { slots });
471
511
  });
472
512
 
473
513
  it('does not render default toggle button', () => {
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import uniqueId from 'lodash/uniqueId';
3
- import { computePosition, autoUpdate, offset, size, autoPlacement } from '@floating-ui/dom';
3
+ import { computePosition, autoUpdate, offset, size, autoPlacement, shift } from '@floating-ui/dom';
4
4
  import {
5
5
  buttonCategoryOptions,
6
6
  buttonSizeOptions,
@@ -9,6 +9,7 @@ import {
9
9
  dropdownVariantOptions,
10
10
  } from '../../../../utils/constants';
11
11
  import {
12
+ GL_DROPDOWN_BOUNDARY_SELECTOR,
12
13
  GL_DROPDOWN_SHOWN,
13
14
  GL_DROPDOWN_HIDDEN,
14
15
  GL_DROPDOWN_BEFORE_CLOSE,
@@ -272,8 +273,10 @@ export default {
272
273
  offset(this.offset),
273
274
  autoPlacement({
274
275
  alignment,
276
+ boundary: this.$el.closest(GL_DROPDOWN_BOUNDARY_SELECTOR) || 'clippingAncestors',
275
277
  allowedPlacements: dropdownAllowedAutoPlacements[this.placement],
276
278
  }),
279
+ shift(),
277
280
  size({
278
281
  apply: ({ availableHeight, elements }) => {
279
282
  const contentsEl = elements.floating.querySelector(`.${GL_DROPDOWN_CONTENTS_CLASS}`);
@@ -1,4 +1,5 @@
1
1
  // base dropdown events
2
+ export const GL_DROPDOWN_BOUNDARY_SELECTOR = 'main';
2
3
  export const GL_DROPDOWN_SHOWN = 'shown';
3
4
  export const GL_DROPDOWN_HIDDEN = 'hidden';
4
5
  export const GL_DROPDOWN_BEFORE_CLOSE = 'beforeClose';
@@ -343,3 +343,31 @@ export default {
343
343
  startOpened: true,
344
344
  },
345
345
  };
346
+
347
+ export const InMainWrapper = (args, { argTypes }) => ({
348
+ toggleId: TOGGLE_ID,
349
+ props: Object.keys(argTypes),
350
+ components: {
351
+ GlDisclosureDropdown,
352
+ GlTooltip,
353
+ },
354
+ template: `
355
+ <div>
356
+ ${template()}
357
+ <gl-tooltip :target="$options.toggleId" placement="right">
358
+ Automatic placement to stay inside &lt;main&gt; boundary
359
+ </gl-tooltip>
360
+ </div>
361
+ `,
362
+ });
363
+ InMainWrapper.args = {
364
+ items: mockItems,
365
+ icon: 'ellipsis_v',
366
+ noCaret: true,
367
+ toggleText: 'Disclosure',
368
+ textSrOnly: true,
369
+ toggleId: TOGGLE_ID,
370
+ };
371
+ InMainWrapper.decorators = [
372
+ makeContainer({ backgroundColor: '#efefef', textAlign: 'right', height: '200px' }, 'main'),
373
+ ];
@@ -5,8 +5,10 @@
5
5
  * @param {object} style The style attribute to apply to the container.
6
6
  * @return {function} The story decorator.
7
7
  */
8
- export const makeContainer = (style) => (Story) => ({
9
- render(h) {
10
- return h('div', { style }, [h(Story())]);
11
- },
12
- });
8
+ export const makeContainer =
9
+ (style, tag = 'div') =>
10
+ (Story) => ({
11
+ render(h) {
12
+ return h(tag, { style }, [h(Story())]);
13
+ },
14
+ });