Skip to content

Commit

Permalink
Add README
Browse files Browse the repository at this point in the history
  • Loading branch information
bernaferrari committed Jun 20, 2020
1 parent d121c66 commit 70df855
Show file tree
Hide file tree
Showing 21 changed files with 783 additions and 47 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

61 changes: 36 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
Below are the steps to get your plugin running. You can also find instructions at:
<p align="center"><img src="assets/icon_256.png" alt="Figma to Code" height="128px"></p>

https://www.figma.com/plugin-docs/setup/
Figma to Code
===========

This plugin template uses Typescript and NPM, two standard tools in creating JavaScript applications.
Most *design to code* plugins are bad. This project aims to solve, or at least improve, the situation. This plugin generates responsive layouts in [Tailwind](https://tailwindcss.com/) and [Flutter](https://flutter.github.io/). The plan is to eventually add support for [Jetpack Compose](https://developer.android.com/jetpack/compose) and possibly standard HTML or other frameworks like [SwiftUI](https://developer.apple.com/xcode/swiftui/), [Bootstrap](https://getbootstrap.com/), [Material](https://material.io/develop/web/), [Fluent](https://www.microsoft.com/design/fluent/), etc. Feedback and partnerships are appreciated!

First, download Node.js which comes with NPM. This will allow you to install TypeScript and other
libraries. You can find the download link here:
![Gif showing the conversion](assets/lossy_gif.gif)

https://nodejs.org/en/download/
### How it works

Next, install TypeScript using the command:
This plugin takes an unconventional approach to improve code quality: it optimizes the layout before the conversion to code even begins. The standard Figma [Nodes](https://www.figma.com/plugin-docs/api/nodes/) (what represents each layer) was a joy to work with, but it can't modify a layer without modifying the user project. For this reason, I decided to virtualize it, remaking the official implementation and naming it `AltNodes`. During the process to convert a `Node` into an `AltNode`, the plugin does the following:

npm install -g typescript
![Conversion Workflow](assets/workflow.png)

Finally, in the directory of your plugin, get the latest type definitions for the plugin API by running:
### Hard cases
When finding the unknown (a `Group` or `Frame` with more than one child and no vertical or horizontal alignment), Tailwind mode uses [insets](https://tailwindcss.com/docs/top-right-bottom-left/#app) for best cases and `left`, `top` from standard CSS for the worst cases. Flutter mode uses `Stack` and `Positioned.fill`. Both are usually not recommended and can easily defeat the responsiveness. In many scenarios, just wrapping some elements in a `Group` or `Frame` can solve:

npm install --saveDev @figma/plugin-typings
![Conversion Workflow](assets/examples.png)

If you are familiar with JavaScript, TypeScript will look very familiar. In fact, valid JavaScript code
is already valid Typescript code.
### Things it still can't do

TypeScript adds type annotations to variables. This allows code editors such as Visual Studio Code
to provide information about the Figma API while you are writing code, as well as help catch bugs
you previously didn't notice.
- Vector (tricky in HTML, unsupported in Flutter)
- Images (they are local, how to support?)
- Gradients (unsupported by Tailwind, todo in Flutter)
- Line/Star/Polygon (todo. Rectangle and Ellipse were prioritized and are more common)
- Identify buttons
- The source code is fully commented and there are about 30 "todo"s spread

For more information, visit https://www.typescriptlang.org/
#### Tailwind limits

Using TypeScript requires a compiler to convert TypeScript (code.ts) into JavaScript (code.js)
for the browser to run.
- **Width:** Tailwind has a maximum width of 256px. If an item passes this, the width will be set to `w-full` (unless it is `w-1/2`, `w-1/3`, etc because of the parent). This is usually a feature, but be careful: if everything in your project is huge, the plugin's result might be less than optimal.
- **Height:** The plugin avoids setting the height whatever possible, because width and height work differently in CSS. `h-full` means get the full height of the parent, but the parent **must** have it, while `w-full` doesn't require it to have. During experiments, avoid a fixed height, in most cases, brought benefits in improved responsiveness.

We recommend writing TypeScript code using Visual Studio code:
#### Flutter limits

1. Download Visual Studio Code if you haven't already: https://code.visualstudio.com/.
2. Open this directory in Visual Studio Code.
3. Compile TypeScript to JavaScript: Run the "Terminal > Run Build Task..." menu item,
then select "tsc: watch - tsconfig.json". You will have to do this again every time
you reopen Visual Studio Code.
- **Align:** currently items are aligned inside a Row/Column according to their average position. Todo: find a way to improve this.
- **Unreadable code:** code is not formatted, but even [dartpad](https://dartpad.dev/) offers a format button.
- **Stack:** in some cases, a `Stack` could be replaced with `Container` and `BoxDecoration`. Find these cases and optimize them.
- **Material Styles**: possible optimization, find if current text size, color, and weight match any material text style.
- **Identify FlatButtons**: instead of outputting `Container` or `Material`, it could identify specific buttons and help the user.

That's it! Visual Studio Code will regenerate the JavaScript file every time you save.
#### Webpack vs Rollup
The project is configured to be built with Webpack or Rollup. The author couldn't find how to correctly configure Svelte in Webpack, so Rollup was added. But Rollup is a lot buggier than Webpack and crashes regularly in watch mode for Typescript files. So, if you are going to edit only Typescript, I reccommend sticking with Webpack. If you are going to make changes in the UI, you **need** to use Rollup for now.

#### Architecture
There is, unfortunately, not a defined architecture pattern in the project. Builders are used, but the result was worse than expected. Ideally, I thought it would help to have a group of interfaces (position, size, opacity, and so on), where each language/framework could implement it, but there were too many changes. For example, Tailwind appends attributes to a string, while Flutter replaces the string as attributes get nested. If you have ideas or would like to discuss further, feel free to contact me.

Issue Tracking
-------
Found a bug? Have an idea for an improvement? Feel free to [add an issue](../../issues). Pull requests are also more than welcome.
Binary file added assets/examples.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/full_gif.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/github.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icon_128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icon_256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/lossy_gif.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/workflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions babel.config.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import App from './PluginUI';
import App from './ui/Main';

const app = new App({
target: document.body,
Expand Down
43 changes: 38 additions & 5 deletions src/tailwind/extract_text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,26 @@ export const extractTailwindText = (
? node.characters.split("\n").join("<br></br>")
: node.characters;

const black = {
r: 0,
g: 0,
b: 0,
};

let contrastBlack = 21;
if (node.fills && node.fills !== figma.mixed && node.fills.length > 0) {
let fill = node.fills[0];
if (fill.type === "SOLID") {
contrastBlack = calculateContrastRatio(fill.color, black);
}
}

textStr.push({
name: node.name,
attr: attr.attributes,
full: `<p ${attr.attributes}>${charsWithLineBreak}</p>`,
style: style(node),
contrastBlack: contrastBlack,
});
}
});
Expand All @@ -61,6 +76,7 @@ type namedText = {
attr: string;
full: string;
style: string;
contrastBlack: number;
};

const style = (node: AltTextNode): string => {
Expand All @@ -73,11 +89,6 @@ const style = (node: AltTextNode): string => {
comp += "font-style: italic; ";
}

if (lowercaseStyle.match("regular")) {
// ignore the font-style when regular (default)
return "";
}

const value = node.fontName.style
.replace("italic", "")
.replace(" ", "")
Expand Down Expand Up @@ -119,6 +130,7 @@ const convertColor = (
): string | undefined => {
// kind can be text, bg, border...
// [when testing] fills can be undefined

if (fills && fills !== figma.mixed && fills.length > 0) {
let fill = fills[0];
if (fill.type === "SOLID") {
Expand All @@ -128,3 +140,24 @@ const convertColor = (

return undefined;
};

// from https://dev.to/alvaromontoro/building-your-own-color-contrast-checker-4j7o
function calculateContrastRatio(color1: RGB, color2: RGB) {
const color1luminance = luminance(color1);
const color2luminance = luminance(color2);

const contrast =
color1luminance > color2luminance
? (color2luminance + 0.05) / (color1luminance + 0.05)
: (color1luminance + 0.05) / (color2luminance + 0.05);

return 1 / contrast;
}

function luminance(color: RGB) {
var a = [color.r * 255, color.g * 255, color.b * 255].map(function (v) {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
File renamed without changes.
8 changes: 4 additions & 4 deletions src/PluginUI.svelte → src/ui/Main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@

</Tabs>

<div class="p-2">
{#if visible}
{#if visible}
<div class="p-2">
<div class="fixed bottom-0 left-0 w-full px-2 mb-2">
<div
class="h-8 w-full flex items-center justify-center bg-green-600
Expand All @@ -64,5 +64,5 @@
<p class="text-white">Copied!</p>
</div>
</div>
{/if}
</div>
</div>
{/if}
24 changes: 16 additions & 8 deletions src/ScreenAbout.svelte → src/ui/ScreenAbout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,18 @@
<p class="text-center font-bold text-xl text-white">Figma to Code</p>
<div class="h-2" />
<p class="text-center font-semibold text-md text-white">
Designed and developed by Bernardo Ferrari
Designed and developed by Bernardo Ferrari.
</p>
<div class="h-4" />
<div class="flex content-center justify-center space-x-4">
<p class="text-center text-sm text-white">
<span class="font-bold">@bernaferrari</span>
on Twitter/Reddit/GitHub.
</p>

<p class="text-center text-xs mt-2 text-white">
This project is open source and has no tracking.
</p>
<div class="h-2" />
<!-- <div class="flex content-center justify-center space-x-4">
<a href="https://twitter.com/@bernaferrari">
<button
class="bg-white hover:bg-gray-300 text-gray-800 font-semibold p-2 border
Expand Down Expand Up @@ -204,12 +212,12 @@
</svg>
</button>
</a>
</div>
<div class="flex justify-center mt-1">
<a
</div> -->
<!-- <div class="flex justify-center mt-2 text-white"> -->
<!-- <a
class="text-center text-white text-xs py-2 px-4"
href="https://github.com/bernaferrari">
This project is open source and has no tracking.
</a>
</div>
</a> -->
<!-- </div> -->
</div>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
15 changes: 14 additions & 1 deletion src/TailwindItemText.svelte → src/ui/TailwindItemText.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,29 @@
export let name = "";
export let attr = "";
export let style = "";
export let contrastBlack;
const dispatch = createEventDispatcher();
const clipboard = () => dispatch("clipboard");
// avoid white text in white background scenario
let backgroundColor = "";
$: if (contrastBlack) {
if (contrastBlack > 7) {
backgroundColor = "black";
} else {
backgroundColor = "white";
}
}
</script>

<button
class="flex space-x-2 items-center px-2 py-1 text-left border rounded-lg
w-full transition duration-300 ease-in-out bg-white transform hover:scale-105"
on:click={clipboard}>
<div class="flex items-center justify-center p-1 h-8">
<div
class="flex items-center justify-center p-1 h-8 rounded-lg"
style="background-color:{backgroundColor}">
<p class="text-xs text-center text-gray-700" {style}>Aa</p>
</div>
<div class="flex flex-col min-w-0">
Expand Down

0 comments on commit 70df855

Please sign in to comment.