howl/src/button.tsx
Chris Wanstrath 0cad100197 ________ ______ _______ ______ ________
/        |/      \ /       \  /      \ /        |
$$$$$$$$//$$$$$$  |$$$$$$$  |/$$$$$$  |$$$$$$$$/
$$ |__   $$ |  $$ |$$ |__$$ |$$ | _$$/ $$ |__
$$    |  $$ |  $$ |$$    $$< $$ |/    |$$    |
$$$$$/   $$ |  $$ |$$$$$$$  |$$ |$$$$ |$$$$$/
$$ |     $$ \__$$ |$$ |  $$ |$$ \__$$ |$$ |_____
$$ |     $$    $$/ $$ |  $$ |$$    $$/ $$       |
$$/       $$$$$$/  $$/   $$/  $$$$$$/  $$$$$$$$/
2026-01-16 08:33:38 -08:00

167 lines
4.2 KiB
TypeScript

import { define } from 'forge'
import { theme } from './theme'
import { VStack, HStack } from './stack'
import { Section } from './section'
import { H2 } from './text'
export const Button = define('Button', {
base: 'button',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 500,
transition: 'all 0.2s',
cursor: 'pointer',
borderRadius: theme('radius-sm'),
border: '1px solid transparent',
outline: 'none',
// Default: primary + md
background: theme('colors-primary'),
color: theme('colors-bg'),
height: 40,
padding: `0 ${theme('spacing-4')}`,
fontSize: theme('fontSize-sm'),
states: {
':not(:disabled):hover': {
background: theme('colors-primaryHover'),
},
':disabled': {
opacity: 0.5,
cursor: 'not-allowed',
},
},
variants: {
variant: {
primary: {
background: theme('colors-primary'),
color: theme('colors-bg'),
states: {
':not(:disabled):hover': {
background: theme('colors-primaryHover'),
},
},
},
secondary: {
background: theme('colors-secondary'),
color: theme('colors-bg'),
states: {
':not(:disabled):hover': {
background: theme('colors-secondaryHover'),
},
},
},
outline: {
background: 'transparent',
color: theme('colors-fg'),
borderColor: theme('colors-border'),
states: {
':not(:disabled):hover': {
borderColor: theme('colors-borderActive'),
},
},
},
ghost: {
background: 'transparent',
color: theme('colors-fg'),
border: 'none',
states: {
':not(:disabled):hover': {
background: theme('colors-bgMuted'),
},
},
},
destructive: {
background: theme('colors-destructive'),
color: theme('colors-bg'),
states: {
':not(:disabled):hover': {
background: theme('colors-destructiveHover'),
},
},
},
},
size: {
sm: {
height: 32,
padding: `0 ${theme('spacing-3')}`,
fontSize: theme('fontSize-sm'),
},
md: {
height: 40,
padding: `0 ${theme('spacing-4')}`,
fontSize: theme('fontSize-sm'),
},
lg: {
height: 48,
padding: `0 ${theme('spacing-6')}`,
fontSize: theme('fontSize-base'),
},
},
},
})
export type ButtonProps = Parameters<typeof Button>[0]
export const Test = () => {
return (
<Section>
{/* Variants */}
<VStack gap={4}>
<H2>Button Variants</H2>
<HStack gap={4}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
</HStack>
</VStack>
{/* Sizes */}
<VStack gap={4}>
<H2>Button Sizes</H2>
<HStack gap={4} v="end">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</HStack>
</VStack>
{/* With custom content */}
<VStack gap={4}>
<H2>Custom Content</H2>
<HStack gap={4}>
<Button variant="primary">
<span>🚀</span>
<span style={{ marginLeft: '8px' }}>Launch</span>
</Button>
<Button variant="outline" style={{ flexDirection: 'column', height: '80px', width: '96px' }}>
<span style={{ fontSize: '24px' }}>💳</span>
<span style={{ fontSize: '12px', marginTop: '4px' }}>Card</span>
</Button>
</HStack>
</VStack>
{/* Native attributes work */}
<VStack gap={4}>
<H2>Native Attributes</H2>
<HStack gap={4}>
<Button onClick={() => alert('Clicked!')} variant="primary">
Click Me
</Button>
<Button disabled variant="secondary">
Disabled
</Button>
<Button type="submit" variant="outline">
Submit
</Button>
</HStack>
</VStack>
</Section>
)
}