From 02b86027ef03e3e1f90e1d7405384c78ffa2370c Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 17 Mar 2025 15:12:25 +0000 Subject: [PATCH] Update Link render prop mechanism Signed-off-by: Charles de Dreuille --- .../src/components/Link/Link.stories.tsx | 3 +- packages/canon/src/components/Link/Link.tsx | 78 +++++++++++-------- packages/canon/src/components/Link/types.ts | 12 ++- packages/canon/src/index.ts | 1 + 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/packages/canon/src/components/Link/Link.stories.tsx b/packages/canon/src/components/Link/Link.stories.tsx index 38e71c310a..856fe2532d 100644 --- a/packages/canon/src/components/Link/Link.stories.tsx +++ b/packages/canon/src/components/Link/Link.stories.tsx @@ -33,7 +33,7 @@ type Story = StoryObj; 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', diff --git a/packages/canon/src/components/Link/Link.tsx b/packages/canon/src/components/Link/Link.tsx index 8b88009a86..e3f636ea06 100644 --- a/packages/canon/src/components/Link/Link.tsx +++ b/packages/canon/src/components/Link/Link.tsx @@ -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((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((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 ; -}); + 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, + ); + return ; + } + // If it's a render function, call it directly + const RenderComponent = render as ( + props: LinkRenderProps, + ) => React.ReactNode; + return ; + } + + return ; + }), +); Link.displayName = 'Link'; diff --git a/packages/canon/src/components/Link/types.ts b/packages/canon/src/components/Link/types.ts index 7bde893de2..134509563a 100644 --- a/packages/canon/src/components/Link/types.ts +++ b/packages/canon/src/components/Link/types.ts @@ -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 { + extends Omit, 'to'> { children: ReactNode; + to: string; variant?: | 'subtitle' | 'body' @@ -30,5 +31,10 @@ export interface LinkProps weight?: 'regular' | 'bold' | Partial>; className?: string; style?: CSSProperties; - render?: (props: Omit) => ReactNode; + render?: + | ((props: Omit) => ReactNode) + | ComponentType>; } + +/** @public */ +export type LinkRenderProps = Omit; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 3c9a28b44d..f30b825c8c 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -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';