Making no-code and code components work as a custom component

Please fill out as many of the following questions as possible so we can help you promptly! If you don’t know how to answer or it does not apply, feel free to remove it or leave it blank.

Builder content link

Builder public api key
3a4a90a3b35c4b44a923b538217c5c34

What are you trying to accomplish
We’re building a bunch of landing pages for marketing to use without developers, we’re integrating a bunch of custom code components. Some of them require Text that need to allow for styling and spacing like paragraphs.

I’ve tried creating a custom component with children and attaching the no code Text to this by using the prop for children, but the no code component is hidden by the custom component and cannot sit where i need it to so i can connect them and make it a section within Builder.io for the marketing team.

We have our own paragraph component, but to edit this we lose all the paragraph styling, meaning the text is just one single blob.

Are there any ways to achieve this with a mix of custom and standard components?

Screenshots or video link
We need to the placeholder copy to be editable and have styling like paragraphs.

Code stack you are integrating Builder with
*e.g. React

Reproducible code example
If you are having integration errors, please link to codesandbox or copy and paste your problematic code here. The more detail the better!

Hi @contactcreatives thanks for the question!

Are you able to post a snippet of the custom component code here so I could see how you have it set up? It sounds to me like you want to achieve something like what is outlined in this forum post: How to Add Dynamic Editing Regions to Custom Components

Check it out and see if that works for you. If not, please feel free to post code snippets here and we can try to take a deeper look!

Thanks for that, managed to get the no-code options into place. However we want to separate paragraphs, with our current code below when editing paragraph1, we also edit paragraph2. I would like these separately but can’t seem to get that to work. I tried adding a tag and was trying to reference it, but it just supplies 2 paragraphs on each location now instead of one in each.

Is there a way to have these 2 paragraphs be independent of each other?

import React from "react";
import { Builder, BuilderBlocks } from "@builder.io/react";
import { AssetKind } from "src/graphql/types";
import { AssetImage as CclAssetImage, Text as CclText } from "src/ccl/document";
import { Flex, Grid } from "src/ccl/layout";
import scratchOverlay from "src/assets/backgrounds/scratch-overlay.jpg";

type TextAndImageVariant = "light" | "dark" | "darkWithScratches";

interface TextAndImageProps {
  variant?: TextAndImageVariant;
  image1: string;
  imageAlt1: string;
  image2: string;
  imageAlt2: string;
  subtitle1: string;
  subtitle2: string;
  builderBlock: any;
}

export const TextAndImage: React.FC<TextAndImageProps> = ({
  variant = "light",
  image1,
  imageAlt1,
  image2,
  imageAlt2,
  subtitle1,
  subtitle2,
  builderBlock,
}) => {
  const variants = {
    light: {
      color: "black",
      background: "white",
    },
    dark: {
      color: "white",
      background: "black",
    },
    darkWithScratches: {
      color: "white",
      background: `url(${scratchOverlay})`,
    },
  };
  return (
    <Grid
      css={{
        background: variants[variant].background,
        gridColumns: 1,
        gridTemplateAreas:
          '"firstImage" "firstParagraph" "secondImage" "secondParagraph"',
        px: "$5",
        "@bp3": {
          gridColumns: 2,
          gridTemplateAreas:
            '"firstImage firstParagraph" "secondParagraph secondImage"',
          gridGap: "84px",
          px: "$17",
        },
        "@bp5": {
          gridGap: "115px",
        },
      }}
    >
      <CclAssetImage
        alt={imageAlt1}
        asset={{
          mediaUrl: image1,
          kind: AssetKind.Image,
          sortWeight: 1,
          id: "1",
        }}
        imgCss={{ gridArea: "firstImage" }}
        size={{
          "@initial": {
            width: 291,
            height: 178,
          },
          "@bp3": {
            width: 474,
            height: 330,
          },
          "@bp5": {
            width: 676,
            height: 472,
          },
        }}
      />
      <Flex
        css={{
          gridArea: "firstParagraph",
          flexDirection: "column",
          justifyContent: "center",
          pb: "$16",
          "@bp3": { pb: "$0" },
        }}
      >
        <CclText
          variant={{ "@initial": "h3", "@bp3": "h2" }}
          css={{
            color: variants[variant].color,
            py: "22px",
            "@bp3": { pb: "44px" },
          }}
        >
          {subtitle1}
        </CclText>
        <BuilderBlocks
          blocks={builderBlock.children}
          parentElementId={builderBlock.id}
          dataPath="children"
        />
      </Flex>
      <CclAssetImage
        alt={imageAlt2}
        imgCss={{ gridArea: "secondImage" }}
        asset={{
          mediaUrl: image2,
          kind: AssetKind.Image,
          sortWeight: 1,
          id: "1",
        }}
        size={{
          "@initial": {
            width: 291,
            height: 178,
          },
          "@bp3": {
            width: 474,
            height: 330,
          },
          "@bp5": {
            width: 676,
            height: 472,
          },
        }}
      />
      <Flex
        css={{
          gridArea: "secondParagraph",
          flexDirection: "column",
          justifyContent: "center",
        }}
      >
        <CclText
          variant={{ "@initial": "h3", "@bp3": "h2" }}
          css={{
            color: variants[variant].color,
            py: "22px",
            "@bp3": { pb: "44px" },
          }}
        >
          {subtitle2}
        </CclText>
        <BuilderBlocks
          blocks={builderBlock.children}
          parentElementId={builderBlock.id}
          dataPath="children"
        />
      </Flex>
    </Grid>
  );
};

Builder.registerComponent(TextAndImage, {
  name: "TextAndImage",
  canHaveChildren: true,
  inputs: [
    { name: "image1", type: "file", allowedFileTypes: ["jpeg", "png"] },
    {
      name: "imageAlt1",
      type: "text",
      defaultValue: "This is an image",
    },
    { name: "image2", type: "file", allowedFileTypes: ["jpeg", "png"] },
    {
      name: "imageAlt2",
      type: "text",
      defaultValue: "This is an image",
    },
    {
      name: "subtitle1",
      type: "text",
      defaultValue: "A short subtitle",
    },
    {
      name: "subtitle2",
      type: "text",
      defaultValue: "A short subtitle",
    },
    {
      name: "variant",
      type: "text",
      defaultValue: "light",
      enum: ["light", "dark", "darkWithScratches"],
    },
  ],
  defaultChildren: [
    {
      "@type": "@builder.io/sdk:Element",
      component: {
        tag: "0",
        name: "Text",
        options: {
          text: "Write text paragraph here",
        },
      },
    },
    {
      "@type": "@builder.io/sdk:Element",
      component: {
        tag: "1",
        name: "Text",
        options: {
          text: "Write text paragraph 2 here",
        },
      },
    },
  ],
});

<BuilderBlocks
          blocks={builderBlock.children}
          parentElementId={builderBlock.id}
          dataPath="children" />

In this block you are passing all children to the blocks attribute. You will need to map the correct children to the correct <BuilderBlocks /> component. As a hard coded example, you could pass the specific children to each component:

<BuilderBlocks blocks={[builderBlock.children[0]]}` .../>
...
<BuilderBlocks blocks={[builderBlock.children[1]]}` .../>

that should show to the default paragraphs. If you plan to allow for fully dynamic areas you would need to write some logic to account for which elements get added, etc

Hopefully that helps, let me know if it works for you!