132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
import type { InertiaLinkProps } from '@inertiajs/react';
|
|
import { Link } from '@inertiajs/react';
|
|
import { cva } from 'class-variance-authority';
|
|
import type { VariantProps } from 'class-variance-authority';
|
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
import Icon from '@/components/site/icon';
|
|
import { cn, toUrl } from '@/lib/utils';
|
|
|
|
export const buttonVariants = cva(
|
|
'font-display inline-flex cursor-pointer items-center justify-center gap-2 font-bold transition-all active:scale-[0.98] disabled:pointer-events-none disabled:opacity-60',
|
|
{
|
|
variants: {
|
|
variant: {
|
|
primary:
|
|
'from-paj-crimson to-primary bg-gradient-to-r text-white shadow-[0_0_24px_rgba(163,29,36,0.35)] hover:shadow-[0_0_32px_rgba(255,77,88,0.5)] hover:brightness-110',
|
|
secondary:
|
|
'border-outline-variant/60 bg-surface-container-high text-on-surface hover:border-primary/50 hover:bg-surface-bright border',
|
|
ghost: 'text-primary hover:text-on-surface',
|
|
},
|
|
size: {
|
|
sm: 'rounded-full px-5 py-2.5 text-xs tracking-wide uppercase',
|
|
md: 'rounded-full px-6 py-3.5 text-sm',
|
|
block: 'w-full rounded-xl px-6 py-3.5 text-xs tracking-wider uppercase',
|
|
inline: 'p-0 text-sm',
|
|
},
|
|
},
|
|
defaultVariants: { variant: 'primary', size: 'md' },
|
|
},
|
|
);
|
|
|
|
type ButtonStyleProps = VariantProps<typeof buttonVariants> & {
|
|
icon?: string;
|
|
/** Icon rendered after the label, e.g. `arrow_forward`. */
|
|
trailingIcon?: string;
|
|
className?: string;
|
|
children: ReactNode;
|
|
};
|
|
|
|
function Content({
|
|
icon,
|
|
trailingIcon,
|
|
children,
|
|
}: Pick<ButtonStyleProps, 'icon' | 'trailingIcon' | 'children'>) {
|
|
return (
|
|
<>
|
|
{icon && <Icon name={icon} className="text-lg" />}
|
|
<span>{children}</span>
|
|
{trailingIcon && <Icon name={trailingIcon} className="text-lg" />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
type ButtonLinkProps = ButtonStyleProps & {
|
|
href: NonNullable<InertiaLinkProps['href']>;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
/**
|
|
* Renders an Inertia <Link> for app routes, and a plain <a> for in-page
|
|
* anchors, tel:/mailto: links and external URLs (opened in a new tab).
|
|
*/
|
|
export function ButtonLink({
|
|
href,
|
|
variant,
|
|
size,
|
|
icon,
|
|
trailingIcon,
|
|
className,
|
|
children,
|
|
onClick,
|
|
}: ButtonLinkProps) {
|
|
const classes = cn(buttonVariants({ variant, size }), className);
|
|
const content = (
|
|
<Content icon={icon} trailingIcon={trailingIcon}>
|
|
{children}
|
|
</Content>
|
|
);
|
|
const url = toUrl(href);
|
|
|
|
if (/^(#|tel:|mailto:)/.test(url)) {
|
|
return (
|
|
<a href={url} className={classes} onClick={onClick}>
|
|
{content}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
if (/^https?:\/\//.test(url)) {
|
|
return (
|
|
<a
|
|
href={url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={classes}
|
|
onClick={onClick}
|
|
>
|
|
{content}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Link href={href} className={classes} onClick={onClick}>
|
|
{content}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export function Button({
|
|
variant,
|
|
size,
|
|
icon,
|
|
trailingIcon,
|
|
className,
|
|
children,
|
|
type = 'button',
|
|
...props
|
|
}: ButtonStyleProps &
|
|
Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'className'>) {
|
|
return (
|
|
<button
|
|
type={type}
|
|
className={cn(buttonVariants({ variant, size }), className)}
|
|
{...props}
|
|
>
|
|
<Content icon={icon} trailingIcon={trailingIcon}>
|
|
{children}
|
|
</Content>
|
|
</button>
|
|
);
|
|
}
|