Refactor Button to SendouButton (#2367)

* initial

* done

* import ordering
This commit is contained in:
Kalle 2025-06-05 21:00:14 +03:00 committed by GitHub
parent 25d3aff133
commit accbaf40db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 667 additions and 660 deletions

View File

@ -1,7 +1,11 @@
## General
- only rarely use comments, prefer descriptive variable and function names
- if you encounter an existing TODO comment assume it is there for a reason and do not remove it
- for new code only rarely use comments, prefer descriptive variable and function names
## Commands
- `npm run typecheck` runs TypeScript type checking
- `npm run biome:fix` runs Biome code formatter and linter
## Typescript
@ -27,7 +31,7 @@
## Styling
- use CSS modules
- one file containing React code should have a matching CSS module file e.g. `Component.tsx` should have a matching `Component.module.css`
- one file containing React code should have a matching CSS module file e.g. `Component.tsx` should have a file with the same root name i.e. `Component.module.css`
- clsx library is used for conditional class names
- prefer using [CSS variables](../app/styles/vars.css) for theming
@ -35,4 +39,3 @@
- database is Sqlite3 used with the Kysely library
- database code should only be written in Repository files
- refer to the [database schema](../app/db/tables.ts) when writing queries

View File

@ -3,8 +3,7 @@ import type { LinkProps } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?:
| "primary"
| "success"
@ -22,50 +21,6 @@ export interface ButtonProps
_ref?: React.LegacyRef<HTMLButtonElement> | React.ForwardedRef<unknown>;
}
export function Button(props: ButtonProps) {
const {
variant,
loading,
children,
loadingText,
size,
className,
icon,
type = "button",
testId,
_ref,
...rest
} = props;
return (
<button
className={clsx(
"button",
variant,
{
"disabled-opaque": props.disabled,
loading,
tiny: size === "tiny",
big: size === "big",
miniscule: size === "miniscule",
},
className,
)}
disabled={props.disabled || loading}
type={type}
data-testid={testId}
ref={props._ref as React.LegacyRef<HTMLButtonElement>}
{...rest}
>
{icon &&
React.cloneElement(icon, {
className: clsx("button-icon", { lonely: !children }),
title: rest.title,
})}
{loading && loadingText ? loadingText : children}
</button>
);
}
type LinkButtonProps = Pick<
ButtonProps,
"variant" | "children" | "className" | "size" | "testId" | "icon"

View File

@ -5,7 +5,6 @@ import {
} from "@remix-run/react";
import * as React from "react";
import { useCopyToClipboard, useLocation } from "react-use";
import { Button } from "~/components/Button";
import { useUser } from "~/features/auth/core/user";
import {
ERROR_GIRL_IMAGE_PATH,
@ -14,6 +13,7 @@ import {
} from "~/utils/urls";
import { Image } from "./Image";
import { Main } from "./Main";
import { SendouButton } from "./elements/Button";
export function Catcher() {
const error = useRouteError();
@ -56,9 +56,9 @@ export function Catcher() {
<>
<div className="mt-4 stack sm items-center">
<textarea readOnly defaultValue={errorText} />
<Button onClick={() => copyToClipboard(errorText)}>
<SendouButton onPress={() => copyToClipboard(errorText)}>
Copy to clipboard
</Button>
</SendouButton>
</div>
</>
) : null}
@ -77,9 +77,9 @@ export function Catcher() {
<form action={LOG_IN_URL} method="post">
<p className="button-text-paragraph">
You should try{" "}
<Button type="submit" variant="minimal">
<SendouButton type="submit" variant="minimal">
logging in
</Button>
</SendouButton>
</p>
</form>
)}

View File

@ -3,9 +3,9 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { useDebounce } from "react-use";
import { CUSTOM_CSS_VAR_COLORS } from "~/features/user-page/user-page-constants";
import { Button } from "./Button";
import { InfoPopover } from "./InfoPopover";
import { Label } from "./Label";
import { SendouButton } from "./elements/Button";
import { AlertIcon } from "./icons/Alert";
import { CheckmarkIcon } from "./icons/Checkmark";
@ -104,10 +104,10 @@ export function CustomizedColorsInput({
}}
data-testid={`color-input-${cssVar}`}
/>
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal-destructive"
onClick={() => {
onPress={() => {
const newColors: Record<string, string | undefined> = {
...colors,
};
@ -123,7 +123,7 @@ export function CustomizedColorsInput({
}}
>
{t("actions.reset")}
</Button>
</SendouButton>
</React.Fragment>
);
})}

View File

