Update IconButton

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-06-17 18:51:10 +01:00
parent 0a5d96e7bb
commit 078a3f64e5
7 changed files with 119 additions and 113 deletions
+24 -23
View File
@@ -697,37 +697,38 @@ export const Icon: (props: IconProps) => JSX_2.Element | null;
// @public (undocumented)
export const IconButton: ForwardRefExoticComponent<
IconButtonProps & RefAttributes<HTMLButtonElement>
IconButtonProps & RefAttributes<HTMLElement>
>;
// @public (undocumented)
export type IconButtonOwnProps = GetPropDefTypes<typeof iconButtonPropDefs>;
export type IconButtonAnchorProps = IconButtonCommonProps &
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'type' | 'onClick'> & {
href: string;
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
};
// @public (undocumented)
export const iconButtonPropDefs: {
variant: {
type: 'enum';
values: ('primary' | 'secondary')[];
className: string;
default: 'primary';
responsive: true;
};
size: {
type: 'enum';
values: ('small' | 'medium')[];
className: string;
default: 'medium';
responsive: true;
};
export type IconButtonCommonProps = {
size?: 'small' | 'medium' | Partial<Record<Breakpoint_2, 'small' | 'medium'>>;
variant?:
| 'primary'
| 'secondary'
| Partial<Record<Breakpoint_2, 'primary' | 'secondary'>>;
icon?: ReactElement;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
};
// @public (undocumented)
export type IconButtonNativeProps = IconButtonCommonProps &
Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'href'> & {
href?: undefined;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
};
// @public
export interface IconButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
icon: ReactElement;
size?: IconButtonOwnProps['size'];
variant?: IconButtonOwnProps['variant'];
}
export type IconButtonProps = IconButtonAnchorProps | IconButtonNativeProps;
// @public (undocumented)
export const IconContext: Context<IconContextProps>;
@@ -33,9 +33,7 @@ export const Button = forwardRef<HTMLElement, ButtonProps>((props, ref) => {
...rest
} = props;
function isAnchor(props: { href?: unknown }): props is { href: string } {
return typeof props.href === 'string';
}
const isAnchor = typeof props.href === 'string';
const responsiveSize = useResponsiveValue(size);
const responsiveVariant = useResponsiveValue(variant);
@@ -64,7 +62,7 @@ export const Button = forwardRef<HTMLElement, ButtonProps>((props, ref) => {
</>
);
if (isAnchor(props)) {
if (isAnchor) {
const { onClick, ...anchorRest } =
rest as AnchorHTMLAttributes<HTMLAnchorElement>;
return (
@@ -1,41 +0,0 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { PropDef, GetPropDefTypes } from '../../props/prop-def';
/** @public */
export const iconButtonPropDefs = {
variant: {
type: 'enum',
values: ['primary', 'secondary'],
className: 'canon-Button--variant',
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
className: 'canon-Button--size',
default: 'medium',
responsive: true,
},
} satisfies {
variant: PropDef<'primary' | 'secondary'>;
size: PropDef<'small' | 'medium'>;
};
/** @public */
export type IconButtonOwnProps = GetPropDefTypes<typeof iconButtonPropDefs>;
@@ -83,6 +83,15 @@ export const Disabled: Story = {
),
};
export const AsLink: Story = {
args: {
icon: <Icon name="cloud" />,
href: 'https://canon.backstage.io',
target: '_blank',
'aria-label': 'Cloud icon button',
},
};
export const Responsive: Story = {
args: {
icon: <Icon name="cloud" />,
@@ -14,46 +14,74 @@
* limitations under the License.
*/
import { forwardRef } from 'react';
import { AnchorHTMLAttributes, ButtonHTMLAttributes, forwardRef } from 'react';
import clsx from 'clsx';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import type { IconButtonProps } from './types';
/** @public */
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
(props: IconButtonProps, ref) => {
export const IconButton = forwardRef<HTMLElement, IconButtonProps>(
(props, ref) => {
const {
size = 'small',
variant = 'primary',
icon,
className,
href,
style,
...rest
} = props;
const isAnchor = typeof props.href === 'string';
const responsiveSize = useResponsiveValue(size);
const responsiveVariant = useResponsiveValue(variant);
return (
<button
ref={ref}
className={clsx('canon-IconButton', className)}
const content = (
<span
className="canon-IconButtonIcon"
aria-hidden="true"
data-size={responsiveSize}
data-variant={responsiveVariant}
style={style}
{...rest}
>
<span
className="canon-IconButtonIcon"
aria-hidden="true"
data-size={responsiveSize}
>
{icon}
</span>
</button>
{icon}
</span>
);
if (isAnchor) {
const { onClick, ...anchorRest } =
rest as AnchorHTMLAttributes<HTMLAnchorElement>;
return (
<a
href={href}
className={clsx('canon-IconButton', className)}
data-variant={responsiveVariant}
data-size={responsiveSize}
style={style}
ref={ref as React.Ref<HTMLAnchorElement>}
onClick={onClick}
{...anchorRest}
>
{content}
</a>
);
} else {
const { onClick, ...buttonRest } =
rest as ButtonHTMLAttributes<HTMLButtonElement>;
return (
<button
type="button"
className={clsx('canon-Button', className)}
data-variant={responsiveVariant}
data-size={responsiveSize}
style={style}
ref={ref as React.Ref<HTMLButtonElement>}
onClick={onClick}
{...buttonRest}
>
{content}
</button>
);
}
},
);
export default IconButton;
IconButton.displayName = 'IconButton';
@@ -14,7 +14,5 @@
* limitations under the License.
*/
export { IconButton } from './IconButton';
export type { IconButtonProps } from './types';
export { iconButtonPropDefs } from './IconButton.props';
export type { IconButtonOwnProps } from './IconButton.props';
export * from './IconButton';
export * from './types';
@@ -14,30 +14,43 @@
* limitations under the License.
*/
import type { IconButtonOwnProps } from './IconButton.props';
import { ReactElement } from 'react';
import {
AnchorHTMLAttributes,
ButtonHTMLAttributes,
ReactElement,
} from 'react';
import { Breakpoint } from '@backstage/canon';
/** @public */
export type IconButtonCommonProps = {
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
variant?:
| 'primary'
| 'secondary'
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
icon?: ReactElement;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
};
/** @public */
export type IconButtonAnchorProps = IconButtonCommonProps &
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'type' | 'onClick'> & {
href: string;
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
};
/** @public */
export type IconButtonNativeProps = IconButtonCommonProps &
Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'href'> & {
href?: undefined;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
};
/**
* Properties for {@link IconButton}
*
* @public
*/
export interface IconButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
/**
* The size of the button
* @defaultValue 'medium'
*/
size?: IconButtonOwnProps['size'];
/**
* The visual variant of the button
* @defaultValue 'primary'
*/
variant?: IconButtonOwnProps['variant'];
/**
* Icon to display in the button
*/
icon: ReactElement;
}
export type IconButtonProps = IconButtonAnchorProps | IconButtonNativeProps;