Loading files
"use client";
import { useEffect, useRef, useState } from "react";
import styles from "./Community.module.css";
// Handing a deck to somebody.
//
// Two ways out — an address and a file — and the address is the one that
// matters, so it is shown as text you can read and select rather than hidden
// behind a button that claims it copied something. Copying a link you never
// saw is a small act of faith no interface has earned.
//
// The panel also answers the question the deck page kept provoking: where do
// I bake the pack? Nowhere. Each pattern's module is compiled once, the first
// time anybody asks for it, and the pack is put together from those whenever
// it is downloaded — so rearranging the deck needs nothing rebuilt and there
// is no publish step to hunt for. A fact worth stating outright, because an
// absent button reads as a missing feature.
//
// "Download .zip" here is the deck page's own download, not a second copy of
// it: pressing it closes the panel and the page shows the progress.
export default function ShareDeckPackModal({
packUrl,
installUrl,
onCopyLink,
onDownload,
onClose,
}: {
/** Absolute address of the deck's pack. */
packUrl: string;
/** One-click install for the viewer's own board, or null before hydration. */
installUrl: string | null;
/** Copies the link and warms the build. Returns once the clipboard is set. */
onCopyLink: () => Promise<boolean>;
/** Starts the page's download (and closes this panel). */
onDownload: () => void;
onClose: () => void;
}) {
const [copied, setCopied] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
// Escape closes it. The other modals here rely on the backdrop alone, which
// is fine when the content is a form you are filling in — this one is a
// thing you glance at and leave, so the fastest way out should work.
useEffect(() => {
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
const copy = async () => {
const ok = await onCopyLink();
if (!ok) {
// Clipboard refused — select the text so the keyboard still works.
inputRef.current?.select();
return;
}
setCopied(true);
window.setTimeout(() => setCopied(false), 2000);
};
return (
<div className={styles.modalOverlay} role="dialog" aria-modal="true" onClick={onClose}>
<div className={styles.modalCard} onClick={(event) => event.stopPropagation()}>
<div className={styles.modalHeader}>
<span className={styles.modalKicker}>Share</span>
<span>this deck</span>
<button type="button" onClick={onClose} aria-label="Close">
✕
</button>
</div>
<div className={styles.modalBody}>
<p className={styles.shareLead}>
Anyone with this link can put the whole deck on their board — no account needed.
</p>
<div className={styles.shareLinkRow}>
<input
ref={inputRef}
className={styles.shareLinkField}
value={packUrl}
readOnly
onFocus={(event) => event.currentTarget.select()}
aria-label="Pack link"
/>
<button type="button" className={styles.btnAccent} onClick={() => void copy()}>
{copied ? "Copied" : "Copy"}
</button>
</div>
<p className={styles.shareNote}>
Each pattern is compiled the first time someone asks for it; after that the pack is
ready at once, and changing the running order needs nothing rebuilt. There is nothing to
publish.
</p>
<div className={styles.shareAlt}>
<button type="button" className={styles.btnSmall} onClick={onDownload}>
Download .zip
</button>
{installUrl && (
<a className={styles.shareAltLink} href={installUrl}>
Install on my own board →
</a>
)}
</div>
</div>
</div>
</div>
);
}
07e931b46This file is pinned to the selected release. Download the original to open it in your design tool.