mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
feat: add Figma import feature
This commit is contained in:
parent
58006f7399
commit
0a0f1e6888
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
|
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
|
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
|
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
|
- ⬜ Granite Integration
|
||||||
- ✅ Popout Window for Web Container(@stijnus)
|
- ✅ Popout Window for Web Container(@stijnus)
|
||||||
- ✅ Ability to change Popout window size (@stijnus)
|
- ✅ Ability to change Popout window size (@stijnus)
|
||||||
|
- ✅ Import from Figma (@open-source)
|
||||||
|
|
||||||
## Features
|
## 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.
|
- **Download projects as ZIP** for easy portability Sync to a folder on the host.
|
||||||
- **Integration-ready Docker support** for a hassle-free setup.
|
- **Integration-ready Docker support** for a hassle-free setup.
|
||||||
- **Deploy** directly to **Netlify**, **Vercel**, or **Cloudflare Pages**
|
- **Deploy** directly to **Netlify**, **Vercel**, or **Cloudflare Pages**
|
||||||
|
- **Import Figma designs** to generate starter UI code.
|
||||||
|
|
||||||
## Setup
|
## 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.
|
- **Download projects as ZIP** for easy portability.
|
||||||
- **Integration-ready Docker support** for a hassle-free setup.
|
- **Integration-ready Docker support** for a hassle-free setup.
|
||||||
- **One-click deployment** to **Netlify**, **Vercel**, or **Cloudflare Pages**.
|
- **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