Add render prop to Button

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-06-06 09:18:57 +02:00
parent a9a521d7ce
commit f2f814a616
5 changed files with 60 additions and 44 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Added a render prop to the Button component in Canon to use it as a link.
+2 -4
View File
@@ -149,7 +149,7 @@ export const breakpoints: Breakpoint[];
// @public (undocumented)
export const Button: ForwardRefExoticComponent<
ButtonProps & RefAttributes<HTMLButtonElement>
Omit<ButtonProps, 'ref'> & RefAttributes<HTMLButtonElement>
>;
// @public (undocumented)
@@ -174,9 +174,7 @@ export const buttonPropDefs: {
};
// @public
export interface ButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
children: React.ReactNode;
export interface ButtonProps extends useRender.ComponentProps<'button'> {
iconEnd?: ReactElement;
iconStart?: ReactElement;
size?: ButtonOwnProps['size'];
@@ -131,6 +131,18 @@ export const Disabled: Story = {
),
};
export const AsLink: Story = {
args: {
children: 'Button',
},
render: args => (
<Button
{...args}
render={<a href="https://canon.backstage.io">Button</a>}
/>
),
};
export const Responsive: Story = {
args: {
children: 'Button',
+39 -33
View File
@@ -14,9 +14,10 @@
* 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 { ButtonProps } from './types';
@@ -26,10 +27,10 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
const {
size = 'small',
variant = 'primary',
disabled,
iconStart,
iconEnd,
children,
render = <button />,
className,
style,
...rest
@@ -38,38 +39,43 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
// Get the responsive value for the variant
const responsiveSize = useResponsiveValue(size);
const responsiveVariant = useResponsiveValue(variant);
const internalRef = useRef<HTMLElement | null>(null);
return (
<button
ref={ref}
disabled={disabled}
className={clsx('canon-Button', className)}
data-size={responsiveSize}
data-variant={responsiveVariant}
style={style}
{...rest}
>
{iconStart && (
<span
className="canon-ButtonIcon"
aria-hidden="true"
data-size={responsiveSize}
>
{iconStart}
</span>
)}
{children}
{iconEnd && (
<span
className="canon-ButtonIcon"
aria-hidden="true"
data-size={responsiveSize}
>
{iconEnd}
</span>
)}
</button>
);
const { renderElement } = useRender({
render,
props: {
className: clsx('canon-Button', className),
['data-variant']: responsiveVariant,
['data-size']: responsiveSize,
...rest,
children: (
<>
{iconStart && (
<span
className="canon-ButtonIcon"
aria-hidden="true"
data-size={responsiveSize}
>
{iconStart}
</span>
)}
{children}
{iconEnd && (
<span
className="canon-ButtonIcon"
aria-hidden="true"
data-size={responsiveSize}
>
{iconEnd}
</span>
)}
</>
),
},
refs: [ref, internalRef],
});
return renderElement();
},
);
@@ -16,14 +16,14 @@
import type { ButtonOwnProps } from './Button.props';
import { ReactElement } from 'react';
import type { useRender } from '@base-ui-components/react/use-render';
/**
* Properties for {@link Button}
*
* @public
*/
export interface ButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
export interface ButtonProps extends useRender.ComponentProps<'button'> {
/**
* The size of the button
* @defaultValue 'medium'
@@ -36,11 +36,6 @@ export interface ButtonProps
*/
variant?: ButtonOwnProps['variant'];
/**
* The content of the button
*/
children: React.ReactNode;
/**
* Optional icon to display at the start of the button
*/