From a834a301a3c5c823befab6a49150f77b96e76798 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Fri, 8 May 2026 12:29:32 +0530 Subject: [PATCH] feat: add render support in link --- .../content/docs/components/link/index.mdx | 10 +++++ .../src/content/docs/components/link/props.ts | 16 +++++++- .../components/link/__tests__/link.test.tsx | 41 +++++++++++++++++++ packages/raystack/components/link/link.tsx | 10 +++-- 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/apps/www/src/content/docs/components/link/index.mdx b/apps/www/src/content/docs/components/link/index.mdx index 84f79b75a..4af3f01c3 100644 --- a/apps/www/src/content/docs/components/link/index.mdx +++ b/apps/www/src/content/docs/components/link/index.mdx @@ -56,6 +56,16 @@ Additional style variations including underlined and external links. +### Custom render + +Use the `render` prop to render as a custom element such as a router `Link`. All props (including `external`, `download`, and ARIA attributes) are forwarded to the provided element. + +```tsx +import { Link as RouterLink } from "react-router-dom"; + +}>About +``` + ## Accessibility The Link component follows accessibility best practices: diff --git a/apps/www/src/content/docs/components/link/props.ts b/apps/www/src/content/docs/components/link/props.ts index c26ef9779..e61cb7de6 100644 --- a/apps/www/src/content/docs/components/link/props.ts +++ b/apps/www/src/content/docs/components/link/props.ts @@ -1,6 +1,8 @@ +import type { ReactElement } from 'react'; + export interface LinkProps { - /** The URL that the link points to. (Required) */ - href: string; + /** The URL that the link points to. */ + href?: string; /** Whether the link should open in a new tab. */ external?: boolean; @@ -8,6 +10,16 @@ export interface LinkProps { /** Whether the link should be downloadable or a string for the filename. */ download?: boolean | string; + /** + * Custom element used to render the Link. + * + * All props are forwarded to the specified element. Useful for integrating + * with router libraries (e.g. ``, ``). + * + * @default "" + */ + render?: ReactElement; + /** Additional CSS class names. */ className?: string; } diff --git a/packages/raystack/components/link/__tests__/link.test.tsx b/packages/raystack/components/link/__tests__/link.test.tsx index 5630fd492..bb73c2763 100644 --- a/packages/raystack/components/link/__tests__/link.test.tsx +++ b/packages/raystack/components/link/__tests__/link.test.tsx @@ -77,6 +77,47 @@ describe('Link', () => { }); }); + describe('Custom render', () => { + it('renders as the element provided via render prop', () => { + render( + }> + Custom + + ); + const link = screen.getByRole('link'); + expect(link.tagName).toBe('BUTTON'); + expect(link).toHaveAttribute('type', 'button'); + }); + + it('forwards Link props to the custom rendered element', () => { + render( + } + > + External + + ); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', 'https://example.com'); + expect(link).toHaveAttribute('target', '_blank'); + expect(link).toHaveAttribute('rel', 'noopener noreferrer'); + expect(link).toHaveAttribute('data-custom', 'yes'); + }); + + it('preserves the link className on the custom rendered element', () => { + render( + }> + Custom + + ); + const link = screen.getByRole('link'); + expect(link.tagName).toBe('DIV'); + expect(link).toHaveClass(styles.link); + }); + }); + describe('HTML Attributes', () => { it('supports title attribute', () => { render( diff --git a/packages/raystack/components/link/link.tsx b/packages/raystack/components/link/link.tsx index 811a7e4a1..bc64975a8 100644 --- a/packages/raystack/components/link/link.tsx +++ b/packages/raystack/components/link/link.tsx @@ -1,10 +1,11 @@ +import { useRender } from '@base-ui/react'; import { cx } from 'class-variance-authority'; -import { ComponentProps } from 'react'; import { Text, type TextBaseProps } from '../text'; import styles from './link.module.css'; -export interface LinkProps extends TextBaseProps, ComponentProps<'a'> { - href: string; +export interface LinkProps + extends TextBaseProps, + useRender.ComponentProps<'a'> { external?: boolean; download?: boolean | string; } @@ -16,6 +17,7 @@ export function Link({ size = 'small', external, download, + render = , ...props }: LinkProps) { const externalProps = external @@ -42,7 +44,7 @@ export function Link({ {...externalProps} {...downloadProps} {...props} - render={} + render={render} > {children}