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 LayerType
Designer
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
blobLayer
to thelayerTypes
prop of theDesigner
component. - We add a default layer to the
layers
prop of theDesigner
component. - Since we're only working with one layer, we set the
singleLayerMode
prop 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>
)
}