import type * as React from "react"; import { type FetcherWithComponents, useFetcher } from "react-router"; import type { z } from "zod"; import { type ActionsOf, type FieldsOf, serializeFieldValue, } from "~/utils/action-schemas"; import { SendouButton, type SendouButtonProps } from "./elements/Button"; import { FormWithConfirm } from "./FormWithConfirm"; import { SubmitButton } from "./SubmitButton"; interface ActionButtonBaseProps< TSchema extends z.ZodTypeAny, TAction extends ActionsOf, > extends Omit { /** Action schema of the route the button submits to. Only used for typing `action` and `fields`. */ schema: TSchema; /** `_action` to submit, narrowed to the literals of the schema. */ action: TAction; /** Route to submit to. Defaults to the current route. */ formAction?: string; formClassName?: string; /** Fetcher to submit with, e.g. to share submitting state between buttons. Defaults to own fetcher. */ fetcher?: FetcherWithComponents; /** When set, submits only after the user confirms via a dialog. */ confirm?: { dialogHeading: string; description?: React.ReactNode; submitButtonText?: string; submitButtonVariant?: SendouButtonProps["variant"]; }; } type ActionButtonProps< TSchema extends z.ZodTypeAny, TAction extends ActionsOf, > = ActionButtonBaseProps & // biome-ignore lint/complexity/noBannedTypes: {} models "branch with no extra fields" ({} extends FieldsOf ? { fields?: FieldsOf } : { fields: FieldsOf }); /** * Button that submits a mutation to a route action as `_action` + hidden fields, * type checked against the route's zod action schema. * * @example * * {t("q:looking.groups.actions.invite")} * */ export function ActionButton< TSchema extends z.ZodTypeAny, const TAction extends ActionsOf, >({ schema, action, fields, formAction, formClassName, fetcher: fetcherFromProps, confirm, children, ...buttonProps }: ActionButtonProps) { const ownFetcher = useFetcher(); const fetcher = fetcherFromProps ?? ownFetcher; const fieldEntries = Object.entries( (fields ?? {}) as Record, ).flatMap(([name, value]) => value === undefined || value === null ? [] : ([[name, serializeFieldValue(value)]] as const), ); if (confirm) { return ( {children} ); } return ( {fieldEntries.map(([name, value]) => ( ))} {children} ); }