├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── figma-imports │ │ └── page.tsx │ └── page.tsx ├── components │ ├── shipping │ │ ├── Checkbox.tsx │ │ ├── InputField.tsx │ │ ├── types.ts │ │ ├── TextAreaField.tsx │ │ ├── LocationSelect.tsx │ │ └── ShippingForm.tsx │ └── builder.tsx ├── mappings │ └── FormShipping.mapper.tsx └── builder-registry.ts ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── postcss.config.mjs ├── next.config.ts ├── README.md ├── eslint.config.mjs ├── tailwind.config.ts ├── .gitignore ├── tsconfig.json └── package.json /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/builder-labs-react-component/main/src/app/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import BuilderDevTools from "@builder.io/dev-tools/next"; 2 | import type { NextConfig } from "next"; 3 | 4 | const nextConfig: NextConfig = BuilderDevTools()( 5 | BuilderDevTools()({ 6 | /* config options here */ 7 | }) 8 | ); 9 | 10 | export default nextConfig; 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # builder-labs-react-component 2 | The final product of our first BuildLabs, How to build React components with AI-powered design to code 3 | 4 | This repo is the output of the session which can be viewed and followed along here: https://www.notion.so/builderio/How-to-build-React-components-with-AI-powered-design-to-code-1803d7274be580468c31cde74e1b2248#1813d7274be5804292c2dc6361dd45e0 5 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } satisfies Config; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /src/components/shipping/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { CheckboxProps } from "./types"; 3 | 4 | export const Checkbox: React.FC = ({ 5 | label, 6 | checked, 7 | onChange, 8 | }) => ( 9 |
10 | onChange(e.target.checked)} 14 | className="w-4 h-4 rounded bg-zinc-800 min-h-[16px] accent-zinc-800" 15 | aria-label={label} 16 | /> 17 | 20 |
21 | ); 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "builder-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@builder.io/dev-tools": "^1.3.9", 13 | "@builder.io/react": "^8.0.4", 14 | "@builder.io/sdk": "^6.0.2", 15 | "next": "15.1.4", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.0.0" 18 | }, 19 | "devDependencies": { 20 | "@eslint/eslintrc": "^3", 21 | "@types/node": "^20", 22 | "@types/react": "^19", 23 | "@types/react-dom": "^19", 24 | "eslint": "^9", 25 | "eslint-config-next": "15.1.4", 26 | "postcss": "^8", 27 | "tailwindcss": "^3.4.1", 28 | "typescript": "^5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const geistSans = Geist({ 6 | variable: "--font-geist-sans", 7 | subsets: ["latin"], 8 | }); 9 | 10 | const geistMono = Geist_Mono({ 11 | variable: "--font-geist-mono", 12 | subsets: ["latin"], 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "Create Next App", 17 | description: "Generated by create next app", 18 | }; 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: Readonly<{ 23 | children: React.ReactNode; 24 | }>) { 25 | return ( 26 | 27 | 30 | {children} 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/mappings/FormShipping.mapper.tsx: -------------------------------------------------------------------------------- 1 | import { figmaMapping, type BaseFigmaProps } from "@builder.io/dev-tools/figma"; 2 | import { ShippingForm } from "@/components/shipping/ShippingForm"; 3 | 4 | // ❖ Form Shipping 5 | interface FigmaFormShippingProps extends BaseFigmaProps { 6 | Title?: string; 7 | SubTitle?: string; 8 | } 9 | 10 | // Read more at https://www.builder.io/c/docs/mapping-functions 11 | figmaMapping({ 12 | componentKey: "0ce3612fd8600e5fc29e790c18a3366d60dfcfba", 13 | mapper(figma: FigmaFormShippingProps) { 14 | return ( 15 | 23 | ); 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/shipping/InputField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { InputFieldProps } from "./types"; 3 | 4 | export const InputField: React.FC = ({ 5 | label, 6 | value, 7 | onChange, 8 | className = "", 9 | }) => ( 10 |
11 | 17 | onChange(e.target.value)} 22 | className="overflow-hidden flex-1 shrink self-stretch px-4 py-3 mt-2 w-full leading-none whitespace-nowrap bg-white rounded-lg border border-solid border-zinc-300 min-w-[240px]" 23 | aria-label={label} 24 | /> 25 |
26 | ); 27 | -------------------------------------------------------------------------------- /src/builder-registry.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { builder, Builder } from "@builder.io/react"; 3 | import { ShippingForm } from "./components/shipping/ShippingForm"; 4 | 5 | builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!); 6 | 7 | Builder.registerComponent(ShippingForm, { 8 | name: "ShippingForm", 9 | inputs: [ 10 | { 11 | name: "submitButtonText", 12 | type: "string", 13 | required: true, 14 | }, 15 | { 16 | name: "subtitle", 17 | type: "string", 18 | required: true, 19 | }, 20 | { 21 | name: "termsLinkText", 22 | type: "string", 23 | required: true, 24 | }, 25 | { 26 | name: "termsText", 27 | type: "string", 28 | required: true, 29 | }, 30 | { 31 | name: "termsUrl", 32 | type: "string", 33 | required: true, 34 | }, 35 | { 36 | name: "title", 37 | type: "string", 38 | required: true, 39 | }, 40 | ], 41 | }); 42 | -------------------------------------------------------------------------------- /src/components/shipping/types.ts: -------------------------------------------------------------------------------- 1 | export interface ShippingFormData { 2 | fullName: string; 3 | location: string; 4 | deliveryNote: string; 5 | termsAccepted: boolean; 6 | } 7 | 8 | export interface InputFieldProps { 9 | label: string; 10 | value: string; 11 | onChange: (value: string) => void; 12 | className?: string; 13 | } 14 | 15 | export interface LocationSelectProps extends Omit { 16 | value: string; 17 | options: Array<{ value: string; label: string }>; 18 | } 19 | 20 | export interface TextAreaFieldProps extends InputFieldProps { 21 | minHeight?: string; 22 | iconSrc: string; 23 | } 24 | 25 | export interface CheckboxProps { 26 | label: string; 27 | checked: boolean; 28 | onChange: (checked: boolean) => void; 29 | } 30 | 31 | export interface ShippingFormProps { 32 | title: string; 33 | subtitle: string; 34 | submitButtonText: string; 35 | termsText: string; 36 | termsLinkText: string; 37 | termsUrl: string; 38 | } 39 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/figma-imports/page.tsx: -------------------------------------------------------------------------------- 1 | import { builder } from "@builder.io/sdk"; 2 | import { RenderBuilderContent } from "../../components/builder"; 3 | 4 | // Builder Public API Key set in .env file 5 | builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!); 6 | 7 | interface PageProps { 8 | params: Promise<{ 9 | page: string[]; 10 | }>; 11 | } 12 | 13 | export default async function Page(props: PageProps) { 14 | const builderModelName = "figma-imports"; 15 | 16 | const content = await builder 17 | // Get the page content from Builder with the specified options 18 | .get(builderModelName, { 19 | userAttributes: { 20 | // Use the page path specified in the URL to fetch the content 21 | urlPath: "/" + ((await props?.params)?.page?.join("/") || ""), 22 | }, 23 | }) 24 | // Convert the result to a promise 25 | .toPromise(); 26 | 27 | return ( 28 | <> 29 | {/* Render the Builder page */} 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/components/builder.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ComponentProps } from "react"; 3 | import { BuilderComponent, useIsPreviewing } from "@builder.io/react"; 4 | import { BuilderContent, builder } from "@builder.io/sdk"; 5 | import DefaultErrorPage from "next/error"; 6 | import "../builder-registry"; 7 | 8 | type BuilderPageProps = ComponentProps; 9 | 10 | // Builder Public API Key set in .env file 11 | builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY!); 12 | 13 | export function RenderBuilderContent({ content, model }: BuilderPageProps) { 14 | // Call the useIsPreviewing hook to determine if 15 | // the page is being previewed in Builder 16 | const isPreviewing = useIsPreviewing(); 17 | // If "content" has a value or the page is being previewed in Builder, 18 | // render the BuilderComponent with the specified content and model props. 19 | if (content || isPreviewing) { 20 | return ; 21 | } 22 | // If the "content" is falsy and the page is 23 | // not being previewed in Builder, render the 24 | // DefaultErrorPage with a 404. 25 | return ; 26 | } 27 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/shipping/TextAreaField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { TextAreaFieldProps } from "./types"; 3 | 4 | export const TextAreaField: React.FC = ({ 5 | label, 6 | value, 7 | onChange, 8 | iconSrc, 9 | minHeight = "80px", 10 | className = "", 11 | }) => ( 12 |
13 | 19 |
23 |