first commit
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-09-28 13:52:25 +07:00
commit 566275348c
262 changed files with 37008 additions and 0 deletions
+131
View File
@@ -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>
);
}