Merge pull request #29247 from backstage/canon-render-prop

Canon: Add render prop to Link
This commit is contained in:
Patrik Oldsberg
2025-04-07 12:08:21 +02:00
committed by GitHub
6 changed files with 74 additions and 19 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
We added a render prop to the Link component to make sure it can work with React Router.
+27
View File
@@ -15,6 +15,7 @@ import { RefAttributes } from 'react';
import type { RemixiconComponentType } from '@remixicon/react';
import { ScrollArea as ScrollArea_2 } from '@base-ui-components/react/scroll-area';
import { Tooltip as Tooltip_2 } from '@base-ui-components/react/tooltip';
import type { useRender } from '@base-ui-components/react/use-render';
// @public (undocumented)
export type AlignItems = 'stretch' | 'start' | 'center' | 'end';
@@ -696,6 +697,32 @@ export type JustifyContent =
| 'around'
| 'between';
// @public (undocumented)
export const Link: React_2.ForwardRefExoticComponent<
Omit<LinkProps, 'ref'> & React_2.RefAttributes<HTMLElement>
>;
// @public (undocumented)
export interface LinkProps extends useRender.ComponentProps<'a'> {
// (undocumented)
children: ReactNode;
// (undocumented)
className?: string;
// (undocumented)
style?: CSSProperties;
// (undocumented)
to?: string;
// (undocumented)
variant?:
| 'subtitle'
| 'body'
| 'caption'
| 'label'
| Partial<Record<Breakpoint, 'subtitle' | 'body' | 'caption' | 'label'>>;
// (undocumented)
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
}
// @public (undocumented)
export const marginPropDefs: (spacingValues: string[]) => {
m: {
@@ -19,6 +19,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Link } from './Link';
import { Flex } from '../Flex';
import { Text } from '../Text';
import { Link as RouterLink, MemoryRouter } from 'react-router-dom';
const meta = {
title: 'Components/Link',
@@ -33,7 +34,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
href: 'https://canon.backstage.io',
to: 'https://canon.backstage.io',
children: 'Sign up for Backstage',
},
};
@@ -66,6 +67,7 @@ export const AllWeights: Story = {
export const Responsive: Story = {
args: {
...Default.args,
variant: {
xs: 'label',
md: 'body',
@@ -73,6 +75,23 @@ export const Responsive: Story = {
},
};
export const CustomRender: Story = {
render: () => {
return (
<Flex gap="4" direction="column" align="start">
<Link render={<RouterLink to="/catalog" />}>Go to Catalog</Link>
</Flex>
);
},
decorators: [
Story => (
<MemoryRouter>
<Story />
</MemoryRouter>
),
],
};
export const Playground: Story = {
args: {
...Default.args,
+18 -16
View File
@@ -15,41 +15,43 @@
*/
import React, { forwardRef } from 'react';
import { useRender } from '@base-ui-components/react/use-render';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import clsx from 'clsx';
import type { LinkProps } from './types';
/** @public */
export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
export const Link = forwardRef<HTMLElement, LinkProps>((props, ref) => {
const {
children,
className,
variant = 'body',
weight = 'regular',
style,
className,
render = <a />,
...restProps
} = props;
// Get the responsive values for the variant and weight
const responsiveVariant = useResponsiveValue(variant);
const responsiveWeight = useResponsiveValue(weight);
const internalRef = React.useRef<HTMLElement | null>(null);
return (
<a
ref={ref}
className={clsx(
const { renderElement } = useRender({
render,
props: {
className: clsx(
'canon-Link',
responsiveVariant && `canon-Link--variant-${responsiveVariant}`,
responsiveWeight && `canon-Link--weight-${responsiveWeight}`,
className,
)}
style={style}
{...restProps}
>
{children}
</a>
);
),
responsiveVariant,
responsiveWeight,
...restProps,
},
refs: [ref, internalRef],
});
return renderElement();
});
Link.displayName = 'Link';
+3 -2
View File
@@ -16,11 +16,12 @@
import type { CSSProperties, ReactNode } from 'react';
import type { Breakpoint } from '../../types';
import type { useRender } from '@base-ui-components/react/use-render';
/** @public */
export interface LinkProps
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
export interface LinkProps extends useRender.ComponentProps<'a'> {
children: ReactNode;
to?: string;
variant?:
| 'subtitle'
| 'body'
+1
View File
@@ -41,6 +41,7 @@ export * from './components/TextField';
export * from './components/Tooltip';
export * from './components/Menu';
export * from './components/ScrollArea';
export * from './components/Link';
export * from './components/Select';
// Types