AddLibraryButton
uses useState
Add a library (tcm lib add): where it comes from (a release its author announced, @alice.dev/geo; a remote, a
tcm serve URL, another codebase's directory, or tcm for tcm's own), and, from a remote, URL or directory, which
package (or namespace hash); what to call it, if not its package's name. Given source and what (a registry's
find), it's one button.
import { addLib, toldOf } from "#codebase/tcmts/studio"; import { useState } from "react"; import { Button, Dialog, DialogTrigger, Heading, Input, Label, Popover, TextField } from "react-aria-components"; export function AddLibraryButton({ source = "", what = "", label = "Add a library", }: { source?: string; what?: string; label?: string; }) { const [form, setForm] = useState({ source, what, as: "" }); const [busy, setBusy] = useState(false); const add = async (close?: () => void) => { setBusy(true); if (await toldOf(addLib(form.source, form.what, form.as), "Added it", "Couldn't add it")) close?.(); setBusy(false); }; if (source) return ( <Button className="btn small" isPending={busy} onPress={() => void add()}> {label} </Button> ); const field = (k: "source" | "what" | "as", name: string, hint: string) => ( <TextField className="fieldrow" value={form[k]} onChange={(v) => setForm({ ...form, [k]: v })}> <Label>{name}</Label> <Input className="field mono" placeholder={hint} /> </TextField> ); return ( <DialogTrigger onOpenChange={(open) => open && setForm({ source: "", what: "", as: "" })}> <Button className="btn">{label}</Button> <Popover className="menu-pop getit" placement="bottom end" offset={6}> <Dialog className="getit-body" aria-label="Add a library"> {({ close }) => ( <form className="prodform" onSubmit={(e) => { e.preventDefault(); void add(close); }} > <Heading slot="title" className="getit-title"> Add a library </Heading> {field("source", "From", "@alice.dev/geo, a remote, a URL, or tcm")} {field("what", "Which (from a remote, URL or tcm)", "geo@1.2.0, geo, or #hash")} {field("as", "Call it", "its package's name")} <p className="muted"> It's fetched with what it uses, checked by hash, and mounted read-only under lib. </p> <div className="actions"> <Button className="btn" onPress={close}> Cancel </Button> <Button className="btn primary" type="submit" isPending={busy} isDisabled={!form.source.trim()} > Add it </Button> </div> </form> )} </Dialog> </Popover> </DialogTrigger> ); }