import clsx from "clsx"; import type { JSX } from "react"; import * as React from "react"; import { Button as ReactAriaButton, type ButtonProps as ReactAriaButtonProps, } from "react-aria-components"; import { Link, type LinkProps } from "react-router"; import { assertUnreachable } from "~/utils/types"; import styles from "./Button.module.css"; type ButtonVariant = | "primary" | "success" | "outlined" | "outlined-success" | "destructive" | "minimal" | "minimal-success" | "minimal-destructive"; export interface SendouButtonProps extends Omit { className?: string; variant?: ButtonVariant; size?: "miniscule" | "small" | "medium" | "big"; icon?: JSX.Element; children?: React.ReactNode; } export function SendouButton({ children, variant, size, className, icon, ...rest }: SendouButtonProps) { return ( {icon && React.cloneElement(icon, { className: iconClassName(icon.props.className, children, size), })} {children} ); } export interface LinkButtonProps { to: LinkProps["to"]; prefetch?: LinkProps["prefetch"]; preventScrollReset?: LinkProps["preventScrollReset"]; isExternal?: boolean; className?: string; variant?: SendouButtonProps["variant"]; size?: SendouButtonProps["size"]; icon?: JSX.Element; children?: React.ReactNode; onClick?: React.MouseEventHandler; testId?: string; } export function LinkButton({ to, prefetch, preventScrollReset, isExternal, className, variant, size, icon, children, onClick, testId, }: LinkButtonProps) { if (isExternal) { return ( {icon && React.cloneElement(icon, { className: iconClassName(icon.props.className, children, size), })} {children} ); } return ( {icon && React.cloneElement(icon, { className: iconClassName(icon.props.className, children, size), })} {children} ); } function buttonClassName({ className, variant, size, }: Pick) { const variantToClassname = (variant: ButtonVariant) => { switch (variant) { case "primary": return styles.primary; case "success": return styles.success; case "outlined": return styles.outlined; case "outlined-success": return styles.outlinedSuccess; case "destructive": return styles.destructive; case "minimal": return styles.minimal; case "minimal-success": return styles.minimalSuccess; case "minimal-destructive": return styles.minimalDestructive; default: return assertUnreachable(variant); } }; return clsx( className, variant ? variantToClassname(variant) : null, styles.button, { [styles.small]: size === "small", [styles.big]: size === "big", [styles.miniscule]: size === "miniscule", }, ); } function iconClassName( baseClassName: string | undefined, children: React.ReactNode, size: SendouButtonProps["size"], ) { return clsx(baseClassName, styles.buttonIcon, { [styles.lonely]: !children, [styles.buttonIconSmall]: size === "small", [styles.buttonIconMiniscule]: size === "miniscule", }); }