mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Merge pull request #12 from vgcman16/codex/add-import-from-figma-feature
Add Figma import feature
This commit is contained in:
commit
609715eef3
20
CHANGES.md
20
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
|
||||
|
@ -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
|
||||
|
||||
|
58
app/components/figma/FigmaImport.client.tsx
Normal file
58
app/components/figma/FigmaImport.client.tsx
Normal file
@ -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 (
|
||||
<ClientOnly fallback={<BaseChat />}>
|
||||
{() => (
|
||||
<>
|
||||
<Chat />
|
||||
{loading && <LoadingOverlay message="Importing Figma design..." />}
|
||||
</>
|
||||
)}
|
||||
</ClientOnly>
|
||||
);
|
||||
}
|
28
app/routes/figma.tsx
Normal file
28
app/routes/figma.tsx
Normal file
@ -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 (
|
||||
<div className="flex flex-col h-full w-full bg-bolt-elements-background-depth-1">
|
||||
<BackgroundRays />
|
||||
<Header />
|
||||
<ClientOnly fallback={<BaseChat />}>{() => <FigmaImport />}</ClientOnly>
|
||||
</div>
|
||||
);
|
||||
}
|
84
app/utils/figmaImport.ts
Normal file
84
app/utils/figmaImport.ts
Normal file
@ -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 <div>${frame.name}</div>;\n}`;
|
||||
return { name, code };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create chat messages from a Figma design
|
||||
*/
|
||||
export async function createChatFromFigma(
|
||||
figmaUrl: string,
|
||||
token: string,
|
||||
): Promise<Message[]> {
|
||||
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) =>
|
||||
`<boltAction type="file" filePath="app/components/${c.name}.tsx">${escapeBoltTags(c.code)}</boltAction>`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
const assistantMessage: Message = {
|
||||
role: 'assistant',
|
||||
content: `Converted Figma design from ${figmaUrl} to React components.\n<boltArtifact id="figma-components" title="Figma Components" type="bundled">${componentActions}</boltArtifact>`,
|
||||
id: generateId(),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
const userMessage: Message = {
|
||||
role: 'user',
|
||||
content: `Import design from ${figmaUrl}`,
|
||||
id: generateId(),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
return [userMessage, assistantMessage];
|
||||
}
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user