From 078a3f64e5233c19e0c9a0e8b2b8032947853119 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 17 Jun 2025 18:51:10 +0100 Subject: [PATCH] Update IconButton Signed-off-by: Charles de Dreuille --- packages/canon/report.api.md | 47 ++++++------- .../canon/src/components/Button/Button.tsx | 6 +- .../components/IconButton/IconButton.props.ts | 41 ----------- .../IconButton/IconButton.stories.tsx | 9 +++ .../src/components/IconButton/IconButton.tsx | 68 +++++++++++++------ .../canon/src/components/IconButton/index.tsx | 6 +- .../canon/src/components/IconButton/types.ts | 55 +++++++++------ 7 files changed, 119 insertions(+), 113 deletions(-) delete mode 100644 packages/canon/src/components/IconButton/IconButton.props.ts diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 5361e8ef2f..872719f8c8 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -697,37 +697,38 @@ export const Icon: (props: IconProps) => JSX_2.Element | null; // @public (undocumented) export const IconButton: ForwardRefExoticComponent< - IconButtonProps & RefAttributes + IconButtonProps & RefAttributes >; // @public (undocumented) -export type IconButtonOwnProps = GetPropDefTypes; +export type IconButtonAnchorProps = IconButtonCommonProps & + Omit, 'type' | 'onClick'> & { + href: string; + onClick?: React.MouseEventHandler; + }; // @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>; + variant?: + | 'primary' + | 'secondary' + | Partial>; + icon?: ReactElement; + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; }; +// @public (undocumented) +export type IconButtonNativeProps = IconButtonCommonProps & + Omit, 'href'> & { + href?: undefined; + onClick?: React.MouseEventHandler; + }; + // @public -export interface IconButtonProps - extends Omit, 'children'> { - icon: ReactElement; - size?: IconButtonOwnProps['size']; - variant?: IconButtonOwnProps['variant']; -} +export type IconButtonProps = IconButtonAnchorProps | IconButtonNativeProps; // @public (undocumented) export const IconContext: Context; diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index aae20c74e4..8693c1e016 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -33,9 +33,7 @@ export const Button = forwardRef((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((props, ref) => { ); - if (isAnchor(props)) { + if (isAnchor) { const { onClick, ...anchorRest } = rest as AnchorHTMLAttributes; return ( diff --git a/packages/canon/src/components/IconButton/IconButton.props.ts b/packages/canon/src/components/IconButton/IconButton.props.ts deleted file mode 100644 index e584997ef5..0000000000 --- a/packages/canon/src/components/IconButton/IconButton.props.ts +++ /dev/null @@ -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; diff --git a/packages/canon/src/components/IconButton/IconButton.stories.tsx b/packages/canon/src/components/IconButton/IconButton.stories.tsx index 7a7272ad05..97778e56a2 100644 --- a/packages/canon/src/components/IconButton/IconButton.stories.tsx +++ b/packages/canon/src/components/IconButton/IconButton.stories.tsx @@ -83,6 +83,15 @@ export const Disabled: Story = { ), }; +export const AsLink: Story = { + args: { + icon: , + href: 'https://canon.backstage.io', + target: '_blank', + 'aria-label': 'Cloud icon button', + }, +}; + export const Responsive: Story = { args: { icon: , diff --git a/packages/canon/src/components/IconButton/IconButton.tsx b/packages/canon/src/components/IconButton/IconButton.tsx index fd80c91ddd..e84599874c 100644 --- a/packages/canon/src/components/IconButton/IconButton.tsx +++ b/packages/canon/src/components/IconButton/IconButton.tsx @@ -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( - (props: IconButtonProps, ref) => { +export const IconButton = forwardRef( + (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 ( - + {icon} + ); + + if (isAnchor) { + const { onClick, ...anchorRest } = + rest as AnchorHTMLAttributes; + return ( + } + onClick={onClick} + {...anchorRest} + > + {content} + + ); + } else { + const { onClick, ...buttonRest } = + rest as ButtonHTMLAttributes; + return ( + + ); + } }, ); -export default IconButton; +IconButton.displayName = 'IconButton'; diff --git a/packages/canon/src/components/IconButton/index.tsx b/packages/canon/src/components/IconButton/index.tsx index 5f9943557e..1d30c871a4 100644 --- a/packages/canon/src/components/IconButton/index.tsx +++ b/packages/canon/src/components/IconButton/index.tsx @@ -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'; diff --git a/packages/canon/src/components/IconButton/types.ts b/packages/canon/src/components/IconButton/types.ts index ece1950d1b..145893991e 100644 --- a/packages/canon/src/components/IconButton/types.ts +++ b/packages/canon/src/components/IconButton/types.ts @@ -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>; + variant?: + | 'primary' + | 'secondary' + | Partial>; + icon?: ReactElement; + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; +}; + +/** @public */ +export type IconButtonAnchorProps = IconButtonCommonProps & + Omit, 'type' | 'onClick'> & { + href: string; + onClick?: React.MouseEventHandler; + }; + +/** @public */ +export type IconButtonNativeProps = IconButtonCommonProps & + Omit, 'href'> & { + href?: undefined; + onClick?: React.MouseEventHandler; + }; /** * Properties for {@link IconButton} * * @public */ -export interface IconButtonProps - extends Omit, '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;