Files
kycnotme/web/src/components/Button.astro
2025-05-19 10:23:36 +00:00

177 lines
4.2 KiB
Plaintext

---
import { Icon } from 'astro-icon/components'
import { tv, type VariantProps } from 'tailwind-variants'
import type { HTMLAttributes, Polymorphic } from 'astro/types'
type Props<Tag extends 'a' | 'button' | 'label' = 'button'> = Polymorphic<
Required<Pick<HTMLAttributes<'label'>, Tag extends 'label' ? 'for' : never>> &
VariantProps<typeof button> & {
as: Tag
label?: string
icon?: string
endIcon?: string
classNames?: {
label?: string
icon?: string
endIcon?: string
}
dataAstroReload?: boolean
children?: never
disabled?: boolean
}
>
export type ButtonProps<Tag extends 'a' | 'button' | 'label' = 'button'> = Props<Tag>
const button = tv({
slots: {
base: 'inline-flex items-center justify-center gap-2 rounded-lg border transition-colors duration-100 focus-visible:ring-2 focus-visible:ring-current focus-visible:ring-offset-2 focus-visible:ring-offset-black focus-visible:outline-hidden',
icon: 'size-4 shrink-0',
label: 'text-left whitespace-nowrap',
endIcon: 'size-4 shrink-0',
},
variants: {
size: {
sm: {
base: 'h-8 px-3 text-sm',
icon: 'size-4',
endIcon: 'size-4',
},
md: {
base: 'h-9 px-4 text-sm',
icon: 'size-4',
endIcon: 'size-4',
label: 'font-medium',
},
lg: {
base: 'h-10 px-5 text-base',
icon: 'size-5',
endIcon: 'size-5',
label: 'font-bold tracking-wider uppercase',
},
},
color: {
black: {
base: 'border-night-500 bg-night-800 hover:bg-night-900 hover:text-day-200 focus-visible:bg-night-500 text-white/50 focus-visible:text-white focus-visible:ring-white',
},
white: {
base: 'border-day-300 bg-day-100 hover:bg-day-200 text-black focus-visible:ring-green-500',
},
gray: {
base: 'border-day-500 bg-day-400 hover:bg-day-500 text-black focus-visible:ring-white',
},
success: {
base: 'border-green-600 bg-green-500 text-black hover:bg-green-600',
},
error: {
base: 'border-red-600 bg-red-500 text-white hover:bg-red-600',
},
warning: {
base: 'border-yellow-600 bg-yellow-500 text-white hover:bg-yellow-600',
},
info: {
base: 'border-blue-600 bg-blue-500 text-white hover:bg-blue-600',
},
},
shadow: {
true: {
base: 'shadow-lg',
},
},
disabled: {
true: {
base: 'cursor-not-allowed',
},
},
},
compoundVariants: [
{
color: 'black',
shadow: true,
class: 'shadow-black/30',
},
{
color: 'white',
shadow: true,
class: 'shadow-white/30',
},
{
color: 'gray',
shadow: true,
class: 'shadow-day-500/30',
},
{
color: 'success',
shadow: true,
class: 'shadow-green-500/30',
},
{
color: 'error',
shadow: true,
class: 'shadow-red-500/30',
},
{
color: 'warning',
shadow: true,
class: 'shadow-yellow-500/30',
},
{
color: 'info',
shadow: true,
class: 'shadow-blue-500/30',
},
],
defaultVariants: {
size: 'md',
color: 'black',
shadow: false,
disabled: false,
},
})
const {
as: Tag = 'button' as 'a' | 'button' | 'label',
label,
icon,
endIcon,
size,
color,
shadow,
class: className,
classNames,
role,
dataAstroReload,
disabled,
...htmlProps
} = Astro.props
const {
base,
icon: iconSlot,
label: labelSlot,
endIcon: endIconSlot,
} = button({ size, color, shadow, disabled })
const ActualTag = disabled && Tag === 'a' ? 'span' : Tag
---
<ActualTag
class={base({ class: className })}
role={role ??
(ActualTag === 'button' || ActualTag === 'label' || ActualTag === 'span' ? undefined : 'button')}
aria-disabled={disabled}
{...dataAstroReload && { 'data-astro-reload': dataAstroReload }}
{...htmlProps}
>
{!!icon && <Icon name={icon} class={iconSlot({ class: classNames?.icon })} />}
{!!label && <span class={labelSlot({ class: classNames?.label })}>{label}</span>}
{
!!endIcon && (
<Icon name={endIcon} class={endIconSlot({ class: classNames?.endIcon })}>
{endIcon}
</Icon>
)
}
</ActualTag>