Add as prop

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-06-17 08:42:26 +01:00
parent a02fd02a0c
commit 78204a2d5f
8 changed files with 127 additions and 149 deletions
+5
View File
@@ -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.
+20 -65
View File
@@ -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<HeadingProps, 'ref'> & RefAttributes<HTMLHeadingElement>
>;
export const Heading: <T extends ElementType = 'h1'>(
props: HeadingProps<T> & {
ref?: React.Ref<any>;
},
) => 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<useRender.ComponentProps<'h1'>, 'color'> {
// (undocumented)
className?: string;
// (undocumented)
color?:
| 'primary'
| 'secondary'
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
// (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<T extends ElementType = 'h1'> = HeadingOwnProps &
Omit<ComponentPropsWithRef<T>, keyof HeadingOwnProps>;
// @public (undocumented)
export const heightPropDefs: {
@@ -1269,9 +1249,11 @@ export interface TabsRootWithoutOrientation
> {}
// @public (undocumented)
const Text_2: ForwardRefExoticComponent<
Omit<TextProps, 'ref'> & RefAttributes<HTMLParagraphElement>
>;
const Text_2: <T extends ElementType = 'p'>(
props: TextProps<T> & {
ref?: React.Ref<any>;
},
) => React.ReactElement | null;
export { Text_2 as Text };
// @public (undocumented)
@@ -1295,38 +1277,11 @@ export interface TextFieldProps
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
}
// 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<useRender.ComponentProps<'p'>, '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<Record<Breakpoint, 'subtitle' | 'body' | 'caption' | 'label'>>;
// (undocumented)
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
}
export type TextProps<T extends ElementType = 'p'> = TextOwnProps &
Omit<ComponentPropsWithRef<T>, keyof TextOwnProps>;
// @public (undocumented)
export const Tooltip: {
@@ -96,7 +96,7 @@ export const WrappedInLink: Story = {
export const CustomRender: Story = {
args: {
...Default.args,
render: <h4 />,
as: 'h4',
},
};
@@ -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<T extends ElementType = 'h1'>(
{
as,
variant = 'title1',
color = 'primary',
truncate,
className,
style,
...restProps
}: HeadingProps<T>,
ref: React.Ref<any>,
) {
const Component = as || 'h1';
const responsiveVariant = useResponsiveValue(variant);
const responsiveColor = useResponsiveValue(color);
return (
<Component
ref={ref}
className={clsx('canon-Heading', className)}
data-variant={responsiveVariant}
data-color={responsiveColor}
data-truncate={truncate}
style={style}
{...restProps}
/>
);
}
HeadingComponent.displayName = 'Heading';
/** @public */
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
(props, ref) => {
const {
variant = 'title1',
color = 'primary',
truncate,
className,
render = <h1 />,
...restProps
} = props;
export const Heading = forwardRef(HeadingComponent) as <
T extends ElementType = 'h1',
>(
props: HeadingProps<T> & { ref?: React.Ref<any> },
) => React.ReactElement | null;
const responsiveVariant = useResponsiveValue(variant);
const responsiveColor = useResponsiveValue(color);
const internalRef = useRef<HTMLElement | null>(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';
@@ -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<useRender.ComponentProps<'h1'>, '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<T extends ElementType = 'h1'> = HeadingOwnProps &
Omit<ComponentPropsWithRef<T>, keyof HeadingOwnProps>;
@@ -111,7 +111,7 @@ export const WrappedInLink: Story = {
export const CustomRender: Story = {
args: {
...Default.args,
render: <span />,
as: 'span',
},
};
+42 -38
View File
@@ -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<T extends ElementType = 'p'>(
{
as,
variant = 'body',
weight = 'regular',
color = 'primary',
className,
truncate,
style,
...restProps
}: TextProps<T>,
ref: React.Ref<any>,
) {
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 (
<Component
ref={ref}
className={clsx('canon-Text', className)}
data-variant={responsiveVariant}
data-weight={responsiveWeight}
data-color={responsiveColor}
data-truncate={truncate}
style={style}
{...restProps}
/>
);
}
TextComponent.displayName = 'Text';
/** @public */
export const Text = forwardRef<HTMLParagraphElement, TextProps>(
(props, ref) => {
const {
variant = 'body',
weight = 'regular',
color = 'primary',
className,
truncate,
render = <p />,
...restProps
} = props;
export const Text = forwardRef(TextComponent) as <T extends ElementType = 'p'>(
props: TextProps<T> & { ref?: React.Ref<any> },
) => 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<HTMLElement | null>(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';
+9 -6
View File
@@ -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<useRender.ComponentProps<'p'>, '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<T extends ElementType = 'p'> = TextOwnProps &
Omit<ComponentPropsWithRef<T>, keyof TextOwnProps>;