blocks that draw whatever the page is about

`/collections/amadoras-mythology` and `/collections/70s` are the same page with a different subject.
Building each would be absurd; hard-coding the arrangement would put it out of reach of the admin. So
the arrangement is built once — a template page, composed exactly like any other — and every
collection is drawn through it.

Four blocks read the subject: its name, its description, its specs, its photographs. They hold nothing
at all, not even a reference. `collections` holds no content because the list is a query; these hold
none because the subject is the URL, and the same block draws a different collection on the next
request.

Offered only on a template, and HIDDEN rather than disabled elsewhere. A greyed-out row invites "why
not here", and the answer — this page has no subject — is not something a tooltip fixes. Available and
silently drawing nothing is the worse failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 14:53:48 +01:00
co-authored by Claude Opus 5
parent ad988e4557
commit 331e615c7c
3 changed files with 64 additions and 5 deletions
+49 -4
View File
@@ -1,4 +1,4 @@
import { Handshake, Image, Images, LayoutGrid, Mail, Type, type LucideIcon } from 'lucide-react';
import { Handshake, Heading, Image, Images, LayoutGrid, Mail, Ruler, Type, type LucideIcon } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
/**
@@ -23,7 +23,30 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
* A kind the server would accept and this has never heard of is not creatable anyway.
*/
export type ComponentKind = 'free-text' | 'media' | 'gallery' | 'contact-form' | 'partners' | 'collections';
export type ComponentKind =
| 'free-text'
| 'media'
| 'gallery'
| 'contact-form'
| 'partners'
| 'collections'
| 'collection-title'
| 'collection-description'
| 'collection-specs'
| 'collection-gallery';
/*
The blocks that draw whatever the page is ABOUT.
Only offered on a template. On an ordinary page they would render nothing, which is the worst kind of
block: one that is available, adds cleanly, and silently does nothing.
*/
export const SUBJECT_KINDS: ComponentKind[] = [
'collection-title',
'collection-description',
'collection-specs',
'collection-gallery',
];
type Choice = {
kind: ComponentKind | null;
@@ -66,16 +89,36 @@ export const CHOICES: Choice[] = [
{ kind: 'contact-form', label: 'Contact form', hint: 'The site\'s form, as it is', Icon: Mail },
{ kind: 'partners', label: 'Partners', hint: 'The partner logos, from Site', Icon: Handshake },
{ kind: 'collections', label: 'Collections', hint: 'Every collection, as a grid', Icon: LayoutGrid },
/*
The four that draw THIS collection, on the template.
They hold nothing — not even a reference. The subject is the URL, so one block draws a different
collection on every request, and that is what lets one arrangement serve all of them.
*/
{ kind: 'collection-title', label: 'Its name', hint: 'The collection this page is about', Icon: Heading },
{ kind: 'collection-description', label: 'Its description', hint: 'What was written about it', Icon: Type },
{ kind: 'collection-specs', label: 'Its specs', hint: 'Measurement, material, colours, delivery', Icon: Ruler },
{ kind: 'collection-gallery', label: 'Its photographs', hint: 'The gallery it points at', Icon: Images },
];
export const AddComponentDialog = ({
open,
onCancel,
onChoose,
template,
}: {
open: boolean;
onCancel: () => void;
onChoose: (kind: ComponentKind) => void;
/**
* Whether this page is a TEMPLATE, which decides whether the subject blocks are offered.
*
* Hidden rather than disabled elsewhere. A greyed-out row invites the question "why not here", and
* the answer — this page has no subject — is not something a tooltip fixes. On a page that has one
* they are the point; on any other they would render nothing at all.
*/
template?: boolean;
}) => (
<Dialog open={open} onOpenChange={(next) => !next && onCancel()}>
<DialogContent className="sm:max-w-lg">
@@ -84,7 +127,8 @@ export const AddComponentDialog = ({
</DialogHeader>
<div className="grid grid-cols-2 gap-2">
{CHOICES.map(({ kind, label, hint, Icon }) => (
{CHOICES.filter((choice) => template || !SUBJECT_KINDS.includes(choice.kind)).map(
({ kind, label, hint, Icon }) => (
<button
key={label}
type="button"
@@ -107,7 +151,8 @@ export const AddComponentDialog = ({
</span>
<span className="text-[11px] text-muted-foreground">{hint}</span>
</button>
))}
),
)}
</div>
</DialogContent>
</Dialog>