Skip to content

Commit

Permalink
Merge pull request #6 from mfrances17/export-plugin
Browse files Browse the repository at this point in the history
Add Figma export plugin
  • Loading branch information
srambach authored Dec 5, 2023
2 parents 74f35eb + 8768e5a commit 0de0b96
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/module/plugins/export-patternfly-tokens/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Export Patternfly Tokens Plugin

This plugin exports Patternfly tokens out of Figma and creates JSON files that can be added to the design tokens repo and processed by style dictionary.

### Setup
1. Clone the [design-tokens](https://github.com/patternfly/design-tokens) repository.
2. Open the Figma app.
3. In Figma, select **Plugins** > **Development** > **Import plugin from manifest**.
4. Browse to **design-tokens\packages\module\plugins\export-patternfly-tokens** and select the **manifest.json** file from your cloned design-tokens repository and click **Open**.

The Export Patternfly Tokens plugin should now be available to use as a development plugin in your Figma environment.

### Usage
Once the plugin has been added to Figma via the manifest file:
1. In Figma, select **Plugins** > **Development** > **Export Patternfly Tokens** > **Export Tokens**.
2. Click **Export Tokens**. The text area will display a concatenated list of all tokens exported from the Figma library. Links to each exported JSON file are displayed at the bottom of the dialog.
3. Click each JSON file link to save them locally (do not rename the JSON files!).
4. Copy the local JSON files to your cloned design-tokens repo:
1. Copy **base.dimension.json**, **base.json**, **semantic.dimension.json**, **semantic.json**, and **palette.color.json** to **\packages\module\tokens\default**.
2. Copy **base.dark.json**, **semantic.dark.json**, and **palette.color.json** to **\packages\module\tokens\dark** to **\packages\module\tokens\dark**.

Note that **palette.color.json** is saved to both the **default** and **dark** directories.
85 changes: 85 additions & 0 deletions packages/module/plugins/export-patternfly-tokens/code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
console.clear();

/* MAIN function */

figma.ui.onmessage = (e) => {
console.log("code received message", e);
if (e.type === "IMPORT") {
const { selectedCollection, selectedMode, body } = e;
importJSONFile({ selectedCollection, selectedMode, body });
getExistingCollectionsAndModes();
} else if (e.type === "EXPORT") {
exportToJSON();
}
};
if (figma.command === "export") {
figma.showUI(__uiFiles__["export"], {
width: 820,
height: 600,
themeColors: true,
});
}

/* EXPORT Functionality */

/* EXPORT - main function */

function exportToJSON() {
const collections = figma.variables.getLocalVariableCollections();

const files = [];
collections.forEach((collection) =>
files.push(...processCollection(collection))
);

figma.ui.postMessage({ type: "EXPORT_RESULT", files });
}

/* EXPORT - helper functions */

function processCollection({ name, modes, variableIds }) {
const files = [];
modes.forEach((mode) => {
let file = { fileName: `${name}.${mode.name}.tokens.json`, body: {} };

variableIds.forEach((variableId) => {
const { name, resolvedType, valuesByMode } =
figma.variables.getVariableById(variableId);
const value = valuesByMode[mode.modeId];

if (value !== undefined && ["COLOR", "FLOAT"].includes(resolvedType)) {
let obj = file.body;
name.split("/").forEach((groupName) => {
obj[groupName] = obj[groupName] || {};
obj = obj[groupName];
});

obj.$type = resolvedType === "COLOR" ? "color" : "number";
if (value.type === "VARIABLE_ALIAS") {
obj.$value = `{${figma.variables
.getVariableById(value.id)
.name.replace(/\//g, ".")}}`;
} else {
obj.$value = resolvedType === "COLOR" ? rgbToHex(value) : value;
}
}
});
files.push(file);
});
return files;
}

function rgbToHex({ r, g, b, a }) {
if (a !== 1) {
return `rgba(${[r, g, b]
.map((n) => Math.round(n * 255))
.join(", ")}, ${a.toFixed(4)})`;
}
const toHex = (value) => {
const hex = Math.round(value * 255).toString(16);
return hex.length === 1 ? "0" + hex : hex;
};

const hex = [toHex(r), toHex(g), toHex(b)].join("");
return `#${hex}`;
}
179 changes: 179 additions & 0 deletions packages/module/plugins/export-patternfly-tokens/export.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<!DOCTYPE html>
<head>
<style>
:root {
--spacing: 0.8rem;
}

* {
box-sizing: border-box;
}

body {
background-color: var(--figma-color-bg);
color: var(--figma-color-text);
margin: 0;
padding: var(--spacing);
}

html,
body,
main {
height: 98%;
}

main {
display: flex;
flex-direction: column;
gap: var(--spacing);
}

button {
appearance: none;
border-radius: 4px;
padding: var(--spacing);
}

textarea {
background-color: var(--figma-color-bg-secondary);
border: 2px solid var(--figma-color-border);
color: var(--figma-color-text-secondary);
flex: 1;
font-family: Andale Mono, monospace;
font-size: 0.9rem;
overflow: auto;
padding: var(--spacing);
white-space: pre;
}
textarea:focus {
border-color: var(--figma-color-border-selected);
outline: none;
}

button,
textarea {
display: block;
width: 100%;
}

a,
p {
color: var(--figma-color-text-secondary);
padding-right: 5px;
}

button {
background-color: var(--figma-color-bg-brand);
color: var(--figma-color-text-onbrand);
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
sans-serif;
font-weight: bold;
}

button-container {
display: flex;
justify-content: space-around;
}

link-container {
display: flex;
justify-content: space-around;
}

#export {
background-color: var(--figma-color-bg-component);
}

