feat(reader): add full nested workflow block
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import TermOutput from "./TermOutput";
|
||||
|
||||
function ShieldCheck() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M8 1.5 2.5 4v3.5c0 3 2.3 5.4 5.5 7 3.2-1.6 5.5-4 5.5-7V4L8 1.5Z"
|
||||
stroke="#1C5D4C"
|
||||
strokeWidth="1.3"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="m5.6 8 1.7 1.7L10.6 6"
|
||||
stroke="#1C5D4C"
|
||||
strokeWidth="1.3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function CheckMark() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<circle cx="8" cy="8" r="7" stroke="currentColor" strokeWidth="1.4" />
|
||||
<path
|
||||
d="m5.2 8.2 1.8 1.8 3.8-4"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.4"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
async function copyText(text) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch {
|
||||
try {
|
||||
const el = document.createElement("textarea");
|
||||
el.value = text;
|
||||
el.setAttribute("readonly", "");
|
||||
el.style.position = "fixed";
|
||||
el.style.left = "-9999px";
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
const ok = document.execCommand("copy");
|
||||
el.remove();
|
||||
return ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function WorkflowBlock({
|
||||
recipe,
|
||||
complete = false,
|
||||
onToggleComplete,
|
||||
focused = false,
|
||||
}) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const onShare = async () => {
|
||||
const ok = await copyText(recipe.shareBlurb);
|
||||
if (!ok) return;
|
||||
setCopied(true);
|
||||
window.setTimeout(() => setCopied(false), 1800);
|
||||
};
|
||||
|
||||
return (
|
||||
<article
|
||||
id={recipe.anchor}
|
||||
className={`recipe recipe-nested${focused ? " recipe-focus" : ""}`}
|
||||
aria-labelledby={`${recipe.anchor}-title`}
|
||||
>
|
||||
<div className="recipe-top">
|
||||
<span className="recipe-id">
|
||||
recipe <b>{recipe.id}</b> · v{recipe.version} · {recipe.time} · {recipe.cost}
|
||||
</span>
|
||||
<span className="recipe-vert">{recipe.vertical}</span>
|
||||
</div>
|
||||
|
||||
<div className="recipe-body">
|
||||
<h3 className="recipe-title" id={`${recipe.anchor}-title`}>
|
||||
{recipe.title}
|
||||
</h3>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Problem</div>
|
||||
<div className="v">
|
||||
{recipe.problem}{" "}
|
||||
<span className="sub">{recipe.problemJob}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Actors</div>
|
||||
<div className="v">
|
||||
<ul className="plain-list">
|
||||
{recipe.actors.map((line) => (
|
||||
<li key={line}>{line}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Setup</div>
|
||||
<div className="v">
|
||||
<ul className="plain-list">
|
||||
{recipe.setup.map((line) => (
|
||||
<li key={line}>{line}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Safeguards</div>
|
||||
<div className="v">
|
||||
{recipe.safeguards.map((line) => (
|
||||
<div className="guard" key={line}>
|
||||
<ShieldCheck />
|
||||
<span>{line}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Method</div>
|
||||
<div className="v">
|
||||
<ol className="steps">
|
||||
{recipe.method.map((step, i) => (
|
||||
<li key={i}>
|
||||
<div className="step-copy">
|
||||
{step.text}
|
||||
{step.term ? <TermOutput id={step.term} /> : null}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Evidence</div>
|
||||
<div className="v">
|
||||
{recipe.evidence.map((line) => (
|
||||
<p key={line} className="evidence-p">
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Try it</div>
|
||||
<div className="v">
|
||||
<p>
|
||||
About {recipe.tryit.minutes} minutes. {recipe.tryit.sample}{" "}
|
||||
{recipe.tryit.produce}
|
||||
</p>
|
||||
<p className="sub-line">
|
||||
{recipe.tryit.done}{" "}
|
||||
<a className="inline-sample" href={recipe.tryit.href} download>
|
||||
{recipe.tryit.hrefLabel}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<span className="chip">◷ {recipe.tryit.chip}</span>
|
||||
</p>
|
||||
{onToggleComplete ? (
|
||||
<div className="tryit-actions">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn ${complete ? "btn-primary tryit-done" : "btn-ghost"}`}
|
||||
aria-pressed={complete}
|
||||
onClick={() => onToggleComplete(recipe.id)}
|
||||
>
|
||||
{complete ? <CheckMark /> : null}
|
||||
{complete ? "Counted as activation (demo)" : "Mark complete"}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Share</div>
|
||||
<div className="v">
|
||||
<p>
|
||||
Copy a short result line. Share the outcome, not the recipe text.
|
||||
</p>
|
||||
<div className="tryit-actions">
|
||||
<button type="button" className="btn btn-ghost" onClick={onShare}>
|
||||
{copied ? "Copied" : "Copy result line"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="k">Record</div>
|
||||
<div className="v recipe-record">
|
||||
<span>Author: {recipe.author}</span>
|
||||
<span>Sources: {recipe.sources}</span>
|
||||
<span>Last updated: {recipe.lastUpdated}</span>
|
||||
{focused ? (
|
||||
<span>
|
||||
<Link href={`/read/real-estate#${recipe.anchor}`}>
|
||||
Back to the chapter
|
||||
</Link>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<Link href={`/read/recipe/${recipe.id}`}>Open on paper</Link>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user