Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 14 additions & 24 deletions components/src/atoms/buttons/AlertPrimaryButton.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
import styled from 'styled-components'
import { Button } from './Button'

import { BORDERS, COLORS } from '../../helix-design-system'
import { Btn, styleProps } from '../../primitives'
import { SPACING, TYPOGRAPHY } from '../../ui-style-constants'
import type { ButtonProps } from './Button'

export const AlertPrimaryButton = styled(Btn)`
color: ${COLORS.white};
background-color: ${COLORS.red50};
border-radius: ${BORDERS.borderRadius8};
padding: ${SPACING.spacing8} ${SPACING.spacing16};
text-transform: ${TYPOGRAPHY.textTransformNone};
box-shadow: 0 0 0;
${TYPOGRAPHY.pSemiBold}
/**
* AlertPrimaryButton component - alert red button variant.
* This is a convenience wrapper around the Button component.
*
* @deprecated Consider using Button component directly with variant="alert"
*/
export type AlertPrimaryButtonProps = Omit<ButtonProps, 'variant'>

${styleProps}

&:hover {
box-shadow: 0 0 0;
background-color: ${COLORS.red55};
}

&:disabled {
background-color: ${COLORS.grey30};
color: ${COLORS.grey40};
}
`
export function AlertPrimaryButton(
props: AlertPrimaryButtonProps
): JSX.Element {
return <Button variant="alert" {...props} />
}
46 changes: 12 additions & 34 deletions components/src/atoms/buttons/AltPrimaryButton.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,15 @@
import styled from 'styled-components'
import { Button } from './Button'

import { BORDERS, COLORS } from '../../helix-design-system'
import { Btn, styleProps } from '../../primitives'
import { SPACING, TYPOGRAPHY } from '../../ui-style-constants'
import type { ButtonProps } from './Button'

export const AltPrimaryButton = styled(Btn)`
background-color: ${COLORS.grey30};
color: ${COLORS.black90};
padding: ${SPACING.spacing8} ${SPACING.spacing16};
border-radius: ${BORDERS.borderRadius8};
box-shadow: none;
font-size: ${TYPOGRAPHY.fontSizeH3};
font-weight: ${TYPOGRAPHY.fontWeightSemiBold};
line-height: ${TYPOGRAPHY.lineHeight20};
/**
* AltPrimaryButton component - alternative grey button variant.
* This is a convenience wrapper around the Button component.
*
* @deprecated Consider using Button component directly with variant="alt"
*/
export type AltPrimaryButtonProps = Omit<ButtonProps, 'variant'>

${styleProps}

&:focus {
background-color: ${COLORS.grey35};
box-shadow: none;
}

&:active {
background-color: ${COLORS.grey40};
}

&:hover {
box-shadow: 0 0 0;
background-color: ${COLORS.grey35};
}

&:disabled {
background-color: ${COLORS.grey30};
color: ${COLORS.grey40};
}
`
export function AltPrimaryButton(props: AltPrimaryButtonProps): JSX.Element {
return <Button variant="alt" {...props} />
}
85 changes: 85 additions & 0 deletions components/src/atoms/buttons/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Flex, STYLE_PROPS } from '../../primitives'
import { DIRECTION_ROW } from '../../styles'
import { SPACING } from '../../ui-style-constants'
import { Button as ButtonComponent } from './Button'

import type { Meta, StoryObj } from '@storybook/react'

const meta: Meta = {
title: 'Helix/Atoms/Button',
argTypes: {
...Object.fromEntries(
[...STYLE_PROPS, 'as', 'ref', 'theme', 'forwardedAs'].map(prop => [
prop,
{ table: { disable: true } },
])
),
variant: {
control: {
type: 'select',
options: ['default', 'alert', 'alt'],
},
},
rounded: {
control: {
type: 'boolean',
},
},
children: {
control: {
type: 'text',
},
},
'aria-disabled': {
control: {
type: 'boolean',
},
},
isDangerous: {
control: {
type: 'boolean',
},
},
},
}

export default meta

export const DefaultButton: StoryObj<typeof ButtonComponent> = {
args: {
variant: 'default',
rounded: false,
children: 'primary button',
},
render: args => (
<Flex flexDirection={DIRECTION_ROW} gridGap={SPACING.spacing16}>
<ButtonComponent {...args} />
</Flex>
),
}

