@gitlab/ui 126.4.0 → 127.0.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.
@@ -36,7 +36,7 @@ var script = {
36
36
  validator: items => {
37
37
  return items.every(item => {
38
38
  const keys = Object.keys(item);
39
- return keys.includes('text') && (keys.includes('href') || keys.includes('to'));
39
+ return keys.includes('text') && item.text && (keys.includes('href') || keys.includes('to'));
40
40
  });
41
41
  }
42
42
  },
@@ -230,12 +230,6 @@ var script = {
230
230
  this.resizeObserver = null;
231
231
  }
232
232
  this.resetItems();
233
- },
234
- hideItemClass(item) {
235
- // TODO once https://gitlab.com/gitlab-org/gitlab/-/issues/520089 is addressed:
236
- // - Remove this hiding of empty breadcrumbs.
237
- // - Tighten `items` validator to require non-empty `text`.
238
- return !item.text ? 'gl-hidden' : '';
239
233
  }
240
234
  }
241
235
  };
@@ -244,7 +238,7 @@ var script = {
244
238
  const __vue_script__ = script;
245
239
 
246
240
  /* template */
247
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('nav',{staticClass:"gl-breadcrumbs",style:(_vm.breadcrumbStyle),attrs:{"aria-label":_vm.ariaLabel}},[_c('ol',_vm._g(_vm._b({staticClass:"gl-breadcrumb-list breadcrumb"},'ol',_vm.$attrs,false),_vm.$listeners),[(_vm.hasCollapsible || !_vm.resizeDone)?_c('li',{ref:"dropdown",class:("gl-breadcrumb-item gl-breadcrumb-item-" + _vm.size)},[_c('gl-disclosure-dropdown',{attrs:{"items":_vm.overflowingItems,"toggle-text":_vm.showMoreLabel,"fluid-width":"","text-sr-only":"","no-caret":"","icon":"ellipsis_h","size":_vm.dropdownSize}})],1):_vm._e(),_vm._v(" "),_vm._l((_vm.fittingItems),function(item,index){return _c('gl-breadcrumb-item',{key:index,ref:"breadcrumbs",refInFor:true,class:[_vm.hideItemClass(item), _vm.itemClass],attrs:{"text":item.text,"href":item.href,"to":item.to,"size":_vm.size,"aria-current":_vm.getAriaCurrentAttr(index)},scopedSlots:_vm._u([{key:"default",fn:function(){return [(item.avatarPath)?_c('gl-avatar',{staticClass:"gl-breadcrumb-avatar-tile gl-border gl-mr-2 !gl-rounded-default",attrs:{"src":item.avatarPath,"size":_vm.avatarSize,"aria-hidden":"true","shape":"rect","data-testid":"avatar"}}):_vm._e(),_c('span',{staticClass:"gl-align-middle"},[_vm._v(_vm._s(item.text))])]},proxy:true}],null,true)})}),_vm._v(" "),(_vm.showClipboardButton)?_c('li',{staticClass:"gl-breadcrumb-clipboard-button"},[_c('clipboard-button',_vm._b({ref:"clipboardButton",staticClass:"gl-ml-2",attrs:{"data-testid":"copy-to-clipboard-button","text":_vm.clipboardButtonText,"size":_vm.dropdownSize}},'clipboard-button',_vm.clipboardTooltipText ? { title: _vm.clipboardTooltipText } : {},false))],1):_vm._e()],2)])};
241
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('nav',{staticClass:"gl-breadcrumbs",style:(_vm.breadcrumbStyle),attrs:{"aria-label":_vm.ariaLabel}},[_c('ol',_vm._g(_vm._b({staticClass:"gl-breadcrumb-list breadcrumb"},'ol',_vm.$attrs,false),_vm.$listeners),[(_vm.hasCollapsible || !_vm.resizeDone)?_c('li',{ref:"dropdown",class:("gl-breadcrumb-item gl-breadcrumb-item-" + _vm.size)},[_c('gl-disclosure-dropdown',{attrs:{"items":_vm.overflowingItems,"toggle-text":_vm.showMoreLabel,"fluid-width":"","text-sr-only":"","no-caret":"","icon":"ellipsis_h","size":_vm.dropdownSize}})],1):_vm._e(),_vm._v(" "),_vm._l((_vm.fittingItems),function(item,index){return _c('gl-breadcrumb-item',{key:index,ref:"breadcrumbs",refInFor:true,class:_vm.itemClass,attrs:{"text":item.text,"href":item.href,"to":item.to,"size":_vm.size,"aria-current":_vm.getAriaCurrentAttr(index)},scopedSlots:_vm._u([{key:"default",fn:function(){return [(item.avatarPath)?_c('gl-avatar',{staticClass:"gl-breadcrumb-avatar-tile gl-border gl-mr-2 !gl-rounded-default",attrs:{"src":item.avatarPath,"size":_vm.avatarSize,"aria-hidden":"true","shape":"rect","data-testid":"avatar"}}):_vm._e(),_c('span',{staticClass:"gl-align-middle"},[_vm._v(_vm._s(item.text))])]},proxy:true}],null,true)})}),_vm._v(" "),(_vm.showClipboardButton)?_c('li',{staticClass:"gl-breadcrumb-clipboard-button"},[_c('clipboard-button',_vm._b({ref:"clipboardButton",staticClass:"gl-ml-2",attrs:{"data-testid":"copy-to-clipboard-button","text":_vm.clipboardButtonText,"size":_vm.dropdownSize}},'clipboard-button',_vm.clipboardTooltipText ? { title: _vm.clipboardTooltipText } : {},false))],1):_vm._e()],2)])};
248
242
  var __vue_staticRenderFns__ = [];
