CommentForm

tcmts.share.CommentForm · #fvdmwzzr · term · runs in the browser

uses localStorage

Comment on a contribution, or on one of its definitions (fixed); the maintainer signs with a name.

import { actOn, Chevron, toasts } from "#codebase/tcmts/share";
import { useEffect, useState } from "react";
import {
  Button, Form, Input, ListBox, ListBoxItem, Popover, Select, SelectValue, TextArea, TextField,
} from "react-aria-components";

export function CommentForm({
  id,
  about,
  fixed,
  named,
  as,
}: {
  id: number;
  about: string[];
  fixed?: string;
  named: boolean;
  as?: string;
}) {
  const [body, setBody] = useState("");
  const [on, setOn] = useState(fixed ?? "");
  const [name, setName] = useState("");
  const [busy, setBusy] = useState(false);
  useEffect(() => setName(localStorage.getItem("tcm.author") ?? ""), []);
  const whole = "(the whole change)";
  return (
    <Form
      className="say"
      id={fixed ? undefined : "comment"}
      onSubmit={async (e) => {
        e.preventDefault();
        setBusy(true);
        try {
          if (named) localStorage.setItem("tcm.author", name);
          await actOn(id, "comment", { body, about: on || null, name });
          setBody("");
          toasts.add({ title: "Comment added", tone: "ok" }, { timeout: 3000 });
        } catch (err) {
          toasts.add(
            { title: "Couldn't comment", description: (err as Error).message, tone: "bad" },
            { timeout: 8000 },
          );
        }
        setBusy(false);
      }}
    >
      <TextField
        aria-label={fixed ? `Comment on ${fixed}` : "Comment"}
        value={body}
        onChange={setBody}
        isRequired
      >
        <TextArea
          placeholder={
            fixed ? `Comment on ${fixed.slice(fixed.lastIndexOf(".") + 1)}` : "Leave a comment"
          }
        />
      </TextField>
      <div className="row">
        {!fixed && about.length > 0 && (
          <Select
            aria-label="What it's about"
            className="select"
            value={on || whole}
            onChange={(k) => setOn(k === whole || k === null ? "" : String(k))}
          >
            <Button className="btn select-btn">
              <SelectValue />
              <Chevron />
            </Button>
            <Popover className="menu-pop" placement="bottom start" offset={4}>
              <ListBox className="menu" items={[whole, ...about].map((n) => ({ id: n }))}>
                {(o) => (
                  <ListBoxItem id={o.id} className="opt">
                    {o.id === whole ? "about the whole change" : `about ${o.id}`}
                  </ListBoxItem>
                )}
              </ListBox>
            </Popover>
          </Select>
        )}
        {named ? (
          <TextField aria-label="Your name" value={name} onChange={setName} isRequired>
            <Input className="field" placeholder="Your name" />
          </TextField>
        ) : (
          <span className="muted">as {as}</span>
        )}
        <Button type="submit" className="btn primary" isPending={busy}>
          Comment
        </Button>
      </div>
    </Form>
  );
}

uses

used by