export const AlertButton: StoryObj<typeof ButtonComponent> = {
args: {
variant: 'alert',
rounded: true,
children: 'alert tertiary button',
},
render: args => (
<Flex>
<ButtonComponent {...args} />
</Flex>
),
}

export const AltButton: StoryObj<typeof ButtonComponent> = {
args: {
variant: 'alt',
rounded: false,
children: 'alt primary button',
},
render: args => (
<Flex>
<ButtonComponent {...args} />
</Flex>
),
}
73 changes: 73 additions & 0 deletions components/src/atoms/buttons/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import clsx from 'classnames'

import styles from './button.module.css'

import type { ButtonHTMLAttributes, MouseEvent, ReactNode } from 'react'

export type ButtonVariant = 'default' | 'alert' | 'alt'

export interface ButtonProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'> {
/** The visual style variant of the button */
variant?: ButtonVariant
/** Custom border radius (default: 8px, rounded: 200px) */
rounded?: boolean
/** Button content */
children: ReactNode
/** Click handler */
onClick?: (event: MouseEvent<HTMLButtonElement>) => void
/** Native HTML disabled - removes button from tab order */
disabled?: boolean
/** Accessible disabled - keeps button in tab order for screen readers */
'aria-disabled'?: boolean
/** Button type attribute */
type?: 'button' | 'submit' | 'reset'
/** Additional CSS class names */
className?: string
}

/**
* Unified Button component using CSS Modules.
* Supports three variants: default (blue), alert (red), and alt (grey).
* Handles both native disabled and aria-disabled for accessibility.
*/
export function Button({
variant = 'default',
rounded = false,
children,
onClick,
disabled = false,
'aria-disabled': ariaDisabled = false,
type = 'button',
className,
...restProps
}: ButtonProps): JSX.Element {
const handleClick = (event: MouseEvent<HTMLButtonElement>): void => {
// Prevent onClick when aria-disabled is true
if (ariaDisabled) {
event.preventDefault()
return
}
onClick?.(event)
}

const buttonClassName = clsx(
styles.button,
styles[`variant_${variant}`],
rounded && styles.rounded,
className
)

return (
<button
className={buttonClassName}
onClick={handleClick}
disabled={disabled}
{...(ariaDisabled ? { 'aria-disabled': true } : {})}
type={type}
{...restProps}
>
{children}
</button>
)
}
49 changes: 12 additions & 37 deletions components/src/atoms/buttons/PrimaryButton.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,15 @@
import styled from 'styled-components'
import { Button } from './Button'

import { BORDERS, COLORS } from '../../helix-design-system'
import { Btn, styleProps } from '../../primitives'
import { SPACING, TYPOGRAPHY } from '../../ui-style-constants'
import type { ButtonProps } from './Button'

export const PrimaryButton = styled(Btn)`
color: ${COLORS.white};
background-color: ${COLORS.blue50};
border-radius: ${BORDERS.borderRadius8};
box-shadow: none;
padding: ${SPACING.spacing8} ${SPACING.spacing16};
line-height: ${TYPOGRAPHY.lineHeight20};
text-transform: ${TYPOGRAPHY.textTransformNone};
font-size: ${TYPOGRAPHY.fontSizeH3};
font-weight: ${TYPOGRAPHY.fontWeightSemiBold};
line-height: ${TYPOGRAPHY.lineHeight20};
/**
* PrimaryButton component - default blue button variant.
* This is a convenience wrapper around the Button component.
*
* @deprecated Consider using Button component directly with variant="default"
*/
export type PrimaryButtonProps = Omit<ButtonProps, 'variant'>

${styleProps}

&:hover,
&:focus {
background-color: ${COLORS.blue55};
box-shadow: none;
}

&:focus-visible {
outline: 2px solid ${COLORS.blue50};
outline-offset: 0.25rem;
}

&:active {
background-color: ${COLORS.blue60};
}

&:disabled {
background-color: ${COLORS.grey30};
color: ${COLORS.grey40};
}
`
export function PrimaryButton(props: PrimaryButtonProps): JSX.Element {
return <Button variant="default" {...props} />
}

This file was deleted.

Loading
Loading