Repository: tailwindlabs/headlessui (ts)

Issue:
Adding disabled prop to <component> within MenuItem inside a <Menu> not working
I believe this is actually a bug so I am transitioning it to a issue unless I have missed something in the docs.

### Discussed in https://github.com/tailwindlabs/headlessui/discussions/2920

<div type='discussions-op-text'>

<sup>Originally posted by **JshGrn** January  9, 2024</sup>
If I add 'disabled' to the MenuItem then it adds the aria-disabled prop, however I want to mark the <Component> within the <MenuItem> as disabled, outputting "disabled" inline, however this doesnt work. It looks like its removed or set by default to false?

Have read the closed issues and can't see anyone reporting the same issue.

A very simplified markup:
```
<Menu as="div">
   <MenuButton>test</MenuButton>
      <MenuItems>
         <MenuItem
            v-for="menuItem in items"
            :key="menuItem.href"
            :href="menuItem.href"
            v-slot="{ active }"
            disabled
         >
            <component is="button" disabled>disabled button</component>
         </MenuItem>
   </MenuItems>
</Menu>
```

I am expecting ```<button disabled>disabled button</button>```</div>

Pull request description:
Don’t override explicit `disabled` prop for components inside `<MenuItem>`
Fixes #2921

Patch:
diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md
index 278a69d..8c5d8fc 100644
--- a/packages/@headlessui-vue/CHANGELOG.md
+++ b/packages/@headlessui-vue/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Expose `disabled` state on `<Tab />` component ([#2918](https://github.com/tailwindlabs/headlessui/pull/2918))
 - Prevent default behaviour when clicking outside of a `DialogPanel` ([#2919](https://github.com/tailwindlabs/headlessui/pull/2919))
+- Don’t override explicit `disabled` prop for components inside `<MenuItem>` ([#2929](https://github.com/tailwindlabs/headlessui/pull/2929))
 
 ## [1.7.17] - 2024-01-08
 
diff --git a/packages/@headlessui-vue/src/components/menu/menu.ts b/packages/@headlessui-vue/src/components/menu/menu.ts
index 39fc6dd..e6fa936 100644
--- a/packages/@headlessui-vue/src/components/menu/menu.ts
+++ b/packages/@headlessui-vue/src/components/menu/menu.ts
@@ -563,16 +563,14 @@ export let MenuItem = defineComponent({
     }
 
     return () => {
-      let { disabled } = props
+      let { id, disabled, ...theirProps } = props
       let slot = { active: active.value, disabled, close: api.closeMenu }
-      let { id, ...theirProps } = props
       let ourProps = {
         id,
         ref: internalItemRef,
         role: 'menuitem',
         tabIndex: disabled === true ? undefined : -1,
         'aria-disabled': disabled === true ? true : undefined,
-        disabled: undefined, // Never forward the `disabled` prop
         onClick: handleClick,
         onFocus: handleFocus,
         onPointerenter: handleEnter,


Test patch:
diff --git a/packages/@headlessui-react/src/components/menu/menu.test.tsx b/packages/@headlessui-react/src/components/menu/menu.test.tsx
index 61dba9f..2a26ef1 100644
--- a/packages/@headlessui-react/src/components/menu/menu.test.tsx
+++ b/packages/@headlessui-react/src/components/menu/menu.test.tsx
@@ -448,6 +448,47 @@ describe('Rendering', () => {
         assertMenu({ state: MenuState.InvisibleUnmounted })
       })
     )
+
+    it('should not override an explicit disabled prop on MenuItems child', async () => {
+      render(
+        <Menu>
+          <Menu.Button>Trigger</Menu.Button>
+          <Menu.Items>
+            <Menu.Item>{({ disabled }) => <button disabled={disabled}>Item A</button>}</Menu.Item>
+            <Menu.Item>{({ disabled }) => <button disabled={disabled}>Item B</button>}</Menu.Item>
+            <Menu.Item disabled>
+              {({ disabled }) => <button disabled={disabled}>Item C</button>}
+            </Menu.Item>
+          </Menu.Items>
+        </Menu>
+      )
+
+      assertMenuButton({
+        state: MenuState.InvisibleUnmounted,
+      })
+      assertMenu({ state: MenuState.InvisibleUnmounted })
+
+      getMenuButton()?.focus()
+
+      await press(Keys.Enter)
+
+      assertMenuButton({
+        state: MenuState.Visible,
+      })
+      assertMenu({ state: MenuState.Visible })
+      assertMenuItem(getMenuItems()[0], {
+        tag: 'button',
+        attributes: { 'data-focus': '' },
+      })
+      assertMenuItem(getMenuItems()[1], {
+        tag: 'button',
+        attributes: {},
+      })
+      assertMenuItem(getMenuItems()[2], {
+        tag: 'button',
+        attributes: { disabled: '' },
+      })
+    })
   })
 
   it('should guarantee the order of DOM nodes when performing actions', async () => {
diff --git a/packages/@headlessui-vue/src/components/menu/menu.test.tsx b/packages/@headlessui-vue/src/components/menu/menu.test.tsx
index 9fc698e..314ccdb 100644
--- a/packages/@headlessui-vue/src/components/menu/menu.test.tsx
+++ b/packages/@headlessui-vue/src/components/menu/menu.test.tsx
@@ -686,6 +686,53 @@ describe('Rendering', () => {
       })
     })
 
+    it('should not override an explicit disabled prop on MenuItems child', async () => {
+      renderTemplate(jsx`
+        <Menu>
+          <MenuButton>Trigger</MenuButton>
+          <MenuItems>
+            <MenuItem v-slot="{ active, disabled }">
+              <button :data-active="active" :disabled="disabled">Item A</button>
+            </MenuItem>
+            <MenuItem v-slot="{ active, disabled }">
+              <button :data-active="active" :disabled="disabled">Item B</button>
+            </MenuItem>
+            <MenuItem disabled v-slot="{ active, disabled }">
+              <button :data-active="active" :disabled="disabled">Item C</button>
+            </MenuItem>
+          </MenuItems>
+        </Menu>
+      `)
+
+      assertMenuButton({
+        state: MenuState.InvisibleUnmounted,
+        attributes: { id: 'headlessui-menu-button-1' },
+      })
+      assertMenu({ state: MenuState.InvisibleUnmounted })
+
+      getMenuButton()?.focus()
+
+      await press(Keys.Enter)
+
+      assertMenuButton({
+        state: MenuState.Visible,
+        attributes: { id: 'headlessui-menu-button-1' },
+      })
+      assertMenu({ state: MenuState.Visible })
+      assertMenuItem(getMenuItems()[0], {
+        tag: 'button',
+        attributes: { 'data-active': 'true' },
+      })
+      assertMenuItem(getMenuItems()[1], {
+        tag: 'button',
+        attributes: { 'data-active': 'false' },
+      })
+      assertMenuItem(getMenuItems()[2], {
+        tag: 'button',
+        attributes: { 'data-active': 'false', disabled: '' },
+      })
+    })
+
     it('should yell when we render a MenuItem using a template `as` prop that contains multiple children', async () => {
       expect.hasAssertions()
 
@@ -712,7 +759,6 @@ describe('Rendering', () => {
                 'The current component <MenuItem /> is rendering a "template".',
                 'However we need to passthrough the following props:',
                 '  - aria-disabled',
-                '  - disabled',
                 '  - id',
                 '  - onClick',
                 '  - onFocus',