@gitlab/ui 64.0.1 → 64.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "64.0.1",
3
+ "version": "64.1.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -86,7 +86,7 @@
86
86
  "@arkweid/lefthook": "0.7.7",
87
87
  "@babel/core": "^7.22.1",
88
88
  "@babel/preset-env": "^7.22.2",
89
- "@babel/preset-react": "^7.22.0",
89
+ "@babel/preset-react": "^7.22.3",
90
90
  "@gitlab/eslint-plugin": "19.0.0",
91
91
  "@gitlab/fonts": "^1.2.0",
92
92
  "@gitlab/stylelint-config": "4.1.0",
@@ -1,9 +1,16 @@
1
1
  .gl-filtered-search-scrollable-container {
2
2
  @include gl-bg-white;
3
+ @include gl-display-flex;
4
+ @include gl-flex-grow-1;
5
+ @include gl-flex-basis-0;
6
+ @include gl-overflow-hidden;
7
+ @include gl-align-items-center;
8
+ @include gl-py-2;
9
+ @include gl-pl-4;
10
+ @include gl-pr-7;
3
11
  @include gl-inset-border-1-gray-400;
12
+ @include gl-border-none;
4
13
  @include gl-rounded-left-base;
5
- position: relative;
6
- flex-grow: 1;
7
14
 
8
15
  .input-group-prepend + & {
9
16
  @include gl-rounded-left-none;
@@ -12,20 +19,7 @@
12
19
 
13
20
  .gl-filtered-search-scrollable {
14
21
  @include gl-display-flex;
15
- @include gl-flex-grow-1;
16
- @include gl-flex-basis-0;
17
- @include gl-align-items-center;
18
- @include gl-py-2;
19
- @include gl-pl-4;
20
- @include gl-border-none;
21
- position: absolute;
22
- max-width: calc(100% - #{$gl-spacing-scale-7});
23
- overflow: hidden;
24
- overflow-x: auto;
25
-
26
- // prevent overlapping with left border
27
- left: $gl-border-size-1;
28
- @include gl-display-flex;
22
+ @include gl-overflow-y-auto;
29
23
  }
30
24
 
31
25
  .gl-filtered-search-item {
@@ -103,6 +103,7 @@ describe('base dropdown', () => {
103
103
  findDropdownMenu().element,
104
104
  {
105
105
  placement: 'bottom-start',
106
+ strategy: 'absolute',
106
107
  middleware: [offset({ mainAxis: DEFAULT_OFFSET })],
107
108
  }
108
109
  );
@@ -115,7 +116,11 @@ describe('base dropdown', () => {
115
116
  expect(computePosition).toHaveBeenCalledWith(
116
117
  findDefaultDropdownToggle().element,
117
118
  findDropdownMenu().element,
118
- { placement: 'bottom', middleware: [offset({ mainAxis: DEFAULT_OFFSET })] }
119
+ {
120
+ placement: 'bottom',
121
+ strategy: 'absolute',
122
+ middleware: [offset({ mainAxis: DEFAULT_OFFSET })],
123
+ }
119
124
  );
120
125
  });
121
126
 
@@ -126,7 +131,11 @@ describe('base dropdown', () => {
126
131
  expect(computePosition).toHaveBeenCalledWith(
127
132
  findDefaultDropdownToggle().element,
128
133
  findDropdownMenu().element,
129
- { placement: 'bottom-end', middleware: [offset({ mainAxis: DEFAULT_OFFSET })] }
134
+ {
135
+ placement: 'bottom-end',
136
+ strategy: 'absolute',
137
+ middleware: [offset({ mainAxis: DEFAULT_OFFSET })],
138
+ }
130
139
  );
131
140
  });
132
141
 
@@ -143,10 +152,45 @@ describe('base dropdown', () => {
143
152
  findDropdownMenu().element,
144
153
  {
145
154
  placement: 'bottom-end',
155
+ strategy: 'absolute',
146
156
  middleware: [offset(customOffset)],
147
157
  }
148
158
  );
149
159
  });
160
+
161
+ describe('positioningStrategy', () => {
162
+ it('uses the absolute positioning strategy by default', async () => {
163
+ buildWrapper();
164
+
165
+ await findDefaultDropdownToggle().trigger('click');
166
+
167
+ expect(computePosition).toHaveBeenCalledWith(
168
+ findDefaultDropdownToggle().element,
169
+ findDropdownMenu().element,
170
+ expect.objectContaining({
171
+ strategy: 'absolute',
172
+ })
173
+ );
174
+ expect(findDropdownMenu().classes()).toContain('gl-absolute');
175
+ });
176
+
177
+ it('applies the fixed positioning strategy properly', async () => {
178
+ buildWrapper({
179
+ positioningStrategy: 'fixed',
180
+ });
181
+
182
+ await findDefaultDropdownToggle().trigger('click');
183
+
184
+ expect(computePosition).toHaveBeenCalledWith(
185
+ findDefaultDropdownToggle().element,
186
+ findDropdownMenu().element,
187
+ expect.objectContaining({
188
+ strategy: 'fixed',
189
+ })
190
+ );
191
+ expect(findDropdownMenu().classes()).toContain('gl-fixed');
192
+ });
193
+ });
150
194
  });
151
195
  });
