Skip to content

Commit

Permalink
v25 (#7)
Browse files Browse the repository at this point in the history
* Add storybook generation

* Generate theme separately, refactors

* Add theme to export

* Bugfixes, conditional rendering, props generation, story improvements

* Finish storybook variants

* Added component instance swap + theme refactor

* Bug fixes and refactors

* Prepare for Tamagui support

* Refactor styles

* Fix storybook issues, update readme, misc bugs
  • Loading branch information
TheUltDev authored Apr 29, 2023
1 parent 72c4326 commit 740fa35
Show file tree
Hide file tree
Showing 41 changed files with 1,532 additions and 14,038 deletions.
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
# [Figaro](https://www.figma.com/community/plugin/821138713091291738)
# [Figma To React Native](https://www.figma.com/community/plugin/821138713091291738)

> The Figma React Toolchain
Transforms Figma components to React components in real time. The goal is to bridge Figma and React. You design components in Figma, then program them in React. The focus is on one way conversion from Figma to React. Two way syncing will be focused on in the future.
> Transforms Figma components to React Native components in real time. The goal is to reduce the handoff time between design and development. Design your UI components in Figma and export them for use in your React Native app. The focus is on one way, continuous, conversion from Figma to React.
[![Preview of plugin](./art/banner.png)](https://www.figma.com/community/plugin/821138713091291738)

## Getting started

[Install the plugin](https://www.figma.com/community/plugin/821138713091291738). Open it in Figma and select a [Figma component](https://help.figma.com/hc/en-us/articles/360038662654-Guide-to-Components-in-Figma). The plugin will generate React component code and render a preview. To change the settings, click the cog icon and edit the JSON. Settings will save and update in real time. You can change the configuration of the editor, plugin, compiler, code output, and more.

## Features

- SVGs and images
- Nested components
- Variants and properties
- AutoLayout to FlexBox
- Conditional rendering
- Background gradients
- Borders and rounding
- Shadows and rotations
- Theme generation from styles
- Generates stories for Storybook
- Realtime code and preview rendering
- Flexible and realtime configuration
- Batch exporting components
- Figma dark mode support :)

## Community

- [Discord Channel](https://discord.com/invite/TzhDRyj)
Expand Down
61 changes: 38 additions & 23 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,51 @@
# INTERFACING
# ROADMAP

- Add commands (export all, export, copy current component)
- Switch to hosted UI to solve mouse issues
- Highlight selected sub node within code
- sourcemap from node ids -> line + column numbers needed when parsing


# PREVIEWING
### Polishing
- Add fill prop to SVGs and provide them
- Fix wrong variants being used (imports seem right)
- Fix various parse errors
- Fix styling differences

- support LinguiJS import
### Interfacing

- Highlight selected sub node within code
- sourcemap from node ids -> line + column numbers needed when parsing

# GENERATING
### Generating

- Borders
- Gradients
- Variants
- Loop through variants
- Build and diff stylesheet from the root for each
- Add suffix (named from the variant) for each diff stylesheet
- Build the dynamic class with the props and the stylesheet classes
- If state is "Hover" or "Focused" or "Pressed" then auto generate a <Pressable> and apply the class?
- Images (including export & previewing)
- Expo Image support: https://docs.expo.dev/versions/unversioned/sdk/image/
- Generate placeholder: https://github.com/evanw/thumbhash
- Optimize: https://github.com/GoogleChromeLabs/squoosh
- Theme values (fonts & effects left)
- Shadows, rotations, etc.
- Gradient backgrounds
- Absolute positioning
- Theme values
- Properties
- Screens (navigation based on prototype settings)
- Variants (conditional styling)
- Interactions (via Pressable & Link)
- Hidden based on props (conditional rendering)
- Images (including export & previewing)
- Generate Thumbhash placeholder
https://github.com/evanw/thumbhash
- Screens (navigation based on prototype settings)

### Exporting

# MAINTENANCE
- Git repository (https://isomorphic-git.org)
- Each document / page export is a branch
- Display diff

### Maintaining

- Refactor types
- Use `--jsx=automatic` option (https://github.com/evanw/esbuild/issues/334)

### Servicing

- Payment system
- Storybook syncing

### Testing

# AFTER MVP
- Payment system
- Ensure https://www.untitledui.com/ translates
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"api": "1.0.0",
"name": "Figaro",
"name": "Figma -> React Native",
"editorType": ["figma"],
"id": "821138713091291738",
"ui": "dist/index.html",
"main": "dist/main.js",
"parameterOnly": false,
"menu": [
{"name": "Open Inspector", "command": "show"},
{"name": "Component Inspector", "command": "show"},
{
"name": "Export Components",
"menu": [
Expand Down
23 changes: 12 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
"scripts": {
"start": "node esbuild.mjs --watch",
"build": "node esbuild.mjs",
"gen-settings-schema": "typescript-json-schema tsconfig.json Settings --out ./src/interface/templates/settings-schema.json"
"gen-schema": "typescript-json-schema tsconfig.json Settings --out ./src/templates/schema.json"
},
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"@donedeal0/superdiff": "^1.0.9",
"@monaco-editor/react": "^4.5.0",
"@radix-ui/react-tabs": "^1.0.3",
"client-zip": "^2.3.1",
"code-block-writer": "^11.0.3",
"esbuild-wasm": "^0.14.39",
"monaco-editor": "^0.36.1",
"code-block-writer": "^12.0.0",
"esbuild-wasm": "^0.14.54",
"monaco-editor": "^0.37.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@figma/plugin-typings": "^1.58.0",
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.17",
"esbuild": "^0.14.39",
"typescript": "^4.9.5",
"typescript-json-schema": "^0.55.0"
"@figma/plugin-typings": "^1.63.0",
"@types/react": "^17.0.57",
"@types/react-dom": "^17.0.19",
"esbuild": "^0.14.54",
"typescript": "^5.0.4",
"typescript-json-schema": "^0.56.0"
}
}
165 changes: 61 additions & 104 deletions src/interface/App.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,59 @@
import React from 'react';
import Editor from '@monaco-editor/react';
import * as Tabs from '@radix-ui/react-tabs';
import {useEffect, useState, useCallback, useMemo, useRef} from 'react';
import {useComponent} from 'interface/hooks/useComponent';
import {useSettings} from 'interface/hooks/useSettings';
import {Root as Tabs, List as Bar, Trigger as Item, Content as Tab} from '@radix-ui/react-tabs';
import {useEffect, useCallback, useMemo, useRef} from 'react';
import {useDarkMode} from 'interface/hooks/useDarkMode';
import {useSettings} from 'interface/hooks/useSettings';
import {useComponent} from 'interface/hooks/useComponent';
import {usePreview} from 'interface/hooks/usePreview';
import {useEditor} from 'interface/hooks/useEditor';
import {useExport} from 'interface/hooks/useExport';
import {IconGear} from 'interface/icons/IconGear';
import {StatusBar} from 'interface/base/StatusBar';
import {useTheme} from 'interface/hooks/useTheme';
import {IconGear} from 'interface/base/IconGear';
import {Loading} from 'interface/base/Loading';
import {Hint} from 'interface/base/Hint';
import {html} from 'interface/templates';
import {Export} from 'interface/Export';
import {debounce} from 'utils/common';
import {html} from 'templates';

export function App() {
const [opacity, setOpacity] = useState(0);
const isDarkMode = useDarkMode();
const component = useComponent();
const theme = useTheme();
const settings = useSettings();
const component = useComponent();
const isDarkMode = useDarkMode();
const preview = usePreview(component, settings.config);
const editor = useEditor(settings.config, component?.links);
const iframe = useRef<HTMLIFrameElement>(null);

const editorTheme = isDarkMode ? 'vs-dark' : 'vs';
const editorOptions = {...settings.config.display.editor.general, theme: editorTheme};
const handleSettings = useMemo(() => debounce(settings.update, 750), [settings.update]);
const updatePreview = useCallback(() => iframe.current?.contentWindow?.postMessage(preview), [preview]);

const handleSettings = useMemo(() =>
debounce(settings.update, 750), [settings.update]);

const updatePreview = useCallback(() =>
iframe.current?.contentWindow?.postMessage(preview), [preview]);

const exportProject = useCallback((target: string) =>
parent.postMessage({pluginMessage: {type: 'export', payload: target}}, '*'), []);

const changeTab = useCallback((value: string) => {
if (value === 'preview') {
setTimeout(() => setOpacity(1), 300);
} else {
setOpacity(0);
}
}, []);

useExport();
useEffect(updatePreview, [preview]);
useEffect(() => {
if (component?.code) {
setTimeout(() => setOpacity(1), 300);
} else {
setOpacity(0);
}
}, [component?.code]);

return (
<Tabs.Root defaultValue="code" className="tabs" onValueChange={changeTab}>
<Tabs.List loop aria-label="header" className="bar">
<Tabs.Trigger title="View component code" value="code" className="tab">
<Tabs defaultValue="code" className="tabs">
<Bar loop aria-label="header" className="bar">
<Item value="code" title="View component code" className="tab">
Code
</Tabs.Trigger>
<Tabs.Trigger title="Preview component" value="preview" className="tab">
</Item>
<Item value="preview" title="Preview component" className="tab">
Preview
</Tabs.Trigger>
<Tabs.Trigger title="View theme file" value="theme" className="tab">
</Item>
<Item value="theme" title="View theme file" className="tab">
Theme
</Tabs.Trigger>
<Tabs.Trigger title="Export project" value="export" className="tab">
</Item>
<Item value="story" title="View story" className="tab">
Story
</Item>
<Item value="export" title="Export project" className="tab">
Export
</Tabs.Trigger>
</Item>
<div className="expand"/>
<Tabs.Trigger title="Configure plugin" value="settings" className="tab icon">
<Item title="Configure plugin" value="settings" className="tab icon">
<IconGear/>
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="code" className="expand">
</Item>
</Bar>
<Tab value="code" className="expand">
{component?.code &&
<Editor
className="editor"
Expand All @@ -87,65 +66,46 @@ export function App() {
/>
}
{!component?.code && <Hint/>}
<StatusBar>
{/* copy, export buttons */}
</StatusBar>
</Tabs.Content>
<Tabs.Content value="preview" className="expand">
{component?.code &&
</Tab>
<Tab value="preview" className="expand">
{component?.preview &&
<iframe
ref={iframe}
style={{opacity}}
srcDoc={html.preview}
onLoad={updatePreview}
/>
}
{!component?.code && <Hint/>}
</Tabs.Content>
<Tabs.Content value="theme" className="expand">
{component?.code &&
{!component?.preview && <Hint/>}
</Tab>
<Tab value="story" className="expand">
{component?.story &&
<Editor
className="editor"
language="typescript"
path="Theme.ts"
value={component?.theme}
path={`${component.name}.story.ts`}
value={component.story}
theme={editorTheme}
loading={<Loading/>}
options={{...editorOptions, readOnly: true}}
/>
}
{!component?.code && <Hint/>}
</Tabs.Content>
<Tabs.Content value="export" className="expand">
<div className="page">
<form onSubmit={(e) => {
e.preventDefault();
const data = new FormData(e.currentTarget);
const type = data.get('type').toString();
exportProject(type);
}}>
<div className="radio-group">
<label>
<input type="radio" name="type" value="all" defaultChecked/>
Document
</label>
<label>
<input type="radio" name="type" value="page"/>
Current Page
</label>
<label>
<input type="radio" name="type" value="selected"/>
Selected Component
</label>
</div>
<input className="button" type="submit" value="Export"/>
</form>
</div>
<StatusBar>
{/* copy, export buttons */}
</StatusBar>
</Tabs.Content>
<Tabs.Content value="settings" className="expand">
{!component?.story && <Hint/>}
</Tab>
<Tab value="theme" className="expand">
<Editor
className="editor"
language="typescript"
path="Theme.ts"
value={theme}
theme={editorTheme}
loading={<Loading/>}
options={{...editorOptions, readOnly: true}}
/>
</Tab>
<Tab value="export" className="expand">
<Export/>
</Tab>
<Tab value="settings" className="expand">
<Editor
className="editor"
language="json"
Expand All @@ -157,19 +117,16 @@ export function App() {
onChange={value => handleSettings(value)}
onValidate={markers => {
if (markers.length === 0) {
const fileUri = editor.Uri.parse('Settings.json');
const fileModel = editor.editor.getModel(fileUri);
settings.update(fileModel.getValue(), true);
const uri = editor.Uri.parse('Settings.json');
const model = editor.editor.getModel(uri);
settings.update(model.getValue(), true);
settings.locked.current = false;
} else {
settings.locked.current = true;
}
}}
/>
<StatusBar>
{/* copy, export buttons */}
</StatusBar>
</Tabs.Content>
</Tabs.Root>
</Tab>
</Tabs>
);
}
Loading

0 comments on commit 740fa35

Please sign in to comment.