);
}
// ── Component ──────────────────────────────────────────────────
// ── Sortable Subpage Row (stage 4 structure list) ──────────────
/**
* One subpage in the page builder's structure list (UX stage 4): drag handle,
* cover, name, state and album count, in one line. Same sortable id as the
* tile it replaces, so the builder's drag handler is unchanged.
*/
export function SortableSubpageRow({
sp,
spIndex,
isActive,
onClick,
getFirstThumb,
}: {
sp: Subpage;
spIndex: number;
isActive: boolean;
onClick: () => void;
getFirstThumb: (sp: Subpage) => string | null;
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: `subpage-${spIndex}`,
});
const totalAlbums =
sp.albums.length + (sp.sections?.reduce((sum, sec) => sum + sec.albums.length, 0) || 0);
const thumb = getFirstThumb(sp);
return (
);
}
// ── Sortable Page Row (#722) ───────────────────────────────────
/**
* A content page in the structure list: document icon and a PAGE tag, so it
* reads as something other than a subpage. The same row sits in the menu
* list and in "Not in menu"; `id` says which, for the drag handler.
*/
export function SortablePageRow({
id,
title,
isActive,
draft,
hasPassword,
missing,
onClick,
}: {
id: string;
title: string;
isActive: boolean;
draft?: boolean;
hasPassword?: boolean;
/** Referenced from gallery.yaml, but there is no file for it. */
missing?: boolean;
onClick: () => void;
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
});
return (
);
}
/**
* The "Not in menu" group as a drop target, so a page dragged out of the
* menu lands there even when the group is empty (#722).
*/
export function OffMenuDropZone({ id, children }: { id: string; children: ReactNode }) {
const { setNodeRef, isOver } = useDroppable({ id });
return (