152
196
 
@@ -15,6 +15,8 @@ import {
15
15
  SPACE,
16
16
  ARROW_DOWN,
17
17
  GL_DROPDOWN_CONTENTS_CLASS,
18
+ POSITION_ABSOLUTE,
19
+ POSITION_FIXED,
18
20
  } from '../constants';
19
21
  import { logWarning, isElementTabbable, isElementFocusable } from '../../../../utils/utils';
20
22
 
@@ -133,6 +135,17 @@ export default {
133
135
  required: false,
134
136
  default: false,
135
137
  },
138
+ /**
139
+ * Strategy to be applied by computePosition. If this is set to fixed, the dropdown's position
140
+ * needs to be set to fixed in CSS as well.
141
+ * https://floating-ui.com/docs/computePosition#strategy
142
+ */
143
+ positioningStrategy: {
144
+ type: String,
145
+ required: false,
146
+ default: POSITION_ABSOLUTE,
147
+ validator: (strategy) => [POSITION_ABSOLUTE, POSITION_FIXED].includes(strategy),
148
+ },
136
149
  },
137
150
  data() {
138
151
  return {
@@ -209,11 +222,17 @@ export default {
209
222
  return {
210
223
  'gl-display-block!': this.visible,
211
224
  [FIXED_WIDTH_CLASS]: !this.fluidWidth,
225
+ 'gl-fixed': this.isFixed,
226
+ 'gl-absolute': !this.isFixed,
212
227
  };
213
228
  },
229
+ isFixed() {
230
+ return this.positioningStrategy === POSITION_FIXED;
231
+ },
214
232
  floatingUIConfig() {
215
233
  return {
216
234
  placement: dropdownPlacements[this.placement],
235
+ strategy: this.positioningStrategy,
217
236
  middleware: [
218
237
  offset(this.offset),
219
238
  flip(),
@@ -11,4 +11,9 @@ export const ENTER = 'Enter';
11
11
  export const HOME = 'Home';
12
12
  export const SPACE = 'Space';
13
13
 
14
+ // Positioning strategies
15
+ // https://floating-ui.com/docs/computePosition#strategy
16
+ export const POSITION_ABSOLUTE = 'absolute';
17
+ export const POSITION_FIXED = 'fixed';
18
+
14
19
  export const GL_DROPDOWN_CONTENTS_CLASS = 'gl-new-dropdown-contents';
@@ -10,6 +10,8 @@ import {
10
10
  ARROW_UP,
11
11
  HOME,
12
12
  END,
13
+ POSITION_ABSOLUTE,
14
+ POSITION_FIXED,
13
15
  } from '../constants';
14
16
  import GlDisclosureDropdown from './disclosure_dropdown.vue';
15
17
  import GlDisclosureDropdownItem from './disclosure_dropdown_item.vue';
@@ -345,4 +347,15 @@ describe('GlDisclosureDropdown', () => {
345
347
  expect(closeSpy).not.toHaveBeenCalled();
346
348
  });
347
349
  });
350
+
351
+ describe('positioningStrategy', () => {
352
+ it.each([POSITION_ABSOLUTE, POSITION_FIXED])(
353
+ 'passes the %s positioning strategy to the base dropdown',
354
+ (positioningStrategy) => {
355
+ buildWrapper({ positioningStrategy });
356
+
357
+ expect(findBaseDropdown().props('positioningStrategy')).toBe(positioningStrategy);
358
+ }
359
+ );
360
+ });
348
361
  });
@@ -41,6 +41,7 @@ const makeBindings = (overrides = {}) =>
41
41
  ':list-aria-labelled-by': 'listAriaLabelledBy',
42
42
  ':fluid-width': 'fluidWidth',
43
43
  ':auto-close': 'autoClose',
44
+ ':positioning-strategy': 'positioningStrategy',
44
45
  ...overrides,
45
46
  })
46
47
  .map(([key, value]) => `${key}="${value}"`)
@@ -14,6 +14,8 @@ import {
14
14
  ARROW_DOWN,
15
15
  ARROW_UP,
16
16
  GL_DROPDOWN_CONTENTS_CLASS,
17
+ POSITION_ABSOLUTE,
18
+ POSITION_FIXED,
17
19
  } from '../constants';
