| import { useState, useRef, useEffect } from "react"; |
| import type { Author, Affiliation } from "./frontmatter-store"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function AuthorInlineForm({ |
| open, |
| initial, |
| affiliations, |
| onSubmit, |
| onCancel, |
| }: { |
| open: boolean; |
| initial?: Author; |
| affiliations: Affiliation[]; |
| onSubmit: (author: Author, newAffiliation?: Affiliation) => void; |
| onCancel: () => void; |
| }) { |
| const [name, setName] = useState(initial?.name || ""); |
| const [url, setUrl] = useState(initial?.url || ""); |
| const [affIndices, setAffIndices] = useState<number[]>(initial?.affiliations || []); |
| const [newAffName, setNewAffName] = useState(""); |
| const nameRef = useRef<HTMLInputElement>(null); |
| const dialogRef = useRef<HTMLDialogElement>(null); |
|
|
| useEffect(() => { |
| const dialog = dialogRef.current; |
| if (!dialog) return; |
| if (open && !dialog.open) { |
| setName(initial?.name || ""); |
| setUrl(initial?.url || ""); |
| setAffIndices(initial?.affiliations || []); |
| setNewAffName(""); |
| dialog.showModal(); |
| setTimeout(() => nameRef.current?.focus(), 50); |
| } else if (!open && dialog.open) { |
| dialog.close(); |
| } |
| }, [open, initial]); |
|
|
| useEffect(() => { |
| const dialog = dialogRef.current; |
| if (!dialog) return; |
| const onClose = () => onCancel(); |
| dialog.addEventListener("close", onClose); |
| return () => dialog.removeEventListener("close", onClose); |
| }, [onCancel]); |
|
|
| const handleSubmit = () => { |
| if (!name.trim()) return; |
| const newAff = newAffName.trim() ? { name: newAffName.trim() } : undefined; |
| onSubmit( |
| { name: name.trim(), url: url.trim() || undefined, affiliations: affIndices }, |
| newAff, |
| ); |
| }; |
|
|
| const handleCancel = () => { |
| dialogRef.current?.close(); |
| onCancel(); |
| }; |
|
|
| const toggleAff = (idx: number) => { |
| setAffIndices((prev) => |
| prev.includes(idx) ? prev.filter((i) => i !== idx) : [...prev, idx], |
| ); |
| }; |
|
|
| return ( |
| <dialog ref={dialogRef} className="ed-dialog ed-dialog--author"> |
| <h3 className="ed-dialog__title"> |
| {initial ? "Edit author" : "Add author"} |
| </h3> |
| <div className="ed-dialog__body"> |
| <div className="author-form__row"> |
| <input |
| ref={nameRef} |
| className="form-input" |
| placeholder="Name" |
| value={name} |
| onChange={(e) => setName(e.target.value)} |
| onKeyDown={(e) => e.key === "Enter" && handleSubmit()} |
| /> |
| <input |
| className="form-input" |
| placeholder="URL (optional)" |
| value={url} |
| onChange={(e) => setUrl(e.target.value)} |
| onKeyDown={(e) => e.key === "Enter" && handleSubmit()} |
| /> |
| </div> |
| |
| {affiliations.length > 0 && ( |
| <div className="author-form__chips" style={{ marginTop: 12 }}> |
| {affiliations.map((aff, i) => ( |
| <span |
| key={`aff-select-${i}`} |
| className={`chip chip--clickable ${affIndices.includes(i + 1) ? "" : "chip--outlined"}`} |
| style={affIndices.includes(i + 1) ? { background: "var(--primary-color)", color: "#000" } : {}} |
| onClick={() => toggleAff(i + 1)} |
| > |
| {i + 1}. {aff.name} |
| </span> |
| ))} |
| </div> |
| )} |
| |
| <input |
| className="form-input" |
| placeholder="New affiliation (optional), e.g. Hugging Face" |
| value={newAffName} |
| onChange={(e) => setNewAffName(e.target.value)} |
| onKeyDown={(e) => e.key === "Enter" && handleSubmit()} |
| style={{ marginTop: 12 }} |
| /> |
| </div> |
| <div className="ed-dialog__actions"> |
| <button className="btn" onClick={handleCancel}>Cancel</button> |
| <button |
| className="btn btn--primary" |
| onClick={handleSubmit} |
| disabled={!name.trim()} |
| > |
| {initial ? "Save" : "Add"} |
| </button> |
| </div> |
| </dialog> |
| ); |
| } |
|
|