Skip to content

Commit

Permalink
Button converted to tailwind
Browse files Browse the repository at this point in the history
  • Loading branch information
corbanbrook committed Jan 17, 2025
1 parent 945c5a7 commit b53a53f
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 88 deletions.
8 changes: 2 additions & 6 deletions src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,15 @@ type Story = StoryObj<typeof Button>

export const LabelOnly: Story = {
args: {
as: 'a',
disabled: false,
href: '#',
label: 'Click me',
size: 'md',
variant: 'glass',
},
}
export const Disabled: Story = {
args: {
as: 'a',
disabled: true,
href: '#',
label: 'Click me',
size: 'md',
variant: 'glass',
Expand Down Expand Up @@ -84,10 +80,10 @@ export const WithCountIcon: Story = {

export const AnchorButton: Story = {
args: {
as: 'a',
href: '/wallet',
label: 'Click me',
leftIcon: ScanIcon,
size: 'md',
asChild: true,
children: <a href="/wallet" />,
},
}
196 changes: 152 additions & 44 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,174 @@
import { clsx } from 'clsx'
import { ComponentType, forwardRef, ElementType, ReactNode } from 'react'
import { Slot, Slottable } from '@radix-ui/react-slot'
import { cva, VariantProps } from 'class-variance-authority'
import { ComponentType, forwardRef, ReactNode } from 'react'

import {
Box,
PolymorphicComponent,
PolymorphicProps,
PolymorphicRef,
} from '~/components/Box'
import { Text } from '~/components/Text'
import { Text, textVariants } from '~/components/Text'
import { IconProps } from '~/icons/types'
import { cn } from '~/utils'

import { buttonVariants, ButtonVariants } from './styles.css'
export const buttonVariants = cva(
[
'inline-flex items-center whitespace-nowrap overflow-hidden border-none text-decoration-none',
'focus-visible:ring-2 focus-visible:ring-border-focus outline-none',
],
{
variants: {
variant: {
base: 'bg-transparent text-text-100',
ghost: 'bg-transparent text-text-100',
feature: [
'bg-gradient-secondary text-white',
'outline outline-2 outline-white/10 -outline-offset-2',
],
primary: 'bg-gradient-primary text-white',
glass: 'bg-button-glass text-text-100',
emphasis: 'bg-button-emphasis text-text-100',
raised: 'bg-background-raised text-text-100',
danger: 'bg-negative text-white',
text: [
'bg-transparent text-text-50 rounded-xs outline-offset-1',
textVariants({ variant: 'small' }),
'font-bold',
],
},
shape: {
circle: 'rounded-full',
square: 'rounded-sm',
},
size: {
xs: [textVariants({ variant: 'xsmall' }), 'h-7 px-3 font-bold'],
sm: [textVariants({ variant: 'normal' }), 'h-9 px-4 font-bold'],
md: [textVariants({ variant: 'normal' }), 'h-11 px-5 font-bold'],
lg: [textVariants({ variant: 'normal' }), 'h-[52px] px-5 font-bold'],
},
disabled: {
true: 'cursor-default opacity-50',
false: 'cursor-pointer hover:opacity-80',
},
iconOnly: {
true: 'p-0 flex flex-shrink-0 items-center justify-center',
},
hasLeftIcon: {
true: '',
},
hasRightIcon: {
true: '',
},
activeOutline: {
light:
'outline outline-2 outline-background-secondary -outline-offset-2',
bold: 'outline outline-2 outline-border-normal -outline-offset-2',
},
},
compoundVariants: [
{
iconOnly: true,
size: 'xs',
className: 'w-7',
},
{
iconOnly: true,
size: 'sm',
className: 'w-9',
},
{
iconOnly: true,
size: 'md',
className: 'w-11',
},
{
iconOnly: true,
size: 'lg',
className: 'w-13',
},
{
iconOnly: false,
hasLeftIcon: true,
size: 'xs',
className: 'pl-2',
},
{
iconOnly: false,
hasLeftIcon: true,
size: 'sm',
className: 'pl-2',
},
{
iconOnly: false,
hasLeftIcon: true,
size: 'md',
className: 'pl-4',
},
{
iconOnly: false,
hasRightIcon: true,
size: 'xs',
className: 'pr-2',
},
{
iconOnly: false,
hasRightIcon: true,
size: 'sm',
className: 'pr-2',
},
{
iconOnly: false,
hasRightIcon: true,
size: 'md',
className: 'pr-4',
},
],
defaultVariants: {
variant: 'glass',
size: 'md',
shape: 'circle',
},
}
)

type ButtonProps = ButtonVariants & {
export interface ButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
disabled?: boolean
pending?: boolean
label?: ReactNode
leftIcon?: ComponentType<IconProps>
rightIcon?: ComponentType<IconProps>
type?: 'button' | 'submit' | 'reset'
}

export const Button: PolymorphicComponent<ButtonProps, 'button'> = forwardRef(
<T extends ElementType>(
props: PolymorphicProps<ButtonProps, T>,
ref: PolymorphicRef<T>
) => {
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const {
as = 'button',
activeOutline,
asChild,
className,
disabled = false,
display = 'inline-flex',
fontWeight = 'bold',
pending = false,
label,
leftIcon: LeftIcon,
rightIcon: RightIcon,
size = 'md',
variant = 'glass',
width = 'fit',
shape = 'circle',
activeOutline,
type = 'button',
...restProps
children,
...rest
} = props

const hasLeftIcon = LeftIcon !== undefined && label !== undefined
const hasRightIcon = RightIcon !== undefined && label !== undefined
const iconOnly = LeftIcon !== undefined && label === undefined

const iconSize = size === 'xs' ? 'xs' : 'sm'
const gap = size === 'xs' ? 'gap-1' : 'gap-2'

const gap = size === 'xs' ? '1' : '2'
const Component = asChild ? Slot : 'button'

return (
<Box
as={as}
className={clsx(
className,
<Component
ref={ref}
className={cn(
buttonVariants({
activeOutline,
disabled: disabled || pending,
Expand All @@ -67,35 +178,32 @@ export const Button: PolymorphicComponent<ButtonProps, 'button'> = forwardRef(
size: variant === 'text' ? undefined : size,
shape: variant === 'text' ? undefined : shape,
variant,
})
}),
className
)}
disabled={disabled || pending}
display={display}
fontWeight={fontWeight}
ref={ref}
type={type}
width={width}
{...restProps}
{...rest}
>
<Slottable>{children}</Slottable>
{iconOnly ? (
<LeftIcon size={iconSize} />
) : (
<Box
width="full"
height="full"
justifyContent="space-between"
alignItems="center"
gap={gap}
<div
className={cn(
'w-full h-full flex items-center justify-between',
gap
)}
>
<Box justifyContent="flex-start" alignItems="center" gap={gap}>
<div className={cn('flex items-center justify-start', gap)}>
{LeftIcon && <LeftIcon size={iconSize} />}
<Text>{label}</Text>
</Box>
</div>

{RightIcon && <RightIcon size={iconSize} />}
</Box>
</div>
)}
</Box>
</Component>
)
}
)
5 changes: 2 additions & 3 deletions src/components/Button/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { Button } from './Button'
export { buttonVariants } from './styles.css'
export type { ButtonVariants } from './styles.css'
export { Button, type ButtonProps } from './Button'
export { buttonVariants } from './Button'
9 changes: 9 additions & 0 deletions src/components/IconButton/IconButton.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ export const Default: Story = {
variant: 'raised',
},
}

export const AnchorButton: Story = {
args: {
icon: ArrowRightIcon,
size: 'md',
asChild: true,
children: <a href="/wallet" />,
},
}
43 changes: 15 additions & 28 deletions src/components/IconButton/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
import { ComponentType, ElementType, forwardRef } from 'react'
import type { VariantProps } from 'class-variance-authority'
import { ComponentType, forwardRef } from 'react'

import {
PolymorphicComponent,
PolymorphicProps,
PolymorphicRef,
} from '~/components/Box'
import { Button, ButtonVariants } from '~/components/Button'
import { Button, buttonVariants } from '~/components/Button'
import { IconProps } from '~/icons/types'

type IconButtonVariants = { variant?: (ButtonVariants & {})['variant'] }

type IconButtonProps = IconButtonVariants & {
export interface IconButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'> {
asChild?: boolean
disabled?: boolean
pending?: boolean
size?: 'xs' | 'sm' | 'md' | 'lg'
variant?: VariantProps<typeof buttonVariants>['variant']
icon: ComponentType<IconProps>
}

export const IconButton: PolymorphicComponent<IconButtonProps, 'button'> =
forwardRef(
<T extends ElementType>(
props: PolymorphicProps<IconButtonProps, T>,
ref: PolymorphicRef<T>
) => {
const { icon, size = 'md', ...rest } = props
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
(props, ref) => {
const { icon, size = 'md', ...rest } = props

return (
<Button
leftIcon={icon}
size={size}
flexShrink="0"
ref={ref}
{...rest}
/>
)
}
)
return <Button leftIcon={icon} size={size} ref={ref} {...rest} />
}
)
1 change: 1 addition & 0 deletions src/components/IconButton/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { IconButton } from './IconButton'
export type { IconButtonProps } from './IconButton'
3 changes: 1 addition & 2 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,8 @@ export const Modal = (props: PropsWithChildren<ModalProps>) => {
<ModalPrimitive.Close asChild>
<IconButton
icon={CloseIcon}
backdropFilter="blur"
size="xs"
className="absolute right-4 top-4 z-20"
className="absolute right-4 top-4 z-20 backdrop-blur"
aria-label="Close"
/>
</ModalPrimitive.Close>
Expand Down
10 changes: 5 additions & 5 deletions src/components/TabbedNav/TabbedNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,19 @@ export const TabbedNav = (props: TabbedNavProps) => {
<Button
className={cn(
tabVariants({ active: isActive, variant }),
className
className,
variant === 'line' ? 'px-2' : undefined,
option.leftIcon ? 'pl-1' : 'pl-2',
'rounded-full'
)}
variant={variant === 'line' ? 'text' : 'base'}
disabled={isLoading || option.disabled}
label={option.label}
leftIcon={option.leftIcon ?? undefined}
size={size}
onClick={(ev: MouseEvent<HTMLButtonElement>) =>
handleTabClick(ev, option, tabIndex)
}
padding={variant === 'line' ? '2' : undefined}
paddingLeft={option.leftIcon ? '1' : '2'}
size={size}
borderRadius="circle"
/>
</li>
)
Expand Down

0 comments on commit b53a53f

Please sign in to comment.