Vectrum
Sign In
129 lines · 4.4 KB · TypeScript
"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>
    </>
  );
}