Files
threadcount-community/components/guide.tsx
T
ThreadCount 1bc2de655a ThreadCount Community edition
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
2026-09-13 08:45:19 +10:00

106 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { Band, CtaBand, PageHead, SiteNav, body, h3, small } from "@/components/site";
import JsonLd from "@/components/JsonLd";
import { graph } from "@/lib/schema";
/* Shared furniture for the guides.
*
* These pages exist to be found by someone searching for the problem rather than the product — a
* linen services manager working out how to run a stocktake, not someone shopping for software.
* So they are written to be useful on their own: a person who reads one, does the thing manually
* and never signs up has still been helped, and the page has still done its job.
*
* The product is mentioned where it genuinely bears on the step and nowhere else. A guide that
* turns into an advertisement three paragraphs in is one people stop trusting, and stop linking
* to, which defeats the entire point of writing it. */
const SITE = process.env.NEXT_PUBLIC_SITE_URL || "https://threadcount.tech";
export type Step = { n: string; h: string; p: React.ReactNode };
export function GuideMeta({ slug, title, description, updated }: { slug: string; title: string; description: string; updated: string }) {
return (
<JsonLd
data={graph({
"@type": "Article",
"@id": `${SITE}/guides/${slug}#article`,
headline: title,
description,
datePublished: updated,
dateModified: updated,
inLanguage: "en-AU",
isAccessibleForFree: true,
author: { "@id": `${SITE}/#organization` },
publisher: { "@id": `${SITE}/#organization` },
mainEntityOfPage: { "@type": "WebPage", "@id": `${SITE}/guides/${slug}` },
})}
/>
);
}
export function GuideHead({ kicker, title, lede }: { kicker: string; title: string; lede: string }) {
return (
<>
<SiteNav />
<PageHead kicker={kicker} title={title} lede={lede} />
</>
);
}
/** A numbered step. The number carries the sequence, so the prose doesnt have to.
*
* The index is drawn in neutral-600, not the neutral-400 it used to be: on the paper ground that
* is 1.9:1, which is under every threshold there is, and a step number nobody can read is a
* numbered list that has stopped being numbered. neutral-400 stays fine where it is used on the
* ink panels — it is only text on paper that fails. */
export function Steps({ steps }: { steps: Step[] }) {
return (
<div>
{steps.map((s) => (
<div key={s.n} style={{ display: "grid", gridTemplateColumns: "44px 1fr", gap: 20, padding: "30px 0", borderBottom: "1px solid var(--color-divider)" }}>
<div style={{ fontFamily: "var(--font-heading)", fontWeight: 800, fontSize: 15, color: "var(--color-neutral-600)" }}>{s.n}</div>
<div>
<h2 style={{ ...h3, fontSize: "clamp(19px,2vw,25px)", margin: 0 }}>{s.h}</h2>
<div style={{ ...body, margin: "10px 0 0", maxWidth: "66ch" }}>{s.p}</div>
</div>
</div>
))}
</div>
);
}
/** The thing that goes wrong, stated plainly. These are the parts people actually search for. */
export function Pitfalls({ items }: { items: [string, string][] }) {
return (
<div className="tcm-2col" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", columnGap: 56, rowGap: 0, marginTop: 6 }}>
{items.map(([h, p]) => (
<div key={h} style={{ padding: "22px 0", borderBottom: "1px solid var(--color-divider)" }}>
<div style={{ fontFamily: "var(--font-heading)", fontWeight: 800, fontSize: 17 }}>{h}</div>
<p style={{ ...small, margin: "7px 0 0", maxWidth: "46ch" }}>{p}</p>
</div>
))}
</div>
);
}
export function GuideFoot({ related }: { related: [string, string][] }) {
return (
<>
<Band tone="surface">
<div style={{ fontSize: 11.5, letterSpacing: "0.12em", textTransform: "uppercase", fontWeight: 800, borderBottom: "2px solid var(--color-text)", paddingBottom: 9 }}>
Read next
</div>
<div className="tcm-2col" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", columnGap: 56, rowGap: 0 }}>
{related.map(([label, href]) => (
<Link key={href} href={href} style={{ textDecoration: "none", color: "var(--color-text)", borderBottom: "1px solid var(--color-divider)", padding: "20px 0", display: "flex", alignItems: "baseline", gap: 14 }}>
<span style={{ fontFamily: "var(--font-heading)", fontWeight: 800, fontSize: 18 }}>{label}</span>
<span aria-hidden style={{ marginLeft: "auto", color: "var(--color-accent)", fontWeight: 800 }}>&rarr;</span>
</Link>
))}
</div>
</Band>
<CtaBand />
</>
);
}