Blob Editor

A custom svg blob editor built using the designer.

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.

components/blob-editor.tsx
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.

  1. We pass the blobLayer to the layerTypes prop of the Designer component.
  2. We add a default layer to the layers prop of the Designer component.
  3. Since we're only working with one layer, we set the singleLayerMode prop to true.
components/blob-editor.tsx
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.

components/blob-editor.tsx
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.

components/blob-editor.tsx
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>
  )
}