diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx index 626cf2b9..6636bd5f 100644 --- a/src/components/Button/Button.stories.tsx +++ b/src/components/Button/Button.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react' import { Button } from './Button' +import { Icon } from '../Icon/Icon' const meta: Meta = { component: Button, @@ -9,78 +10,113 @@ const meta: Meta = { export default meta type Story = StoryObj -export const Primary: Story = { - decorators: [ - (Story) => ( -
-
base
-
hover
-
active
-
disabled
- -
- ), - ], - render: () => ( +function ButtonRow(control: { + disabled?: boolean + pressed?: boolean + hovered?: boolean +}) { + return ( <> - - - - + - - - - + - ), + ) } -export const WithIcon: Story = { +export const Primary: Story = { render: () => ( -
- - - +
+
base
+ + +
hover
+ + +
pressed
+ + +
disabled
+
), } export const Sizes: Story = { render: () => ( -
+
+
small
+ + + + +
default
+ + + + +
large
+ + +
), } - -export const WithAnchor: Story = { - args: { - href: '#test', - children: 'Button', - }, - parameters: { - docs: { - description: 'Buttons with an `href` will be rendered as anchors', - }, - }, -} diff --git a/src/components/Button/Button.styles.ts b/src/components/Button/Button.styles.ts index b59667d1..ad3c3dd6 100644 --- a/src/components/Button/Button.styles.ts +++ b/src/components/Button/Button.styles.ts @@ -7,6 +7,8 @@ import { Theme } from '../../theme/theme' export const buttonStyles = (theme: Theme): ButtonStyles => ({ // BASE '.C9Y-Button-base': { + height: '2rem', + padding: '0 1rem', alignItems: 'center', display: 'inline-flex', justifyContent: 'center', @@ -21,35 +23,44 @@ export const buttonStyles = (theme: Theme): ButtonStyles => ({ '&:disabled, &.C9Y-disabled': { cursor: 'not-allowed', }, + + // ICONS + '&:has(> svg:last-child)': { + paddingRight: '0.75rem', + }, + + '&:has(> svg:first-child)': { + paddingLeft: '0.75rem', + }, + + '&:has(> svg:only-child)': { + width: '2rem', + padding: 0, + }, }, // VARIANTS '.C9Y-Button-filled': { - height: '2rem', - padding: '0 1rem', backgroundColor: theme.colors.primary[500], borderRadius: theme.borderRadius.DEFAULT, color: theme.colors.inverse, transition: 'background-color 0.15s linear', - '&:hover, &.C9Y-hover': { + '&:hover, &.C9Y-hovered': { backgroundColor: theme.colors.primary[700], }, - '&:active, &.C9Y-active': { + '&:active, &.C9Y-pressed': { backgroundColor: theme.colors.primary[900], }, '&:disabled, &.C9Y-disabled': { backgroundColor: theme.colors.primary[300], }, - // For buttons with color options you'll typically define them in each variant - // like: + // For buttons with color options you can define them in each variant, eg: // '&.C9Y-Button-errorColor': { ... } }, '.C9Y-Button-outlined': { - height: '2rem', - padding: '0 1rem', backgroundColor: 'transparent', border: `1px solid ${theme.colors.primary[500]}`, borderRadius: theme.borderRadius.DEFAULT, @@ -57,11 +68,11 @@ export const buttonStyles = (theme: Theme): ButtonStyles => ({ transition: 'color, border-color 0.15s linear', - '&:hover, &.C9Y-hover': { + '&:hover, &.C9Y-hovered': { borderColor: theme.colors.primary[700], color: theme.colors.primary[700], }, - '&:active, &.C9Y-active': { + '&:active, &.C9Y-pressed': { borderColor: theme.colors.primary[900], color: theme.colors.primary[900], }, @@ -74,55 +85,79 @@ export const buttonStyles = (theme: Theme): ButtonStyles => ({ // SIZES '.C9Y-Button-smallSize': { height: '1.5rem', + gap: '0.375rem', + padding: '0rem 0.5rem', borderRadius: theme.borderRadius.DEFAULT, fontSize: '0.75rem', - padding: '0rem 0.5rem', + + '&:has(> svg:last-child)': { + paddingRight: '0.5rem', + }, + + '&:has(> svg:first-child)': { + paddingLeft: '0.5rem', + }, + + '&:has(> svg:only-child)': { + width: '1.5rem', + padding: 0, + }, }, + '.C9Y-Button-largeSize': { height: '2.5rem', + gap: '0.625rem', + padding: '0 1.5rem', borderRadius: theme.borderRadius.md, fontSize: '1rem', - padding: '0 2rem', - }, - // ICONS - '.C9Y-Button-icon': { - fontSize: '1rem', - }, - '.C9Y-Button-smallSizeIcon': { - fontSize: '0.75rem', - }, - '.C9Y-Button-largeSizeIcon': { - fontSize: '1.25rem', + '&:has(> svg:last-child)': { + paddingRight: '1.25rem', + }, + + '&:has(> svg:first-child)': { + paddingLeft: '1.25rem', + }, + + '&:has(> svg:only-child)': { + width: '2.5rem', + padding: 0, + }, }, }) export interface ButtonStyles { /** Base class applied to all variants for shared structural styles */ - '.C9Y-Button-base': { '&:disabled, &.C9Y-disabled': CSSProperties } & CSSProperties + '.C9Y-Button-base': { + '&:has(> svg:last-child)': CSSProperties + '&:has(> svg:first-child)': CSSProperties + '&:has(> svg:only-child)': CSSProperties + '&:disabled, &.C9Y-disabled': CSSProperties + } & CSSProperties /** Variant class applied when `variant="filled"` */ '.C9Y-Button-filled': { - '&:hover, &.C9Y-hover': CSSProperties - '&:active, &.C9Y-active': CSSProperties + '&:hover, &.C9Y-hovered': CSSProperties + '&:active, &.C9Y-pressed': CSSProperties '&:disabled, &.C9Y-disabled': CSSProperties } & CSSProperties /** Variant class applied when `variant="outlined"` */ '.C9Y-Button-outlined': { - '&:hover, &.C9Y-hover': CSSProperties - '&:active, &.C9Y-active': CSSProperties + '&:hover, &.C9Y-hovered': CSSProperties + '&:active, &.C9Y-pressed': CSSProperties '&:disabled, &.C9Y-disabled': CSSProperties } & CSSProperties /** Sizing class applied when `size="small"` */ - '.C9Y-Button-smallSize': CSSProperties + '.C9Y-Button-smallSize': { + '&:has(> svg:last-child)': CSSProperties + '&:has(> svg:first-child)': CSSProperties + '&:has(> svg:only-child)': CSSProperties + } & CSSProperties /** Sizing class applied when `size="large"` */ - '.C9Y-Button-largeSize': CSSProperties - - /** Base class applied to all Button Icons */ - '.C9Y-Button-icon': CSSProperties - /** Sizing class applied to Button Icons when `size="small"` */ - '.C9Y-Button-smallSizeIcon': CSSProperties - /** Sizing class applied Button Icons when `size="large"` */ - '.C9Y-Button-largeSizeIcon': CSSProperties + '.C9Y-Button-largeSize': { + '&:has(> svg:last-child)': CSSProperties + '&:has(> svg:first-child)': CSSProperties + '&:has(> svg:only-child)': CSSProperties + } & CSSProperties } diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index fd7acfbf..c56a27b1 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,9 +1,7 @@ -import clsx from 'clsx' -import React, { forwardRef } from 'react' +import React, { Children, forwardRef } from 'react' import { createElement } from '../../utils/create-element' import { MergeTypes, Resolve } from '../../utils/types' import { ElementTypeProps, UtilityProps } from '../../utils/utility-props' -import { Icon } from '../Icon/Icon' import { useThemeProps } from '../Provider/Provider' /** Module augmentation interface for overriding component props' types */ @@ -14,18 +12,8 @@ export interface ButtonPropsDefaults { variant?: 'filled' | 'outlined' /** Theme color for display variant */ color?: 'primary' - /** Disables the element, preventing mouse and keyboard events */ - disabled?: boolean - /** Icon positioned after button content */ - endIcon?: string | React.ReactElement - /** @deprecated use `width="full" */ - fullWidth?: boolean - /** HTML element href */ - href?: string /** Display size */ size?: 'small' | 'large' - /** Icon positioned before button content */ - startIcon?: string | React.ReactElement } export type ButtonProps = Resolve< @@ -55,11 +43,7 @@ export const Button = forwardRef((props, ref) => { const { variant = 'filled', children, - disabled, - startIcon, - endIcon, color, - fullWidth, size, ...merged } = { @@ -69,42 +53,19 @@ export const Button = forwardRef((props, ref) => { return createElement({ ref, - disabled, - // If an href is passed, this instance should render an anchor tag - as: merged.href ? 'a' : 'button', - // @ts-expect-error - Ensure button works for router library usage even though to isn't in props - type: merged.href || merged.to ? undefined : 'button', + as: 'button', + type: 'button', componentClassName: [ `C9Y-Button-base C9Y-Button-${variant}`, { [`C9Y-Button-${color}Color`]: color, [`C9Y-Button-${size}Size`]: size, - 'w-full': fullWidth, }, ], - children: ( - <> - {startIcon && ( - - {typeof startIcon === 'string' ? : startIcon} - - )} - {children} - {endIcon && ( - - {typeof endIcon === 'string' ? : endIcon} - - )} - - ), + children: Children.map(children, (child) => { + // Wrap string children in a span so that CSS child selectors work + return typeof child === 'string' ? {child} : child + }), ...merged, }) }) as Button