#save {
background-color: var(--figma-color-bg-component);
}
</style>
</head>
<body>
<main>
<button-container>
<button id="export" type="button">Export Tokens</button>
</button-container>
<textarea
placeholder="All exported tokens will render here..."
readonly
></textarea>
<link-spacer>
<p>Click each exported json file to download:</p>
</link-spacer>
</main>
<script>
window.onmessage = ({ data: { pluginMessage } }) => {
if (pluginMessage.type === "EXPORT_RESULT") {
let saveFileName = "";
let textOutput = pluginMessage.files.map( ({ fileName, body }) =>
`/* ${fileName} */\n\n${JSON.stringify(body, null, 2)}`
)
.join("\n\n\n");
textOutput = textOutput
.replaceAll("$type", "type")
.replaceAll("$value", "value");
document.querySelector("textarea").innerHTML = textOutput;

saveVars(textOutput, saveFileName);
}

function saveVars(text, fileName) {
var splitFiles = text.split('\n\n\n');

for (var i = 0; i < splitFiles.length; i++) {
var splitFileName = splitFiles[i].split('\n', 1)[0];
var saveFileName = "";

switch (splitFileName) {
case "/* Base Dimension Tokens.Mode 1.tokens.json */":
saveFileName = "base.dimension.json";
break;
case "/* Base Color Tokens - Light.Value.tokens.json */":
saveFileName = "base.json";
break;
case "/* Color Palette.Mode 1.tokens.json */":
saveFileName = "palette.color.json";
break;
case "/* Semantic Dimension Tokens.Mode 1.tokens.json */":
saveFileName = "semantic.dimension.json";
break;
case "/* Semantic Color Tokens.Light.tokens.json */":
saveFileName = "semantic.json";
break;
case "/* Base Color Tokens - Dark.Mode 1.tokens.json */":
saveFileName = "base.dark.json";
break;
case "/* Semantic Color Tokens.Dark.tokens.json */":
saveFileName = "semantic.dark.json";
break;
default:
saveFileName = splitFiles[i].split('\n', 1)[0]
}

const fileToExport = (splitFiles[i].substring(splitFiles[i].indexOf("\n") + 1) );
var textToSaveAsBlob = new Blob([fileToExport], {type:"text/plain"});

createLink(textToSaveAsBlob, saveFileName);
}
}

function createLink(text, file) {
var textToSaveAsURL = window.URL.createObjectURL(text);
var downloadLink = document.createElement("a");

downloadLink.download = file;
downloadLink.innerHTML = file;
downloadLink.href = textToSaveAsURL;
document.body.appendChild(downloadLink);
}
};

document.getElementById("export").addEventListener("click", () => {
parent.postMessage({ pluginMessage: { type: "EXPORT" } }, "*");
});

</script>
</body>
</html>
12 changes: 12 additions & 0 deletions packages/module/plugins/export-patternfly-tokens/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "Export Patternfly Tokens",
"id": "1305931922901292580",
"api": "1.0.0",
"editorType": ["figma"],
"permissions": [],
"main": "code.js",
"menu": [
{ "command": "export", "name": "Export Tokens" }
],
"ui": { "export": "export.html" }
}

0 comments on commit 0de0b96

Please sign in to comment.