diff --git a/.changeset/modern-eyes-wait.md b/.changeset/modern-eyes-wait.md new file mode 100644 index 0000000000..7f1a345f6f --- /dev/null +++ b/.changeset/modern-eyes-wait.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +**Breaking** We are adding a new as prop on the Heading and Text component to make it easier to change the component tag. We are removing the render prop in favour of the as prop. diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index bcc32ef651..938aa3d829 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -8,8 +8,10 @@ import { Breakpoint as Breakpoint_2 } from '@backstage/canon'; import { ChangeEvent } from 'react'; import { Collapsible as Collapsible_2 } from '@base-ui-components/react/collapsible'; import { ComponentProps } from 'react'; +import type { ComponentPropsWithRef } from 'react'; import { Context } from 'react'; import type { CSSProperties } from 'react'; +import type { ElementType } from 'react'; import { FC } from 'react'; import { FocusEvent as FocusEvent_2 } from 'react'; import { ForwardRefExoticComponent } from 'react'; @@ -612,39 +614,17 @@ export interface GridProps extends SpaceProps { } // @public (undocumented) -export const Heading: ForwardRefExoticComponent< - Omit & RefAttributes ->; +export const Heading: ( + props: HeadingProps & { + ref?: React.Ref; + }, +) => React.ReactElement | null; +// Warning: (ae-forgotten-export) The symbol "HeadingOwnProps" needs to be exported by the entry point index.d.ts +// // @public (undocumented) -export interface HeadingProps - extends Omit, 'color'> { - // (undocumented) - className?: string; - // (undocumented) - color?: - | 'primary' - | 'secondary' - | Partial>; - // (undocumented) - style?: React.CSSProperties; - // (undocumented) - truncate?: boolean; - // (undocumented) - variant?: - | 'display' - | 'title1' - | 'title2' - | 'title3' - | 'title4' - | 'title5' - | Partial< - Record< - Breakpoint, - 'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5' - > - >; -} +export type HeadingProps = HeadingOwnProps & + Omit, keyof HeadingOwnProps>; // @public (undocumented) export const heightPropDefs: { @@ -1269,9 +1249,11 @@ export interface TabsRootWithoutOrientation > {} // @public (undocumented) -const Text_2: ForwardRefExoticComponent< - Omit & RefAttributes ->; +const Text_2: ( + props: TextProps & { + ref?: React.Ref; + }, +) => React.ReactElement | null; export { Text_2 as Text }; // @public (undocumented) @@ -1295,38 +1277,11 @@ export interface TextFieldProps size?: 'small' | 'medium' | Partial>; } +// Warning: (ae-forgotten-export) The symbol "TextOwnProps" needs to be exported by the entry point index.d.ts +// // @public (undocumented) -export interface TextProps - extends Omit, 'color'> { - // (undocumented) - className?: string; - // (undocumented) - color?: - | 'primary' - | 'secondary' - | 'danger' - | 'warning' - | 'success' - | Partial< - Record< - Breakpoint, - 'primary' | 'secondary' | 'danger' | 'warning' | 'success' - > - >; - // (undocumented) - style?: CSSProperties; - // (undocumented) - truncate?: boolean; - // (undocumented) - variant?: - | 'subtitle' - | 'body' - | 'caption' - | 'label' - | Partial>; - // (undocumented) - weight?: 'regular' | 'bold' | Partial>; -} +export type TextProps = TextOwnProps & + Omit, keyof TextOwnProps>; // @public (undocumented) export const Tooltip: { diff --git a/packages/canon/src/components/Heading/Heading.stories.tsx b/packages/canon/src/components/Heading/Heading.stories.tsx index 956118ddeb..2e608c09a3 100644 --- a/packages/canon/src/components/Heading/Heading.stories.tsx +++ b/packages/canon/src/components/Heading/Heading.stories.tsx @@ -96,7 +96,7 @@ export const WrappedInLink: Story = { export const CustomRender: Story = { args: { ...Default.args, - render:

