Files
fofos-admin/web/sections/AddComponentDialog.tsx
T
pastilhasandClaude Opus 5 8ab0a4a516 Add component asks what kind, even with one to choose
It opened the text editor straight away, and I had argued for that: Free Text is the only kind that
works, so a picker offering it is a click asking to be got past.

That was wrong in the way a shortcut usually is. It optimised the state of the app today and hid its
shape — somebody adding a component could not see what a component IS, or that there will be others,
and the day the second kind lands the interaction changes underneath them.

It is also the pattern this admin already uses. The nav lists unbuilt sections as `soon` rather than
hiding them, because a list of only what is finished gives no sense of what the thing is for. The same
argument applied here and I had not noticed.

Image, Video and Gallery are named and disabled. Those three are things the site already HAS — media,
video and galleries all exist as concepts here — which is the difference between saying what is coming
and inventing a roadmap.

The list lives in the plugin rather than the API. The API decides what may be STORED; this decides what
may be OFFERED, and it has to be here because every kind needs a label, an icon and an editor that only
exists in this plugin. A kind the server would accept and this has never heard of is not creatable
anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 05:45:21 +01:00

93 lines
3.7 KiB
TypeScript

import { Images, Type, Video, type LucideIcon } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
/**
* Choosing what to put in a column.
*
* ── One option is still a choice worth showing ──
*
* I had argued the opposite: Free Text is the only kind that works, so opening the editor straight away
* saves a click that asks to be got past. That was wrong in the way a shortcut usually is — it optimised
* the state of the app today and hid the shape of it. Somebody adding a component cannot see what a
* component IS, or that there will be others, and the day the second kind lands the interaction changes
* underneath them.
*
* It is also the pattern this admin already uses. The nav lists unbuilt sections as `soon` rather than
* hiding them, on the reasoning that a nav showing only what is finished gives no sense of what the
* thing is for. The same argument applies here.
*
* ── The list is in the plugin, not the API ──
*
* The API decides what may be STORED — `free-text`, today. This decides what may be OFFERED, and it has
* to live here because every kind needs a label, an icon and an editor that only exists in this plugin.
* A kind the server would accept and this has never heard of is not creatable anyway.
*/
export type ComponentKind = 'free-text';
type Choice = {
kind: ComponentKind | null;
label: string;
hint: string;
Icon: LucideIcon;
};
/**
* `kind: null` means not built.
*
* Named rather than omitted, and the three named are things the site already HAS — media, video and
* galleries all exist as concepts here. That is the difference between saying what is coming and
* inventing a roadmap.
*/
const CHOICES: Choice[] = [
{ kind: 'free-text', label: 'Text', hint: 'Words, headings and links', Icon: Type },
{ kind: null, label: 'Image', hint: 'One picture from the media store', Icon: Images },
{ kind: null, label: 'Video', hint: 'A video, with a poster', Icon: Video },
{ kind: null, label: 'Gallery', hint: 'A named set of media', Icon: Images },
];
export const AddComponentDialog = ({
open,
onCancel,
onChoose,
}: {
open: boolean;
onCancel: () => void;
onChoose: (kind: ComponentKind) => void;
}) => (
<Dialog open={open} onOpenChange={(next) => !next && onCancel()}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Add a component</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2">
{CHOICES.map(({ kind, label, hint, Icon }) => (
<button
key={label}
type="button"
disabled={kind === null}
onClick={() => kind && onChoose(kind)}
className={`flex flex-col items-start gap-1 rounded-lg border p-3 text-left transition-colors ${
kind === null
? 'cursor-not-allowed border-border opacity-45'
: 'border-border hover:border-primary hover:bg-muted/50'
}`}
>
<span className="flex items-baseline gap-2">
<Icon className="size-4 shrink-0 self-center text-muted-foreground" />
<span className="text-sm">{label}</span>
{/* Dimmed and labelled rather than hidden, the same way the nav marks a section that is
not built. A list of only what is finished gives no sense of what this is for. */}
{kind === null && (
<span className="text-[10px] tracking-wide text-muted-foreground/70 uppercase">soon</span>
)}
</span>
<span className="text-[11px] text-muted-foreground">{hint}</span>
</button>
))}
</div>
</DialogContent>
</Dialog>
);