- Move studio from root to frontends/studio/ - Add owner-tools frontend for live blog admin UI - Add shared ui component library - Set up npm workspaces for frontends - Add enhanced code block extension for editor Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
64 lines
1.6 KiB
TypeScript
64 lines
1.6 KiB
TypeScript
import type { ButtonHTMLAttributes, AnchorHTMLAttributes, ReactNode } from 'react'
|
|
import { Icons, type IconComponent } from './Icons'
|
|
|
|
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'accent'
|
|
|
|
type ButtonBaseProps = {
|
|
variant?: ButtonVariant
|
|
loading?: boolean
|
|
Icon?: IconComponent
|
|
children: ReactNode
|
|
}
|
|
|
|
type ButtonAsButton = ButtonBaseProps & ButtonHTMLAttributes<HTMLButtonElement> & { href?: never }
|
|
type ButtonAsLink = ButtonBaseProps & AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }
|
|
|
|
type ButtonProps = ButtonAsButton | ButtonAsLink
|
|
|
|
const variantClasses: Record<ButtonVariant, string> = {
|
|
primary: 'btn-primary',
|
|
secondary: 'btn-secondary',
|
|
danger: 'btn-danger',
|
|
ghost: 'btn-ghost',
|
|
accent: 'btn-accent',
|
|
}
|
|
|
|
export function Button({
|
|
variant = 'secondary',
|
|
loading = false,
|
|
Icon,
|
|
children,
|
|
className = '',
|
|
...props
|
|
}: ButtonProps) {
|
|
const content = (
|
|
<>
|
|
{loading ? (
|
|
<Icons.Loader className="animate-spin text-xs" />
|
|
) : Icon ? (
|
|
<Icon className="text-xs opacity-70" />
|
|
) : null}
|
|
<span>{children}</span>
|
|
</>
|
|
)
|
|
|
|
if ('href' in props && props.href) {
|
|
const { href, ...linkProps } = props
|
|
return (
|
|
<a href={href} className={`${variantClasses[variant]} ${className}`} {...linkProps}>
|
|
{content}
|
|
</a>
|
|
)
|
|
}
|
|
|
|
const { disabled, ...buttonProps } = props as ButtonAsButton
|
|
return (
|
|
<button
|
|
className={`${variantClasses[variant]} ${className}`}
|
|
disabled={disabled || loading}
|
|
{...buttonProps}
|
|
>
|
|
{content}
|
|
</button>
|
|
)
|
|
}
|