blocks nest under their band, with a button per column
Adding a block means choosing which column it goes in, so a flat list cannot express it. A one-column band shows one button and a two-column band shows two — and a band with one column never has to answer a question it has no answer to. The blocks are links, like everywhere else: pressing one opens its editor. That is the last of the four actions the painted-on bars used to carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -920,6 +920,8 @@ export const PageBuilderPanel = () => {
|
||||
onAdd={() => handlersRef.current.add()}
|
||||
onDelete={(section) => handlersRef.current.remove(section.id)}
|
||||
onReorder={(ids) => void reorderRef.current(ids)}
|
||||
onAddComponent={(section, column) => handlersRef.current.addComponent(section, column)}
|
||||
onDeleteComponent={(block) => handlersRef.current.removeComponent(block)}
|
||||
/>,
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,8 @@ import { GripVertical, Plus, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Link } from 'react-router';
|
||||
import { href } from '../routes';
|
||||
import type { CanvasSection } from '../canvas';
|
||||
import type { CanvasComponent, CanvasSection } from '../canvas';
|
||||
import { CHOICES } from './AddComponentDialog';
|
||||
|
||||
/**
|
||||
* The page, as a list of its bands.
|
||||
@@ -31,6 +32,8 @@ export const PageStructure = ({
|
||||
onAdd,
|
||||
onDelete,
|
||||
onReorder,
|
||||
onAddComponent,
|
||||
onDeleteComponent,
|
||||
}: {
|
||||
/** For the links: pressing a band still takes you to it. */
|
||||
slug: string;
|
||||
@@ -39,6 +42,9 @@ export const PageStructure = ({
|
||||
onAdd: () => void;
|
||||
onDelete: (section: CanvasSection) => void;
|
||||
onReorder: (ids: number[]) => void;
|
||||
/** A block needs a COLUMN to go in, which is the whole reason blocks are nested here. */
|
||||
onAddComponent: (section: CanvasSection, column: number) => void;
|
||||
onDeleteComponent: (component: CanvasComponent) => void;
|
||||
}) => {
|
||||
const [dragged, setDragged] = useState<number | null>(null);
|
||||
const [order, setOrder] = useState<CanvasSection[] | null>(null);
|
||||
@@ -82,10 +88,11 @@ export const PageStructure = ({
|
||||
key={section.id}
|
||||
onDragOver={(event) => moveOver(event, section.id)}
|
||||
onDrop={commit}
|
||||
className={`flex items-center gap-1 rounded border border-border/60 ${
|
||||
className={`flex flex-col rounded border border-border/60 ${
|
||||
dragged === section.id ? 'opacity-40' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
draggable
|
||||
onDragStart={(event) => {
|
||||
@@ -119,6 +126,56 @@ export const PageStructure = ({
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
The band's columns, and what is in each.
|
||||
|
||||
Nested rather than listed flat, because ADDING a block means choosing which column it
|
||||
goes in — and a one-column band should not have to answer a question it has no answer
|
||||
to. So a band with one column shows one button and a band with two shows two.
|
||||
|
||||
The blocks themselves are links, like everywhere else: pressing one opens its editor.
|
||||
*/}
|
||||
<div className="flex flex-col gap-1 border-t border-border/60 px-2 py-1.5">
|
||||
{Array.from({ length: section.layout === 'two' ? 2 : 1 }, (_, column) => (
|
||||
<div key={column} className="flex flex-col gap-0.5">
|
||||
{section.components
|
||||
.filter((block) => block.column === column)
|
||||
.map((block) => (
|
||||
<span key={block.id} className="flex items-center gap-1">
|
||||
<Link
|
||||
to={href('pages', slug, String(section.id), String(block.id))}
|
||||
replace
|
||||
className="min-w-0 flex-1 truncate py-0.5 pl-3 text-[11px] text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{CHOICES.find((choice) => choice.kind === block.kind)?.label ?? block.kind}
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 shrink-0 px-1 text-destructive"
|
||||
disabled={busy}
|
||||
onClick={() => onDeleteComponent(block)}
|
||||
>
|
||||
<Trash2 className="size-3" />
|
||||
</Button>
|
||||
</span>
|
||||
))}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 justify-start px-1 text-[11px] text-muted-foreground"
|
||||
disabled={busy}
|
||||
onClick={() => onAddComponent(section, column)}
|
||||
>
|
||||
<Plus className="size-3" />
|
||||
{section.layout === 'two' ? (column === 0 ? 'Add left' : 'Add right') : 'Add block'}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user