diff --git a/MIGRATION.md b/MIGRATION.md index ceb73feaba6a..fea774ce9279 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -3,6 +3,7 @@ - [From version 7.x to 8.0.0](#from-version-7x-to-800) - [Core changes](#core-changes) - [UI layout state has changed shape](#ui-layout-state-has-changed-shape) + - [New UI and props for Button and IconButton components](#new-ui-and-props-for-button-and-iconbutton-components) - [From version 7.4.0 to 7.5.0](#from-version-740-to-750) - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) @@ -320,6 +321,24 @@ In Storybook 7 it was possible to use `addons.setConfig({...});` to configure St - `showPanel: boolean` is now split into `bottomPanelHeight: number` and `rightPanelWidth: number`, where the numbers represents the size of the panel in pixels. - `isFullscreen: boolean` is no longer supported, but can be achieved by setting a combination of the above. +#### New UI and props for Button and IconButton components + +We used to have a lot of different buttons in `@storybook/components` that were not used anywhere. In Storybook 8.0 we are deprecating `Form.Button` and added a new `Button` component that can be used in all places. The `IconButton` component has also been updated to use the new `Button` component under the hood. Going forward addon creators and Storybook maintainers should use the new `Button` component instead of `Form.Button`. + +For the `Button` component, the following props are now deprecated: + +- `isLink` - Please use the `asChild` prop instead like this: `` +- `primary` - Please use the `variant` prop instead. +- `secondary` - Please use the `variant` prop instead. +- `tertiary` - Please use the `variant` prop instead. +- `gray` - Please use the `variant` prop instead. +- `inForm` - Please use the `variant` prop instead. +- `small` - Please use the `size` prop instead. +- `outline` - Please use the `variant` prop instead. +- `containsIcon`. Please add your icon as a child directly. No need for this prop anymore. + +The `IconButton` doesn't have any deprecated props but it now uses the new `Button` component under the hood so all props for `IconButton` will be the same as `Button`. + ## From version 7.4.0 to 7.5.0 #### `storyStoreV6` and `storiesOf` is deprecated diff --git a/code/ui/blocks/src/controls/Object.tsx b/code/ui/blocks/src/controls/Object.tsx index 11c51f8f6392..e3cf71bef394 100644 --- a/code/ui/blocks/src/controls/Object.tsx +++ b/code/ui/blocks/src/controls/Object.tsx @@ -288,7 +288,6 @@ export const ObjectControl: FC = ({ name, value, onChange }) => { {['Object', 'Array'].includes(getObjectType(data)) && ( { e.preventDefault(); setShowRaw((v) => !v); diff --git a/code/ui/components/package.json b/code/ui/components/package.json index 85c425ea05bf..4ae6bbe3e2f7 100644 --- a/code/ui/components/package.json +++ b/code/ui/components/package.json @@ -59,8 +59,7 @@ "prep": "../../../scripts/prepare/bundle.ts" }, "dependencies": { - "@radix-ui/react-select": "^1.2.2", - "@radix-ui/react-toolbar": "^1.0.4", + "@radix-ui/react-slot": "^1.0.2", "@storybook/client-logger": "workspace:*", "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", diff --git a/code/ui/components/src/components/Button/Button.deprecated.stories.tsx b/code/ui/components/src/components/Button/Button.deprecated.stories.tsx new file mode 100644 index 000000000000..7aaadc6140b4 --- /dev/null +++ b/code/ui/components/src/components/Button/Button.deprecated.stories.tsx @@ -0,0 +1,90 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { Button } from './Button'; +import { Icons } from '../icon/icon'; +import { Form } from '../form'; + +const meta: Meta = { + title: 'Button/Deprecated', + component: Button, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Default = { args: { children: 'Default' } }; + +export const FormButton: Story = { + render: (args) => , + args: { children: 'Form.Button' }, +}; + +export const Primary: Story = { args: { primary: true, children: 'Primary' } }; +export const Secondary: Story = { args: { secondary: true, children: 'Secondary' } }; +export const Tertiary: Story = { args: { tertiary: true, children: 'Tertiary' } }; +export const Gray: Story = { args: { gray: true, children: 'Gray' } }; + +export const Outline: Story = { args: { outline: true, children: 'Outline' } }; +export const OutlinePrimary: Story = { + args: { outline: true, primary: true, children: 'Outline Primary' }, +}; +export const OutlineSecondary: Story = { + args: { outline: true, secondary: true, children: 'Outline Secondary' }, +}; +export const OutlineTertiary: Story = { + args: { outline: true, tertiary: true, children: 'Outline Tertiary' }, +}; + +export const Disabled: Story = { args: { disabled: true, children: 'Disabled' } }; +export const DisabledPrimary: Story = { + args: { disabled: true, primary: true, children: 'Disabled Priary' }, +}; +export const DisabledSecondary: Story = { + args: { disabled: true, secondary: true, children: 'Disabled Secondary' }, +}; +export const DisabledTertiary: Story = { + args: { disabled: true, tertiary: true, children: 'Disabled Tertiary' }, +}; +export const DisabledGray: Story = { + args: { disabled: true, gray: true, children: 'Disabled Gray' }, +}; + +export const Small: Story = { args: { small: true, children: 'Small' } }; +export const SmallPrimary: Story = { + args: { small: true, primary: true, children: 'Small Priary' }, +}; +export const SmallSecondary: Story = { + args: { small: true, secondary: true, children: 'Small Secondary' }, +}; +export const SmallTertiary: Story = { + args: { small: true, tertiary: true, children: 'Small Tertiary' }, +}; +export const SmallGray: Story = { + args: { small: true, gray: true, children: 'Small Gray' }, +}; + +export const IsLink: Story = { + args: { isLink: true, children: 'Button as a link' }, +}; + +export const IconPrimary: Story = { + args: { + primary: true, + containsIcon: true, + title: 'link', + children: , + }, +}; +export const IconOutline: Story = { + args: { outline: true, containsIcon: true, title: 'link', children: }, +}; +export const IconOutlineSmall: Story = { + args: { + outline: true, + containsIcon: true, + small: true, + title: 'link', + children: , + }, +}; diff --git a/code/ui/components/src/components/Button/Button.stories.tsx b/code/ui/components/src/components/Button/Button.stories.tsx index e1fc021087b8..bdf87aeab2e7 100644 --- a/code/ui/components/src/components/Button/Button.stories.tsx +++ b/code/ui/components/src/components/Button/Button.stories.tsx @@ -1,82 +1,185 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import type { ReactNode } from 'react'; import React from 'react'; -import type { Args } from '@storybook/types'; - import { Button } from './Button'; import { Icons } from '../icon/icon'; -import { Form } from '../form/index'; -export default { +const meta = { + title: 'Button', component: Button, -}; + args: { children: 'Button' }, +} satisfies Meta; -export const Default = { args: { children: 'Default' } }; +export default meta; +type Story = StoryObj; -export const FormButton = { - render: (args: Args) => , - args: { children: 'Form.Button' }, -}; +const Stack = ({ children }: { children: ReactNode }) => ( +
{children}
+); -export const Primary = { args: { primary: true, children: 'Primary' } }; -export const Secondary = { args: { secondary: true, children: 'Secondary' } }; -export const Tertiary = { args: { tertiary: true, children: 'Tertiary' } }; -export const Gray = { args: { gray: true, children: 'Gray' } }; +const Row = ({ children }: { children: ReactNode }) => ( +
{children}
+); -export const Outline = { args: { outline: true, children: 'Outline' } }; -export const OutlinePrimary = { - args: { outline: true, primary: true, children: 'Outline Primary' }, -}; -export const OutlineSecondary = { - args: { outline: true, secondary: true, children: 'Outline Secondary' }, -}; -export const OutlineTertiary = { - args: { outline: true, tertiary: true, children: 'Outline Tertiary' }, -}; +export const Base: Story = {}; -export const Disabled = { args: { disabled: true, children: 'Disabled' } }; -export const DisabledPrimary = { - args: { disabled: true, primary: true, children: 'Disabled Priary' }, -}; -export const DisabledSecondary = { - args: { disabled: true, secondary: true, children: 'Disabled Secondary' }, -}; -export const DisabledTertiary = { - args: { disabled: true, tertiary: true, children: 'Disabled Tertiary' }, -}; -export const DisabledGray = { - args: { disabled: true, gray: true, children: 'Disabled Gray' }, +export const Variants: Story = { + render: (args) => ( + + + + + + + + + + + + + + + + + + ), }; -export const Small = { args: { small: true, children: 'Small' } }; -export const SmallPrimary = { - args: { small: true, primary: true, children: 'Small Priary' }, +export const Active: Story = { + args: { + active: true, + children: ( + <> + + Button + + ), + }, + render: (args) => ( + + + + + ), }; -export const IconPrimary = { +export const Disabled: Story = { args: { - primary: true, - containsIcon: true, - title: 'link', - children: , + disabled: true, + children: 'Disabled Button', }, }; -export const IconOutline = { - args: { outline: true, containsIcon: true, title: 'link', children: }, + +export const WithHref: Story = { + render: () => ( + + + + + ), }; -export const IconOutlineSmall = { + +export const Animated: Story = { args: { - outline: true, - containsIcon: true, - small: true, - title: 'link', - children: , + variant: 'outline', }, + render: (args) => ( + + + + + + + + + + + + + + + + + + ), }; diff --git a/code/ui/components/src/components/Button/Button.tsx b/code/ui/components/src/components/Button/Button.tsx index 72598c214ec7..16744abb3472 100644 --- a/code/ui/components/src/components/Button/Button.tsx +++ b/code/ui/components/src/components/Button/Button.tsx @@ -1,272 +1,218 @@ -import type { FC, ComponentProps, ReactNode } from 'react'; -import React, { forwardRef } from 'react'; -import { styled } from '@storybook/theming'; +import type { ButtonHTMLAttributes, SyntheticEvent } from 'react'; +import React, { forwardRef, useEffect, useState } from 'react'; +import { isPropValid, styled } from '@storybook/theming'; import { darken, lighten, rgba, transparentize } from 'polished'; +import { Slot } from '@radix-ui/react-slot'; +import { deprecate } from '@storybook/client-logger'; + +export interface ButtonProps extends ButtonHTMLAttributes { + asChild?: boolean; + size?: 'small' | 'medium'; + padding?: 'small' | 'medium'; + variant?: 'outline' | 'solid' | 'ghost'; + onClick?: (event: SyntheticEvent) => void; + disabled?: boolean; + active?: boolean; + animation?: 'none' | 'rotate360' | 'glow' | 'jiggle'; -const ButtonWrapper = styled.button<{ + /** @deprecated Use {@link asChild} instead */ isLink?: boolean; + /** @deprecated Use {@link variant} instead */ primary?: boolean; + /** @deprecated Use {@link variant} instead */ secondary?: boolean; + /** @deprecated Use {@link variant} instead */ tertiary?: boolean; + /** @deprecated Use {@link variant} instead */ gray?: boolean; + /** @deprecated Use {@link variant} instead */ inForm?: boolean; - disabled?: boolean; + /** @deprecated Use {@link size} instead */ small?: boolean; + /** @deprecated Use {@link variant} instead */ outline?: boolean; + /** @deprecated Add your icon as a child directly */ containsIcon?: boolean; - children?: ReactNode; - href?: string; -}>( - ({ small, theme }) => ({ - border: 0, - borderRadius: '3em', - cursor: 'pointer', - display: 'inline-block', - overflow: 'hidden', - padding: small ? '8px 16px' : '13px 20px', - position: 'relative', - textAlign: 'center', - textDecoration: 'none', - transitionProperty: 'background, box-shadow', - transitionDuration: '150ms', - transitionTimingFunction: 'ease-out', - verticalAlign: 'top', - whiteSpace: 'nowrap', - userSelect: 'none', - opacity: 1, - margin: 0, - background: 'transparent', - - fontSize: `${small ? theme.typography.size.s1 : theme.typography.size.s2 - 1}px`, - fontWeight: theme.typography.weight.bold, - lineHeight: '1', - - svg: { - display: 'inline-block', - height: small ? 12 : 14, - width: small ? 12 : 14, - - verticalAlign: 'top', - marginRight: small ? 4 : 6, - marginTop: small ? 0 : -1, - marginBottom: small ? 0 : -1, +} - /* Necessary for js mouse events to not glitch out when hovering on svgs */ - pointerEvents: 'none', - - path: { - fill: 'currentColor', - }, +export const Button = forwardRef( + ( + { + asChild = false, + animation = 'none', + size = 'small', + variant = 'outline', + padding = 'medium', + disabled = false, + active = false, + onClick, + ...props }, - }), - ({ disabled }) => - disabled - ? { - cursor: 'not-allowed !important', - opacity: 0.5, - '&:hover': { - transform: 'none', - }, - } - : {}, - ({ containsIcon, small }) => - containsIcon - ? { - svg: { - display: 'block', - margin: 0, - }, - ...(small ? { padding: 10 } : { padding: 13 }), - } - : {}, - ({ theme, primary, secondary, gray }) => { - let color; - - if (gray) { - color = theme.color.mediumlight; - } else if (secondary) { - color = theme.color.secondary; - } else if (primary) { - color = theme.color.primary; + ref + ) => { + let Comp: 'button' | 'a' | typeof Slot = 'button'; + if (props.isLink) Comp = 'a'; + if (asChild) Comp = Slot; + let localVariant = variant; + let localSize = size; + + const [isAnimating, setIsAnimating] = useState(false); + + const handleClick = (event: SyntheticEvent) => { + if (onClick) onClick(event); + if (animation === 'none') return; + setIsAnimating(true); + }; + + useEffect(() => { + const timer = setTimeout(() => { + if (isAnimating) setIsAnimating(false); + }, 1000); + return () => clearTimeout(timer); + }, [isAnimating]); + + // Match the old API with the new API + // TODO: Remove this after 9.0 + if (props.primary) { + localVariant = 'solid'; + localSize = 'medium'; } - return color - ? { - background: color, - color: gray ? theme.color.darkest : theme.color.lightest, - - '&:hover': { - background: darken(0.05, color), - }, - '&:active': { - boxShadow: 'rgba(0, 0, 0, 0.1) 0 0 0 3em inset', - }, - '&:focus': { - boxShadow: `${rgba(color, 1)} 0 1px 9px 2px`, - outline: 'none', - }, - '&:focus:hover': { - boxShadow: `${rgba(color, 0.2)} 0 8px 18px 0px`, - }, - } - : {}; - }, - ({ theme, tertiary, inForm, small }) => - tertiary - ? { - background: theme.button.background, - color: theme.input.color, - boxShadow: `${theme.button.border} 0 0 0 1px inset`, - borderRadius: theme.input.borderRadius, - - ...(inForm && small ? { padding: '10px 16px' } : {}), - - '&:hover': { - background: - theme.base === 'light' - ? darken(0.02, theme.button.background) - : lighten(0.03, theme.button.background), - ...(inForm - ? {} - : { - boxShadow: 'rgba(0,0,0,.2) 0 2px 6px 0, rgba(0,0,0,.1) 0 0 0 1px inset', - }), - }, - '&:active': { - background: theme.button.background, - }, - '&:focus': { - boxShadow: `${rgba(theme.color.secondary, 1)} 0 0 0 1px inset`, - outline: 'none', - }, - } - : {}, - ({ theme, outline }) => - outline - ? { - boxShadow: `${transparentize(0.8, theme.color.defaultText)} 0 0 0 1px inset`, - color: transparentize(0.3, theme.color.defaultText), - background: 'transparent', - - '&:hover, &:focus': { - boxShadow: `${transparentize(0.5, theme.color.defaultText)} 0 0 0 1px inset`, - outline: 'none', - }, + // Match the old API with the new API + // TODO: Remove this after 9.0 + if (props.secondary || props.tertiary || props.gray || props.outline || props.inForm) { + localVariant = 'outline'; + localSize = 'medium'; + } - '&:active': { - boxShadow: `${transparentize(0.5, theme.color.defaultText)} 0 0 0 2px inset`, - color: transparentize(0, theme.color.defaultText), - }, - } - : {}, - ({ theme, outline, primary }) => { - const color = theme.color.primary; + if ( + props.small || + props.isLink || + props.primary || + props.secondary || + props.tertiary || + props.gray || + props.outline || + props.inForm || + props.containsIcon + ) { + const buttonContent = React.Children.toArray(props.children).filter( + (e) => typeof e === 'string' && e !== '' + ); - return outline && primary - ? { - boxShadow: `${color} 0 0 0 1px inset`, - color, + deprecate( + `Use of deprecated props in the button ${ + buttonContent.length > 0 ? `"${buttonContent.join(' ')}"` : 'component' + } detected, see the migration notes at https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-ui-and-props-for-button-and-iconbutton-components` + ); + } - 'svg path:not([fill])': { - fill: color, - }, + return ( + + ); + } +); - '&:hover': { - boxShadow: `${color} 0 0 0 1px inset`, - background: 'transparent', - }, +Button.displayName = 'Button'; - '&:active': { - background: color, - boxShadow: `${color} 0 0 0 1px inset`, - color: theme.color.tertiary, - }, - '&:focus': { - boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.4)} 0 1px 9px 2px`, - outline: 'none', - }, - '&:focus:hover': { - boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.2)} 0 8px 18px 0px`, - }, - } - : {}; +const StyledButton = styled('button', { + shouldForwardProp: (prop) => isPropValid(prop), +})< + ButtonProps & { + animating: boolean; + animation: ButtonProps['animation']; + } +>(({ theme, variant, size, disabled, active, animating, animation, padding }) => ({ + border: 0, + cursor: disabled ? 'not-allowed' : 'pointer', + display: 'inline-flex', + gap: '6px', + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + padding: (() => { + if (padding === 'small' && size === 'small') return '0 7px'; + if (padding === 'small' && size === 'medium') return '0 9px'; + if (size === 'small') return '0 10px'; + if (size === 'medium') return '0 12px'; + return 0; + })(), + height: size === 'small' ? '28px' : '32px', + position: 'relative', + textAlign: 'center', + textDecoration: 'none', + transitionProperty: 'background, box-shadow', + transitionDuration: '150ms', + transitionTimingFunction: 'ease-out', + verticalAlign: 'top', + whiteSpace: 'nowrap', + userSelect: 'none', + opacity: disabled ? 0.5 : 1, + margin: 0, + fontSize: `${theme.typography.size.s1}px`, + fontWeight: theme.typography.weight.bold, + lineHeight: '1', + background: (() => { + if (variant === 'solid') return theme.color.secondary; + if (variant === 'outline') return theme.button.background; + if (variant === 'ghost' && active) return theme.background.hoverable; + return 'transparent'; + })(), + color: (() => { + if (variant === 'solid') return theme.color.lightest; + if (variant === 'outline') return theme.input.color; + if (variant === 'ghost' && active) return theme.color.secondary; + if (variant === 'ghost') return theme.color.mediumdark; + return theme.input.color; + })(), + boxShadow: variant === 'outline' ? `${theme.button.border} 0 0 0 1px inset` : 'none', + borderRadius: theme.input.borderRadius, + // Making sure that the button never shrinks below its minimum size + flexShrink: 0, + + '&:hover': { + color: variant === 'ghost' ? theme.color.secondary : null, + background: (() => { + let bgColor = theme.color.secondary; + if (variant === 'solid') bgColor = theme.color.secondary; + if (variant === 'outline') bgColor = theme.button.background; + + if (variant === 'ghost') return transparentize(0.86, theme.color.secondary); + return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor); + })(), }, - ({ theme, outline, primary, secondary }) => { - let color; - - if (secondary) { - color = theme.color.secondary; - } else if (primary) { - color = theme.color.primary; - } - - return outline && color - ? { - boxShadow: `${color} 0 0 0 1px inset`, - color, - 'svg path:not([fill])': { - fill: color, - }, + '&:active': { + color: variant === 'ghost' ? theme.color.secondary : null, + background: (() => { + let bgColor = theme.color.secondary; + if (variant === 'solid') bgColor = theme.color.secondary; + if (variant === 'outline') bgColor = theme.button.background; - '&:hover': { - boxShadow: `${color} 0 0 0 1px inset`, - background: 'transparent', - }, - - '&:active': { - background: color, - boxShadow: `${color} 0 0 0 1px inset`, - color: theme.color.tertiary, - }, - '&:focus': { - boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.4)} 0 1px 9px 2px`, - outline: 'none', - }, - '&:focus:hover': { - boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.2)} 0 8px 18px 0px`, - }, - } - : {}; - } -); + if (variant === 'ghost') return theme.background.hoverable; + return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor); + })(), + }, -const ButtonLink = ButtonWrapper.withComponent('a'); + '&:focus': { + boxShadow: `${rgba(theme.color.secondary, 1)} 0 0 0 1px inset`, + outline: 'none', + }, -export const Button: FC> = Object.assign( - forwardRef< - any, - { - isLink?: boolean; - primary?: boolean; - secondary?: boolean; - tertiary?: boolean; - gray?: boolean; - inForm?: boolean; - disabled?: boolean; - small?: boolean; - outline?: boolean; - containsIcon?: boolean; - children?: ReactNode; - href?: string; - } - >(function Button({ isLink, children, ...props }, ref) { - if (isLink) { - return ( - - {children} - - ); - } - return ( - - {children} - - ); - }), - { - defaultProps: { - isLink: false, - }, - } -); + '> svg': { + animation: + animating && animation !== 'none' ? `${theme.animation[animation]} 1000ms ease-out` : '', + }, +})); diff --git a/code/ui/components/src/components/Button/Docs.mdx b/code/ui/components/src/components/Button/Docs.mdx new file mode 100644 index 000000000000..a3259c4667c3 --- /dev/null +++ b/code/ui/components/src/components/Button/Docs.mdx @@ -0,0 +1,159 @@ +import { Canvas, Meta, Controls, Source } from '@storybook/blocks'; + +import * as ButtonStories from './Button.stories'; + + + +# Button + +Button component is used to trigger an action or event, such as submitting a form, opening a Dialog, canceling an action, or performing a delete operation. + +## Import + + + +## Usage +Hello world! + +// Using the asChild prop to render a custom child + +`} /> + + + +### Button sizes + +Use the `size` prop to change the size of the button. You can set the value to `small` or `medium`. + + +Small Button + +`} /> + +### Button variants + +Use the `variant` prop to change the visual style of the button. You can set the value to `outline`, `solid` or `ghost`. + + +Outline + + +`} /> + +### Button with icon + +You can add an icon to the button by adding the icon on the left of the text. Please use any icon from the icon library `@storybook/icons`. + + + + Button + +`} /> + +### Icon only buttons + +You can also use the button as an icon only button by removing the text. to make sure the button is square, please set the padding prop to `small` + + + + + +`} /> + +### Button with custom wrapper + +If you want to use a custom wrapper to set the button as an external link or to use your custom router, you can use the `asChild` prop. This will render the button as a child of the wrapper. + + + + Hello world! + + +`} /> + +### Button with animations + +You can use the `animate` prop to add animations to the button. You can set the value to `glow`, `jiggle` or `rotate360`. + + + + Button + + + +`} /> + +### Active button + +You can use the `active` prop to set the button as active. This will change the background color of the button. + + + + Button + + `} +/> + +### Disabled button + +You can use the `disabled` prop to set the button as disabled. + + + + Button + + `} +/> \ No newline at end of file diff --git a/code/ui/components/src/components/IconButton/IconButton.stories.tsx b/code/ui/components/src/components/IconButton/IconButton.stories.tsx new file mode 100644 index 000000000000..2f88a3e898a2 --- /dev/null +++ b/code/ui/components/src/components/IconButton/IconButton.stories.tsx @@ -0,0 +1,81 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { IconButton } from './IconButton'; +import { Icons } from '../icon/icon'; + +const meta = { + title: 'IconButton', + component: IconButton, + tags: ['autodocs'], + args: { children: }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Base = {}; + +export const Types: Story = { + render: ({ ...args }) => ( +
+ + + +
+ ), +}; + +export const Active: Story = { + args: { active: true }, + render: ({ ...args }) => ( +
+ + + +
+ ), +}; + +export const Sizes: Story = { + args: { variant: 'solid' }, + render: ({ ...args }) => ( +
+ + +
+ ), +}; + +export const Disabled: Story = { + args: { disabled: true }, + render: ({ ...args }) => ( +
+ + + +
+ ), +}; + +export const Animated: Story = { + render: ({ ...args }) => ( +
+ + + +
+ ), +}; + +export const WithHref: Story = { + render: ({ ...args }) => ( +
+ console.log('Hello')} /> + + + + + +
+ ), +}; diff --git a/code/ui/components/src/components/IconButton/IconButton.tsx b/code/ui/components/src/components/IconButton/IconButton.tsx new file mode 100644 index 000000000000..87088d342b52 --- /dev/null +++ b/code/ui/components/src/components/IconButton/IconButton.tsx @@ -0,0 +1,11 @@ +import React, { forwardRef } from 'react'; +import type { ButtonProps } from '../Button/Button'; +import { Button } from '../Button/Button'; + +export const IconButton = forwardRef( + ({ padding = 'small', variant = 'ghost', ...props }, ref) => { + return