Brochure-style content editor with a 3D panel preview, per-panel completion tracking, and side flipping.
Default brand color scheme
npx shadcn@latest add https://sdk-components.thesqd.com/r/panel-editor.jsonimport {
PanelEditor,
type PanelDefinition,
} from "@/components/blocks/panel-editor";import * as React from "react";
import {
PanelEditor,
type PanelDefinition,
} from "@/components/blocks/panel-editor";
const panels: PanelDefinition[] = [
{ key: "out-1", label: "Panel 1", side: "outside" },
{ key: "out-2", label: "Panel 2", side: "outside" },
{ key: "out-3", label: "Panel 3", side: "outside" },
{ key: "in-1", label: "Panel 4", side: "inside" },
{ key: "in-2", label: "Panel 5", side: "inside" },
{ key: "in-3", label: "Panel 6", side: "inside" },
];
export function Example() {
const [values, setValues] = React.useState<Record<string, string>>({});
return (
<PanelEditor
label="Tri-fold brochure"
caption="Default brand color scheme"
required
foldType="tri_fold"
panels={panels}
value={values}
onChange={setValues}
/>
);
}PanelEditor
├── Header (label / description / required indicator)
├── 3D fold preview (tri_fold | bi_fold | no_fold)
├── Side toggle (outside / inside, or front / back)
└── Per-panel content cards (one per PanelDefinition)Default brand color scheme
Per-panel colors via `customColors`.
Click the mic in any panel and start talking.
Each panel hosts a full RichTextEditor.
Same editor, no 3D fold rotation.
Click Validate with unfilled panels to trigger the error.
PanelEditor
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Field label rendered above the editor. |
caption | string | — | Sub-text under the label. |
required | boolean | false | Renders a required indicator. |
foldType | "tri_fold" | "bi_fold" | "no_fold" | — | Drives the 3D preview geometry and side vocabulary. |
panels | PanelDefinition[] | — | Panels rendered in display order. |
value | Record<string, string> | — | Per-panel content keyed by `PanelDefinition.key`. Panels marked "leave empty" store the exported `PANEL_LEFT_EMPTY` sentinel so validation/persistence see the decision — strip it to "" when shaping the final payload. |
onChange | (next: Record<string, string>) => void | — | Fired whenever any panel content changes. |
colorScheme | "brand" | "coral" | "rainbow" | "violet" | "pink" | "brand" | Color palette for the panel headers. |
flat | boolean | false | Render the panels flat (2D) — disables the 3D fold rotation. |
PanelDefinition
| Prop | Type | Default | Description |
|---|---|---|---|
key | string | — | Stable identifier — also the value-map key. |
label | string | — | Panel header label. |
side | "outside" | "inside" | "front" | "back" | — | Which face the panel belongs to (use front/back for `no_fold`). |