Update Link render prop mechanism

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-03-17 15:12:25 +00:00
parent 4fe5b08db6
commit 02b86027ef
4 changed files with 58 additions and 36 deletions
@@ -33,7 +33,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 +66,7 @@ export const AllWeights: Story = {
export const Responsive: Story = {
args: {
...Default.args,
variant: {
xs: 'label',
md: 'body',
+46 -32
View File
@@ -14,45 +14,59 @@
* limitations under the License.
*/
import React, { forwardRef } from 'react';
import React, { forwardRef, memo, ComponentType } from 'react';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import clsx from 'clsx';
import type { LinkProps } from './types';
import type { LinkProps, LinkRenderProps } from './types';
/** @public */
export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const {
children,
variant = 'body',
weight = 'regular',
style,
className,
render,
...restProps
} = props;
const responsiveVariant = useResponsiveValue(variant);
const responsiveWeight = useResponsiveValue(weight);
const linkProps = {
ref,
className: clsx(
'canon-Link',
responsiveVariant && `canon-Link--variant-${responsiveVariant}`,
responsiveWeight && `canon-Link--weight-${responsiveWeight}`,
export const Link = memo(
forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const {
children,
variant = 'body',
weight = 'regular',
style,
className,
),
style,
children,
...restProps,
};
render,
to,
...restProps
} = props;
if (render) {
return render(linkProps);
}
const responsiveVariant = useResponsiveValue(variant);
const responsiveWeight = useResponsiveValue(weight);
return <a {...linkProps} />;
});
const linkProps: LinkRenderProps = {
className: clsx(
'canon-Link',
responsiveVariant && `canon-Link--variant-${responsiveVariant}`,
responsiveWeight && `canon-Link--weight-${responsiveWeight}`,
className,
),
style,
children,
to,
...restProps,
};
if (render) {
// If render is a component type, wrap it in memo to prevent unnecessary re-renders
if (typeof render === 'function' && !render.length) {
const MemoizedComponent = memo(
render as ComponentType<LinkRenderProps>,
);
return <MemoizedComponent {...linkProps} />;
}
// If it's a render function, call it directly
const RenderComponent = render as (
props: LinkRenderProps,
) => React.ReactNode;
return <RenderComponent {...linkProps} />;
}
return <a ref={ref} href={to} {...linkProps} />;
}),
);
Link.displayName = 'Link';
+9 -3
View File
@@ -14,13 +14,14 @@
* limitations under the License.
*/
import type { CSSProperties, ReactNode } from 'react';
import type { CSSProperties, ReactNode, ComponentType } from 'react';
import type { Breakpoint } from '../../types';
/** @public */
export interface LinkProps
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'to'> {
children: ReactNode;
to: string;
variant?:
| 'subtitle'
| 'body'
@@ -30,5 +31,10 @@ export interface LinkProps
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
className?: string;
style?: CSSProperties;
render?: (props: Omit<LinkProps, 'render'>) => ReactNode;
render?:
| ((props: Omit<LinkProps, 'render'>) => ReactNode)
| ComponentType<Omit<LinkProps, 'render'>>;
}
/** @public */
export type LinkRenderProps = Omit<LinkProps, 'render'>;
+1
View File
@@ -42,6 +42,7 @@ export * from './components/Field';
export * from './components/Tooltip';
export * from './components/Menu';
export * from './components/ScrollArea';
export * from './components/Link';
// Types
export * from './types';