Improve the way we treat custom render on Text and Heading

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-05-16 18:57:36 +01:00
parent 694fb79e7c
commit ccb1fc68b6
12 changed files with 188 additions and 63 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': minor
---
We are modifying the way we treat custom render using 'useRender()' under the hood from BaseUI.
+20
View File
@@ -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;
+20
View File
@@ -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;
+20
View File
@@ -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;
+19 -10
View File
@@ -613,18 +613,28 @@ export interface GridProps extends SpaceProps {
// @public (undocumented)
export const Heading: ForwardRefExoticComponent<
HeadingProps & RefAttributes<HTMLHeadingElement>
Omit<HeadingProps, 'ref'> & RefAttributes<HTMLHeadingElement>
>;
// @public (undocumented)
export interface HeadingProps {
// (undocumented)
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
// (undocumented)
children: React.ReactNode;
export interface HeadingProps
extends Omit<useRender.ComponentProps<'h1'>, '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<HTMLParagraphElement>
Omit<TextProps, 'ref'> & RefAttributes<HTMLParagraphElement>
>;
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<useRender.ComponentProps<'p'>, 'color'> {
// (undocumented)
className?: string;
// (undocumented)
@@ -50,6 +50,21 @@ export const AllVariants: Story = {
),
};
export const AllColors: Story = {
args: {
...Default.args,
},
render: args => (
<Flex gap="4" direction="column">
<Heading color="primary" {...args} />
<Heading color="secondary" {...args} />
<Heading color="danger" {...args} />
<Heading color="warning" {...args} />
<Heading color="success" {...args} />
</Flex>
),
};
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: <h4 />,
},
};
export const Playground: Story = {
render: () => (
<Flex direction="column" gap="4">
@@ -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<HTMLHeadingElement, HeadingProps>(
(props, ref) => {
const {
children,
variant = 'title1',
as = 'h1',
color = 'primary',
truncate,
className,
render = <h1 />,
...restProps
} = props;
// Get the responsive value for the variant
const responsiveVariant = useResponsiveValue(variant);
const responsiveColor = useResponsiveValue(color);
const internalRef = useRef<HTMLElement | null>(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 (
<Component
ref={ref}
className={clsx('canon-Heading', className)}
data-variant={responsiveVariant}
data-truncate={truncate}
{...restProps}
>
{children}
</Component>
);
return renderElement();
},
);
@@ -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;
+15 -3
View File
@@ -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<useRender.ComponentProps<'h1'>, '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;
@@ -108,6 +108,13 @@ export const WrappedInLink: Story = {
],
};
export const CustomRender: Story = {
args: {
...Default.args,
render: <span />,
},
};
export const Playground: Story = {
render: () => (
<Flex gap="4" direction="column">
+18 -17
View File
@@ -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<HTMLParagraphElement, TextProps>(
(props, ref) => {
const {
children,
variant = 'body',
weight = 'regular',
color = 'primary',
style,
className,
truncate,
render = <p />,
...restProps
} = props;
@@ -38,21 +38,22 @@ export const Text = forwardRef<HTMLParagraphElement, TextProps>(
const responsiveVariant = useResponsiveValue(variant);
const responsiveWeight = useResponsiveValue(weight);
const responsiveColor = useResponsiveValue(color);
const internalRef = useRef<HTMLElement | null>(null);
return (
<p
ref={ref}
className={clsx('canon-Text', className)}
data-variant={responsiveVariant}
data-weight={responsiveWeight}
data-color={responsiveColor}
data-truncate={truncate}
style={style}
{...restProps}
>
{children}
</p>
);
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();
},
);
+4 -3
View File
@@ -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<useRender.ComponentProps<'p'>, 'color'> {
variant?:
| 'subtitle'
| 'body'