mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-26 04:22:08 -05:00
Move app to apps/web-react in pnpm workspace layout
This commit is contained in:
190
apps/web-react/app/components/elements/Button.tsx
Normal file
190
apps/web-react/app/components/elements/Button.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
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"
|
||||
| "destructive"
|
||||
| "outlined"
|
||||
| "outlined-success"
|
||||
| "outlined-destructive"
|
||||
| "minimal"
|
||||
| "minimal-success"
|
||||
| "minimal-destructive";
|
||||
|
||||
export interface SendouButtonProps
|
||||
extends Omit<ReactAriaButtonProps, "onClick" | "className"> {
|
||||
className?: string;
|
||||
variant?: ButtonVariant;
|
||||
size?: "miniscule" | "small" | "medium" | "big";
|
||||
shape?: "circle" | "square";
|
||||
icon?: JSX.Element;
|
||||
children?: React.ReactNode;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export function SendouButton({
|
||||
children,
|
||||
variant,
|
||||
size,
|
||||
shape,
|
||||
className,
|
||||
icon,
|
||||
testId,
|
||||
...rest
|
||||
}: SendouButtonProps) {
|
||||
return (
|
||||
<ReactAriaButton
|
||||
data-testid={testId}
|
||||
{...rest}
|
||||
className={buttonClassName({ className, variant, size, shape })}
|
||||
>
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})}
|
||||
{children}
|
||||
</ReactAriaButton>
|
||||
);
|
||||
}
|
||||
|
||||
export interface LinkButtonProps {
|
||||
to: LinkProps["to"];
|
||||
prefetch?: LinkProps["prefetch"];
|
||||
preventScrollReset?: LinkProps["preventScrollReset"];
|
||||
state?: LinkProps["state"];
|
||||
isExternal?: boolean;
|
||||
className?: string;
|
||||
variant?: SendouButtonProps["variant"];
|
||||
size?: SendouButtonProps["size"];
|
||||
shape?: SendouButtonProps["shape"];
|
||||
icon?: JSX.Element;
|
||||
children?: React.ReactNode;
|
||||
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
|
||||
testId?: string;
|
||||
"aria-label"?: string;
|
||||
}
|
||||
|
||||
export function LinkButton({
|
||||
to,
|
||||
prefetch,
|
||||
preventScrollReset,
|
||||
state,
|
||||
isExternal,
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
shape,
|
||||
icon,
|
||||
children,
|
||||
onClick,
|
||||
testId,
|
||||
"aria-label": ariaLabel,
|
||||
}: LinkButtonProps) {
|
||||
if (isExternal) {
|
||||
return (
|
||||
<a
|
||||
className={buttonClassName({ className, variant, size, shape })}
|
||||
href={to as string}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={onClick}
|
||||
data-testid={testId}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})}
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={buttonClassName({ className, variant, size, shape })}
|
||||
to={to}
|
||||
data-testid={testId}
|
||||
prefetch={prefetch}
|
||||
preventScrollReset={preventScrollReset}
|
||||
state={state}
|
||||
onClick={onClick}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: iconClassName(icon.props.className, children, size),
|
||||
})}
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function buttonClassName({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
shape,
|
||||
}: Pick<SendouButtonProps, "className" | "variant" | "size" | "shape">) {
|
||||
const variantToClassname = (variant: ButtonVariant) => {
|
||||
switch (variant) {
|
||||
case "primary":
|
||||
return styles.primary;
|
||||
case "success":
|
||||
return styles.success;
|
||||
case "destructive":
|
||||
return styles.destructive;
|
||||
case "outlined":
|
||||
return styles.outlined;
|
||||
case "outlined-success":
|
||||
return styles.outlinedSuccess;
|
||||
case "outlined-destructive":
|
||||
return styles.outlinedDestructive;
|
||||
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",
|
||||
},
|
||||
{
|
||||
[styles.circle]: shape === "circle",
|
||||
[styles.square]: shape === "square",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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",
|
||||
[styles.buttonIconBig]: size === "big",
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user