@gitlab/duo-ui 15.19.0 → 15.19.1

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.
@@ -58,6 +58,7 @@ var script = {
58
58
  * @property {string} text - Display text for button/dropdown
59
59
  * @property {boolean} primary - Mark as primary action (exactly one required)
60
60
  * @property {boolean} disabled - Disable this option (passed through to GitLab UI)
61
+ * @property {string} [secondaryText] - Secondary text shown below the option (e.g., reason for disabled state)
61
62
  */
62
63
  approvalOptions: {
63
64
  type: Array,
@@ -88,6 +89,9 @@ var script = {
88
89
  if (option.primary === true && option.disabled === true) {
89
90
  return false;
90
91
  }
92
+ if ('secondaryText' in option && typeof option.secondaryText !== 'string') {
93
+ return false;
94
+ }
91
95
  return true;
92
96
  });
93
97
  }
@@ -1,4 +1,4 @@
1
- import { GlModal, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
1
+ import { GlModal, GlButton, GlButtonGroup, GlDisclosureDropdown } from '@gitlab/ui';
2
2
  import { translate } from '../../../../../utils/i18n';
3
3
  import { acceptedApproveToolPayloads } from '../../../../chat/constants';
4
4
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
@@ -18,8 +18,8 @@ var script = {
18
18
  components: {
19
19
  GlModal,
20
20
  GlButton,
21
- GlDropdown,
22
- GlDropdownItem
21
+ GlButtonGroup,
22
+ GlDisclosureDropdown
23
23
  },
24
24
  props: {
25
25
  /**
@@ -79,6 +79,25 @@ var script = {
79
79
  showSplitButton() {
80
80
  return this.safeApprovalOptions.length > 1;
81
81
  },
82
+ approvalDropdownItems() {
83
+ return this.additionalApprovalOptions.map(option => ({
84
+ text: option.text,
85
+ ...(option.secondaryText ? {
86
+ secondaryText: option.secondaryText
87
+ } : {}),
88
+ extraAttrs: {
89
+ ...(option.disabled ? {
90
+ disabled: true
91
+ } : {}),
92
+ 'data-testid': 'approve-dropdown-item'
93
+ },
94
+ action: () => {
95
+ var _this$$refs$approvalD;
96
+ (_this$$refs$approvalD = this.$refs.approvalDropdown) === null || _this$$refs$approvalD === void 0 ? void 0 : _this$$refs$approvalD.close();
97
+ this.handleDropdownSelection(option);
98
+ }
99
+ }));
100
+ },
82
101
  denyAction() {
83
102
  return {
84
103
  text: this.$options.i18n.DENY_TEXT,
@@ -152,7 +171,9 @@ var script = {
152
171
  const __vue_script__ = script;
153
172
 
154
173
  /* template */
155
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-modal',{attrs:{"visible":_vm.visible,"modal-id":"agentic-tool-approval-modal","title":_vm.$options.i18n.TITLE,"action-primary":null,"action-cancel":null,"no-close-on-backdrop":true,"no-close-on-esc":true,"data-testid":"agentic-tool-approval-modal"},on:{"hide":_vm.handleModalHide},scopedSlots:_vm._u([{key:"modal-footer",fn:function(){return [_c('div',{staticClass:"gl-flex gl-justify-end gl-gap-3"},[_c('gl-button',_vm._b({on:{"click":_vm.handleDeny}},'gl-button',_vm.denyAction.attributes,false),[_vm._v("\n "+_vm._s(_vm.denyAction.text)+"\n ")]),_vm._v(" "),(_vm.showSplitButton)?_c('gl-dropdown',{attrs:{"variant":"confirm","size":"small","split":"","text":_vm.primaryApprovalOption.text,"disabled":_vm.primaryApprovalOption.disabled,"right":"","data-testid":"approve-dropdown"},on:{"click":_vm.handlePrimaryApprove}},_vm._l((_vm.additionalApprovalOptions),function(option){return _c('gl-dropdown-item',{key:option.type,attrs:{"disabled":option.disabled},on:{"click":function($event){return _vm.handleDropdownSelection(option)}}},[_vm._v("\n "+_vm._s(option.text)+"\n ")])}),1):_c('gl-button',{attrs:{"variant":"confirm","size":"small","disabled":_vm.primaryApprovalOption.disabled,"data-testid":"approve-button"},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")])],1)]},proxy:true}])},[_c('div',{staticClass:"gl-mb-4"},[_c('p',{staticClass:"gl-mb-3",attrs:{"data-testid":"approval-description"}},[_vm._v("\n "+_vm._s(_vm.description)+"\n ")]),_vm._v(" "),(_vm.toolDetails)?_c('div',{staticClass:"gl-mb-3 gl-rounded-base gl-bg-gray-10 gl-p-3",attrs:{"data-testid":"tool-details"}},[_c('strong',[_vm._v(_vm._s(_vm.$options.i18n.TOOL_LABEL)+" "+_vm._s(_vm.toolName))]),_vm._v(" "),(_vm.toolDetails.parameters)?_c('pre',{staticClass:"gl-mb-0 gl-mt-2 gl-text-inherit",attrs:{"data-testid":"tool-parameters"}},[_vm._v(_vm._s(JSON.stringify(_vm.toolDetails.parameters, null, 2)))]):_vm._e()]):_vm._e()])])};
174
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-modal',{attrs:{"visible":_vm.visible,"modal-id":"agentic-tool-approval-modal","title":_vm.$options.i18n.TITLE,"action-primary":null,"action-cancel":null,"no-close-on-backdrop":true,"no-close-on-esc":true,"data-testid":"agentic-tool-approval-modal"},on:{"hide":_vm.handleModalHide},scopedSlots:_vm._u([{key:"modal-footer",fn:function(){return [_c('div',{staticClass:"gl-flex gl-justify-end gl-gap-3"},[_c('gl-button',_vm._b({on:{"click":_vm.handleDeny}},'gl-button',_vm.denyAction.attributes,false),[_vm._v("\n "+_vm._s(_vm.denyAction.text)+"\n ")]),_vm._v(" "),(_vm.showSplitButton)?_c('gl-button-group',{attrs:{"data-testid":"approve-dropdown"}},[_c('gl-button',{attrs:{"variant":"confirm","size":"small","disabled":_vm.primaryApprovalOption.disabled,"data-testid":"approve-dropdown-primary"},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")]),_vm._v(" "),_c('gl-disclosure-dropdown',{ref:"approvalDropdown",attrs:{"variant":"confirm","category":"primary","size":"small","no-caret":"","icon":"chevron-down","toggle-text":"More approval options","text-sr-only":"","placement":"bottom-end","disabled":_vm.primaryApprovalOption.disabled,"items":_vm.approvalDropdownItems},scopedSlots:_vm._u([{key:"list-item",fn:function(ref){
175
+ var item = ref.item;
176
+ return [_c('span',[_vm._v(_vm._s(item.text))]),_vm._v(" "),(item.secondaryText)?_c('span',{staticClass:"gl-block gl-text-sm gl-text-subtle"},[_vm._v("\n "+_vm._s(item.secondaryText)+"\n ")]):_vm._e()]}}],null,false,1049862671)})],1):_c('gl-button',{attrs:{"variant":"confirm","size":"small","disabled":_vm.primaryApprovalOption.disabled,"data-testid":"approve-button"},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")])],1)]},proxy:true}])},[_c('div',{staticClass:"gl-mb-4"},[_c('p',{staticClass:"gl-mb-3",attrs:{"data-testid":"approval-description"}},[_vm._v("\n "+_vm._s(_vm.description)+"\n ")]),_vm._v(" "),(_vm.toolDetails)?_c('div',{staticClass:"gl-mb-3 gl-rounded-base gl-bg-gray-10 gl-p-3",attrs:{"data-testid":"tool-details"}},[_c('strong',[_vm._v(_vm._s(_vm.$options.i18n.TOOL_LABEL)+" "+_vm._s(_vm.toolName))]),_vm._v(" "),(_vm.toolDetails.parameters)?_c('pre',{staticClass:"gl-mb-0 gl-mt-2 gl-text-inherit",attrs:{"data-testid":"tool-parameters"}},[_vm._v(_vm._s(JSON.stringify(_vm.toolDetails.parameters, null, 2)))]):_vm._e()]):_vm._e()])])};
156
177
  var __vue_staticRenderFns__ = [];
157
178
 
158
179
  /* style */
@@ -1,4 +1,4 @@
1
- import { GlButton, GlCard, GlFormTextarea, GlFormGroup, GlBadge, GlDropdown, GlDropdownItem } from '@gitlab/ui';
1
+ import { GlButton, GlButtonGroup, GlCard, GlFormTextarea, GlFormGroup, GlBadge, GlDisclosureDropdown } from '@gitlab/ui';
2
2
  import { capitalize, startCase } from 'lodash-es';
3
3
  import { translate } from '../../../../utils/i18n';
4
4
  import { convertKeysToCamelCase } from '../../../../utils/object';
@@ -48,12 +48,12 @@ var script = {
48
48
  name: 'MessageToolApproval',
49
49
  components: {
50
50
  GlButton,
51
+ GlButtonGroup,
51
52
  GlCard,
52
53
  GlFormTextarea,
53
54
  GlFormGroup,
54
55
  GlBadge,
55
- GlDropdown,
56
- GlDropdownItem,
56
+ GlDisclosureDropdown,
57
57
  CreateCommitToolParams,
58
58
  DefaultToolParams,
59
59
  RunCommandToolParams
@@ -84,6 +84,7 @@ var script = {
84
84
  * @property {string} text - The display text for the option
85
85
  * @property {boolean} primary - Whether this is the primary option (appears as main button)
86
86
  * @property {boolean} disabled - Whether this option is disabled
87
+ * @property {string} [secondaryText] - Secondary text shown below the option (e.g., reason for disabled state)
87
88
  */
88
89
  approvalOptions: {
89
90
  type: Array,
@@ -96,7 +97,15 @@ var script = {
96
97
  validator(value) {
97
98
  if (!Array.isArray(value)) return false;
98
99
  // Must have at least one approve-tool-once option
99
- return value.some(option => option.type === acceptedApproveToolPayloads.APPROVE_TOOL_ONCE);
100
+ if (!value.some(option => option.type === acceptedApproveToolPayloads.APPROVE_TOOL_ONCE)) {
101
+ return false;
102
+ }
103
+ return value.every(option => {
104
+ if ('secondaryText' in option && typeof option.secondaryText !== 'string') {
105
+ return false;
106
+ }
107
+ return true;
108
+ });
100
109
  }
101
110
  }
102
111
  },
@@ -202,6 +211,25 @@ var script = {
202
211
  },
203
212
  showSplitButton() {
204
213
  return this.safeApprovalOptions.length > 1;
214
+ },
215
+ approvalDropdownItems() {
216
+ return this.additionalApprovalOptions.map(option => ({
217
+ text: option.text,
218
+ ...(option.secondaryText ? {
219
+ secondaryText: option.secondaryText
220
+ } : {}),
221
+ extraAttrs: {
222
+ ...(option.disabled ? {
223
+ disabled: true
224
+ } : {}),
225
+ 'data-testid': 'approve-dropdown-item'
226
+ },
227
+ action: () => {
228
+ var _this$$refs$approvalD;
229
+ (_this$$refs$approvalD = this.$refs.approvalDropdown) === null || _this$$refs$approvalD === void 0 ? void 0 : _this$$refs$approvalD.close();
230
+ this.handleDropdownSelection(option);
231
+ }
232
+ }));
205
233
  }
206
234
  },
207
235
  watch: {
@@ -265,7 +293,9 @@ var script = {
265
293
  const __vue_script__ = script;
266
294
 
267
295
  /* template */
268
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-card',{staticClass:"gl-w-full",class:{ 'message-tool-approval-collapsed': _vm.collapsed },attrs:{"header-class":{ 'message-tool-approval-collapsed-header': _vm.collapsed },"body-class":{ 'gl-hidden': _vm.collapsed }},scopedSlots:_vm._u([{key:"header",fn:function(){return [_c('div',{staticClass:"gl-flex gl-items-center gl-justify-between gl-gap-3"},[_c('span',{staticClass:"gl-inline-flex gl-items-center gl-gap-2"},[(_vm.collapsible)?_c('gl-button',_vm._b({attrs:{"variant":"default","category":"tertiary","size":"small","data-testid":"toggle-details-button"},on:{"click":function($event){_vm.collapsed = !_vm.collapsed;}}},'gl-button',_vm.collapseButtonProps,false)):_vm._e(),_vm._v(" "),_c('span',[_vm._v("\n "+_vm._s(_vm.toolApprovalTitle)+"\n ")])],1),_vm._v(" "),_c('gl-badge',{attrs:{"variant":_vm.toolStatusVariant}},[_vm._v("\n "+_vm._s(_vm.toolStatusLabel)+"\n ")])],1)]},proxy:true},(!_vm.isApproved)?{key:"footer",fn:function(){return [(!_vm.showDenialReason)?_c('div',{staticClass:"gl-flex gl-gap-2"},[(_vm.showSplitButton)?_c('gl-dropdown',{attrs:{"variant":"confirm","size":"small","split":"","text":_vm.primaryApprovalOption.text,"disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving,"data-testid":"approve-dropdown"},on:{"click":_vm.handlePrimaryApprove}},_vm._l((_vm.additionalApprovalOptions),function(option){return _c('gl-dropdown-item',{key:option.type,attrs:{"disabled":option.disabled,"data-testid":"approve-dropdown-item"},on:{"click":function($event){return _vm.handleDropdownSelection(option)}}},[_vm._v("\n "+_vm._s(option.text)+"\n ")])}),1):_c('gl-button',{attrs:{"variant":"confirm","size":"small","data-testid":"approve-tool-inline","disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"deny-tool-inline","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.handleDeny}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")])],1):_c('div',[_c('gl-form-group',{staticClass:"gl-mb-3",attrs:{"label":_vm.$options.i18n.DENIAL_REASON_LABEL,"label-for":"inline-rejection-reason","optional":true}},[_c('gl-form-textarea',{attrs:{"id":"inline-rejection-reason","placeholder":_vm.$options.i18n.DENIAL_REASON_PLACEHOLDER,"rows":2,"no-resize":true,"submit-on-enter":false,"disabled":_vm.buttonsDisabled,"data-testid":"denial-reason-textarea","autofocus":""},on:{"submit":_vm.submitDenial},model:{value:(_vm.denialReason),callback:function ($$v) {_vm.denialReason=$$v;},expression:"denialReason"}})],1),_vm._v(" "),_c('div',{staticClass:"gl-flex gl-gap-3"},[_c('gl-button',{attrs:{"size":"small","data-testid":"submit-denial","variant":"confirm","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.submitDenial}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"cancel-denial","disabled":_vm.buttonsDisabled},on:{"click":_vm.cancelDenial}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CANCEL_TEXT)+"\n ")])],1)],1)]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._l((_vm.messages),function(toolMsg,index){return _c('div',{key:toolMsg.id || index},[_c(_vm.getToolParamsComponent(toolMsg.tool_info && toolMsg.tool_info.name),_vm._b({tag:"component",class:['gl-leading-20', { 'gl-border-t gl-mt-3 gl-border-gray-100 gl-pt-3': index > 0 }],attrs:{"tool-name":toolMsg.tool_info && toolMsg.tool_info.name,"tool-params":_vm.convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {}),"tool-response":toolMsg.tool_info && toolMsg.tool_info.tool_response,"working-directory":_vm.workingDirectory}},'component',_vm.convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {}),false))],1)})],2)};
296
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-card',{staticClass:"gl-w-full",class:{ 'message-tool-approval-collapsed': _vm.collapsed },attrs:{"header-class":{ 'message-tool-approval-collapsed-header': _vm.collapsed },"body-class":{ 'gl-hidden': _vm.collapsed }},scopedSlots:_vm._u([{key:"header",fn:function(){return [_c('div',{staticClass:"gl-flex gl-items-center gl-justify-between gl-gap-3"},[_c('span',{staticClass:"gl-inline-flex gl-items-center gl-gap-2"},[(_vm.collapsible)?_c('gl-button',_vm._b({attrs:{"variant":"default","category":"tertiary","size":"small","data-testid":"toggle-details-button"},on:{"click":function($event){_vm.collapsed = !_vm.collapsed;}}},'gl-button',_vm.collapseButtonProps,false)):_vm._e(),_vm._v(" "),_c('span',[_vm._v("\n "+_vm._s(_vm.toolApprovalTitle)+"\n ")])],1),_vm._v(" "),_c('gl-badge',{attrs:{"variant":_vm.toolStatusVariant}},[_vm._v("\n "+_vm._s(_vm.toolStatusLabel)+"\n ")])],1)]},proxy:true},(!_vm.isApproved)?{key:"footer",fn:function(){return [(!_vm.showDenialReason)?_c('div',{staticClass:"gl-flex gl-gap-2"},[(_vm.showSplitButton)?_c('gl-button-group',{attrs:{"data-testid":"approve-dropdown"}},[_c('gl-button',{attrs:{"variant":"confirm","size":"small","disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving,"data-testid":"approve-dropdown-primary"},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")]),_vm._v(" "),_c('gl-disclosure-dropdown',{ref:"approvalDropdown",attrs:{"variant":"confirm","category":"primary","size":"small","no-caret":"","icon":"chevron-down","toggle-text":"More approval options","text-sr-only":"","placement":"bottom-end","disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"items":_vm.approvalDropdownItems},scopedSlots:_vm._u([{key:"list-item",fn:function(ref){
297
+ var item = ref.item;
298
+ return [_c('span',[_vm._v(_vm._s(item.text))]),_vm._v(" "),(item.secondaryText)?_c('span',{staticClass:"gl-block gl-text-sm gl-text-subtle"},[_vm._v("\n "+_vm._s(item.secondaryText)+"\n ")]):_vm._e()]}}],null,false,1049862671)})],1):_c('gl-button',{attrs:{"variant":"confirm","size":"small","data-testid":"approve-tool-inline","disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"deny-tool-inline","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.handleDeny}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")])],1):_c('div',[_c('gl-form-group',{staticClass:"gl-mb-3",attrs:{"label":_vm.$options.i18n.DENIAL_REASON_LABEL,"label-for":"inline-rejection-reason","optional":true}},[_c('gl-form-textarea',{attrs:{"id":"inline-rejection-reason","placeholder":_vm.$options.i18n.DENIAL_REASON_PLACEHOLDER,"rows":2,"no-resize":true,"submit-on-enter":false,"disabled":_vm.buttonsDisabled,"data-testid":"denial-reason-textarea","autofocus":""},on:{"submit":_vm.submitDenial},model:{value:(_vm.denialReason),callback:function ($$v) {_vm.denialReason=$$v;},expression:"denialReason"}})],1),_vm._v(" "),_c('div',{staticClass:"gl-flex gl-gap-3"},[_c('gl-button',{attrs:{"size":"small","data-testid":"submit-denial","variant":"confirm","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.submitDenial}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"cancel-denial","disabled":_vm.buttonsDisabled},on:{"click":_vm.cancelDenial}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CANCEL_TEXT)+"\n ")])],1)],1)]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._l((_vm.messages),function(toolMsg,index){return _c('div',{key:toolMsg.id || index},[_c(_vm.getToolParamsComponent(toolMsg.tool_info && toolMsg.tool_info.name),_vm._b({tag:"component",class:['gl-leading-20', { 'gl-border-t gl-mt-3 gl-border-gray-100 gl-pt-3': index > 0 }],attrs:{"tool-name":toolMsg.tool_info && toolMsg.tool_info.name,"tool-params":_vm.convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {}),"tool-response":toolMsg.tool_info && toolMsg.tool_info.tool_response,"working-directory":_vm.workingDirectory}},'component',_vm.convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {}),false))],1)})],2)};
269
299
  var __vue_staticRenderFns__ = [];
