@@ -0,0 +1,131 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user