18
20
  import {
19
21
  buttonCategoryOptions,
@@ -196,6 +198,17 @@ export default {
196
198
  required: false,
197
199
  default: true,
198
200
  },
201
+ /**
202
+ * Strategy to be applied by computePosition. If the dropdown's container is too short for it to
203
+ * fit in, setting this to fixed will let it position itself above its container.
204
+ * https://floating-ui.com/docs/computePosition#strategy
205
+ */
206
+ positioningStrategy: {
207
+ type: String,
208
+ required: false,
209
+ default: POSITION_ABSOLUTE,
210
+ validator: (strategy) => [POSITION_ABSOLUTE, POSITION_FIXED].includes(strategy),
211
+ },
199
212
  },
200
213
  data() {
201
214
  return {
@@ -323,6 +336,7 @@ export default {
323
336
  :placement="placement"
324
337
  :offset="dropdownOffset"
325
338
  :fluid-width="fluidWidth"
339
+ :positioning-strategy="positioningStrategy"
326
340
  class="gl-disclosure-dropdown"
327
341
  @[$options.events.GL_DROPDOWN_SHOWN]="onShow"
328
342
  @[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
@@ -20,7 +20,6 @@
20
20
  @include gl-border-gray-200;
21
21
  @include gl-rounded-lg;
22
22
  @include gl-shadow-md;
23
- position: absolute;
24
23
  top: 0;
25
24
  left: 0;
26
25
  min-width: $gl-new-dropdown-min-width;
@@ -11,6 +11,8 @@ import {
11
11
  HOME,
12
12
  END,
13
13
  ENTER,
14
+ POSITION_ABSOLUTE,
15
+ POSITION_FIXED,
14
16
  } from '../constants';
15
17
  import GlIntersectionObserver from '../../../utilities/intersection_observer/intersection_observer.vue';
16
18
  import GlCollapsibleListbox, { ITEM_SELECTOR } from './listbox.vue';
@@ -744,4 +746,15 @@ describe('GlCollapsibleListbox', () => {
744
746
  expect(findBaseDropdown().props('fluidWidth')).toBe(true);
745
747
  });
746
748
  });
749
+
750
+ describe('positioningStrategy', () => {
751
+ it.each([POSITION_ABSOLUTE, POSITION_FIXED])(
752
+ 'passes the %s positioning strategy to the base dropdown',
753
+ (positioningStrategy) => {
754
+ buildWrapper({ positioningStrategy });
755
+
756
+ expect(findBaseDropdown().props('positioningStrategy')).toBe(positioningStrategy);
757
+ }
758
+ );
759
+ });
747
760
  });
@@ -59,6 +59,7 @@ const generateProps = ({
59
59
  showSelectAllButtonLabel = defaultValue('showSelectAllButtonLabel'),
60
60
  startOpened = true,
61
61
  fluidWidth,
62
+ positioningStrategy,
62
63
  } = {}) => ({
63
64
  items,
64
65
  category,
@@ -88,6 +89,7 @@ const generateProps = ({
88
89
  showSelectAllButtonLabel,
89
90
  startOpened,
90
91
  fluidWidth,
92
+ positioningStrategy,
91
93
  });
92
94
 
93
95
  const makeBindings = (overrides = {}) =>
@@ -119,6 +121,7 @@ const makeBindings = (overrides = {}) =>
119
121
  ':reset-button-label': 'resetButtonLabel',
120
122
  ':show-select-all-button-label': 'showSelectAllButtonLabel',
121
123
  ':fluid-width': 'fluidWidth',
124
+ ':positioning-strategy': 'positioningStrategy',
122
125
  ...overrides,
123
126
  })
124
127
  .map(([key, value]) => `${key}="${value}"`)
@@ -12,6 +12,8 @@ import {
12
12
  ARROW_DOWN,
13
13
  ARROW_UP,
14
14
  GL_DROPDOWN_CONTENTS_CLASS,
15
+ POSITION_ABSOLUTE,
16
+ POSITION_FIXED,
15
17
  } from '../constants';
16
18
  import {
17
19
  buttonCategoryOptions,
@@ -318,6 +320,17 @@ export default {
318
320
  required: false,
319
321
  default: false,
320
322
  },
323
+ /**
324
+ * Strategy to be applied by computePosition. If the dropdown's container is too short for it to
325
+ * fit in, setting this to fixed will let it position itself above its container.
326
+ * https://floating-ui.com/docs/computePosition#strategy
327
+ */
328
+ positioningStrategy: {
329
+ type: String,
330
+ required: false,
331
+ default: POSITION_ABSOLUTE,
332
+ validator: (strategy) => [POSITION_ABSOLUTE, POSITION_FIXED].includes(strategy),
333
+ },
321
334
  },
322
335
  data() {
323
336
  return {
@@ -709,6 +722,7 @@ export default {
709
722
  :placement="placement"
710
723
  :offset="dropdownOffset"
711
724
  :fluid-width="fluidWidth"
725
+ :positioning-strategy="positioningStrategy"
712
726
  @[$options.events.GL_DROPDOWN_SHOWN]="onShow"
713
727
  @[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
714
728
  >