Repository: TanStack/router (ts)

Issue:
Link's `_asChild` / `createLink` do not forward all props
### Describe the bug

The `_asChild` prop of `Link` as well as the `createLink` function that uses it look like the intended way to build wrapped links, e.g. for use in design systems (see https://github.com/TanStack/router/discussions/721 for a discussion that shows that a lot of folks want to do this). 

However, both of them remove the `disabled` prop which is used in some design systems to style the resulting link. I am not sure if any other props are similarly removed, but the `_asChild` component should probably have access to all props that are passed in.

### Your Example Website or App

https://stackblitz.com/edit/github-mrrmdd?file=src%2Fmain.tsx

### Steps to Reproduce the Bug or Issue

1. See the third link does not have the disabled style
2. See the console does not log the disabled prop

### Expected behavior

1. I expect the `_asChild` component to have access to all props

### Screenshots or Videos

_No response_

### Platform

- OS: Any
- Browser: Any
- Version: 1.31.3


### Additional context

_No response_

Pull request:
fix: persisting the 'disabled' prop for custom link elements using `createLink`
Closes #1542 

Patch:
diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx
index 008391f63..e95e67561 100644
--- a/packages/react-router/src/link.tsx
+++ b/packages/react-router/src/link.tsx
@@ -711,6 +711,7 @@ export function useLinkProps<
     onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
     onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
     onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
+    disabled: !!disabled,
     target,
     ...(Object.keys(resolvedStyle).length && { style: resolvedStyle }),
     ...(resolvedClassName && { className: resolvedClassName }),
@@ -810,6 +811,12 @@ export const Link: LinkComponent<'a'> = React.forwardRef((props: any, ref) => {
         })
       : rest.children
 
+  if (typeof _asChild === 'undefined') {
+    // the ReturnType of useLinkProps returns the correct type for a <a> element, not a general component that has a delete prop
+    // @ts-expect-error
+    delete linkProps.disabled
+  }
+
   return React.createElement(
     _asChild ? _asChild : 'a',
     {


Test patch:
diff --git a/packages/react-router/tests/link.test.tsx b/packages/react-router/tests/link.test.tsx
new file mode 100644
index 000000000..5d08bb469
--- /dev/null
+++ b/packages/react-router/tests/link.test.tsx
@@ -0,0 +1,103 @@
+import React from 'react'
+import { describe, it, expect, afterEach } from 'vitest'
+import { render, cleanup } from '@testing-library/react'
+
+import {
+  createRouter,
+  createRootRoute,
+  createRoute,
+  createMemoryHistory,
+  RouterProvider,
+  createLink,
+  Link,
+} from '../src'
+
+afterEach(() => {
+  cleanup()
+})
+
+describe('Link', () => {
+  it('should NOT pass the "disabled" prop to the rendered Link component', async () => {
+    const rootRoute = createRootRoute()
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => (
+        <Link to="/" disabled>
+          Index
+        </Link>
+      ),
+    })
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([indexRoute]),
+      history: createMemoryHistory({ initialEntries: ['/'] }),
+    })
+
+    await router.load()
+
+    const rendered = render(<RouterProvider router={router} />)
+    const customElement = rendered.queryByText('Index')
+
+    expect(customElement!.hasAttribute('disabled')).toBe(false)
+  })
+})
+
+describe('createLink', () => {
+  it('should pass the "disabled" prop to the rendered target element', async () => {
+    const CustomLink = createLink('button')
+
+    const rootRoute = createRootRoute()
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => (
+        <CustomLink to="/" disabled>
+          Index
+        </CustomLink>
+      ),
+    })
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([indexRoute]),
+      history: createMemoryHistory({ initialEntries: ['/'] }),
+    })
+
+    await router.load()
+
+    const rendered = render(<RouterProvider router={router} />)
+    const customElement = rendered.queryByText('Index')
+
+    expect(customElement!.hasAttribute('disabled')).toBe(true)
+    expect(customElement!.getAttribute('disabled')).toBe('')
+  })
+
+  it('should pass the "foo" prop to the rendered target element', async () => {
+    const CustomLink = createLink('button')
+
+    const rootRoute = createRootRoute()
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => (
+        <CustomLink
+          to="/"
+          // @ts-expect-error
+          foo="bar"
+        >
+          Index
+        </CustomLink>
+      ),
+    })
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([indexRoute]),
+      history: createMemoryHistory({ initialEntries: ['/'] }),
+    })
+
+    await router.load()
+
+    const rendered = render(<RouterProvider router={router} />)
+    const customElement = rendered.queryByText('Index')
+
+    expect(customElement!.hasAttribute('foo')).toBe(true)
+    expect(customElement!.getAttribute('foo')).toBe('bar')
+  })
+})