From f8cf48987ea8e0c8ba00dad7c552cf7cb9d9d5a9 Mon Sep 17 00:00:00 2001 From: soryu Date: Tue, 5 May 2026 16:26:39 +0100 Subject: feat: soryu.co - makima: Extract NewEphemeralTaskModal into shared component --- .../directives/NewEphemeralTaskModal.tsx | 120 +++++++++++++++++++++ makima/frontend/src/routes/document-directives.tsx | 119 +------------------- makima/frontend/tsconfig.tsbuildinfo | 2 +- 3 files changed, 122 insertions(+), 119 deletions(-) create mode 100644 makima/frontend/src/components/directives/NewEphemeralTaskModal.tsx diff --git a/makima/frontend/src/components/directives/NewEphemeralTaskModal.tsx b/makima/frontend/src/components/directives/NewEphemeralTaskModal.tsx new file mode 100644 index 0000000..88bc52b --- /dev/null +++ b/makima/frontend/src/components/directives/NewEphemeralTaskModal.tsx @@ -0,0 +1,120 @@ +import { useEffect, useRef, useState } from "react"; +import type { DirectiveSummary } from "../../lib/api"; + +/** + * Modal for spawning an ephemeral task under a directive. Mirrors the + * existing right-click "+ New task" flow. + */ +export function NewEphemeralTaskModal({ + directive, + onClose, + onSubmit, +}: { + directive: DirectiveSummary; + onClose: () => void; + onSubmit: (name: string, plan: string) => Promise; +}) { + const [name, setName] = useState(""); + const [plan, setPlan] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [err, setErr] = useState(null); + const nameRef = useRef(null); + + useEffect(() => { + nameRef.current?.focus(); + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + }, [onClose]); + + const submit = async (e: React.FormEvent) => { + e.preventDefault(); + const n = name.trim(); + const p = plan.trim(); + if (!n || !p || submitting) return; + setSubmitting(true); + setErr(null); + try { + await onSubmit(n, p); + } catch (caught) { + setErr(caught instanceof Error ? caught.message : String(caught)); + } finally { + setSubmitting(false); + } + }; + + return ( +
+
e.stopPropagation()} + className="w-[480px] max-w-[90vw] bg-[#0a1628] border border-[rgba(117,170,252,0.4)] shadow-xl flex flex-col" + > +
+

+ New ephemeral task in +

+

+ {directive.title} +

+
+
+
+ + setName(e.target.value)} + placeholder="e.g. Investigate flaky test" + className="w-full bg-transparent border border-[rgba(117,170,252,0.2)] focus:border-[#75aafc] outline-none px-3 py-2 text-[13px] text-[#dbe7ff] placeholder-[#445566]" + /> +
+
+ +