From 0a0f1e6888096d461564d9b3067707b9a30ae4c7 Mon Sep 17 00:00:00 2001 From: vgcman16 <155417613+vgcman16@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:47:35 -0500 Subject: [PATCH] feat: add Figma import feature --- CHANGES.md | 20 +++++ README.md | 2 + app/components/figma/FigmaImport.client.tsx | 58 ++++++++++++++ app/routes/figma.tsx | 28 +++++++ app/utils/figmaImport.ts | 84 +++++++++++++++++++++ docs/docs/index.md | 1 + 6 files changed, 193 insertions(+) create mode 100644 app/components/figma/FigmaImport.client.tsx create mode 100644 app/routes/figma.tsx create mode 100644 app/utils/figmaImport.ts diff --git a/CHANGES.md b/CHANGES.md index 0b706640..cd338136 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -90,3 +90,23 @@ This implementation adds persistent file and folder locking functionality to the 2. **Contextual Icons**: Different icons and colors for different types of alerts 3. **Improved Error Details**: Better formatting of error details with monospace font and left border 4. **Responsive Buttons**: Better positioned and styled buttons with appropriate hover effects + +# Built-in Figma Import Feature + +## Overview + +This update adds the ability to import a Figma URL directly into BoltDIY. The application fetches the Figma file and generates starter React component files for each top-level frame, creating a new chat with the generated code ready for editing. + +## New Files + +### 1. `app/utils/figmaImport.ts` + +- Fetches Figma files and converts frames to simple React components + +### 2. `app/components/figma/FigmaImport.client.tsx` + +- Client component that imports a Figma design using query parameters + +### 3. `app/routes/figma.tsx` + +- Route to initiate the Figma import flow diff --git a/README.md b/README.md index 400d07e1..f0ea1d78 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ project, please check the [project management guide](./PROJECT.md) to get starte - ⬜ Granite Integration - ✅ Popout Window for Web Container(@stijnus) - ✅ Ability to change Popout window size (@stijnus) +- ✅ Import from Figma (@open-source) ## Features @@ -107,6 +108,7 @@ project, please check the [project management guide](./PROJECT.md) to get starte - **Download projects as ZIP** for easy portability Sync to a folder on the host. - **Integration-ready Docker support** for a hassle-free setup. - **Deploy** directly to **Netlify**, **Vercel**, or **Cloudflare Pages** +- **Import Figma designs** to generate starter UI code. ## Setup diff --git a/app/components/figma/FigmaImport.client.tsx b/app/components/figma/FigmaImport.client.tsx new file mode 100644 index 00000000..295d856d --- /dev/null +++ b/app/components/figma/FigmaImport.client.tsx @@ -0,0 +1,58 @@ +import { useSearchParams } from '@remix-run/react'; +import { useEffect, useState } from 'react'; +import { ClientOnly } from 'remix-utils/client-only'; +import { BaseChat } from '~/components/chat/BaseChat'; +import { Chat } from '~/components/chat/Chat.client'; +import { useChatHistory } from '~/lib/persistence'; +import { createChatFromFigma } from '~/utils/figmaImport'; +import { LoadingOverlay } from '~/components/ui/LoadingOverlay'; +import { toast } from 'react-toastify'; + +export function FigmaImport() { + const [searchParams] = useSearchParams(); + const { ready, importChat } = useChatHistory(); + const [imported, setImported] = useState(false); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!ready || imported) { + return; + } + + const url = searchParams.get('url'); + const token = searchParams.get('token'); + + if (!url || !token) { + window.location.href = '/'; + return; + } + + createChatFromFigma(url, token) + .then(async (messages) => { + if (importChat) { + const name = url.split('/').slice(-1)[0]; + await importChat(`Figma:${name}`, messages); + } + }) + .catch((error) => { + console.error('Error importing Figma design:', error); + toast.error('Failed to import Figma design'); + window.location.href = '/'; + }) + .finally(() => { + setLoading(false); + setImported(true); + }); + }, [ready, imported, searchParams, importChat]); + + return ( + }> + {() => ( + <> + + {loading && } + + )} + + ); +} diff --git a/app/routes/figma.tsx b/app/routes/figma.tsx new file mode 100644 index 00000000..bcf9105d --- /dev/null +++ b/app/routes/figma.tsx @@ -0,0 +1,28 @@ +import { json } from '@remix-run/cloudflare'; +import type { LoaderFunctionArgs, MetaFunction } from '@remix-run/cloudflare'; +import { ClientOnly } from 'remix-utils/client-only'; +import { BaseChat } from '~/components/chat/BaseChat'; +import { FigmaImport } from '~/components/figma/FigmaImport.client'; +import { Header } from '~/components/header/Header'; +import BackgroundRays from '~/components/ui/BackgroundRays'; + +export const meta: MetaFunction = () => { + return [ + { title: 'Bolt' }, + { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }, + ]; +}; + +export async function loader(args: LoaderFunctionArgs) { + return json({ url: args.params.url }); +} + +export default function FigmaPage() { + return ( +
+ +
+ }>{() => } +
+ ); +} diff --git a/app/utils/figmaImport.ts b/app/utils/figmaImport.ts new file mode 100644 index 00000000..d7c79d19 --- /dev/null +++ b/app/utils/figmaImport.ts @@ -0,0 +1,84 @@ +// Utility to import Figma designs and convert them to basic React components +import type { Message } from 'ai'; +import { generateId } from './fileUtils'; +import { escapeBoltTags } from './projectCommands'; + +/** + * Extract the file key from a Figma URL + */ +function extractFileKey(url: string): string { + const match = url.match(/file\/(\w+)/); + if (!match) { + throw new Error('Invalid Figma URL'); + } + return match[1]; +} + +interface GeneratedComponent { + name: string; + code: string; +} + +/** + * Recursively collect frame nodes from the Figma document + */ +function collectFrames(node: any, frames: any[]) { + if (node.type === 'FRAME') { + frames.push(node); + } + if (Array.isArray(node.children)) { + node.children.forEach((child) => collectFrames(child, frames)); + } +} + +function figmaFramesToComponents(frames: any[]): GeneratedComponent[] { + return frames.map((frame) => { + const name = frame.name.replace(/[^a-zA-Z0-9]/g, '') || 'Component'; + const code = `export function ${name}() {\n return
${frame.name}
;\n}`; + return { name, code }; + }); +} + +/** + * Create chat messages from a Figma design + */ +export async function createChatFromFigma( + figmaUrl: string, + token: string, +): Promise { + const fileKey = extractFileKey(figmaUrl); + const res = await fetch(`https://api.figma.com/v1/files/${fileKey}`, { + headers: { 'X-Figma-Token': token }, + }); + + if (!res.ok) { + throw new Error(`Failed to fetch Figma file: ${res.status}`); + } + + const data = await res.json(); + const frames: any[] = []; + collectFrames(data.document, frames); + const components = figmaFramesToComponents(frames); + const componentActions = components + .map( + (c) => + `${escapeBoltTags(c.code)}`, + ) + .join('\n'); + + const assistantMessage: Message = { + role: 'assistant', + content: `Converted Figma design from ${figmaUrl} to React components.\n${componentActions}`, + id: generateId(), + createdAt: new Date(), + }; + + const userMessage: Message = { + role: 'user', + content: `Import design from ${figmaUrl}`, + id: generateId(), + createdAt: new Date(), + }; + + return [userMessage, assistantMessage]; +} diff --git a/docs/docs/index.md b/docs/docs/index.md index ce637280..b5dbbb10 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -41,6 +41,7 @@ Also [this pinned post in our community](https://thinktank.ottomator.ai/t/videos - **Download projects as ZIP** for easy portability. - **Integration-ready Docker support** for a hassle-free setup. - **One-click deployment** to **Netlify**, **Vercel**, or **Cloudflare Pages**. +- **Import Figma designs** to bootstrap your UI code. ---