, + as: 'h4', }, }; diff --git a/packages/canon/src/components/Heading/Heading.tsx b/packages/canon/src/components/Heading/Heading.tsx index 6ea2fe006b..4df5f85bc9 100644 --- a/packages/canon/src/components/Heading/Heading.tsx +++ b/packages/canon/src/components/Heading/Heading.tsx @@ -14,42 +14,49 @@ * limitations under the License. */ -import { forwardRef, useRef } from 'react'; +import { forwardRef } from 'react'; import clsx from 'clsx'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; -import { useRender } from '@base-ui-components/react/use-render'; +import type { ElementType } from 'react'; import type { HeadingProps } from './types'; +function HeadingComponent( + { + as, + variant = 'title1', + color = 'primary', + truncate, + className, + style, + ...restProps + }: HeadingProps, + ref: React.Ref, +) { + const Component = as || 'h1'; + + const responsiveVariant = useResponsiveValue(variant); + const responsiveColor = useResponsiveValue(color); + + return ( + + ); +} + +HeadingComponent.displayName = 'Heading'; + /** @public */ -export const Heading = forwardRef( - (props, ref) => { - const { - variant = 'title1', - color = 'primary', - truncate, - className, - render =

, - ...restProps - } = props; +export const Heading = forwardRef(HeadingComponent) as < + T extends ElementType = 'h1', +>( + props: HeadingProps & { ref?: React.Ref }, +) => React.ReactElement | null; - const responsiveVariant = useResponsiveValue(variant); - const responsiveColor = useResponsiveValue(color); - const internalRef = useRef(null); - - const { renderElement } = useRender({ - render, - props: { - className: clsx('canon-Heading', className), - ['data-variant']: responsiveVariant, - ['data-color']: responsiveColor, - ['data-truncate']: truncate, - ...restProps, - }, - refs: [ref, internalRef], - }); - - return renderElement(); - }, -); - -Heading.displayName = 'Heading'; +(Heading as any).displayName = 'Heading'; diff --git a/packages/canon/src/components/Heading/types.ts b/packages/canon/src/components/Heading/types.ts index 7a94bdf2a4..f2317462c2 100644 --- a/packages/canon/src/components/Heading/types.ts +++ b/packages/canon/src/components/Heading/types.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { Breakpoint } from '../../types'; -import type { useRender } from '@base-ui-components/react/use-render'; +import type { ElementType, ComponentPropsWithRef } from 'react'; +import type { Breakpoint } from '../../types'; /** @public */ -export interface HeadingProps - extends Omit, 'color'> { +export type HeadingOwnProps = { + as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; variant?: | 'display' | 'title1' @@ -40,4 +40,8 @@ export interface HeadingProps truncate?: boolean; className?: string; style?: React.CSSProperties; -} +}; + +/** @public */ +export type HeadingProps = HeadingOwnProps & + Omit, keyof HeadingOwnProps>; diff --git a/packages/canon/src/components/Text/Text.stories.tsx b/packages/canon/src/components/Text/Text.stories.tsx index 5fe0c12cb5..5bb918f52e 100644 --- a/packages/canon/src/components/Text/Text.stories.tsx +++ b/packages/canon/src/components/Text/Text.stories.tsx @@ -111,7 +111,7 @@ export const WrappedInLink: Story = { export const CustomRender: Story = { args: { ...Default.args, - render: , + as: 'span', }, }; diff --git a/packages/canon/src/components/Text/Text.tsx b/packages/canon/src/components/Text/Text.tsx index 7e4dc74720..abf03ef0bd 100644 --- a/packages/canon/src/components/Text/Text.tsx +++ b/packages/canon/src/components/Text/Text.tsx @@ -14,47 +14,51 @@ * limitations under the License. */ -import { forwardRef, useRef } from 'react'; +import { forwardRef } from 'react'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; -import { useRender } from '@base-ui-components/react/use-render'; import clsx from 'clsx'; - +import type { ElementType } from 'react'; import type { TextProps } from './types'; +function TextComponent( + { + as, + variant = 'body', + weight = 'regular', + color = 'primary', + className, + truncate, + style, + ...restProps + }: TextProps, + ref: React.Ref, +) { + const Component = as || 'p'; + + // Get the responsive values for the variant and weight + const responsiveVariant = useResponsiveValue(variant); + const responsiveWeight = useResponsiveValue(weight); + const responsiveColor = useResponsiveValue(color); + + return ( + + ); +} + +TextComponent.displayName = 'Text'; + /** @public */ -export const Text = forwardRef( - (props, ref) => { - const { - variant = 'body', - weight = 'regular', - color = 'primary', - className, - truncate, - render =

, - ...restProps - } = props; +export const Text = forwardRef(TextComponent) as ( + props: TextProps & { ref?: React.Ref }, +) => React.ReactElement | null; - // Get the responsive values for the variant and weight - const responsiveVariant = useResponsiveValue(variant); - const responsiveWeight = useResponsiveValue(weight); - const responsiveColor = useResponsiveValue(color); - const internalRef = useRef(null); - - const { renderElement } = useRender({ - render, - props: { - className: clsx('canon-Text', className), - ['data-variant']: responsiveVariant, - ['data-weight']: responsiveWeight, - ['data-color']: responsiveColor, - ['data-truncate']: truncate, - ...restProps, - }, - refs: [ref, internalRef], - }); - - return renderElement(); - }, -); - -Text.displayName = 'Text'; +(Text as any).displayName = 'Text'; diff --git a/packages/canon/src/components/Text/types.ts b/packages/canon/src/components/Text/types.ts index f9c3c0aaca..ed01956248 100644 --- a/packages/canon/src/components/Text/types.ts +++ b/packages/canon/src/components/Text/types.ts @@ -14,13 +14,12 @@ * limitations under the License. */ -import type { CSSProperties } from 'react'; +import type { ElementType, ComponentPropsWithRef } from 'react'; import type { Breakpoint } from '../../types'; -import type { useRender } from '@base-ui-components/react/use-render'; /** @public */ -export interface TextProps - extends Omit, 'color'> { +export type TextOwnProps = { + as?: 'p' | 'span' | 'label'; variant?: | 'subtitle' | 'body' @@ -42,5 +41,9 @@ export interface TextProps >; truncate?: boolean; className?: string; - style?: CSSProperties; -} + style?: React.CSSProperties; +}; + +/** @public */ +export type TextProps = TextOwnProps & + Omit, keyof TextOwnProps>;