Files
starr-philproperties/src/components/generic/BlockList.jsx
T
2026-08-03 12:03:11 +08:00

96 lines
3.9 KiB
React

// components/generic/CMS/BlockList.jsx
import { BlockWrapper } from "./BlockWrapper";
import { TextBlock } from "./Blocks/Admin/TextBlock";
import { ImageBlock } from "./Blocks/Admin/ImageBlock";
import { TextImageBlock } from "./Blocks/Admin/TextImageBlock";
import { VideoBlock } from "./Blocks/Admin/VideoBlock";
import { TextVideoBlock } from "./Blocks/Admin/TextVideoBlock";
import { AudioBlock } from "./Blocks/Admin/AudioBlock";
import { CodeBlock } from "./Blocks/Admin/CodeBlock";
import { MarkdownBlock } from "./Blocks/Admin/MarkdownBlock";
// ─── Block renderer ───────────────────────────────────────────────────────────
//
// blockId is forwarded to TextBlock (and any future rich-text blocks) so the
// WYSIWYG editor knows exactly when to seed its innerHTML from saved data.
function BlockContent({ block, onUpdate }) {
const { id, type, content } = block;
switch (type) {
case "text":
return (
<TextBlock
blockId={id}
content={content}
onUpdate={onUpdate}
/>
);
case "image":
return <ImageBlock content={content} onUpdate={onUpdate} />;
case "text-image":
return <TextImageBlock blockId={id} content={content} onUpdate={onUpdate} />;
case "video":
return <VideoBlock content={content} onUpdate={onUpdate} />;
case "text-video":
return <TextVideoBlock blockId={id} content={content} onUpdate={onUpdate} />;
case "audio":
return <AudioBlock content={content} onUpdate={onUpdate} />;
case "code":
return <CodeBlock content={content} onUpdate={onUpdate} />;
case "markdown":
return <MarkdownBlock content={content} onUpdate={onUpdate} />;
default:
return <p className="text-sm text-muted-foreground">Unknown block type.</p>;
}
}
// ─── Default content per type ─────────────────────────────────────────────────
export const DEFAULT_CONTENT = {
"text": { body: "" },
"image": { asset_id: null, url: "", alt: "" },
"text-image": { body: "", asset_id: null, url: "", alt: "", image_position: "right" },
"video": { asset_id: null, url: "", thumbnail_url: "", duration_seconds: 0 },
"text-video": { body: "", asset_id: null, url: "", thumbnail_url: "", video_position: "right", duration_seconds: 0 },
"audio": { asset_id: null, url: "", title: "", artist: "", tag: "", thumbnail: "", duration_seconds: 0 },
"code": { language: "javascript", code: "" },
"markdown": { body: "" },
};
// ─── List ─────────────────────────────────────────────────────────────────────
export function BlockList({ blocks, onUpdate, onMove, onDelete }) {
if (!blocks.length) {
return (
<div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-muted-foreground/20 py-16 gap-2">
<p className="text-sm text-muted-foreground">No blocks yet.</p>
<p className="text-xs text-muted-foreground">
Use the button below to add your first block.
</p>
</div>
);
}
return (
<div className="flex flex-col gap-3">
{blocks.map((block, index) => (
<BlockWrapper
key={block.id}
type={block.type}
index={index}
total={blocks.length}
onMoveUp={() => onMove(block.id, "up")}
onMoveDown={() => onMove(block.id, "down")}
onDelete={() => onDelete(block.id)}
>
<BlockContent
block={block}
onUpdate={(updated) => onUpdate(block.id, updated)}
/>
</BlockWrapper>
))}
</div>
);
}