@ -2,10 +2,10 @@ import { type FetcherWithComponents, useFetcher } from "@remix-run/react";
import * as React from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import type { SendouButtonProps } from "~/components/elements/Button";
import { SendouDialog } from "~/components/elements/Dialog";
import { useIsMounted } from "~/hooks/useIsMounted";
import invariant from "~/utils/invariant";
import type { ButtonProps } from "./Button";
import { SubmitButton } from "./SubmitButton";
export function FormWithConfirm({
@ -27,7 +27,7 @@ export function FormWithConfirm({
submitButtonText?: string;
action?: string;
submitButtonTestId?: string;
submitButtonVariant?: ButtonProps["variant"];
submitButtonVariant?: SendouButtonProps["variant"];
fetcher?: FetcherWithComponents<any>;
}) {
const componentsFetcher = useFetcher();

View File

@ -1,7 +1,6 @@
import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { Image } from "~/components/Image";
import type { Tables } from "~/db/tables";
import type { SerializedMapPoolEvent } from "~/features/calendar/routes/map-pool-events";
@ -14,6 +13,7 @@ import { split, startsWith } from "~/utils/strings";
import { assertType } from "~/utils/types";
import { modeImageUrl, stageImageUrl } from "~/utils/urls";
import { MapPoolEventsCombobox } from "./Combobox";
import { SendouButton } from "./elements/Button";
import { ArrowLongLeftIcon } from "./icons/ArrowLongLeft";
import { CrossIcon } from "./icons/Cross";
@ -120,18 +120,18 @@ export function MapPoolSelector({
{Boolean(handleRemoval || allowBulkEdit) && (
<div className="stack horizontal sm justify-end">
{handleRemoval && (
<Button variant="minimal" onClick={handleRemoval}>
<SendouButton variant="minimal" onPress={handleRemoval}>
{t("actions.remove")}
</Button>
</SendouButton>
)}
{allowBulkEdit && (
<Button
<SendouButton
variant="minimal-destructive"
disabled={mapPool.isEmpty()}
onClick={handleClear}
isDisabled={mapPool.isEmpty()}
onPress={handleClear}
>
{t("actions.clear")}
</Button>
</SendouButton>
)}
</div>
)}
@ -321,24 +321,26 @@ export function MapPoolStages({
{!isPresentational &&
allowBulkEdit &&
(mapPool.hasStage(stageId) ? (
<Button
<SendouButton
key="clear"
onClick={() => handleStageClear(stageId)}
icon={<CrossIcon />}
onPress={() => handleStageClear(stageId)}
icon={<CrossIcon title={t("common:actions.remove")} />}
variant="minimal"
aria-label={t("common:actions.remove")}
title={t("common:actions.remove")}
size="tiny"
size="small"
/>
) : (
<Button
<SendouButton
key="select-all"
onClick={() => handleStageAdd(stageId)}
icon={<ArrowLongLeftIcon />}
onPress={() => handleStageAdd(stageId)}
icon={
<ArrowLongLeftIcon
title={t("common:actions.selectAll")}
/>
}
variant="minimal"
aria-label={t("common:actions.selectAll")}
title={t("common:actions.selectAll")}
size="tiny"
size="small"
/>
))}
</div>

View File

@ -1,5 +1,5 @@
import clsx from "clsx";
import { Button } from "~/components/Button";
import { SendouButton } from "~/components/elements/Button";
import { ArrowLeftIcon } from "~/components/icons/ArrowLeft";
import { ArrowRightIcon } from "~/components/icons/ArrowRight";
import { nullFilledArray } from "~/utils/arrays";
@ -19,12 +19,12 @@ export function Pagination({
}) {
return (
<div className="pagination__container">
<Button
<SendouButton
icon={<ArrowLeftIcon />}
variant="outlined"
className="fix-rtl"
disabled={currentPage === 1}
onClick={previousPage}
isDisabled={currentPage === 1}
onPress={previousPage}
aria-label="Previous page"
/>
<div className="pagination__dots">
@ -41,12 +41,12 @@ export function Pagination({
<div className="pagination__page-count">
{currentPage}/{pagesCount}
</div>
<Button
<SendouButton
icon={<ArrowRightIcon />}
variant="outlined"
className="fix-rtl"
disabled={currentPage === pagesCount}
onClick={nextPage}
isDisabled={currentPage === pagesCount}
onPress={nextPage}
aria-label="Next page"
/>
</div>

View File

@ -1,10 +1,11 @@
import { type FetcherWithComponents, useNavigation } from "@remix-run/react";
import { Button, type ButtonProps } from "./Button";
import { SendouButton, type SendouButtonProps } from "./elements/Button";
interface SubmitButtonProps extends ButtonProps {
interface SubmitButtonProps extends SendouButtonProps {
/** If the page has multiple forms you can pass in fetcher.state to differentiate when this SubmitButton should be in submitting state */
state?: FetcherWithComponents<any>["state"];
_action?: string;
testId?: string;
}
export function SubmitButton({
@ -33,15 +34,15 @@ export function SubmitButton({
};
return (
<Button
<SendouButton
{...rest}
disabled={rest.disabled || isSubmitting}
isDisabled={rest.isDisabled || isSubmitting}
type="submit"
name={name()}
value={value()}
data-testid={testId ?? "submit-button"}
>
{children}
</Button>
</SendouButton>
);
}

View File

@ -1,5 +1,5 @@
import { useTranslation } from "react-i18next";
import { Button } from "../Button";
import { SendouButton } from "../elements/Button";
import { PlusIcon } from "../icons/Plus";
export function AddFieldButton({
@ -10,16 +10,16 @@ export function AddFieldButton({
const { t } = useTranslation(["common"]);
return (
<Button
<SendouButton
icon={<PlusIcon />}
aria-label="Add form field"
size="tiny"
size="small"
variant="minimal"
onClick={onClick}
onPress={onClick}
className="self-start"
testId="add-field-button"
data-testid="add-field-button"
>
{t("common:actions.add")}
</Button>
</SendouButton>
);
}

View File

@ -1,14 +1,14 @@
import { Button } from "../Button";
import { SendouButton } from "../elements/Button";
import { TrashIcon } from "../icons/Trash";
export function RemoveFieldButton({ onClick }: { onClick: () => void }) {
return (
<Button
<SendouButton
icon={<TrashIcon />}
aria-label="Remove form field"
size="tiny"
size="small"
variant="minimal-destructive"
onClick={onClick}
onPress={onClick}
/>
);
}

View File

@ -1,4 +1,7 @@
export function ArrowLongLeftIcon({ className }: { className?: string }) {
export function ArrowLongLeftIcon({
className,
title,
}: { className?: string; title?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@ -8,7 +11,7 @@ export function ArrowLongLeftIcon({ className }: { className?: string }) {
stroke="currentColor"
className={className}
>
<title>Arrow Long Left Icon</title>
<title>{title ?? "Arrow Long Left Icon"}</title>
<path
strokeLinecap="round"
strokeLinejoin="round"

View File

@ -2,10 +2,12 @@ export function CheckmarkIcon({
className,
testId,
onClick,
title,
}: {
className?: string;
testId?: string;
onClick?: () => void;
title?: string;
}) {
return (
<svg
@ -16,7 +18,7 @@ export function CheckmarkIcon({
data-testid={testId}
onClick={onClick}
>
<title>Checkmark Icon</title>
<title>{title ?? "Checkmark Icon"}</title>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"

View File

@ -1,4 +1,7 @@
export function CrossIcon({ className }: { className?: string }) {
export function CrossIcon({
className,
title,
}: { className?: string; title?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@ -6,7 +9,7 @@ export function CrossIcon({ className }: { className?: string }) {
viewBox="0 0 20 20"
fill="currentColor"
>
<title>Cross Icon</title>
<title>{title ?? "Cross Icon"}</title>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"

View File

@ -1,4 +1,3 @@
import { Button } from "react-aria-components";
import { useTranslation } from "react-i18next";
import { useUser } from "~/features/auth/core/user";
import { FF_SCRIMS_ENABLED } from "~/features/scrims/scrims-constants";
@ -15,6 +14,7 @@ import {
plusSuggestionsNewPage,
userNewBuildPage,
} from "~/utils/urls";
import { SendouButton } from "../elements/Button";
import {
SendouMenu,
SendouMenuItem,
@ -100,12 +100,12 @@ export function AnythingAdder() {
return (
<SendouMenu
trigger={
<Button
<SendouButton
className="layout__header__button"
data-testid="anything-adder-menu-button"
>
<PlusIcon className="layout__header__button__icon" />
</Button>
</SendouButton>
}
>
{items.map((item) => (

View File

@ -5,8 +5,8 @@ import { navItems } from "~/components/layout/nav-items";
import { useUser } from "~/features/auth/core/user";
import { LOG_OUT_URL, navIconUrl, userPage } from "~/utils/urls";
import { Avatar } from "../Avatar";
import { Button } from "../Button";
import { Image } from "../Image";
import { SendouButton } from "../elements/Button";
import { CrossIcon } from "../icons/Cross";
import { LogOutIcon } from "../icons/LogOut";
import { LogInButtonContainer } from "./LogInButtonContainer";
@ -29,11 +29,11 @@ export function NavDialog({
aria-label="Site navigation"
isFullScreen
>
<Button
<SendouButton
icon={<CrossIcon />}
variant="minimal-destructive"
className="layout__overlay-nav__close-button"
onClick={close}
onPress={close}
aria-label="Close navigation dialog"
/>
<div className="layout__overlay-nav__nav-items-container">
@ -61,14 +61,14 @@ export function NavDialog({
{user ? (
<div className="mt-6 stack items-center">
<form method="post" action={LOG_OUT_URL}>
<Button
size="tiny"
<SendouButton
size="small"
variant="outlined"
icon={<LogOutIcon />}
type="submit"
>
{t("common:header.logout")}
</Button>
</SendouButton>
</form>
</div>
) : null}

View File

@ -4,8 +4,8 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import type { RootLoaderData } from "~/root";
import type { Breadcrumb, SendouRouteHandle } from "~/utils/remix.server";
import { Button } from "../Button";
import { Image } from "../Image";
import { SendouButton } from "../elements/Button";
import { HamburgerIcon } from "../icons/Hamburger";
import { Footer } from "./Footer";
import { NavDialog } from "./NavDialog";
@ -54,11 +54,11 @@ export function Layout({
<div className="layout__container">
<NavDialog isOpen={navDialogOpen} close={() => setNavDialogOpen(false)} />
{isFrontPage ? (
<Button
<SendouButton
icon={<HamburgerIcon />}
className="layout__hamburger-fab"
variant="outlined"
onClick={() => setNavDialogOpen(true)}
onPress={() => setNavDialogOpen(true)}
/>
) : null}
<header className="layout__header layout__item_size">

View File

@ -9,12 +9,12 @@ import {
} from "@remix-run/react";
import * as React from "react";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { Catcher } from "~/components/Catcher";
import { Input } from "~/components/Input";
import { Main } from "~/components/Main";
import { NewTabs } from "~/components/NewTabs";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { UserSearch } from "~/components/elements/UserSearch";
import { SearchIcon } from "~/components/icons/Search";
import { FRIEND_CODE_REGEXP_PATTERN } from "~/features/sendouq/q-constants";
@ -149,13 +149,13 @@ function Impersonate() {
onChange={(newUser) => setUserId(newUser.id)}
/>
<div className="stack horizontal md">
<Button type="submit" disabled={!userId}>
<SendouButton type="submit" isDisabled={!userId}>
Go
</Button>
</SendouButton>
{isImpersonating ? (
<Button type="submit" formAction={STOP_IMPERSONATING_URL}>
<SendouButton type="submit" formAction={STOP_IMPERSONATING_URL}>
Stop impersonating
</Button>
</SendouButton>
) : null}
</div>
</Form>
@ -193,7 +193,7 @@ function MigrateUser() {
<div className="stack horizontal md">
<SubmitButton
type="submit"
disabled={!oldUserId || !newUserId || navigation.state !== "idle"}
isDisabled={!oldUserId || !newUserId || navigation.state !== "idle"}
_action="MIGRATE"
state={fetcher.state}
>

View File

@ -3,7 +3,7 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Pagination } from "~/components/Pagination";
import { SendouButton } from "~/components/elements/Button";
@ -204,7 +204,11 @@ function ImagePreview({
["_action", "DELETE_ART"],
]}
>
<Button icon={<TrashIcon />} variant="destructive" size="tiny" />
<SendouButton
icon={<TrashIcon />}
variant="destructive"
size="small"
/>
</FormWithConfirm>
</div>
</div>
@ -242,7 +246,11 @@ function ImagePreview({
]}
submitButtonText={t("common:actions.remove")}
>
<Button icon={<UnlinkIcon />} variant="destructive" size="tiny" />
<SendouButton
icon={<UnlinkIcon />}
variant="destructive"
size="small"
/>
</FormWithConfirm>
) : null}
</div>

View File

@ -6,11 +6,11 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { useFetcher } from "react-router-dom";
import { Alert } from "~/components/Alert";
import { Button } from "~/components/Button";
import { Combobox } from "~/components/Combobox";
import { FormMessage } from "~/components/FormMessage";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { SendouButton } from "~/components/elements/Button";
import { SendouSwitch } from "~/components/elements/Switch";
import { UserSearch } from "~/components/elements/UserSearch";
import { CrossIcon } from "~/components/icons/Cross";
@ -90,9 +90,12 @@ export default function NewArtPage() {
<LinkedUsers />
{data.art ? <ShowcaseToggle /> : null}
<div>
<Button onClick={handleSubmit} disabled={submitButtonDisabled()}>
<SendouButton
onPress={handleSubmit}
isDisabled={submitButtonDisabled()}
>
{t("common:actions.save")}
</Button>
</SendouButton>
</div>
</Form>
</Main>
@ -243,16 +246,19 @@ function Tags() {
<input type="hidden" name="tags" value={JSON.stringify(tags)} />
{creationMode ? (
<div className="art__creation-mode-switcher-container">
<Button variant="minimal" onClick={() => setCreationMode(false)}>
<SendouButton
variant="minimal"
onPress={() => setCreationMode(false)}
>
{t("art:forms.tags.selectFromExisting")}
</Button>
</SendouButton>
</div>
) : (
<div className="stack horizontal sm text-xs text-lighter art__creation-mode-switcher-container">
{t("art:forms.tags.cantFindExisting")}{" "}
<Button variant="minimal" onClick={() => setCreationMode(true)}>
<SendouButton variant="minimal" onPress={() => setCreationMode(true)}>
{t("art:forms.tags.addNew")}
</Button>
</SendouButton>
</div>
)}
{tags.length >= ART.TAGS_MAX_LENGTH ? (
@ -272,9 +278,13 @@ function Tags() {
}
}}
/>
<Button size="tiny" variant="outlined" onClick={handleAddNewTag}>
<SendouButton
size="small"
variant="outlined"
onPress={handleAddNewTag}
>
{t("common:actions.add")}
</Button>
</SendouButton>
</div>
) : (
<Combobox
@ -301,12 +311,12 @@ function Tags() {
return (
<div key={t.name} className="stack horizontal">
{t.name}{" "}
<Button
<SendouButton
icon={<CrossIcon />}
size="tiny"
size="small"
variant="minimal-destructive"
className="art__delete-tag-button"
onClick={() => {
onPress={() => {
setTags(tags.filter((tag) => tag.name !== t.name));
}}
/>
@ -353,10 +363,10 @@ function LinkedUsers() {
initialUserId={userId}
/>
{users.length > 1 || users[0].userId ? (
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal-destructive"
onClick={() => {
onPress={() => {
if (users.length === 1) {
setUsers([{ inputId: nanoid() }]);
} else {
@ -369,15 +379,15 @@ function LinkedUsers() {
</div>
);
})}
<Button
size="tiny"
onClick={() => setUsers([...users, { inputId: nanoid() }])}
disabled={users.length >= ART.LINKED_USERS_MAX_LENGTH}
<SendouButton
size="small"
onPress={() => setUsers([...users, { inputId: nanoid() }])}
isDisabled={users.length >= ART.LINKED_USERS_MAX_LENGTH}
className="my-3"
variant="outlined"
>
{t("art:forms.linkedUsers.anotherOne")}
</Button>
</SendouButton>
<FormMessage type="info">{t("art:forms.linkedUsers.info")}</FormMessage>
</div>
);

View File

@ -2,10 +2,10 @@ import type { MetaFunction, SerializeFrom } from "@remix-run/node";
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { Combobox } from "~/components/Combobox";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { SendouButton } from "~/components/elements/Button";
import { SendouSwitch } from "~/components/elements/Switch";
import { CrossIcon } from "~/components/icons/Cross";
import type { SendouRouteHandle } from "~/utils/remix.server";
@ -107,19 +107,20 @@ export default function ArtPage() {
{filteredTag ? (
<div className="text-xs text-lighter stack md horizontal items-center">
{t("art:filteringByTag", { tag: filteredTag })}
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal-destructive"
icon={<CrossIcon />}
onClick={() => {
onPress={() => {
setSearchParams((prev) => {
prev.delete(FILTERED_TAG_KEY_SEARCH_PARAM_KEY);
return prev;
});
}}
data-testid="clear-filter-button"
>
{t("common:actions.clear")}
</Button>
</SendouButton>
</div>
) : null}
<ArtGrid arts={arts} />

View File

@ -71,7 +71,7 @@ function JoinForm() {
})}
</Label>
<SubmitButton
size="tiny"
size="small"
_action="JOIN_ASSOCIATION"
state={fetcher.state}
>
@ -196,7 +196,7 @@ function AssociationInviteCodeActions({
<input type="hidden" name="associationId" value={associationId} />
<SubmitButton
variant="minimal-destructive"
size="tiny"
size="small"
className="mt-4"
_action="REFRESH_INVITE_CODE"
state={fetcher.state}

View File

@ -2,7 +2,6 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Badge } from "~/components/Badge";
import { Button } from "~/components/Button";
import { SendouButton } from "~/components/elements/Button";
import { TrashIcon } from "~/components/icons/Trash";
import type { Tables } from "~/db/tables";
@ -96,10 +95,10 @@ export function BadgeDisplay({
<div className={styles.badgeExplanation}>
{badgeExplanationText(t, bigBadge)}
{onChange ? (
<Button
<SendouButton
icon={<TrashIcon />}
variant="minimal-destructive"
onClick={() =>
onPress={() =>
onChange(
badges.filter((b) => b.id !== bigBadge.id).map((b) => b.id),
)

View File

@ -1,7 +1,7 @@
import { Form, useMatches, useOutletContext } from "@remix-run/react";
import * as React from "react";
import { Button } from "~/components/Button";
import { Divider } from "~/components/Divider";
import { SendouButton } from "~/components/elements/Button";
import { SendouDialog } from "~/components/elements/Dialog";
import { UserSearch } from "~/components/elements/UserSearch";
import { TrashIcon } from "~/components/icons/Trash";
@ -73,11 +73,11 @@ function Managers({ data }: { data: BadgeDetailsLoaderData }) {
{managers.map((manager) => (
<li key={manager.id}>
{manager.username}
<Button
<SendouButton
icon={<TrashIcon />}
variant="minimal-destructive"
aria-label="Delete badge manager"
onClick={() =>
onPress={() =>
setManagers(managers.filter((m) => m.id !== manager.id))
}
/>
@ -91,14 +91,14 @@ function Managers({ data }: { data: BadgeDetailsLoaderData }) {
value={JSON.stringify(managers.map((m) => m.id))}
/>
<div>
<Button
<SendouButton
type="submit"
disabled={amountOfChanges === 0}
isDisabled={amountOfChanges === 0}
name="_action"
value="MANAGERS"
>
{submitButtonText(amountOfChanges)}
</Button>
</SendouButton>
</div>
</div>
);
@ -193,14 +193,14 @@ function Owners({ data }: { data: BadgeDetailsLoaderData }) {
value={JSON.stringify(countArrayToDuplicatedIdsArray(owners))}
/>
<div>
<Button
<SendouButton
type="submit"
disabled={ownerDifferences.length === 0}
isDisabled={ownerDifferences.length === 0}
name="_action"
value="OWNERS"
>
Submit
</Button>
</SendouButton>
</div>
</div>
);

View File

@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import { Ability } from "~/components/Ability";
import { Button } from "~/components/Button";
import { ModeImage } from "~/components/Image";
import { SendouButton } from "~/components/elements/Button";
import { CrossIcon } from "~/components/icons/Cross";
import { possibleApValues } from "~/features/build-analyzer";
import { abilities } from "~/modules/in-game-lists/abilities";
@ -40,13 +40,13 @@ export function FilterSection({
{nthOfSame > 1 ? nthOfSame : ""}
</div>
<div>
<Button
<SendouButton
icon={<CrossIcon />}
size="tiny"
size="small"
variant="minimal-destructive"
onClick={remove}
onPress={remove}
aria-label="Delete filter"
testId="delete-filter-button"
data-testid="delete-filter-button"
/>
</div>
</div>

View File

@ -1,11 +1,11 @@
import { nanoid } from "nanoid";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { DateInput } from "~/components/DateInput";
import { FormMessage } from "~/components/FormMessage";
import { Input } from "~/components/Input";
import { Label } from "~/components/Label";
import { SendouButton } from "~/components/elements/Button";
import { SendouSwitch } from "~/components/elements/Switch";
import { PlusIcon } from "~/components/icons/Plus";
import * as Progression from "~/features/tournament-bracket/core/Progression";
@ -113,15 +113,16 @@ export function BracketProgressionSelector({
/>
))}
</div>
<Button
<SendouButton
icon={<PlusIcon />}
size="tiny"
size="small"
variant="outlined"
onClick={handleAddBracket}
disabled={brackets.length >= TOURNAMENT.MAX_BRACKETS_PER_TOURNAMENT}
onPress={handleAddBracket}
isDisabled={brackets.length >= TOURNAMENT.MAX_BRACKETS_PER_TOURNAMENT}
data-testid="add-bracket-button"
>
Add bracket
</Button>
</SendouButton>
{Progression.isError(validated) ? (
<ErrorMessage error={validated} />
) : null}
@ -171,15 +172,15 @@ function TournamentFormatBracketSelector({
<div>
<div className="format-selector__count">Bracket #{count}</div>
{onDelete ? (
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal-destructive"
onClick={onDelete}
onPress={onDelete}
className="mx-auto"
testId="delete-bracket-button"
data-testid="delete-bracket-button"
>
Delete
</Button>
</SendouButton>
) : null}
</div>
<div className="format-selector__divider" />

View File

@ -1,7 +1,7 @@
import clsx from "clsx";
import React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { SendouButton } from "~/components/elements/Button";
import { CrossIcon } from "~/components/icons/Cross";
import type { CalendarEventTag } from "~/db/tables";
import { tags as allTags } from "../calendar-constants";
@ -33,13 +33,13 @@ export function Tags({
>
{t(`tag.name.${tag}`)}
{onDelete && (
<Button
onClick={() => onDelete(tag)}
<SendouButton
onPress={() => onDelete(tag)}
className="calendar__event__tag-delete-button"
icon={<CrossIcon />}
variant="minimal"
aria-label="Remove date"
size="tiny"
size="small"
/>
)}
</li>

View File

@ -3,11 +3,11 @@ import { Form, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { FormErrors } from "~/components/FormErrors";
import { FormMessage } from "~/components/FormMessage";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { SendouButton } from "~/components/elements/Button";
import { UserSearch } from "~/components/elements/UserSearch";
import type { SendouRouteHandle } from "~/utils/remix.server";
import type { Unpacked } from "~/utils/types";
@ -36,9 +36,9 @@ export default function ReportWinnersPage() {
{t("calendar:forms.reportResultsInfo")}
</FormMessage>
<TeamInputs />
<Button type="submit" className="mt-4">
<SendouButton type="submit" className="mt-4">
{t("common:actions.submit")}
</Button>
</SendouButton>
<FormErrors namespace="calendar" />
</Form>
</Main>
@ -101,12 +101,12 @@ function TeamInputs() {
</React.Fragment>
);
})}
<Button
onClick={() => setAmountOfTeams((amountOfTeams) => amountOfTeams + 1)}
size="tiny"
<SendouButton
onPress={() => setAmountOfTeams((amountOfTeams) => amountOfTeams + 1)}
size="small"
>
{t("forms.team.add")}
</Button>
</SendouButton>
</>
);
}
@ -202,14 +202,14 @@ function Team({
setPlayers={(players) => setResults({ ...results, players })}
/>
{onRemoveTeam && (
<Button
onClick={onRemoveTeam}
size="tiny"
<SendouButton
onPress={onRemoveTeam}
size="small"
variant="minimal-destructive"
className="mt-4"
>
{t("forms.team.remove")}
</Button>
</SendouButton>
)}
</div>
);
@ -256,16 +256,16 @@ function Players({
<label htmlFor={formId} className="mb-0">
{t("forms.team.player.header", { number: i + 1 })}
</label>
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal"
onClick={() => handlePlayerInputTypeChange(i)}
onPress={() => handlePlayerInputTypeChange(i)}
className="outline-theme"
>
{asPlainInput
? t("forms.team.player.addAsUser")
: t("forms.team.player.addAsText")}
</Button>
</SendouButton>
</div>
{asPlainInput ? (
<input
@ -286,22 +286,24 @@ function Players({
);
})}
<div className="stack horizontal sm mt-2">
<Button
size="tiny"
onClick={handleAddPlayer}
disabled={players.length === CALENDAR_EVENT_RESULT.MAX_PLAYERS_LENGTH}
<SendouButton
size="small"
onPress={handleAddPlayer}
isDisabled={
players.length === CALENDAR_EVENT_RESULT.MAX_PLAYERS_LENGTH
}
variant="outlined"
>
{t("forms.team.player.add")}
</Button>{" "}
<Button
size="tiny"
</SendouButton>{" "}
<SendouButton
size="small"
variant="destructive"
onClick={handleRemovePlayer}
disabled={players.length === 1}
onPress={handleRemovePlayer}
isDisabled={players.length === 1}
>
{t("forms.team.player.remove")}
</Button>
</SendouButton>
</div>
</div>
);

View File

@ -5,7 +5,7 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Image } from "~/components/Image";
import { Main } from "~/components/Main";
@ -13,6 +13,7 @@ import { MapPoolStages } from "~/components/MapPoolSelector";
import { Placement } from "~/components/Placement";
import { Section } from "~/components/Section";
import { Table } from "~/components/Table";
import { SendouButton } from "~/components/elements/Button";
import { useUser } from "~/features/auth/core/user";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import { useIsMounted } from "~/hooks/useIsMounted";
@ -175,14 +176,14 @@ export default function CalendarEventPage() {
name: data.event.name,
})}
>
<Button
<SendouButton
className="ml-auto"
size="tiny"
size="small"
variant="minimal-destructive"
type="submit"
>
{t("calendar:actions.delete")}
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
</div>

View File

@ -7,7 +7,6 @@ import { useTranslation } from "react-i18next";
import type { AlertVariation } from "~/components/Alert";
import { Alert } from "~/components/Alert";
import { Badge } from "~/components/Badge";
import { Button } from "~/components/Button";
import { DateInput } from "~/components/DateInput";
import { Divider } from "~/components/Divider";
import { FormMessage } from "~/components/FormMessage";
@ -17,6 +16,7 @@ import { Main } from "~/components/Main";
import { MapPoolSelector } from "~/components/MapPoolSelector";
import { RequiredHiddenInput } from "~/components/RequiredHiddenInput";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { CrossIcon } from "~/components/icons/Cross";
import { TrashIcon } from "~/components/icons/Trash";
import type { CalendarEventTag, Tables } from "~/db/tables";
@ -159,7 +159,7 @@ function TemplateTournamentForm() {
</option>
))}
</select>
<SubmitButton disabled={!eventId}>Use template</SubmitButton>
<SubmitButton isDisabled={!eventId}>Use template</SubmitButton>
</Form>
</div>
<hr />
@ -279,14 +279,14 @@ function EventForm() {
/>
</div>
) : null}
<Button
<SendouButton
className="mt-4"
onClick={handleSubmit}
disabled={submitButtonDisabled()}
testId="submit-button"
onPress={handleSubmit}
isDisabled={submitButtonDisabled()}
data-testid="submit-button"
>
{t("actions.submit")}
</Button>
</SendouButton>
</Form>
);
}
@ -406,9 +406,9 @@ function AddButton({ onAdd, id }: { onAdd: () => void; id?: string }) {
const { t } = useTranslation();
return (
<Button size="tiny" variant="outlined" onClick={onAdd} id={id}>
<SendouButton size="small" variant="outlined" onPress={onAdd} id={id}>
{t("actions.add")}
</Button>
</SendouButton>
);
}
@ -509,9 +509,9 @@ function DatesInput({ allowMultiDate }: { allowMultiDate?: boolean }) {
/>
{/* "Remove" button */}
{datesCount > 1 && (
<Button
size="tiny"
onClick={() => {
<SendouButton
size="small"
onPress={() => {
setDatesInputState((current) =>
current.filter((e) => e.key !== key),
);
@ -519,8 +519,7 @@ function DatesInput({ allowMultiDate }: { allowMultiDate?: boolean }) {
aria-controls={`date-input-${key}`}
aria-label={t("common:actions.remove")}
aria-describedby={`date-input-${key}-label`}
title={t("common:actions.remove")}
icon={<CrossIcon />}
icon={<CrossIcon title={t("common:actions.remove")} />}
variant="minimal-destructive"
/>
)}
@ -678,9 +677,9 @@ function BadgesAdder() {
<div className="stack horizontal md items-center" key={badge.id}>
<Badge badge={badge} isAnimated size={32} />
<span>{badge.displayName}</span>
<Button
<SendouButton
className="ml-auto"
onClick={() => handleBadgeDelete(badge.id)}
onPress={() => handleBadgeDelete(badge.id)}
icon={<TrashIcon />}
variant="minimal-destructive"
aria-label="Remove badge"
@ -719,13 +718,13 @@ function AvatarImageInput({
alt=""
className="calendar-new__avatar-preview"
/>
<Button
<SendouButton
variant="outlined"
size="tiny"
onClick={() => setShowPrevious(false)}
size="small"
onPress={() => setShowPrevious(false)}
>
Edit logo
</Button>
</SendouButton>
</div>
</div>
);
@ -783,14 +782,14 @@ function AvatarImageInput({
shown.
</FormMessage>
{hasPreviousAvatar && (
<Button
<SendouButton
variant="minimal-destructive"
size="tiny"
onClick={() => setShowPrevious(true)}
size="small"
onPress={() => setShowPrevious(true)}
className="mt-2"
>
Cancel changing avatar image
</Button>
</SendouButton>
)}
</div>
);

View File

@ -11,8 +11,8 @@ import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
import { soundPath } from "~/utils/urls";
import { Avatar } from "../../../components/Avatar";
import { Button } from "../../../components/Button";
import { SubmitButton } from "../../../components/SubmitButton";
import { SendouButton } from "../../../components/elements/Button";
import { MESSAGE_MAX_LENGTH } from "../chat-constants";
import { useChatAutoScroll } from "../chat-hooks";
import type { ChatMessage } from "../chat-types";
@ -136,12 +136,13 @@ export function Chat({
const unseen = unseenMessages.get(room.code);
return (
<Button
<SendouButton
key={room.code}
className={clsx("chat__room-button", {
current: currentRoom === room.code,
})}
onClick={() => {
size="small"
onPress={() => {
setCurrentRoom(room.code);
resetScroller();
}}
@ -153,7 +154,7 @@ export function Chat({
) : (
<span className="chat__room-button__unseen invisible" />
)}
</Button>
</SendouButton>
);
})}
</div>
@ -189,9 +190,12 @@ export function Chat({
})}
</ol>
{unseenMessagesInTheRoom ? (
<Button className="chat__unseen-messages" onClick={scrollToBottom}>
<SendouButton
className="chat__unseen-messages"
onPress={scrollToBottom}
>
{t("common:chat.newMessages")}
</Button>
</SendouButton>
) : null}
<form onSubmit={handleSubmit} className="mt-4">
<input
@ -216,9 +220,9 @@ export function Chat({
</div>
)}
<SubmitButton
size="tiny"
size="small"
variant="minimal"
disabled={sendingMessagesDisabled}
isDisabled={sendingMessagesDisabled}
>
{t("common:chat.send")}
</SubmitButton>

View File

@ -3,11 +3,11 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { Divider } from "~/components/Divider";
import { Image } from "~/components/Image";
import { Main } from "~/components/Main";
import { NewTabs } from "~/components/NewTabs";
import { SendouButton } from "~/components/elements/Button";
import { ArrowRightIcon } from "~/components/icons/ArrowRight";
import { BSKYLikeIcon } from "~/components/icons/BSKYLike";
import { BSKYReplyIcon } from "~/components/icons/BSKYReply";
@ -84,15 +84,15 @@ function DesktopSideNav() {
})}
{user ? (
<form method="post" action={LOG_OUT_URL}>
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal"
icon={<LogOutIcon />}
type="submit"
className="front-page__side-nav__log-out"
>
{t("common:header.logout")}
</Button>
</SendouButton>
</form>
) : null}
</nav>

View File

@ -2,8 +2,8 @@ import { useFetcher, useLoaderData } from "@remix-run/react";
import Compressor from "compressorjs";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { Main } from "~/components/Main";
import { SendouButton } from "~/components/elements/Button";
import invariant from "~/utils/invariant";
import { imgTypeToDimensions, imgTypeToStyle } from "../upload-constants";
import type { ImageUploadType } from "../upload-types";
@ -98,13 +98,14 @@ export default function FileUploadPage() {
/>
</div>
{img ? <PreviewImage img={img} type={data.type} /> : null}
<Button
<SendouButton
className="self-start"
disabled={!img || fetcher.state !== "idle"}
onClick={handleSubmit}
isDisabled={!img || fetcher.state !== "idle"}
onPress={handleSubmit}
data-testid="upload-button"
>
{t("common:actions.upload")}
</Button>
</SendouButton>
</Main>
);
}

View File

@ -1,9 +1,9 @@
import { useTranslation } from "react-i18next";
import * as R from "remeda";
import { Button } from "~/components/Button";
import { WeaponCombobox } from "~/components/Combobox";
import { WeaponImage } from "~/components/Image";
import { Label } from "~/components/Label";
import { SendouButton } from "~/components/elements/Button";
import { CrossIcon } from "~/components/icons/Cross";
import type { Tables } from "~/db/tables";
import type { TierName } from "~/features/mmr/mmr-constants";
@ -59,11 +59,11 @@ function Filter({
<Label>
{t(`lfg:filters.${filter._tag}`)} {t("lfg:filters.suffix")}
</Label>
<Button
<SendouButton
icon={<CrossIcon />}
size="tiny"
size="small"
variant="minimal-destructive"
onClick={removeFilter}
onPress={removeFilter}
aria-label="Delete filter"
/>
</div>
@ -139,10 +139,10 @@ function WeaponFilterFields({
}
/>
{value.map((weapon) => (
<Button
<SendouButton
key={weapon}
variant="minimal"
onClick={() =>
onPress={() =>
changeFilter({
_tag: "Weapon",
weaponSplIds: value.filter((weaponId) => weaponId !== weapon),
@ -150,7 +150,7 @@ function WeaponFilterFields({
}
>
<WeaponImage weaponSplId={weapon} size={32} variant="badge" />
</Button>
</SendouButton>
))}
</div>
);

View File

@ -4,11 +4,11 @@ import { formatDistanceToNow } from "date-fns";
import React from "react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { Divider } from "~/components/Divider";
import { Flag } from "~/components/Flag";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Image, TierImage, WeaponImage } from "~/components/Image";
import { SendouButton } from "~/components/elements/Button";
import { EditIcon } from "~/components/icons/Edit";
import { TrashIcon } from "~/components/icons/Trash";
import { useUser } from "~/features/auth/core/user";
@ -463,15 +463,15 @@ function PostDeleteButton({ id, type }: { id: number; type: Post["type"] }) {
]}
fetcher={fetcher}
>
<Button
<SendouButton
className="build__small-text"
variant="minimal-destructive"
size="tiny"
size="small"
type="submit"
icon={<TrashIcon className="build__icon" />}
>
{t("common:actions.delete")}
</Button>
</SendouButton>
</FormWithConfirm>
);
}
@ -501,18 +501,18 @@ function PostExpandableText({
>
<div className={styles.text}>{text}</div>
{isExpandable ? (
<Button
onClick={() => setIsExpanded(!isExpanded)}
<SendouButton
onPress={() => setIsExpanded(!isExpanded)}
className={clsx([styles.showAllButton], {
[styles.showAllButtonExpanded]: isExpanded,
})}
variant="outlined"
size="tiny"
size="small"
>
{isExpanded
? t("common:actions.showLess")
: t("common:actions.showMore")}
</Button>
</SendouButton>
) : null}
{!isExpanded ? <div className={styles.textCut} /> : null}
</div>

View File

@ -146,7 +146,7 @@ function PostExpiryAlert({ postId }: { postId: number }) {
<fetcher.Form method="post" className="stack md horizontal items-center">
<input type="hidden" name="id" value={postId} />
{t("lfg:expiring")}{" "}
<SubmitButton _action="BUMP_POST" variant="outlined" size="tiny">
<SubmitButton _action="BUMP_POST" variant="outlined" size="small">
{t("common:actions.clickHere")}
</SubmitButton>
</fetcher.Form>

View File

@ -4,10 +4,10 @@ import { Link, useLoaderData, useSearchParams } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useCopyToClipboard } from "react-use";
import { Button } from "~/components/Button";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { MapPoolSelector, MapPoolStages } from "~/components/MapPoolSelector";
import { SendouButton } from "~/components/elements/Button";
import { SendouSwitch } from "~/components/elements/Switch";
import { EditIcon } from "~/components/icons/Edit";
import type { Tables } from "~/db/tables";
@ -77,14 +77,14 @@ export default function MapListPage() {
{data.calendarEvent.name}
</Link>
</div>
<Button
<SendouButton
variant="outlined"
onClick={switchToEditMode}
size="tiny"
onPress={switchToEditMode}
size="small"
icon={<EditIcon />}
>
{t("common:actions.edit")}
</Button>
</SendouButton>
</div>
)}
{readonly ? (
@ -194,9 +194,9 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) {
size="small"
/>
</div>
<Button onClick={handleCreateMaplist} disabled={disabled}>
<SendouButton onPress={handleCreateMaplist} isDisabled={disabled}>
{t("common:maps.createMapList")}
</Button>
</SendouButton>
{mapList && (
<>
<ol className={styles.mapList}>
@ -212,10 +212,10 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) {
</li>
))}
</ol>
<Button
size="tiny"
<SendouButton
size="small"
variant="outlined"
onClick={() =>
onPress={() =>
copyToClipboard(
mapList
.map(
@ -229,7 +229,7 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) {
}
>
{t("common:actions.copyToClipboard")}
</Button>
</SendouButton>
</>
)}
</div>

View File

@ -37,8 +37,8 @@ import {
subWeaponImageUrl,
weaponCategoryUrl,
} from "~/utils/urls";
import { Button } from "../../../components/Button";
import { Image } from "../../../components/Image";
import { SendouButton } from "../../../components/elements/Button";
import type { StageBackgroundStyle } from "../plans-types";
export default function Planner() {
@ -275,9 +275,9 @@ function OutlineToggle({
return (
<div className="plans__outline-toggle">
<Button
<SendouButton
variant="minimal"
onClick={handleClick}
onPress={handleClick}
className={clsx("plans__outline-toggle__button", {
"plans__outline-toggle__button__outlined": outlined,
})}
@ -285,7 +285,7 @@ function OutlineToggle({
{outlined
? t("common:actions.outlined")
: t("common:actions.noOutline")}
</Button>
</SendouButton>
</div>
);
}
@ -320,10 +320,10 @@ function WeaponImageSelector({
<div className="plans__weapons-container">
{category.weaponIds.map((weaponId) => {
return (
<Button
<SendouButton
key={weaponId}
variant="minimal"
onClick={() =>
onPress={() =>
handleAddWeapon(
`${outlinedMainWeaponImageUrl(weaponId)}.png`,
)
@ -336,7 +336,7 @@ function WeaponImageSelector({
width={36}
height={36}
/>
</Button>
</SendouButton>
);
})}
</div>
@ -351,10 +351,10 @@ function WeaponImageSelector({
<div className="plans__weapons-container">
{subWeaponIds.map((subWeaponId) => {
return (
<Button
<SendouButton
key={subWeaponId}
variant="minimal"
onClick={() =>
onPress={() =>
handleAddWeapon(`${subWeaponImageUrl(subWeaponId)}.png`)
}
>
@ -365,7 +365,7 @@ function WeaponImageSelector({
width={28}
height={28}
/>
</Button>
</SendouButton>
);
})}
</div>
@ -383,10 +383,10 @@ function WeaponImageSelector({
<div className="plans__weapons-container">
{specialWeaponIds.map((specialWeaponId) => {
return (
<Button
<SendouButton
key={specialWeaponId}
variant="minimal"
onClick={() =>
onPress={() =>
handleAddWeapon(
`${specialWeaponImageUrl(specialWeaponId)}.png`,
)
@ -399,7 +399,7 @@ function WeaponImageSelector({
width={28}
height={28}
/>
</Button>
</SendouButton>
);
})}
</div>
@ -412,10 +412,10 @@ function WeaponImageSelector({
<div className="plans__weapons-container">
{(["TC", "RM", "CB"] as const).map((mode) => {
return (
<Button
<SendouButton
key={mode}
variant="minimal"
onClick={() => handleAddWeapon(`${modeImageUrl(mode)}.png`)}
onPress={() => handleAddWeapon(`${modeImageUrl(mode)}.png`)}
>
<Image
alt={t(`game-misc:MODE_LONG_${mode}`)}
@ -424,7 +424,7 @@ function WeaponImageSelector({
width={28}
height={28}
/>
</Button>
</SendouButton>
);
})}
</div>
@ -499,15 +499,15 @@ function StageBackgroundSelector({
);
})}
</select>
<Button
size="tiny"
onClick={() =>
<SendouButton
size="small"
onPress={() =>
onAddBackground({ style: backgroundStyle, stageId, mode })
}
className="w-max"
>
{t("common:actions.setBg")}
</Button>
</SendouButton>
</div>
);
}

View File

@ -1,6 +1,6 @@
import { Form, useMatches, useParams } from "@remix-run/react";
import { Button } from "~/components/Button";
import { Redirect } from "~/components/Redirect";
import { SendouButton } from "~/components/elements/Button";
import { SendouDialog } from "~/components/elements/Dialog";
import { useUser } from "~/features/auth/core/user";
import { atOrError } from "~/utils/arrays";
@ -51,7 +51,7 @@ export default function PlusCommentModalPage() {
<input type="hidden" name="suggestedId" value={targetUserId} />
<CommentTextarea maxLength={PLUS_SUGGESTION.COMMENT_MAX_LENGTH} />
<div>
<Button type="submit">Submit</Button>
<SendouButton type="submit">Submit</SendouButton>
</div>
</Form>
</SendouDialog>

View File

@ -4,7 +4,7 @@ import { Link, Outlet, useLoaderData, useSearchParams } from "@remix-run/react";
import clsx from "clsx";
import { Alert } from "~/components/Alert";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { Catcher } from "~/components/Catcher";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { RelativeTime } from "~/components/RelativeTime";
@ -26,6 +26,7 @@ import {
canSuggestNewUser,
} from "../plus-suggestions-utils";
import { SendouButton } from "~/components/elements/Button";
import { action } from "../actions/plus.suggestions.server";
import { loader } from "../loaders/plus.suggestions.server";
export { action, loader };
@ -175,14 +176,14 @@ function SuggestedForInfo() {
]}
dialogHeading={`Delete your suggestion to +${tier}? You won't appear in next voting.`}
>
<Button
<SendouButton
key={tier}
size="tiny"
size="small"
variant="destructive"
type="submit"
>
Delete your +{tier} suggestion
</Button>
Delete
</SendouButton>
</FormWithConfirm>
))}
</div>
@ -325,7 +326,7 @@ function CommentDeleteButton({
: `Delete your comment to ${suggestedUsername}'s +${tier} suggestion?`
}
>
<Button
<SendouButton
className="plus__delete-button"
icon={<TrashIcon />}
variant="minimal-destructive"

View File

@ -2,8 +2,8 @@ import type { MetaFunction } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
import * as React from "react";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { RelativeTime } from "~/components/RelativeTime";
import { SendouButton } from "~/components/elements/Button";
import { CheckmarkIcon } from "~/components/icons/Checkmark";
import { usePlusVoting } from "~/features/plus-voting/core";
import { metaTags } from "~/utils/remix";
@ -108,9 +108,13 @@ function Voting(data: Extract<PlusVotingLoaderData, { type: "voting" }>) {
{previous.score}
</span>{" "}
on {previous.user.username}.
<Button className="ml-auto" variant="minimal" onClick={undoLast}>
<SendouButton
className="ml-auto"
variant="minimal"
onPress={undoLast}
>
Undo?
</Button>
</SendouButton>
</p>
) : (
<p className="text-sm text-lighter">Tip: {randomTip}</p>
@ -120,20 +124,20 @@ function Voting(data: Extract<PlusVotingLoaderData, { type: "voting" }>) {
<Avatar user={currentUser.user} size="lg" />
<h2>{currentUser.user.username}</h2>
<div className="stack horizontal lg">
<Button
<SendouButton
className="plus-voting__vote-button downvote"
variant="outlined"
onClick={() => addVote("downvote")}
onPress={() => addVote("downvote")}
>
-1
</Button>
<Button
</SendouButton>
<SendouButton
className="plus-voting__vote-button"
variant="outlined"
onClick={() => addVote("upvote")}
onPress={() => addVote("upvote")}
>
+1
</Button>
</SendouButton>
</div>
{currentUser.suggestion ? (
<PlusSuggestionComments
@ -153,9 +157,9 @@ function Voting(data: Extract<PlusVotingLoaderData, { type: "voting" }>) {
) : (
<Form method="post">
<input type="hidden" name="votes" value={JSON.stringify(votes)} />
<Button className="plus-voting__submit-button" type="submit">
<SendouButton className="plus-voting__submit-button" type="submit">
Submit votes
</Button>
</SendouButton>
</Form>
)}
</div>

View File

@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import * as R from "remeda";
import type { z } from "zod";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { Divider } from "~/components/Divider";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Table } from "~/components/Table";
@ -471,14 +471,14 @@ function ScrimsTable({
) : null}
{user && requestScrim && post.requests.length === 0 ? (
<td className={styles.postFloatingActionCell}>
<Button
size="tiny"
onClick={() => requestScrim(post.id)}
<SendouButton
size="small"
onPress={() => requestScrim(post.id)}
icon={<ArrowUpOnSquareIcon />}
className="ml-auto"
>
{t("scrims:actions.request")}
</Button>
</SendouButton>
</td>
) : null}
{showDeletePost && !isAccepted ? (
@ -492,13 +492,13 @@ function ScrimsTable({
["_action", "DELETE_POST"],
]}
>
<Button
size="tiny"
<SendouButton
size="small"
variant="destructive"
className="ml-auto"
>
{t("common:actions.delete")}
</Button>
</SendouButton>
</FormWithConfirm>
) : (
<SendouPopover
@ -531,14 +531,14 @@ function ScrimsTable({
["_action", "CANCEL_REQUEST"],
]}
>
<Button
size="tiny"
<SendouButton
size="small"
variant="destructive"
icon={<CrossIcon />}
className="ml-auto"
>
{t("common:actions.cancel")}
</Button>
</SendouButton>
</FormWithConfirm>
</td>
) : null}
@ -645,9 +645,9 @@ function RequestRow({
submitButtonVariant="primary"
submitButtonText={t("common:actions.accept")}
>
<Button size="tiny" className="ml-auto">
<SendouButton size="small" className="ml-auto">
{t("common:actions.accept")}
</Button>
</SendouButton>
</FormWithConfirm>
) : !request.isAccepted && !canAccept ? (
<SendouPopover

View File

@ -13,7 +13,7 @@ import { Flipped, Flipper } from "react-flip-toolkit";
import { useTranslation } from "react-i18next";
import { Alert } from "~/components/Alert";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { WeaponCombobox } from "~/components/Combobox";
import { Divider } from "~/components/Divider";
import { FormWithConfirm } from "~/components/FormWithConfirm";
@ -381,15 +381,15 @@ function AfterMatchActions({
</SubmitButton>
) : null}
{showWeaponsFormButton ? (
<Button
<SendouButton
icon={<ArchiveBoxIcon />}
onClick={() => setShowWeaponsForm(!showWeaponsForm)}
onPress={() => setShowWeaponsForm(!showWeaponsForm)}
variant={showWeaponsForm ? "destructive" : undefined}
>
{showWeaponsForm
? t("q:match.actions.stopReportingWeapons")
: t("q:match.actions.reportWeapons")}
</Button>
</SendouButton>
) : null}
</lookAgainFetcher.Form>
{showWeaponsForm ? <ReportWeaponsForm /> : null}
@ -515,17 +515,17 @@ function ReportWeaponsForm() {
showReportedOwnWeapon={false}
/>
{i !== 0 && reportingMode !== "MYSELF" ? (
<Button
size="tiny"
<SendouButton
size="small"
variant="outlined"
className="self-center"
onClick={handleCopyWeaponsFromPreviousMap({
onPress={handleCopyWeaponsFromPreviousMap({
groupMatchMapId,
mapIndex: i,
})}
>
{t("q:match.report.copyWeapons")}
</Button>
</SendouButton>
) : null}
<div className="stack sm">
{playersToReport().map((member, j) => {
@ -779,15 +779,15 @@ function BottomSection({
submitButtonText={t("common:actions.cancel")}
fetcher={cancelFetcher}
>
<Button
<SendouButton
variant="minimal-destructive"
size="tiny"
size="small"
type="submit"
disabled={ownTeamReported && !data.match.mapList[0].winnerGroupId}
isDisabled={ownTeamReported && !data.match.mapList[0].winnerGroupId}
className="build__small-text mt-4"
>
{t("q:match.cancelMatch")}
</Button>
</SendouButton>
</FormWithConfirm>
) : null;
@ -1288,8 +1288,8 @@ function MapListMap({
{typeof ownWeapon === "number" ? (
<div className="font-bold stack sm horizontal">
{t(`weapons:MAIN_${ownWeapon}`)}
<Button
size="tiny"
<SendouButton
size="small"
icon={<CrossIcon />}
variant="minimal-destructive"
onClick={() => {

View File

@ -4,13 +4,13 @@ import * as React from "react";
import { useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { WeaponCombobox } from "~/components/Combobox";
import { FormMessage } from "~/components/FormMessage";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { ModeImage, WeaponImage } from "~/components/Image";
import { Main } from "~/components/Main";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { SendouSwitch } from "~/components/elements/Switch";
import { CrossIcon } from "~/components/icons/Cross";
import { MapIcon } from "~/components/icons/Map";
@ -361,10 +361,10 @@ function Languages() {
return (
<div key={code} className="stack horizontal items-center sm">
{name}{" "}
<Button
<SendouButton
icon={<CrossIcon />}
variant="minimal-destructive"
onClick={() => {
onPress={() => {
const newLanguages = value.filter(
(codeInArr) => codeInArr !== code,
);
@ -441,11 +441,11 @@ function WeaponPool() {
/>
</div>
<div className="stack sm horizontal items-center justify-center">
<Button
<SendouButton
icon={weapon.isFavorite ? <StarFilledIcon /> : <StarIcon />}
variant="minimal"
aria-label="Favorite weapon"
onClick={() =>
onPress={() =>
setWeapons(
weapons.map((w) =>
w.weaponSplId === weapon.weaponSplId
@ -458,19 +458,19 @@ function WeaponPool() {
)
}
/>
<Button
<SendouButton
icon={<TrashIcon />}
variant="minimal-destructive"
aria-label="Delete weapon"
onClick={() =>
onPress={() =>
setWeapons(
weapons.filter(
(w) => w.weaponSplId !== weapon.weaponSplId,
),
)
}
testId={`delete-weapon-${weapon.weaponSplId}`}
size="tiny"
data-testid={`delete-weapon-${weapon.weaponSplId}`}
size="small"
/>
</div>
</div>
@ -640,14 +640,14 @@ function TrustedUsers() {
]}
submitButtonText="Remove"
>
<Button
<SendouButton
className="build__small-text"
variant="minimal-destructive"
size="tiny"
size="small"
type="submit"
>
<TrashIcon className="build__icon" />
</Button>
</SendouButton>
</FormWithConfirm>
</div>
);

View File

@ -5,7 +5,7 @@ import * as React from "react";
import { Flipped } from "react-flip-toolkit";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Image, ModeImage, TierImage, WeaponImage } from "~/components/Image";
import { SubmitButton } from "~/components/SubmitButton";
@ -187,7 +187,7 @@ export function GroupCard({
<fetcher.Form className="stack items-center" method="post">
<input type="hidden" name="targetGroupId" value={group.id} />
<SubmitButton
size="tiny"
size="small"
variant={action === "UNLIKE" ? "destructive" : "outlined"}
_action={action}
state={fetcher.state}
@ -424,14 +424,14 @@ function MemberNote({
<div className="text-lighter text-center text-xs mt-1">
{note}{" "}
{editable ? (
<Button
<SendouButton
size="miniscule"
variant="minimal"
onClick={startEditing}
onPress={startEditing}
className="mt-2 ml-auto"
>
{t("q:looking.groups.editNote")}
</Button>
</SendouButton>
) : null}
</div>
);
@ -440,9 +440,9 @@ function MemberNote({
if (!editable) return null;
return (
<Button variant="minimal" size="miniscule" onClick={startEditing}>
<SendouButton variant="minimal" size="miniscule" onPress={startEditing}>
{t("q:looking.groups.addNote")}
</Button>
</SendouButton>
);
}
@ -478,13 +478,13 @@ function AddPrivateNoteForm({
ref={textareaRef}
/>
<div className="stack horizontal justify-between">
<Button
<SendouButton
variant="minimal-destructive"
size="miniscule"
onClick={stopEditing}
onPress={stopEditing}
>
{t("common:actions.cancel")}
</Button>
</SendouButton>
{newValueLegal ? (
<SubmitButton
_action="UPDATE_NOTE"
@ -550,7 +550,7 @@ function DeletePrivateNoteForm({
["_action", "DELETE_PRIVATE_USER_NOTE"],
]}
>
<SubmitButton variant="minimal-destructive" size="tiny" type="submit">
<SubmitButton variant="minimal-destructive" size="small" type="submit">
<TrashIcon className="build__icon" />
</SubmitButton>
</FormWithConfirm>
@ -677,7 +677,7 @@ function MemberRoleManager({
{member.role === "REGULAR" ? (
<SubmitButton
variant="outlined"
size="tiny"
size="small"
_action="GIVE_MANAGER"
state={fetcher.state}
>
@ -687,7 +687,7 @@ function MemberRoleManager({
{member.role === "MANAGER" ? (
<SubmitButton
variant="destructive"
size="tiny"
size="small"
_action="REMOVE_MANAGER"
state={fetcher.state}
>
@ -697,7 +697,7 @@ function MemberRoleManager({
{enableKicking && member.id !== loggedInUser?.id ? (
<SubmitButton
variant="destructive"
size="tiny"
size="small"
_action="KICK_FROM_GROUP"
state={fetcher.state}
>

View File

@ -1,8 +1,8 @@
import { useFetcher } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { SENDOUQ_LOOKING_PAGE } from "~/utils/urls";
export function GroupLeaver({
@ -21,9 +21,9 @@ export function GroupLeaver({
submitButtonText="Leave"
action={SENDOUQ_LOOKING_PAGE}
>
<Button variant="minimal-destructive" size="tiny">
<SendouButton variant="minimal-destructive" size="small">
{t("q:looking.groups.actions.leaveGroup")}
</Button>
</SendouButton>
</FormWithConfirm>
);
}
@ -34,7 +34,7 @@ export function GroupLeaver({
<SubmitButton
_action="LEAVE_GROUP"
variant="minimal-destructive"
size="tiny"
size="small"
state={fetcher.state}
>
{type === "LEAVE_Q"

View File

@ -2,8 +2,8 @@ import { useFetcher } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useCopyToClipboard } from "react-use";
import { Button } from "~/components/Button";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { CheckmarkIcon } from "~/components/icons/Checkmark";
import { ClipboardIcon } from "~/components/icons/Clipboard";
import { PlusIcon } from "~/components/icons/Plus";
@ -58,9 +58,9 @@ export function MemberAdder({
id="invite"
className="q__member-adder__input"
/>
<Button
<SendouButton
variant={copySuccess ? "outlined-success" : "outlined"}
onClick={() => copyToClipboard(inviteLink)}
onPress={() => copyToClipboard(inviteLink)}
icon={copySuccess ? <CheckmarkIcon /> : <ClipboardIcon />}
aria-label="Copy to clipboard"
/>
@ -75,8 +75,8 @@ export function MemberAdder({
/>
<SubmitButton
variant="outlined"
_action="ADD_TRUSTED"
disabled={!truster}
_action="ADD_MEMBER"
isDisabled={!truster}
icon={<PlusIcon />}
/>
</div>

View File

@ -10,8 +10,8 @@ import {
} from "~/utils/urls";
import "../q.css";
import type { MetaFunction } from "@remix-run/node";
import { Button } from "~/components/Button";
import { Image } from "~/components/Image";
import { SendouButton } from "~/components/elements/Button";
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "~/features/leaderboards/leaderboards-constants";
import { USER_LEADERBOARD_MIN_ENTRIES_FOR_LEVIATHAN } from "~/features/mmr/mmr-constants";
import { metaTags } from "~/utils/remix";
@ -57,18 +57,21 @@ function TableOfContents() {
<h2>Table of contents</h2>
<ul>
<li>
<Button onClick={handleTitleClick("general-info")} variant="minimal">
<SendouButton
onPress={handleTitleClick("general-info")}
variant="minimal"
>
General info
</Button>
</SendouButton>
</li>
<li>
<Button
onClick={handleTitleClick("before-joining")}
<SendouButton
onPress={handleTitleClick("before-joining")}
variant="minimal"
>
Before joining
</Button>
</SendouButton>
</li>
<li>Make a sendou.ink account</li>
<li>Select your map pool</li>
@ -80,23 +83,23 @@ function TableOfContents() {
<li>Reading rules</li>
<li>
<Button
onClick={handleTitleClick("joining-the-queue")}
<SendouButton
onPress={handleTitleClick("joining-the-queue")}
variant="minimal"
>
Joining the queue
</Button>
</SendouButton>
</li>
<li>Joining solo</li>
<li>Joining with 1-3 mates</li>
<li>
<Button
onClick={handleTitleClick("finding-a-group")}
<SendouButton
onPress={handleTitleClick("finding-a-group")}
variant="minimal"
>
Finding a group
</Button>
</SendouButton>
</li>
<li>Plus Server icons</li>
<li>Adding a note</li>
@ -104,22 +107,22 @@ function TableOfContents() {
<li>Group managers</li>
<li>
<Button
onClick={handleTitleClick("finding-an-opponent")}
<SendouButton
onPress={handleTitleClick("finding-an-opponent")}
variant="minimal"
>
Finding an opponent
</Button>
</SendouButton>
</li>
<li>Rechallenging</li>
<li>
<Button
onClick={handleTitleClick("playing-the-match")}
<SendouButton
onPress={handleTitleClick("playing-the-match")}
variant="minimal"
>
Playing the match
</Button>
</SendouButton>
</li>
<li>Canceling the match</li>
<li>Enemy not reporting</li>
@ -128,9 +131,12 @@ function TableOfContents() {
<li>Stats</li>
<li>
<Button onClick={handleTitleClick("other-topics")} variant="minimal">
<SendouButton
onPress={handleTitleClick("other-topics")}
variant="minimal"
>
Other topics
</Button>
</SendouButton>
</li>
<li>Ranking algorithm</li>
<li>Ranking tiers</li>

View File

@ -105,7 +105,7 @@ function InfoText() {
>
{t("q:looking.inactiveGroup")}{" "}
<SubmitButton
size="tiny"
size="small"
variant="minimal"
_action="REFRESH_GROUP"
state={fetcher.state}
@ -124,7 +124,7 @@ function InfoText() {
>
{t("q:looking.inactiveGroup.soon")}{" "}
<SubmitButton
size="tiny"
size="small"
variant="minimal"
_action="REFRESH_GROUP"
state={fetcher.state}

View File

@ -4,7 +4,7 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Alert } from "~/components/Alert";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { Flag } from "~/components/Flag";
import { FormMessage } from "~/components/FormMessage";
import { FriendCodeInput } from "~/components/FriendCodeInput";
@ -116,7 +116,7 @@ export default function QPage() {
<div className="stack horizontal md items-center mt-4 mx-auto">
<SubmitButton
icon={<UsersIcon />}
disabled={queueJoinStatus !== "NOW"}
isDisabled={queueJoinStatus !== "NOW"}
>
{t("q:front.actions.joinWithGroup")}
</SubmitButton>
@ -126,7 +126,7 @@ export default function QPage() {
state={fetcher.state}
icon={<UserIcon />}
variant="outlined"
disabled={queueJoinStatus !== "NOW"}
isDisabled={queueJoinStatus !== "NOW"}
>
{t("q:front.actions.joinSolo")}
</SubmitButton>
@ -160,9 +160,9 @@ export default function QPage() {
action={LOG_IN_URL}
method="post"
>
<Button size="big" type="submit">
<SendouButton size="big" type="submit">
{t("q:front.actions.logIn")}
</Button>
</SendouButton>
</form>
)}
</>

View File

@ -2,7 +2,6 @@ import type { MetaFunction } from "@remix-run/node";
import { useFetcher, useNavigate, useSearchParams } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { FormMessage } from "~/components/FormMessage";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
@ -229,9 +228,9 @@ function PushNotificationsEnabler() {
: t("common:settings.notifications.permissionDenied")}
</SendouPopover>
) : (
<Button size="tiny" variant="minimal" onClick={askPermission}>
<SendouButton size="small" variant="minimal" onPress={askPermission}>
{t("common:actions.enable")}
</Button>
</SendouButton>
)}
<FormMessage type="info">
{t("common:settings.notifications.description")}

View File

@ -2,7 +2,6 @@ import type { MetaFunction, SerializeFrom } from "@remix-run/node";
import { Form, Link, useLoaderData } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { CustomizedColorsInput } from "~/components/CustomizedColorsInput";
import { FormErrors } from "~/components/FormErrors";
import { FormMessage } from "~/components/FormMessage";
@ -11,6 +10,7 @@ import { Input } from "~/components/Input";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { useUser } from "~/features/auth/core/user";
import type { SendouRouteHandle } from "~/utils/remix.server";
import {
@ -69,13 +69,13 @@ export default function EditTeamPage() {
dialogHeading={t("team:deleteTeam.header", { teamName: team.name })}
fields={[["_action", "DELETE_TEAM"]]}
>
<Button
<SendouButton
className="ml-auto"
variant="minimal-destructive"
data-testid="delete-team-button"
>
{t("team:actionButtons.deleteTeam")}
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
<Form method="post" className="stack md items-start">
@ -150,9 +150,9 @@ function ImageRemoveButtons() {
fields={[["_action", "DELETE_AVATAR"]]}
submitButtonText={t("common:actions.remove")}
>
<Button className="ml-auto" variant="minimal-destructive">
<SendouButton className="ml-auto" variant="minimal-destructive">
{t("team:actionButtons.deleteTeam.profilePicture")}
</Button>
</SendouButton>
</FormWithConfirm>
</li>
) : null}
@ -165,9 +165,9 @@ function ImageRemoveButtons() {
fields={[["_action", "DELETE_BANNER"]]}
submitButtonText={t("common:actions.remove")}
>
<Button className="ml-auto" variant="minimal-destructive">
<SendouButton className="ml-auto" variant="minimal-destructive">
{t("team:actionButtons.deleteTeam.banner")}
</Button>
</SendouButton>
</FormWithConfirm>
</li>
) : null}

View File

@ -5,7 +5,6 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { useCopyToClipboard } from "react-use";
import { Alert } from "~/components/Alert";
import { Button } from "~/components/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Main } from "~/components/Main";
import { SubmitButton } from "~/components/SubmitButton";
@ -110,13 +109,16 @@ function InviteCodeSection() {
{inviteLink}
</div>
<Form method="post" className="stack horizontal md">
<Button size="tiny" onClick={() => copyToClipboard(inviteLink)}>
<SendouButton
size="small"
onPress={() => copyToClipboard(inviteLink)}
>
{t("common:actions.copyToClipboard")}
</Button>
</SendouButton>
<SubmitButton
variant="minimal-destructive"
_action="RESET_INVITE_LINK"
size="tiny"
size="small"
testId="reset-invite-link-button"
>
{t("common:actions.reset")}
@ -243,14 +245,14 @@ function MemberRow({
["userId", member.id],
]}
>
<Button
size="tiny"
<SendouButton
size="small"
variant="destructive"
icon={<TrashIcon />}
testId={!isSelf ? "kick-button" : undefined}
data-testid={!isSelf ? "kick-button" : undefined}
>
{t("team:actionButtons.kick")}
</Button>
</SendouButton>
</FormWithConfirm>
</div>
<hr className="team__roster__separator" />

View File

@ -5,12 +5,13 @@ import React from "react";
import { useTranslation } from "react-i18next";
import * as R from "remeda";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { Flag } from "~/components/Flag";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { WeaponImage } from "~/components/Image";
import { Main } from "~/components/Main";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { BskyIcon } from "~/components/icons/Bsky";
import { EditIcon } from "~/components/icons/Edit";
import { StarIcon } from "~/components/icons/Star";
@ -221,13 +222,13 @@ function ActionButtons() {
submitButtonText={t("team:actionButtons.leaveTeam.confirm")}
fields={[["_action", "LEAVE_TEAM"]]}
>
<Button
size="tiny"
<SendouButton
size="small"
variant="destructive"
data-testid="leave-team-button"
>
{t("team:actionButtons.leaveTeam")}
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
{isTeamManager({ user, team }) || isAdmin ? (
@ -266,7 +267,7 @@ function ChangeMainTeamButton() {
<fetcher.Form method="post">
<SubmitButton
_action="MAKE_MAIN_TEAM"
size="tiny"
size="small"
variant="outlined"
icon={<StarIcon />}
testId="make-main-team-button"

View File

@ -1,7 +1,7 @@
import { Link, useFetcher } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { Button } from "../../../../components/Button";
import { SendouButton } from "../../../../components/elements/Button";
import { CheckmarkIcon } from "../../../../components/icons/Checkmark";
import { CrossIcon } from "../../../../components/icons/Cross";
import { EditIcon } from "../../../../components/icons/Edit";
@ -306,19 +306,17 @@ function EditableDestination({
</td>
<td>
<div className="stack horizontal xs">
<Button
<SendouButton
variant="minimal"
title="Save destination"
icon={<CheckmarkIcon />}
size="tiny"
onClick={handleSubmit}
icon={<CheckmarkIcon title="Save destination" />}
size="small"
onPress={handleSubmit}
/>
<Button
<SendouButton
variant="minimal-destructive"
title="Cancel"
size="tiny"
icon={<CrossIcon />}
onClick={() => setEditingDestination(false)}
size="small"
icon={<CrossIcon title="Cancel" />}
onPress={() => setEditingDestination(false)}
/>
</div>
</td>
@ -347,12 +345,11 @@ function EditableDestination({
)}
{canEditDestination ? (
<td>
<Button
<SendouButton
variant="minimal"
title="Edit destination"
icon={<EditIcon />}
size="tiny"
onClick={() => setEditingDestination(true)}
icon={<EditIcon title="Edit destination" />}
size="small"
onPress={() => setEditingDestination(true)}
/>
</td>
) : null}

View File

@ -1,8 +1,8 @@
import { useFetcher } from "@remix-run/react";
import clsx from "clsx";
import { Button } from "~/components/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { useUser } from "~/features/auth/core/user";
import {
useBracketExpanded,
@ -102,7 +102,7 @@ export function SwissBracket({
{groups.length > 1 && (
<div className="stack horizontal">
{groups.map((g) => (
<Button
<SendouButton
key={g.groupId}
onClick={() => setSelectedGroupId(g.groupId)}
className={clsx(
@ -112,10 +112,10 @@ export function SwissBracket({
selectedGroupId === g.groupId,
},
)}
testId={`group-${g.groupName.split(" ")[1]}-button`}
data-testid={`group-${g.groupName.split(" ")[1]}-button`}
>
{g.groupName.split(" ")[1]}
</Button>
</SendouButton>
))}
</div>
)}
@ -190,15 +190,15 @@ export function SwissBracket({
["_action", "UNADVANCE_BRACKET"],
]}
>
<Button
<SendouButton
variant="minimal-destructive"
type="submit"
className="build__small-text mb-4"
size="tiny"
testId="reset-round-button"
size="small"
data-testid="reset-round-button"
>
Reset round
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
</div>

View File

@ -2,7 +2,6 @@ import { type FetcherWithComponents, Link, useFetcher } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { ModeImage, StageImage } from "~/components/Image";
import { Label } from "~/components/Label";
import { SubmitButton } from "~/components/SubmitButton";
@ -435,11 +434,11 @@ export function BracketMapListDialog({
) : null}
</div>
{tournament.ctx.toSetMapPool.length > 0 ? (
<Button
size="tiny"
<SendouButton
size="small"
icon={<RefreshArrowsIcon />}
variant="outlined"
onClick={() =>
onPress={() =>
setMaps(
generateTournamentRoundMaplist({
mapCounts,
@ -454,7 +453,7 @@ export function BracketMapListDialog({
}
>
Reroll all maps
</Button>
</SendouButton>
) : null}
</div>
{needsToPickEliminationTeamCount ? (
@ -592,7 +591,7 @@ export function BracketMapListDialog({
) : (
<SubmitButton
variant="outlined"
size="tiny"
size="small"
testId="confirm-finalize-bracket-button"
_action={isPreparing ? "PREPARE_MAPS" : "START_BRACKET"}
className="mx-auto"
@ -894,13 +893,13 @@ function RoundMapList({
<div>
<h3 className="stack horizontal sm">
<div>{name}</div>{" "}
<Button
<SendouButton
variant={editing ? "minimal-success" : "minimal"}
onClick={() => setEditing(!editing)}
testId="edit-round-maps-button"
onPress={() => setEditing(!editing)}
data-testid="edit-round-maps-button"
>
{editing ? "Save" : "Edit"}
</Button>
</SendouButton>
</h3>
{unlink ? (
<SendouButton

View File

@ -1,8 +1,8 @@
import { Form, useFetcher, useLoaderData } from "@remix-run/react";
import * as React from "react";
import { Button } from "~/components/Button";
import { Label } from "~/components/Label";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { EditIcon } from "~/components/icons/Edit";
import { useUser } from "~/features/auth/core/user";
import { useTournament } from "~/features/tournament/routes/to.$id";
@ -336,10 +336,10 @@ function ReportScoreButtons({
</div>
) : null}
<SubmitButton
size="tiny"
size="small"
_action="REPORT_SCORE"
testId="report-score-button"
disabled={submitButtonDisabled()}
isDisabled={submitButtonDisabled()}
>
{wouldEndSet ? "Report & end set" : "Report"}
</SubmitButton>
@ -380,37 +380,37 @@ function EditScoreForm({
<input type="hidden" name="points" value={JSON.stringify(points)} />
) : undefined}
<SubmitButton
size="tiny"
size="small"
state={fetcher.state}
_action="UPDATE_REPORTED_SCORE"
disabled={submitDisabled}
isDisabled={submitDisabled}
testId="save-revise-button"
>
Save
</SubmitButton>
<Button
<SendouButton
variant="destructive"
size="tiny"
size="small"
onClick={() => setEditing(false)}
>
Cancel
</Button>
</SendouButton>
</fetcher.Form>
);
}
return (
<div className="mt-6">
<Button
<SendouButton
icon={<EditIcon />}
variant="outlined"
size="tiny"
size="small"
className="mx-auto"
onClick={() => setEditing(true)}
testId="revise-button"
data-testid="revise-button"
>
Edit
</Button>
</SendouButton>
</div>
);
}

View File

@ -2,7 +2,7 @@ import type { SerializeFrom } from "@remix-run/node";
import type { TFunction } from "i18next";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { SendouButton } from "~/components/elements/Button";
import { SendouDialog } from "~/components/elements/Dialog";
import { MapIcon } from "~/components/icons/Map";
import { useTournament } from "~/features/tournament/routes/to.$id";
@ -102,14 +102,14 @@ export function OrganizerMatchMapListDialog({
</div>
) : null}
</SendouDialog>
<Button
<SendouButton
variant="outlined"
size="tiny"
size="small"
icon={<MapIcon />}
onClick={() => setIsOpen(true)}
>
Show maplist
</Button>
</SendouButton>
</>
);
}

View File

@ -2,9 +2,9 @@ import { Link, useFetcher, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { Label } from "~/components/Label";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { useUser } from "~/features/auth/core/user";
import { inGameNameWithoutDiscriminator } from "~/utils/strings";
import { tournamentTeamPage, userPage } from "~/utils/urls";
@ -491,15 +491,15 @@ function RosterFormWithButtons({
if (!editingRoster) {
return (
<div className="tournament-bracket__roster-buttons__container">
<Button
size="tiny"
<SendouButton
size="small"
onClick={() => setEditingRoster(true)}
className="tournament-bracket__edit-roster-button"
variant="minimal"
testId="edit-active-roster-button"
data-testid="edit-active-roster-button"
>
Edit active roster
</Button>
</SendouButton>
</div>
);
}
@ -517,23 +517,23 @@ function RosterFormWithButtons({
<input type="hidden" name="teamId" value={teamId} />
<SubmitButton
state={fetcher.state}
size="tiny"
size="small"
_action="SET_ACTIVE_ROSTER"
disabled={!valid}
isDisabled={!valid}
testId="save-active-roster-button"
>
Save
</SubmitButton>
{showCancelButton ? (
<Button
size="tiny"
<SendouButton
size="small"
variant="destructive"
onClick={() => {
setEditingRoster(false);
}}
>
Cancel
</Button>
</SendouButton>
) : null}
</fetcher.Form>
);

View File

@ -52,7 +52,7 @@ export function TournamentTeamActions() {
<input type="hidden" name="bracketIdx" value={status.bracketIdx} />
{status.canCheckIn ? (
<SubmitButton
size="tiny"
size="small"
variant="minimal"
_action="CHECK_IN"
state={fetcher.state}
@ -85,7 +85,7 @@ export function TournamentTeamActions() {
<fetcher.Form method="post">
<input type="hidden" name="bracketIdx" value={status.bracketIdx} />
<SubmitButton
size="tiny"
size="small"
variant="minimal"
_action="BRACKET_CHECK_IN"
state={fetcher.state}

View File

@ -7,7 +7,6 @@ import { useTranslation } from "react-i18next";
import { useCopyToClipboard } from "react-use";
import { useEventSource } from "remix-utils/sse/react";
import { Alert } from "~/components/Alert";
import { Button } from "~/components/Button";
import { Divider } from "~/components/Divider";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { SendouButton } from "~/components/elements/Button";
@ -172,9 +171,12 @@ export default function TournamentBracketsPage() {
submitButtonText={t("tournament:actions.finalize.action")}
submitButtonVariant="outlined"
>
<Button variant="minimal" testId="finalize-tournament-button">
<SendouButton
variant="minimal"
data-testid="finalize-tournament-button"
>
{t("tournament:actions.finalize.question")}
</Button>
</SendouButton>
</FormWithConfirm>
</div>
) : null}
@ -325,14 +327,14 @@ function BracketStarter({
key={bracketIdx}
/>
) : null}
<Button
<SendouButton
variant="outlined"
size="tiny"
testId="finalize-bracket-button"
onClick={() => setDialogOpen(true)}
size="small"
data-testid="finalize-bracket-button"
onPress={() => setDialogOpen(true)}
>
Start the bracket
</Button>
</SendouButton>
</>
);
}
@ -380,15 +382,15 @@ function MapPreparer({
testId="prepared-maps-check-icon"
/>
) : null}
<Button
size="tiny"
<SendouButton
size="small"
variant="outlined"
icon={<MapIcon />}
onClick={() => setDialogOpen(true)}
testId="prepare-maps-button"
onPress={() => setDialogOpen(true)}
data-testid="prepare-maps-button"
>
Prepare maps
</Button>
</SendouButton>
</div>
</>
);
@ -424,15 +426,15 @@ function AddSubsPopOver() {
<Divider className="my-2" />
<div>{t("tournament:actions.shareLink", { inviteLink })}</div>
<div className="my-2 flex justify-center">
<Button
size="tiny"
onClick={() => copyToClipboard(inviteLink)}
<SendouButton
size="small"
onPress={() => copyToClipboard(inviteLink)}
variant="minimal"
className="tiny"
testId="copy-invite-link-button"
data-testid="copy-invite-link-button"
>
{t("common:actions.copyToClipboard")}
</Button>
</SendouButton>
</div>
</>
) : null}
@ -522,16 +524,16 @@ function BracketNav({
<div className="tournament-bracket__bracket-nav tournament-bracket__button-row">
{visibleBrackets.map((bracket, i) => {
return (
<Button
<SendouButton
key={bracket.name}
onClick={() => setBracketIdx(i)}
onPress={() => setBracketIdx(i)}
className={clsx("tournament-bracket__bracket-nav__link", {
"tournament-bracket__bracket-nav__link__selected":
bracketIdx === i,
})}
>
{bracketNameForButton(bracket.name)}
</Button>
</SendouButton>
);
})}
</div>
@ -543,14 +545,14 @@ function CompactifyButton() {
const { bracketExpanded, setBracketExpanded } = useBracketExpanded();
return (
<Button
onClick={() => {
<SendouButton
onPress={() => {
setBracketExpanded(!bracketExpanded);
}}
className="tournament-bracket__compactify-button"
icon={bracketExpanded ? <EyeSlashIcon /> : <EyeIcon />}
>
{bracketExpanded ? "Compactify" : "Show all"}
</Button>
</SendouButton>
);
}

View File

@ -509,7 +509,7 @@
border-radius: var(--rounded);
}
.tournament-bracket__compactify-button .button-icon {
.tournament-bracket__compactify-button svg {
width: 0.85rem;
}

View File

@ -1,7 +1,6 @@
import { Form, useLoaderData } from "@remix-run/react";
import React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { WeaponCombobox } from "~/components/Combobox";
import { FormMessage } from "~/components/FormMessage";
import { WeaponImage } from "~/components/Image";
@ -13,6 +12,7 @@ import { useUser } from "~/features/auth/core/user";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { TOURNAMENT_SUB } from "../tournament-subs-constants";
import { SendouButton } from "~/components/elements/Button";
import { action } from "../actions/to.$id.subs.new.server";
import { loader } from "../loaders/to.$id.subs.new.server";
@ -262,11 +262,11 @@ function WeaponPoolSelect({
height={38}
/>
</div>
<Button
<SendouButton
icon={<TrashIcon />}
variant="minimal-destructive"
aria-label="Delete weapon"
onClick={() =>
onPress={() =>
setWeapons(weapons.filter((w) => w !== weapon))
}
/>

View File

@ -2,7 +2,7 @@ import { Link, useLoaderData } from "@remix-run/react";
import React from "react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { Flag } from "~/components/Flag";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { WeaponImage } from "~/components/Image";
@ -158,14 +158,14 @@ function SubInfoSection({ sub }: { sub: SubByTournamentId }) {
}
fields={[["userId", sub.userId]]}
>
<Button
<SendouButton
variant="minimal-destructive"
size="tiny"
size="small"
type="submit"
icon={<TrashIcon />}
>
{t("common:actions.delete")}
</Button>
</SendouButton>
</FormWithConfirm>
</div>
) : null}

View File

@ -3,7 +3,7 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { Divider } from "~/components/Divider";
import { FormMessage } from "~/components/FormMessage";
import { FormWithConfirm } from "~/components/FormWithConfirm";
@ -30,9 +30,9 @@ import {
} from "~/utils/urls";
import { BracketProgressionSelector } from "../../calendar/components/BracketProgressionSelector";
import { useTournament } from "./to.$id";
import { SendouButton } from "~/components/elements/Button";
import { action } from "../actions/to.$id.admin.server";
export { action };
export { action } from "../actions/to.$id.admin.server";
export default function TournamentAdminPage() {
const { t } = useTranslation(["calendar"]);
@ -70,14 +70,14 @@ export default function TournamentAdminPage() {
action={calendarEventPage(tournament.ctx.eventId)}
submitButtonTestId="delete-submit-button"
>
<Button
<SendouButton
className="ml-auto"
size="tiny"
size="small"
variant="minimal-destructive"
type="submit"
>
{t("calendar:actions.delete")}
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
</div>
@ -86,14 +86,14 @@ export default function TournamentAdminPage() {
tournament.hasStarted &&
!tournament.ctx.isFinalized ? (
<div className="stack horizontal justify-end">
<Button
onClick={() => setEditingProgression(true)}
size="tiny"
<SendouButton
onPress={() => setEditingProgression(true)}
size="small"
variant="outlined"
testId="edit-event-info-button"
data-testid="edit-event-info-button"
>
Edit brackets
</Button>
</SendouButton>
{editingProgression ? (
<BracketProgressionEditDialog
close={() => setEditingProgression(false)}
@ -497,13 +497,13 @@ function RemoveStaffButton({
]}
submitButtonText="Remove"
>
<Button
<SendouButton
variant="minimal-destructive"
size="tiny"
size="small"
data-testid="remove-staff-button"
>
<TrashIcon className="build__icon" />
</Button>
</SendouButton>
</FormWithConfirm>
);
}
@ -615,9 +615,9 @@ function DownloadParticipants() {
return (
<div>
<div className="stack horizontal sm flex-wrap">
<Button
size="tiny"
onClick={() =>
<SendouButton
size="small"
onPress={() =>
handleDownload({
filename: "all-participants.txt",
content: allParticipantsContent(),
@ -625,10 +625,10 @@ function DownloadParticipants() {
}
>
All participants
</Button>
<Button
size="tiny"
onClick={() =>
</SendouButton>
<SendouButton
size="small"
onPress={() =>
handleDownload({
filename: "checked-in-participants.txt",
content: checkedInParticipantsContent(),
@ -636,10 +636,10 @@ function DownloadParticipants() {
}
>
Checked in participants
</Button>
<Button
size="tiny"
onClick={() =>
</SendouButton>
<SendouButton
size="small"
onPress={() =>
handleDownload({
filename: "not-checked-in-participants.txt",
content: notCheckedInParticipantsContent(),
@ -647,10 +647,10 @@ function DownloadParticipants() {
}
>
Not checked in participants
</Button>
<Button
size="tiny"
onClick={() =>
</SendouButton>
<SendouButton
size="small"
onPress={() =>
handleDownload({
filename: "teams-in-seeded-order.txt",
content: simpleListInSeededOrder(),
@ -658,11 +658,11 @@ function DownloadParticipants() {
}
>
Simple list in seeded order
</Button>
</SendouButton>
{tournament.isLeagueSignup ? (
<Button
size="tiny"
onClick={() =>
<SendouButton
size="small"
onPress={() =>
handleDownload({
filename: "league-format.csv",
content: leagueFormat(),
@ -670,7 +670,7 @@ function DownloadParticipants() {
}
>
League format
</Button>
</SendouButton>
) : null}
</div>
</div>
@ -745,7 +745,7 @@ function BracketReset() {
<SubmitButton
_action="RESET_BRACKET"
state={fetcher.state}
disabled={confirmText !== bracketToDeleteName}
isDisabled={confirmText !== bracketToDeleteName}
testId="reset-bracket-button"
>
Reset
@ -791,7 +791,7 @@ function BracketProgressionEditDialog({ close }: { close: () => void }) {
<div className="stack md horizontal justify-center mt-6">
<SubmitButton
_action="UPDATE_TOURNAMENT_PROGRESSION"
disabled={bracketProgressionErrored}
isDisabled={bracketProgressionErrored}
>
Save changes
</SubmitButton>

View File

@ -84,7 +84,7 @@ export default function JoinTeamPage() {
<Form method="post" className="tournament__invite-container">
{validationStatus === "VALID" ? (
<div className="stack md items-center">
<SubmitButton size="big" disabled={!user?.friendCode}>
<SubmitButton size="big" isDisabled={!user?.friendCode}>
{t("common:actions.join")}
</SubmitButton>
{!user?.friendCode ? (

View File

@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
import { useCopyToClipboard } from "react-use";
import { Alert } from "~/components/Alert";
import { Avatar } from "~/components/Avatar";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { Divider } from "~/components/Divider";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { FriendCodeInput } from "~/components/FriendCodeInput";
@ -267,13 +267,13 @@ function TournamentRegisterInfoTabs() {
fields={[["_action", "LEAVE_TEAM"]]}
submitButtonText="Leave"
>
<Button
<SendouButton
className="build__small-text"
variant="minimal-destructive"
type="submit"
>
Leave the team
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
</div>
@ -317,9 +317,9 @@ function PleaseLogIn() {
return (
<form className="stack items-center mt-4" action={LOG_IN_URL} method="post">
<Button size="big" type="submit">
<SendouButton size="big" type="submit">
{t("tournament:pre.logIn")}
</Button>
</SendouButton>
</form>
);
}
@ -593,7 +593,7 @@ function CheckIn({
return (
<fetcher.Form method="post" className="stack items-center">
<SubmitButton
size="tiny"
size="small"
_action="CHECK_IN"
state={fetcher.state}
testId="check-in-button"
@ -710,13 +710,13 @@ function TeamInfo({
submitButtonText={t("tournament:pre.info.unregister")}
fields={[["_action", "UNREGISTER"]]}
>
<Button
<SendouButton
className="build__small-text"
variant="minimal-destructive"
size="tiny"
size="small"
>
{t("tournament:pre.info.unregister")}
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
</div>
@ -781,26 +781,26 @@ function TeamInfo({
<div className="stack horizontal md items-center">
<Avatar size="xsm" url={avatarUrl} />
{canEditAvatar ? (
<Button
<SendouButton
variant="minimal"
size="tiny"
size="small"
onClick={() => setUploadedAvatar(null)}
>
{t("common:actions.edit")}
</Button>
</SendouButton>
) : null}
{canDeleteAvatar ? (
<FormWithConfirm
dialogHeading="Delete team logo?"
fields={[["_action", "DELETE_LOGO"]]}
>
<Button
<SendouButton
variant="minimal-destructive"
size="tiny"
size="small"
type="submit"
>
<TrashIcon className="small-icon" />
</Button>
</SendouButton>
</FormWithConfirm>
) : null}
</div>
@ -838,13 +838,13 @@ function TeamInfo({
) : null}
</div>
</div>
<Button
testId="save-team-button"
disabled={submitButtonDisabled()}
<SendouButton
data-testid="save-team-button"
isDisabled={submitButtonDisabled()}
onClick={handleSubmit}
>
{t("common:actions.save")}
</Button>
</SendouButton>
</Form>
</section>
</div>
@ -1018,13 +1018,13 @@ function FillRoster({
{t("tournament:actions.shareLink", { inviteLink })}
</div>
<div>
<Button
size="tiny"
<SendouButton
size="small"
onClick={() => copyToClipboard(inviteLink)}
variant="outlined"
>
{t("common:actions.copyToClipboard")}
</Button>
</SendouButton>
</div>
</div>
) : null}
@ -1164,13 +1164,13 @@ function DeleteMember({ members }: { members: TournamentDataTeam["members"] }) {
if (!expanded) {
return (
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal-destructive"
onClick={() => setExpanded(true)}
>
{t("tournament:pre.roster.delete.button")}
</Button>
</SendouButton>
);
}

View File

@ -17,11 +17,11 @@ import { Link, useFetcher, useNavigation } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { Alert } from "~/components/Alert";
import { Button } from "~/components/Button";
import { Catcher } from "~/components/Catcher";
import { Draggable } from "~/components/Draggable";
import { SubmitButton } from "~/components/SubmitButton";
import { Table } from "~/components/Table";
import { SendouButton } from "~/components/elements/Button";
import { SendouDialog } from "~/components/elements/Dialog";
import type { TournamentDataTeam } from "~/features/tournament-bracket/core/Tournament.server";
import { useTimeoutState } from "~/hooks/useTimeoutState";
@ -91,12 +91,12 @@ export default function TournamentSeedsPage() {
players change
</div>
) : (
<Button
<SendouButton
className="tournament__seeds__order-button"
variant="minimal"
size="tiny"
size="small"
type="button"
onClick={() => {
onPress={() => {
setTeamOrder(
structuredClone(tournament.ctx.teams)
.sort(
@ -109,7 +109,7 @@ export default function TournamentSeedsPage() {
}}
>
Sort automatically
</Button>
</SendouButton>
)}
</div>
{tournament.isMultiStartingBracket ? (
@ -234,13 +234,13 @@ function StartingBracketDialog() {
return (
<div>
<Button
size="tiny"
onClick={() => setIsOpen(true)}
testId="set-starting-brackets"
<SendouButton
size="small"
onPress={() => setIsOpen(true)}
data-testid="set-starting-brackets"
>
Set starting brackets
</Button>
</SendouButton>
<SendouDialog
heading="Setting starting brackets"
isOpen={isOpen}
@ -372,8 +372,8 @@ function SeedAlert({ teamOrder }: { teamOrder: number[] }) {
{(!showSuccess || teamOrderChanged) && (
<SubmitButton
state={fetcher.state}
disabled={!teamOrderChanged}
size="tiny"
isDisabled={!teamOrderChanged}
size="small"
>
Save seeds
</SubmitButton>

View File

@ -8,7 +8,6 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { AbilitiesSelector } from "~/components/AbilitiesSelector";
import { Alert } from "~/components/Alert";
import { Button } from "~/components/Button";
import { GearCombobox, WeaponCombobox } from "~/components/Combobox";
import { FormMessage } from "~/components/FormMessage";
import { Image } from "~/components/Image";
@ -16,6 +15,7 @@ import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { RequiredHiddenInput } from "~/components/RequiredHiddenInput";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { CrossIcon } from "~/components/icons/Cross";
import { PlusIcon } from "~/components/icons/Plus";
import type { GearType } from "~/db/tables";
@ -234,17 +234,17 @@ function WeaponsSelector() {
</div>
{i === weapons.length - 1 && (
<>
<Button
size="tiny"
disabled={weapons.length === BUILD.MAX_WEAPONS_COUNT}
onClick={() => setWeapons((weapons) => [...weapons, 0])}
<SendouButton
size="small"
isDisabled={weapons.length === BUILD.MAX_WEAPONS_COUNT}
onPress={() => setWeapons((weapons) => [...weapons, 0])}
icon={<PlusIcon />}
testId="add-weapon-button"
data-testid="add-weapon-button"
/>
{weapons.length > 1 && (
<Button
size="tiny"
onClick={() =>
<SendouButton
size="small"
onPress={() =>
setWeapons((weapons) => {
const newWeapons = [...weapons];
newWeapons.pop();

View File

@ -2,10 +2,10 @@ import { useFetcher, useLoaderData, useMatches } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { BuildCard } from "~/components/BuildCard";
import { Button } from "~/components/Button";
import { FormMessage } from "~/components/FormMessage";
import { Image, WeaponImage } from "~/components/Image";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { SendouDialog } from "~/components/elements/Dialog";
import { SendouMenu, SendouMenuItem } from "~/components/elements/Menu";
import { LockIcon } from "~/components/icons/Lock";
@ -19,7 +19,6 @@ import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { atOrError } from "~/utils/arrays";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { weaponCategoryUrl } from "~/utils/urls";
import { SendouButton } from "../../../components/elements/Button";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
import { DEFAULT_BUILD_SORT } from "../user-page-constants";
@ -154,24 +153,24 @@ function BuildsFilters({
</SendouButton>
{showPublicPrivateFilters ? (
<>
<Button
onClick={() => setWeaponFilter("PUBLIC")}
<SendouButton
onPress={() => setWeaponFilter("PUBLIC")}
variant={weaponFilter === "PUBLIC" ? undefined : "outlined"}
size="tiny"
size="small"
className="u__build-filter-button"
icon={<UnlockIcon />}
>
{t("builds:stats.public")} ({publicBuildsCount})
</Button>
<Button
onClick={() => setWeaponFilter("PRIVATE")}
</SendouButton>
<SendouButton
onPress={() => setWeaponFilter("PRIVATE")}
variant={weaponFilter === "PRIVATE" ? undefined : "outlined"}
size="tiny"
size="small"
className="u__build-filter-button"
icon={<LockIcon />}
>
{t("builds:stats.private")} ({privateBuildsCount})
</Button>
</SendouButton>
</>
) : null}
@ -237,14 +236,14 @@ function ChangeSortingDialog({ close }: { close: () => void }) {
<FormMessage type="info">
{t("user:builds.sorting.info")}
</FormMessage>
<Button
<SendouButton
className="ml-auto"
variant="minimal"
size="tiny"
onClick={() => setBuildSorting([...DEFAULT_BUILD_SORT, null])}
size="small"
onPress={() => setBuildSorting([...DEFAULT_BUILD_SORT, null])}
>
{t("user:builds.sorting.backToDefaults")}
</Button>
</SendouButton>
{buildSorting.map((sort, i) => {
const isLast = i === buildSorting.length - 1;
const isSecondToLast = i === buildSorting.length - 2;
@ -270,10 +269,10 @@ function ChangeSortingDialog({ close }: { close: () => void }) {
</div>
{(isLast && !canAddMoreSorting) ||
(canAddMoreSorting && isSecondToLast) ? (
<Button
<SendouButton
icon={<TrashIcon />}
variant="minimal-destructive"
onClick={deleteLastSorting}
onPress={deleteLastSorting}
/>
) : null}
</div>

View File

@ -2,7 +2,6 @@ import { Form, Link, useLoaderData, useMatches } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { WeaponCombobox } from "~/components/Combobox";
import { CustomizedColorsInput } from "~/components/CustomizedColorsInput";
import { FormErrors } from "~/components/FormErrors";
@ -11,6 +10,7 @@ import { WeaponImage } from "~/components/Image";
import { Input } from "~/components/Input";
import { Label } from "~/components/Label";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouButton } from "~/components/elements/Button";
import { SendouSelect, SendouSelectItem } from "~/components/elements/Select";
import { SendouSwitch } from "~/components/elements/Switch";
import { StarIcon } from "~/components/icons/Star";
@ -318,11 +318,11 @@ function WeaponPoolSelect() {
/>
</div>
<div className="stack sm horizontal items-center justify-center">
<Button
<SendouButton
icon={weapon.isFavorite ? <StarFilledIcon /> : <StarIcon />}
variant="minimal"
aria-label="Favorite weapon"
onClick={() =>
onPress={() =>
setWeapons(
weapons.map((w) =>
w.weaponSplId === weapon.weaponSplId
@ -335,19 +335,19 @@ function WeaponPoolSelect() {
)
}
/>
<Button
<SendouButton
icon={<TrashIcon />}
variant="minimal-destructive"
aria-label="Delete weapon"
onClick={() =>
onPress={() =>
setWeapons(
weapons.filter(
(w) => w.weaponSplId !== weapon.weaponSplId,
),
)
}
testId={`delete-weapon-${weapon.weaponSplId}`}
size="tiny"
data-testid={`delete-weapon-${weapon.weaponSplId}`}
size="small"
/>
</div>
</div>

View File

@ -1,11 +1,12 @@
import { useLoaderData, useMatches } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { useUser } from "~/features/auth/core/user";
import { UserResultsTable } from "~/features/user-page/components/UserResultsTable";
import { useSearchParamState } from "~/hooks/useSearchParamState";
import invariant from "~/utils/invariant";
import { userResultsEditHighlightsPage } from "~/utils/urls";
import { SendouButton } from "../../../components/elements/Button";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
import { loader } from "../loaders/u.$identifier.results.server";
@ -51,15 +52,15 @@ export default function UserResultsPage() {
</div>
<UserResultsTable id="user-results-table" results={resultsToShow} />
{hasHighlightedResults ? (
<Button
<SendouButton
variant="minimal"
size="tiny"
onClick={() => setShowAll(!showAll)}
size="small"
onPress={() => setShowAll(!showAll)}
>
{showAll
? t("results.button.showHighlights")
: t("results.button.showAll")}
</Button>
</SendouButton>
) : null}
</div>
);

View File

@ -4,9 +4,9 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { useDebounce } from "react-use";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { Input } from "~/components/Input";
import { Main } from "~/components/Main";
import { SendouButton } from "~/components/elements/Button";
import { DiscordIcon } from "~/components/icons/Discord";
import { SearchIcon } from "~/components/icons/Search";
import { useUser } from "~/features/auth/core/user";
@ -68,9 +68,9 @@ export default function UserSearchPage() {
action={LOG_IN_URL}
method="post"
>
<Button size="big" type="submit" icon={<DiscordIcon />}>
<SendouButton size="big" type="submit" icon={<DiscordIcon />}>
{t("user:search.pleaseLogIn.button")}
</Button>
</SendouButton>
</form>
</Main>
);

View File

@ -3,7 +3,7 @@ import { useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Button, LinkButton } from "~/components/Button";
import { LinkButton } from "~/components/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Image, WeaponImage } from "~/components/Image";
import { Main } from "~/components/Main";
@ -25,6 +25,7 @@ import {
stageImageUrl,
vodVideoPage,
} from "~/utils/urls";
import { SendouButton } from "../../../components/elements/Button";
import { PovUser } from "../components/VodPov";
import type { Vod } from "../vods-types";
import { canEditVideo, secondsToHoursMinutesSecondString } from "../vods-utils";
@ -130,14 +131,14 @@ export default function VodPage() {
title: data.vod.title,
})}
>
<Button
<SendouButton
variant="minimal-destructive"
size="tiny"
size="small"
type="submit"
icon={<TrashIcon />}
>
{t("common:actions.delete")}
</Button>
</SendouButton>
</FormWithConfirm>
</div>
) : null}
@ -227,13 +228,13 @@ function Match({
</div>
</div>
) : null}
<Button
size="tiny"
onClick={() => setStart(match.startsAt)}
<SendouButton
size="small"
onPress={() => setStart(match.startsAt)}
variant="outlined"
>
{secondsToHoursMinutesSecondString(match.startsAt)}
</Button>
</SendouButton>
</div>
);
}

View File

@ -9,11 +9,11 @@ import {
} from "react-hook-form";
import { useTranslation } from "react-i18next";
import type { z } from "zod";
import { Button } from "~/components/Button";
import { WeaponCombobox } from "~/components/Combobox";
import { FormMessage } from "~/components/FormMessage";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { SendouButton } from "~/components/elements/Button";
import { UserSearch } from "~/components/elements/UserSearch";
import { AddFieldButton } from "~/components/form/AddFieldButton";
import { RemoveFieldButton } from "~/components/form/RemoveFieldButton";
@ -191,16 +191,16 @@ function PovFormField() {
onBlur={onBlur}
/>
)}
<Button
size="tiny"
<SendouButton
size="small"
variant="minimal"
onClick={toggleInputType}
onPress={toggleInputType}
className="outline-theme mt-2"
>
{asPlainInput
? t("calendar:forms.team.player.addAsUser")
: t("calendar:forms.team.player.addAsText")}
</Button>
</SendouButton>
{error && (
<FormMessage type="error">{error.message as string}</FormMessage>
)}

View File

@ -1,10 +1,10 @@
import type { MetaFunction } from "@remix-run/node";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { WeaponCombobox } from "~/components/Combobox";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { SendouButton } from "~/components/elements/Button";
import { modesShort } from "~/modules/in-game-lists/modes";
import { stageIds } from "~/modules/in-game-lists/stage-ids";
import { mainWeaponIds } from "~/modules/in-game-lists/weapon-ids";
@ -61,15 +61,15 @@ export default function VodsSearchPage() {
))}
</div>
{data.hasMoreVods && (
<Button
<SendouButton
className="m-0-auto"
size="tiny"
onClick={() =>
size="small"
onPress={() =>
addToSearchParams("limit", data.limit + VODS_PAGE_BATCH_SIZE)
}
>
{t("common:actions.loadMore")}
</Button>
</SendouButton>
)}
</>
) : (