From ccb1fc68b6a0c248ff0da98db78b1f96e87b55c2 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 16 May 2025 18:57:36 +0100 Subject: [PATCH] Improve the way we treat custom render on Text and Heading Signed-off-by: Charles de Dreuille --- .changeset/puny-garlics-bathe.md | 5 +++ packages/canon/css/components.css | 20 +++++++++ packages/canon/css/heading.css | 20 +++++++++ packages/canon/css/styles.css | 20 +++++++++ packages/canon/report.api.md | 29 ++++++++----- .../components/Heading/Heading.stories.tsx | 29 +++++++++---- .../canon/src/components/Heading/Heading.tsx | 41 ++++++++----------- .../canon/src/components/Heading/styles.css | 20 +++++++++ .../canon/src/components/Heading/types.ts | 18 ++++++-- .../src/components/Text/Text.stories.tsx | 7 ++++ packages/canon/src/components/Text/Text.tsx | 35 ++++++++-------- packages/canon/src/components/Text/types.ts | 7 ++-- 12 files changed, 188 insertions(+), 63 deletions(-) create mode 100644 .changeset/puny-garlics-bathe.md diff --git a/.changeset/puny-garlics-bathe.md b/.changeset/puny-garlics-bathe.md new file mode 100644 index 0000000000..97071ac2b7 --- /dev/null +++ b/.changeset/puny-garlics-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +We are modifying the way we treat custom render using 'useRender()' under the hood from BaseUI. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 23133c9966..496dd1303d 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -447,6 +447,26 @@ font-weight: var(--canon-font-weight-bold); } +.canon-Heading[data-color="primary"] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color="secondary"] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-color="danger"] { + color: var(--canon-fg-danger); +} + +.canon-Heading[data-color="warning"] { + color: var(--canon-fg-warning); +} + +.canon-Heading[data-color="success"] { + color: var(--canon-fg-success); +} + .canon-Heading[data-truncate] { text-overflow: ellipsis; white-space: nowrap; diff --git a/packages/canon/css/heading.css b/packages/canon/css/heading.css index 53e8d8f25a..50e9d3503d 100644 --- a/packages/canon/css/heading.css +++ b/packages/canon/css/heading.css @@ -36,6 +36,26 @@ font-weight: var(--canon-font-weight-bold); } +.canon-Heading[data-color="primary"] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color="secondary"] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-color="danger"] { + color: var(--canon-fg-danger); +} + +.canon-Heading[data-color="warning"] { + color: var(--canon-fg-warning); +} + +.canon-Heading[data-color="success"] { + color: var(--canon-fg-success); +} + .canon-Heading[data-truncate] { text-overflow: ellipsis; white-space: nowrap; diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 42d47267f8..aceb4e39db 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9671,6 +9671,26 @@ font-weight: var(--canon-font-weight-bold); } +.canon-Heading[data-color="primary"] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color="secondary"] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-color="danger"] { + color: var(--canon-fg-danger); +} + +.canon-Heading[data-color="warning"] { + color: var(--canon-fg-warning); +} + +.canon-Heading[data-color="success"] { + color: var(--canon-fg-success); +} + .canon-Heading[data-truncate] { text-overflow: ellipsis; white-space: nowrap; diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index ecfdfde188..0ffa6052c2 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -613,18 +613,28 @@ export interface GridProps extends SpaceProps { // @public (undocumented) export const Heading: ForwardRefExoticComponent< - HeadingProps & RefAttributes + Omit & RefAttributes >; // @public (undocumented) -export interface HeadingProps { - // (undocumented) - as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; - // (undocumented) - children: React.ReactNode; +export interface HeadingProps + extends Omit, 'color'> { // (undocumented) className?: string; // (undocumented) + color?: + | 'primary' + | 'secondary' + | 'danger' + | 'warning' + | 'success' + | Partial< + Record< + Breakpoint, + 'primary' | 'secondary' | 'danger' | 'warning' | 'success' + > + >; + // (undocumented) style?: React.CSSProperties; // (undocumented) truncate?: boolean; @@ -1208,7 +1218,7 @@ export interface TableCellTextProps // @public (undocumented) const Text_2: ForwardRefExoticComponent< - TextProps & RefAttributes + Omit & RefAttributes >; export { Text_2 as Text }; @@ -1231,9 +1241,8 @@ export interface TextFieldProps } // @public (undocumented) -export interface TextProps { - // (undocumented) - children: ReactNode; +export interface TextProps + extends Omit, 'color'> { // (undocumented) className?: string; // (undocumented) diff --git a/packages/canon/src/components/Heading/Heading.stories.tsx b/packages/canon/src/components/Heading/Heading.stories.tsx index 59ac1aab91..aa9625ae03 100644 --- a/packages/canon/src/components/Heading/Heading.stories.tsx +++ b/packages/canon/src/components/Heading/Heading.stories.tsx @@ -50,6 +50,21 @@ export const AllVariants: Story = { ), }; +export const AllColors: Story = { + args: { + ...Default.args, + }, + render: args => ( + + + + + + + + ), +}; + export const Truncate: Story = { args: { ...Title1.args, @@ -67,13 +82,6 @@ export const Responsive: Story = { }, }; -export const CustomTag: Story = { - args: { - variant: 'title5', - as: 'h2', - }, -}; - export const WrappedInLink: Story = { args: { ...Default.args, @@ -87,6 +95,13 @@ export const WrappedInLink: Story = { ], }; +export const CustomRender: Story = { + args: { + ...Default.args, + render:

, + }, +}; + export const Playground: Story = { render: () => ( diff --git a/packages/canon/src/components/Heading/Heading.tsx b/packages/canon/src/components/Heading/Heading.tsx index b7f99cd102..6ea2fe006b 100644 --- a/packages/canon/src/components/Heading/Heading.tsx +++ b/packages/canon/src/components/Heading/Heading.tsx @@ -14,46 +14,41 @@ * limitations under the License. */ -import { forwardRef } from 'react'; +import { forwardRef, useRef } from 'react'; import clsx from 'clsx'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; - +import { useRender } from '@base-ui-components/react/use-render'; import type { HeadingProps } from './types'; /** @public */ export const Heading = forwardRef( (props, ref) => { const { - children, variant = 'title1', - as = 'h1', + color = 'primary', truncate, className, + render =

, ...restProps } = props; - // Get the responsive value for the variant const responsiveVariant = useResponsiveValue(variant); + const responsiveColor = useResponsiveValue(color); + const internalRef = useRef(null); - // Determine the component to render based on the variant - let Component = as; - if (variant === 'title2') Component = 'h2'; - if (variant === 'title3') Component = 'h3'; - if (variant === 'title4') Component = 'h4'; - if (variant === 'title5') Component = 'h5'; - if (as) Component = as; + const { renderElement } = useRender({ + render, + props: { + className: clsx('canon-Heading', className), + ['data-variant']: responsiveVariant, + ['data-color']: responsiveColor, + ['data-truncate']: truncate, + ...restProps, + }, + refs: [ref, internalRef], + }); - return ( - - {children} - - ); + return renderElement(); }, ); diff --git a/packages/canon/src/components/Heading/styles.css b/packages/canon/src/components/Heading/styles.css index f0f4561400..c629c1ba48 100644 --- a/packages/canon/src/components/Heading/styles.css +++ b/packages/canon/src/components/Heading/styles.css @@ -52,6 +52,26 @@ font-weight: var(--canon-font-weight-bold); } +.canon-Heading[data-color='primary'] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color='secondary'] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-color='danger'] { + color: var(--canon-fg-danger); +} + +.canon-Heading[data-color='warning'] { + color: var(--canon-fg-warning); +} + +.canon-Heading[data-color='success'] { + color: var(--canon-fg-success); +} + .canon-Heading[data-truncate] { overflow: hidden; text-overflow: ellipsis; diff --git a/packages/canon/src/components/Heading/types.ts b/packages/canon/src/components/Heading/types.ts index b87f16eb34..312682033d 100644 --- a/packages/canon/src/components/Heading/types.ts +++ b/packages/canon/src/components/Heading/types.ts @@ -15,10 +15,11 @@ */ import { Breakpoint } from '../../types'; +import type { useRender } from '@base-ui-components/react/use-render'; /** @public */ -export interface HeadingProps { - children: React.ReactNode; +export interface HeadingProps + extends Omit, 'color'> { variant?: | 'display' | 'title1' @@ -32,7 +33,18 @@ export interface HeadingProps { 'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5' > >; - as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + color?: + | 'primary' + | 'secondary' + | 'danger' + | 'warning' + | 'success' + | Partial< + Record< + Breakpoint, + 'primary' | 'secondary' | 'danger' | 'warning' | 'success' + > + >; truncate?: boolean; className?: string; style?: React.CSSProperties; diff --git a/packages/canon/src/components/Text/Text.stories.tsx b/packages/canon/src/components/Text/Text.stories.tsx index ba1155daed..5fe0c12cb5 100644 --- a/packages/canon/src/components/Text/Text.stories.tsx +++ b/packages/canon/src/components/Text/Text.stories.tsx @@ -108,6 +108,13 @@ export const WrappedInLink: Story = { ], }; +export const CustomRender: Story = { + args: { + ...Default.args, + render: , + }, +}; + export const Playground: Story = { render: () => ( diff --git a/packages/canon/src/components/Text/Text.tsx b/packages/canon/src/components/Text/Text.tsx index 90cc9bb2ac..7e4dc74720 100644 --- a/packages/canon/src/components/Text/Text.tsx +++ b/packages/canon/src/components/Text/Text.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import { forwardRef } from 'react'; +import { forwardRef, useRef } from 'react'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; +import { useRender } from '@base-ui-components/react/use-render'; import clsx from 'clsx'; import type { TextProps } from './types'; @@ -24,13 +25,12 @@ import type { TextProps } from './types'; export const Text = forwardRef( (props, ref) => { const { - children, variant = 'body', weight = 'regular', color = 'primary', - style, className, truncate, + render =

, ...restProps } = props; @@ -38,21 +38,22 @@ export const Text = forwardRef( const responsiveVariant = useResponsiveValue(variant); const responsiveWeight = useResponsiveValue(weight); const responsiveColor = useResponsiveValue(color); + const internalRef = useRef(null); - return ( -

- {children} -

- ); + 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(); }, ); diff --git a/packages/canon/src/components/Text/types.ts b/packages/canon/src/components/Text/types.ts index 7adafd73d7..f9c3c0aaca 100644 --- a/packages/canon/src/components/Text/types.ts +++ b/packages/canon/src/components/Text/types.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import type { CSSProperties, ReactNode } from 'react'; +import type { CSSProperties } from 'react'; import type { Breakpoint } from '../../types'; +import type { useRender } from '@base-ui-components/react/use-render'; /** @public */ -export interface TextProps { - children: ReactNode; +export interface TextProps + extends Omit, 'color'> { variant?: | 'subtitle' | 'body'