"use client";
import { useState } from "react";
import { Dialog, Field as MuField, FormField, Radio, RadioGroup, Spinner, Textarea } from "@unlocalhosted/metalui";
import { BranchIcon, CommitIcon, TrashIcon } from "./icons";
import { Alert, Button, RefChip } from "./ui";
type Target = "direct" | "branch";
/**
* GitHub-style "Commit changes" dialog, on MetalUI's Dialog. It must be rendered inside the form it
* submits. The popup is portaled out of that form, so its fields and its submit cap join the form
* through the `form` attribute (`formId` is the form's id); the radio choice rides a hidden input.
*/
export function CommitDialog({
formId,
branch,
baseSha,
defaultMessage,
pending,
error,
open,
onOpenChange,
danger,
}: {
/** The id of the form this dialog submits. */
formId: string;
branch: string;
baseSha: string;
defaultMessage: string;
pending: boolean;
error?: string;
open: boolean;
onOpenChange: (open: boolean) => void;
danger?: boolean;
}) {
const [message, setMessage] = useState("");
const [description, setDescription] = useState("");
const [target, setTarget] = useState<Target>("direct");
const [newBranch, setNewBranch] = useState("");
return (
<>
<input type="hidden" name="branch" value={branch} />
<input type="hidden" name="baseSha" value={baseSha} />
<input type="hidden" name="target" value={target} />
<Dialog.Root open={open} onOpenChange={(next) => (next || !pending) && onOpenChange(next)}>
<Dialog.Popup className="w-[min(460px,calc(100vw-32px))]!" aria-busy={pending || undefined}>
<div className="flex items-center gap-3">
<span className={`icon-tile size-9 shrink-0 rounded-row [&_svg]:size-4 ${danger ? "text-red!" : ""}`}>
<CommitIcon />
</span>
<Dialog.Title>Commit changes</Dialog.Title>
</div>
{error && <Alert>{error}</Alert>}
<FormField>
<FormField.Label>Commit message</FormField.Label>
<MuField size="regular">
<MuField.Input
form={formId}
name="message"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder={defaultMessage}
/>
</MuField>
</FormField>
<FormField>
<FormField.Label>
Extended description <span className="text-ink3">· optional</span>
</FormField.Label>
<Textarea
form={formId}
name="description"
minRows={3}
maxRows={8}
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Add an optional extended description…"
/>
</FormField>
<div className="grid gap-2">
<RadioGroup aria-label="Where to commit" value={target} onValueChange={(value) => setTarget(value as Target)}>
<Radio value="direct">
Commit directly to the <RefChip>{branch}</RefChip> branch
</Radio>
<Radio value="branch">Create a new branch for this commit</Radio>
</RadioGroup>
{target === "branch" && (
<MuField size="regular" className="ml-6">
<MuField.Icon>
<BranchIcon />
</MuField.Icon>
<MuField.Input
form={formId}
name="newBranch"
required
value={newBranch}
onChange={(e) => setNewBranch(e.target.value)}
placeholder="my-feature"
aria-label="New branch name"
className="font-mono"
/>
</MuField>
)}
</div>
<Dialog.Actions>
<Button type="button" onClick={() => onOpenChange(false)} disabled={pending}>
Cancel
</Button>
<Button
form={formId}
variant={danger ? "danger" : "primary"}
disabled={pending}
icon={pending ? <Spinner size="small" label="Committing" /> : danger ? <TrashIcon /> : <CommitIcon />}
>
{pending ? "Committing…" : "Commit changes"}
</Button>
</Dialog.Actions>
</Dialog.Popup>
</Dialog.Root>
</>
);
}