270
300
 
271
301
  /* style */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "15.19.0",
3
+ "version": "15.19.1",
4
4
  "description": "Duo UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -66,6 +66,7 @@ export default {
66
66
  * @property {string} text - Display text for button/dropdown
67
67
  * @property {boolean} primary - Mark as primary action (exactly one required)
68
68
  * @property {boolean} disabled - Disable this option (passed through to GitLab UI)
69
+ * @property {string} [secondaryText] - Secondary text shown below the option (e.g., reason for disabled state)
69
70
  */
70
71
  approvalOptions: {
71
72
  type: Array,
@@ -99,6 +100,9 @@ export default {
99
100
  if (option.primary === true && option.disabled === true) {
100
101
  return false;
101
102
  }
103
+ if ('secondaryText' in option && typeof option.secondaryText !== 'string') {
104
+ return false;
105
+ }
102
106
  return true;
103
107
  });
104
108
  },
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { GlModal, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
2
+ import { GlModal, GlButton, GlButtonGroup, GlDisclosureDropdown } from '@gitlab/ui';
3
3
  import { translate } from '../../../../../utils/i18n';
4
4
  import { acceptedApproveToolPayloads } from '../../../../chat/constants';
5
5
 
@@ -25,8 +25,8 @@ export default {
25
25
  components: {
26
26
  GlModal,
27
27
  GlButton,
28
- GlDropdown,
29
- GlDropdownItem,
28
+ GlButtonGroup,
29
+ GlDisclosureDropdown,
30
30
  },
31
31
  props: {
32
32
  /**
@@ -88,6 +88,20 @@ export default {
88
88
  showSplitButton() {
89
89
  return this.safeApprovalOptions.length > 1;
90
90
  },
91
+ approvalDropdownItems() {
92
+ return this.additionalApprovalOptions.map((option) => ({
93
+ text: option.text,
94
+ ...(option.secondaryText ? { secondaryText: option.secondaryText } : {}),
95
+ extraAttrs: {
96
+ ...(option.disabled ? { disabled: true } : {}),
97
+ 'data-testid': 'approve-dropdown-item',
98
+ },
99
+ action: () => {
100
+ this.$refs.approvalDropdown?.close();
101
+ this.handleDropdownSelection(option);
102
+ },
103
+ }));
104
+ },
91
105
  denyAction() {
92
106
  return {
93
107
  text: this.$options.i18n.DENY_TEXT,
@@ -190,26 +204,37 @@ export default {
190
204
  </gl-button>
191
205
 
192
206
  <!-- Split button when multiple options available -->
193
- <gl-dropdown
194
- v-if="showSplitButton"
195
- variant="confirm"
196
- size="small"
197
- split
198
- :text="primaryApprovalOption.text"
199
- :disabled="primaryApprovalOption.disabled"
200
- right
201
- data-testid="approve-dropdown"
202
- @click="handlePrimaryApprove"
203
- >
204
- <gl-dropdown-item
205
- v-for="option in additionalApprovalOptions"
206
- :key="option.type"
207
- :disabled="option.disabled"
208
- @click="handleDropdownSelection(option)"
207
+ <gl-button-group v-if="showSplitButton" data-testid="approve-dropdown">
208
+ <gl-button
209
+ variant="confirm"
210
+ size="small"
211
+ :disabled="primaryApprovalOption.disabled"
212
+ data-testid="approve-dropdown-primary"
213
+ @click="handlePrimaryApprove"
214
+ >
215
+ {{ primaryApprovalOption.text }}
216
+ </gl-button>
217
+ <gl-disclosure-dropdown
218
+ ref="approvalDropdown"
219
+ variant="confirm"
220
+ category="primary"
221
+ size="small"
222
+ no-caret
223
+ icon="chevron-down"
224
+ toggle-text="More approval options"
225
+ text-sr-only
226
+ placement="bottom-end"
227
+ :disabled="primaryApprovalOption.disabled"
228
+ :items="approvalDropdownItems"
209
229
  >
210
- {{ option.text }}
211
- </gl-dropdown-item>
212
- </gl-dropdown>
230
+ <template #list-item="{ item }">
231
+ <span>{{ item.text }}</span>
232
+ <span v-if="item.secondaryText" class="gl-block gl-text-sm gl-text-subtle">
233
+ {{ item.secondaryText }}
234
+ </span>
235
+ </template>
236
+ </gl-disclosure-dropdown>
237
+ </gl-button-group>
213
238
 
214
239
  <!-- Simple button when only one option -->
215
240
  <gl-button
@@ -1,12 +1,12 @@
1
1
  <script>
2
2
  import {
3
3
  GlButton,
4
+ GlButtonGroup,
4
5
  GlFormTextarea,
5
6
  GlFormGroup,
6
7
  GlCard,
7
8
  GlBadge,
8
- GlDropdown,
9
- GlDropdownItem,
9
+ GlDisclosureDropdown,
10
10
  } from '@gitlab/ui';
11
11
  import { startCase, capitalize } from 'lodash-es';
12
12
  import { translate } from '../../../../utils/i18n';
@@ -79,12 +79,12 @@ export default {
79
79
  name: 'MessageToolApproval',
80
80
  components: {
81
81
  GlButton,
82
+ GlButtonGroup,
82
83
  GlCard,
83
84
  GlFormTextarea,
84
85
  GlFormGroup,
85
86
  GlBadge,
86
- GlDropdown,
87
- GlDropdownItem,
87
+ GlDisclosureDropdown,
88
88
  CreateCommitToolParams,
89
89
  DefaultToolParams,
90
90
  RunCommandToolParams,
@@ -115,6 +115,7 @@ export default {
115
115
  * @property {string} text - The display text for the option
116
116
  * @property {boolean} primary - Whether this is the primary option (appears as main button)
117
117
  * @property {boolean} disabled - Whether this option is disabled
118
+ * @property {string} [secondaryText] - Secondary text shown below the option (e.g., reason for disabled state)
118
119
  */
119
120
  approvalOptions: {
120
121
  type: Array,
@@ -129,9 +130,17 @@ export default {
129
130
  validator(value) {
130
131
  if (!Array.isArray(value)) return false;
131
132
  // Must have at least one approve-tool-once option
132
- return value.some(
133
- (option) => option.type === acceptedApproveToolPayloads.APPROVE_TOOL_ONCE
134
- );
133
+ if (
134
+ !value.some((option) => option.type === acceptedApproveToolPayloads.APPROVE_TOOL_ONCE)
135
+ ) {
136
+ return false;
137
+ }
138
+ return value.every((option) => {
139
+ if ('secondaryText' in option && typeof option.secondaryText !== 'string') {
140
+ return false;
141
+ }
142
+ return true;
143
+ });
135
144
  },
136
145
  },
137
146
  },
@@ -231,6 +240,20 @@ export default {
231
240
  showSplitButton() {
232
241
  return this.safeApprovalOptions.length > 1;
233
242
  },
243
+ approvalDropdownItems() {
244
+ return this.additionalApprovalOptions.map((option) => ({
245
+ text: option.text,
246
+ ...(option.secondaryText ? { secondaryText: option.secondaryText } : {}),
247
+ extraAttrs: {
248
+ ...(option.disabled ? { disabled: true } : {}),
249
+ 'data-testid': 'approve-dropdown-item',
250
+ },
251
+ action: () => {
252
+ this.$refs.approvalDropdown?.close();
253
+ this.handleDropdownSelection(option);
254
+ },
255
+ }));
256
+ },
234
257
  },
235
258
  watch: {
236
259
  approvalStatus(newVal) {
@@ -333,27 +356,38 @@ export default {
333
356
  <template v-if="!isApproved" #footer>
334
357
  <div v-if="!showDenialReason" class="gl-flex gl-gap-2">
335
358
  <!-- Split button when multiple approval options available -->
336
- <gl-dropdown
337
- v-if="showSplitButton"
338
- variant="confirm"
339
- size="small"
340
- split
341
- :text="primaryApprovalOption.text"
342
- :disabled="buttonsDisabled || primaryApprovalOption.disabled"
343
- :loading="isApproving"
344
- data-testid="approve-dropdown"
345
- @click="handlePrimaryApprove"
346
- >
347
- <gl-dropdown-item
348
- v-for="option in additionalApprovalOptions"
349
- :key="option.type"
350
- :disabled="option.disabled"
351
- data-testid="approve-dropdown-item"
352
- @click="handleDropdownSelection(option)"
359
+ <gl-button-group v-if="showSplitButton" data-testid="approve-dropdown">
360
+ <gl-button
361
+ variant="confirm"
362
+ size="small"
363
+ :disabled="buttonsDisabled || primaryApprovalOption.disabled"
364
+ :loading="isApproving"
365
+ data-testid="approve-dropdown-primary"
366
+ @click="handlePrimaryApprove"
367
+ >
368
+ {{ primaryApprovalOption.text }}
369
+ </gl-button>
370
+ <gl-disclosure-dropdown
371
+ ref="approvalDropdown"
372
+ variant="confirm"
373
+ category="primary"
374
+ size="small"
375
+ no-caret
376
+ icon="chevron-down"
377
+ toggle-text="More approval options"
378
+ text-sr-only
379
+ placement="bottom-end"
380
+ :disabled="buttonsDisabled || primaryApprovalOption.disabled"
381
+ :items="approvalDropdownItems"
353
382
  >
354
- {{ option.text }}
355
- </gl-dropdown-item>
356
- </gl-dropdown>
383
+ <template #list-item="{ item }">
384
+ <span>{{ item.text }}</span>
385
+ <span v-if="item.secondaryText" class="gl-block gl-text-sm gl-text-subtle">
386
+ {{ item.secondaryText }}
387
+ </span>
388
+ </template>
389
+ </gl-disclosure-dropdown>
390
+ </gl-button-group>
357
391
 
358
392
  <!-- Simple button when only one approval option -->
359
393
  <gl-button