249
243
 
250
244
  /* style */
@@ -39,6 +39,14 @@ var script = {
39
39
  required: false,
40
40
  default: 'h4'
41
41
  },
42
+ /**
43
+ * HTML tag to use for the modal header. Default is 'div' for accessibility reasons.
44
+ */
45
+ headerTag: {
46
+ type: String,
47
+ required: false,
48
+ default: 'div'
49
+ },
42
50
  /**
43
51
  * Title text to display in the modal header.
44
52
  */
@@ -55,6 +63,14 @@ var script = {
55
63
  required: false,
56
64
  default: ''
57
65
  },
66
+ /**
67
+ * HTML tag to use for the modal footer. Default is 'div' for accessibility reasons.
68
+ */
69
+ footerTag: {
70
+ type: String,
71
+ required: false,
72
+ default: 'div'
73
+ },
58
74
  /**
59
75
  * Configuration object for the primary action button. Should contain 'text' and optionally 'attributes' properties.
60
76
  */
@@ -210,7 +226,7 @@ var script = {
210
226
  const __vue_script__ = script;
211
227
 
212
228
  /* template */
213
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('b-modal',_vm._g(_vm._b({ref:"modal",attrs:{"id":_vm.modalId,"title-tag":_vm.titleTag,"size":_vm.size,"visible":_vm.visible,"aria-label":_vm.ariaLabel || _vm.title,"lazy":"","modal-class":['gl-modal', _vm.modalClass]},on:{"shown":_vm.setFocus,"ok":_vm.primary,"cancel":_vm.canceled,"change":function($event){return _vm.$emit('change', $event)}},scopedSlots:_vm._u([{key:"default",fn:function(){return [_vm._t("default")]},proxy:true},{key:"modal-header",fn:function(){return [_vm._t("modal-header",function(){return [_c('h2',{staticClass:"modal-title"},[_vm._t("modal-title",function(){return [_vm._v(_vm._s(_vm.title))]})],2)]}),_vm._v(" "),_c('close-button',{ref:"close-button",attrs:{"label":_vm.dismissLabel},on:{"click":_vm.close}})]},proxy:true},(_vm.shouldRenderModalOk)?{key:"modal-ok",fn:function(){return [_vm._t("modal-ok")]},proxy:true}:null,(_vm.shouldRenderModalCancel)?{key:"modal-cancel",fn:function(){return [_vm._t("modal-cancel")]},proxy:true}:null,(_vm.shouldRenderModalFooter)?{key:"modal-footer",fn:function(){return [_vm._t("modal-footer",function(){return [(_vm.actionCancel)?_c('gl-button',_vm._b({staticClass:"js-modal-action-cancel",on:{"click":_vm.cancel}},'gl-button',_vm.buttonBinding(_vm.actionCancel, 'actionCancel'),false),[_vm._v("\n "+_vm._s(_vm.actionCancel.text)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.actionSecondary)?_c('gl-button',_vm._b({staticClass:"js-modal-action-secondary",on:{"click":_vm.secondary}},'gl-button',_vm.buttonBinding(_vm.actionSecondary, 'actionSecondary'),false),[_vm._v("\n "+_vm._s(_vm.actionSecondary.text)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.actionPrimary)?_c('gl-button',_vm._b({staticClass:"js-modal-action-primary",on:{"click":_vm.ok}},'gl-button',_vm.buttonBinding(_vm.actionPrimary, 'actionPrimary'),false),[_vm._v("\n "+_vm._s(_vm.actionPrimary.text)+"\n ")]):_vm._e()]})]},proxy:true}:null],null,true)},'b-modal',_vm.$attrs,false),_vm.$listeners))};
229
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('b-modal',_vm._g(_vm._b({ref:"modal",attrs:{"id":_vm.modalId,"title-tag":_vm.titleTag,"size":_vm.size,"visible":_vm.visible,"aria-label":_vm.ariaLabel || _vm.title,"header-tag":_vm.headerTag,"footer-tag":_vm.footerTag,"lazy":"","modal-class":['gl-modal', _vm.modalClass]},on:{"shown":_vm.setFocus,"ok":_vm.primary,"cancel":_vm.canceled,"change":function($event){return _vm.$emit('change', $event)}},scopedSlots:_vm._u([{key:"default",fn:function(){return [_vm._t("default")]},proxy:true},{key:"modal-header",fn:function(){return [_vm._t("modal-header",function(){return [_c('h2',{staticClass:"modal-title"},[_vm._t("modal-title",function(){return [_vm._v(_vm._s(_vm.title))]})],2)]}),_vm._v(" "),_c('close-button',{ref:"close-button",attrs:{"label":_vm.dismissLabel},on:{"click":_vm.close}})]},proxy:true},(_vm.shouldRenderModalOk)?{key:"modal-ok",fn:function(){return [_vm._t("modal-ok")]},proxy:true}:null,(_vm.shouldRenderModalCancel)?{key:"modal-cancel",fn:function(){return [_vm._t("modal-cancel")]},proxy:true}:null,(_vm.shouldRenderModalFooter)?{key:"modal-footer",fn:function(){return [_vm._t("modal-footer",function(){return [(_vm.actionCancel)?_c('gl-button',_vm._b({staticClass:"js-modal-action-cancel",on:{"click":_vm.cancel}},'gl-button',_vm.buttonBinding(_vm.actionCancel, 'actionCancel'),false),[_vm._v("\n "+_vm._s(_vm.actionCancel.text)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.actionSecondary)?_c('gl-button',_vm._b({staticClass:"js-modal-action-secondary",on:{"click":_vm.secondary}},'gl-button',_vm.buttonBinding(_vm.actionSecondary, 'actionSecondary'),false),[_vm._v("\n "+_vm._s(_vm.actionSecondary.text)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.actionPrimary)?_c('gl-button',_vm._b({staticClass:"js-modal-action-primary",on:{"click":_vm.ok}},'gl-button',_vm.buttonBinding(_vm.actionPrimary, 'actionPrimary'),false),[_vm._v("\n "+_vm._s(_vm.actionPrimary.text)+"\n ")]):_vm._e()]})]},proxy:true}:null],null,true)},'b-modal',_vm.$attrs,false),_vm.$listeners))};
214
230
  var __vue_staticRenderFns__ = [];
215
231
 
216
232
  /* style */
@@ -2,6 +2,11 @@ const dashboardConfigValidator = config => {
2
2
  if (config.panels) {
3
3
  if (!Array.isArray(config.panels)) return false;
4
4
  if (!config.panels.every(panel => panel.id && panel.gridAttributes)) return false;
5
+
6
+ // Validate that all panel IDs are unique
7
+ const panelIds = config.panels.map(panel => panel.id);
8
+ const uniquePanelIds = new Set(panelIds);
9
+ if (panelIds.length !== uniquePanelIds.size) return false;
5
10
  }
6
11
  return true;
7
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "126.4.0",
3
+ "version": "127.0.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -131,7 +131,7 @@
131
131
  "autoprefixer": "10.4.23",
132
132
  "axe-playwright": "^2.2.2",
133
133
  "babel-loader": "^9.2.1",
134
- "cypress": "15.8.1",
134
+ "cypress": "15.8.2",
135
135
  "cypress-real-events": "^1.15.0",
136
136
  "dompurify": "^3.1.2",
137
137
  "emoji-regex": "^10.6.0",
@@ -165,7 +165,7 @@
165
165
  "start-server-and-test": "^2.1.3",
166
166
  "storybook": "^7.6.20",
167
167
  "storybook-dark-mode": "4.0.2",
168
- "style-dictionary": "^5.1.1",
168
+ "style-dictionary": "^5.1.3",
169
169
  "style-loader": "^4",
170
170
  "tailwindcss": "3.4.19",
171
171
  "vue": "2.7.16",
@@ -32,7 +32,9 @@ export default {
32
32
  validator: (items) => {
33
33
  return items.every((item) => {
34
34
  const keys = Object.keys(item);
35
- return keys.includes('text') && (keys.includes('href') || keys.includes('to'));
35
+ return (
36
+ keys.includes('text') && item.text && (keys.includes('href') || keys.includes('to'))
37
+ );
36
38
  });
37
39
  },
38
40
  },
@@ -233,12 +235,6 @@ export default {
233
235
  }
234
236
  this.resetItems();
235
237
  },
236
- hideItemClass(item) {
237
- // TODO once https://gitlab.com/gitlab-org/gitlab/-/issues/520089 is addressed:
238
- // - Remove this hiding of empty breadcrumbs.
239
- // - Tighten `items` validator to require non-empty `text`.
240
- return !item.text ? 'gl-hidden' : '';
241
- },
242
238
  },
243
239
  };
244
240
  </script>
@@ -270,7 +266,7 @@ export default {
270
266
  :to="item.to"
271
267
  :size="size"
272
268
  :aria-current="getAriaCurrentAttr(index)"
273
- :class="[hideItemClass(item), itemClass]"
269
+ :class="itemClass"
274
270
  ><template #default>
275
271
  <gl-avatar
276
272
  v-if="item.avatarPath"
@@ -44,6 +44,14 @@ export default {
44
44
  required: false,
45
45
  default: 'h4',
46
46
  },
47
+ /**
48
+ * HTML tag to use for the modal header. Default is 'div' for accessibility reasons.
49
+ */
50
+ headerTag: {
51
+ type: String,
52
+ required: false,
53
+ default: 'div',
54
+ },
47
55
  /**
48
56
  * Title text to display in the modal header.
49
57
  */
@@ -60,6 +68,14 @@ export default {
60
68
  required: false,
61
69
  default: '',
62
70
  },
71
+ /**
72
+ * HTML tag to use for the modal footer. Default is 'div' for accessibility reasons.
73
+ */
74
+ footerTag: {
75
+ type: String,
76
+ required: false,
77
+ default: 'div',
78
+ },
63
79
  /**
64
80
  * Configuration object for the primary action button. Should contain 'text' and optionally 'attributes' properties.
65
81
  */
@@ -228,6 +244,8 @@ export default {
228
244
  :size="size"
229
245
  :visible="visible"
230
246
  :aria-label="ariaLabel || title"
247
+ :header-tag="headerTag"
248
+ :footer-tag="footerTag"
231
249
  v-bind="$attrs"
232
250
  lazy
233
251
  :modal-class="['gl-modal', modalClass]"
@@ -2,6 +2,11 @@ export const dashboardConfigValidator = (config) => {
2
2
  if (config.panels) {
3
3
  if (!Array.isArray(config.panels)) return false;
4
4
  if (!config.panels.every((panel) => panel.id && panel.gridAttributes)) return false;
5
+
6
+ // Validate that all panel IDs are unique
7
+ const panelIds = config.panels.map((panel) => panel.id);
8
+ const uniquePanelIds = new Set(panelIds);
9
+ if (panelIds.length !== uniquePanelIds.size) return false;
5
10
  }
6
11
 
7
12
  return true;