The following example shows how to build a custom svg blob editor using the designer.
We use custom layers and controls to create a blob editor that allows you to customize the blob's shape and fill color.
Custom layers
The blob editor uses a custom layer type to render the blob. We use the LayerType type to define the layer type and its default values.
The render function is used to render the blob. It receives the layer and uses the layer custom values to generate the blob path.
import { type LayerType } from "@shadcn/designer"
import { IconBlob } from "@tabler/icons-react"
const SVG_SIZE = 1024
// Define a blob layer type and its default values.
const blobLayer = {
name: "Blob",
type: "blob",
icon: <IconBlob />,
defaultValues: {
name: "Blob",
value: {
points: 5,
randomness: 0.5,
size: SVG_SIZE,
smoothness: 0.5,
},
cssVars: {},
},
render: (layer) => {
const blobPath = React.useMemo(() => {
return generateBlob(layer.value)
}, [layer.value])
return (
<svg
viewBox={`0 0 ${SVG_SIZE} ${SVG_SIZE}`}
width={SVG_SIZE}
height={SVG_SIZE}
xmlns="http://www.w3.org/2000/svg"
>
<title>Blob</title>
<path d={blobPath} fill={layer.cssVars?.["--fill"]} />
</svg>
)
},
} satisfies LayerTypeDesigner
Next, we create our custom designer.
The Designer component is the main component that renders the blob editor. It composes the DesignerHeader, DesignerContent, DesignerCanvas, and DesignerFrame components.
- We pass the
blobLayerto thelayerTypesprop of theDesignercomponent. - We add a default layer to the
layersprop of theDesignercomponent. - Since we're only working with one layer, we set the
singleLayerModeprop totrue.
import { type Layer } from "@shadcn/designer"
const defaultLayers = [
{
id: "blob-1",
type: "blob",
name: "Blob",
isLocked: true,
value: {
points: 5,
randomness: 0.5,
size: SVG_SIZE,
smoothness: 0.5,
},
cssVars: {
"--width": `${SVG_SIZE}px`,
"--height": `${SVG_SIZE}px`,
"--fill": "#2151ef",
},
},
] satisfies Layer[]
function BlobEditor() {
return (
<Designer layerTypes={[blobLayer]} layers={defaultLayers}>
<DesignerHeader>
<DesignerTitle>Blob Editor</DesignerTitle>
</DesignerHeader>
<DesignerContent>
<DesignerCanvas>
<DesignerFrame />
</DesignerCanvas>
</DesignerContent>
</Designer>
)
}Custom controls
The blob editor uses custom controls to allow you to customize the blob's shape, size, and color.
We use the Action component to create custom controls. See the Action page for more information on how to use the Action component.
To see a list of built-in actions, see the Actions page.
ActionBlobFill
The ActionBlobFill component is used to create a custom control for the blob's fill color. It uses the useLayerCssVarAction hook to get the current fill color and update it when the user selects a new color.
import { useLayerCssVarAction, createLayerCssVarAction } from "@shadcn/designer"
const DEFAULT_FILL = "#2151ef"
const fillAction = createLayerCssVarAction("--fill", DEFAULT_FILL)
function ActionBlobFill() {
const [fill, setFill] = useLayerCssVarAction(fillAction)
return (
<Popover>
<Action>
<PopoverAnchor />
<ActionLabel>Fill</ActionLabel>
<ActionControls>
<PopoverTrigger asChild>
<Button
className="flex-1 uppercase data-[empty=true]:text-muted-foreground data-[empty=true]:capitalize "
data-empty={fill === ""}
>
{fill ? (
<span>{fill}</span>
) : (
<span className="font-normal text-muted-foreground">
Add...
</span>
)}
<span
className="ml-auto flex size-3 shrink-0 rounded-xs"
style={{ backgroundColor: fill === "" ? DEFAULT_FILL : fill }}
/>
</Button>
</PopoverTrigger>
<Button
size="icon"
onClick={() => setFill(DEFAULT_FILL)}
disabled={!fill || fill === ""}
>
<IconX />
</Button>
</ActionControls>
</Action>
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Fill</PopoverTitle>
</PopoverHeader>
<ColorPicker value={fill} onValueChange={setFill} />
</PopoverContent>
</Popover>
)
}ActionBlobPoints
The ActionBlobPoints component controls the layer's value.points property. It uses the <Slider /> component to allow the user to select the number of points.
The value is updated using the setLayersProperty function.
import { useSelectedLayers, useSetLayersProperty } from "@shadcn/designer"
import { Slider, Action, ActionLabel, ActionControls } from "@shadcn/designer/ui"
function ActionBlobPoints() {
const [selectedLayer] = useSelectedLayers()
const setLayersProperty = useSetLayersProperty()
if (!selectedLayer) {
return null
}
return (
<Action orientation="vertical">
<ActionLabel htmlFor="points" className="sr-only">
Points
</ActionLabel>
<ActionControls>
<Slider
id="points"
min={3}
max={24}
value={selectedLayer.value.points}
onValueChange={(value) =>
setLayersProperty(
selectedLayer.id,
"value",
{
...selectedLayer.value,
points: value,
}
)
}
/>
</ActionControls>
</Action>
)
}