Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
MH4GF committed Oct 11, 2024
1 parent 0f9d8a6 commit eb1dfd5
Show file tree
Hide file tree
Showing 21 changed files with 18,742 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { promises as fs } from 'node:fs'
import { mkdir } from 'node:fs/promises'

/**
* Fetches local variables from the Figma API and saves them to a temporary file.
* @returns {Promise<void>}
*/
export async function fetchFigmaLocalVariables() {
const FIGMA_FILE_KEY = process.env.FIGMA_FILE_KEY
const FIGMA_ACCESS_TOKEN = process.env.FIGMA_ACCESS_TOKEN

if (!FIGMA_FILE_KEY || !FIGMA_ACCESS_TOKEN) {
throw new Error(
'FIGMA_FILE_KEY and FIGMA_ACCESS_TOKEN environment variables are required.',
)
}

const url = `https://api.figma.com/v1/files/${FIGMA_FILE_KEY}/variables/local`
const headers = {
'X-Figma-Token': FIGMA_ACCESS_TOKEN,
}

const response = await fetch(url, { headers })
if (!response.ok) {
throw new Error(`Failed to fetch variables: ${response.statusText}`)
}

const data = await response.json()

await mkdir('tmp', { recursive: true })
await fs.writeFile(
'tmp/local-variables.json',
JSON.stringify(data.meta, null, 2),
)
console.info('Local variables fetched and saved to tmp/local-variables.json')
}
27 changes: 27 additions & 0 deletions frontend/packages/figma-to-css-variables/bin/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { fetchFigmaLocalVariables } from './fetchFigmaLocalVariables.mjs'
import { runStyleDictionary } from './runStyleDictionary.mjs'
import { transformVariablesForStyleDictionary } from './transformVariablesForStyleDictionary.mjs'

/**
* Main function to handle options and execute the necessary actions.
*/
async function main() {
const args = process.argv.slice(2)

if (args.includes('--fetch')) {
await fetchFigmaLocalVariables()
} else if (args.includes('--transform')) {
await transformVariablesForStyleDictionary()
} else if (args.includes('--generate')) {
await runStyleDictionary()
} else {
// Default to running all steps
await fetchFigmaLocalVariables()
await transformVariablesForStyleDictionary()
await runStyleDictionary()
}
}

main().catch((err) => {
console.error('Error:', err)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import fs from 'node:fs'
import path from 'node:path'
import StyleDictionary from 'style-dictionary'

function source(input) {
return [`${input}/**/*.json`]
}

/**
* Generates CSS variables using Style Dictionary.
* @returns {Promise<void>}
*/
export async function runStyleDictionary() {
try {
const inputDir = path.resolve('tmp/transformed-variables')
const subDirs = fs
.readdirSync(inputDir, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)

for (const subDir of subDirs) {
const subDirInput = path.join(inputDir, subDir)
const subDirOutput = `${path.join('build/css', subDir)}/`

const config = {
source: [source(subDirInput)],
platforms: {
css: {
transformGroup: 'css',
buildPath: subDirOutput,
files: [
{
destination: 'variables.css',
format: 'css/variables',
},
],
},
},
log: {
warnings: 'warn', // 'warn' | 'error' | 'disabled'
verbosity: 'verbose', // 'default' | 'silent' | 'verbose'
errors: {
brokenReferences: 'console', // 'throw' | 'console'
},
},
}

const styleDictionary = new StyleDictionary(config)
styleDictionary.buildAllPlatforms()
}

console.info('Style Dictionary build complete.')
} catch (error) {
console.error(`Error during Style Dictionary generation: ${error.message}`)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { promises as fs } from 'node:fs'
import { mkdir } from 'node:fs/promises'
import path from 'node:path'

/**
* Retrieves the token value from the variable based on the mode.
* @param {Object} variable
* @param {string} modeId
* @param {Object[]} localVariables
* @returns {string|number}
*/
function tokenValueFromVariable(variable, modeId, localVariables) {
const value = variable.valuesByMode[modeId]
if (typeof value === 'object') {
if ('type' in value && value.type === 'VARIABLE_ALIAS') {
const aliasedVariable = localVariables[value.id]
return `{${aliasedVariable?.name.replace(/\//g, '.')}}`
}
if ('r' in value) {
return rgbToHex(value) // Assuming rgbToHex is defined elsewhere
}

throw new Error(`Format of variable value is invalid: ${value}`)
}

if (typeof value === 'number') {
return Number.isInteger(value) ? value : Math.floor(value * 100) / 100
}

return value
}

function rgbToHex({ r, g, b, a }) {
if (a === undefined) {
a = 1
}

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}${a !== 1 ? toHex(a) : ''}`
}

/**
* Infers token type from the variable.
* @param {Object} variable
* @returns {string}
*/
function tokenTypeFromVariable(variable) {
switch (variable.resolvedType) {
case 'BOOLEAN':
return 'boolean'
case 'COLOR':
return 'color'
case 'FLOAT':
return 'number'
case 'STRING':
return 'string'
}
}

/**
* Transforms Figma local variables into Style Dictionary format.
* @returns {Promise<void>}
*/
export async function transformVariablesForStyleDictionary() {
const localVariablesPath = 'tmp/local-variables.json'
const transformedDir = 'tmp/transformed-variables'

const data = JSON.parse(await fs.readFile(localVariablesPath, 'utf-8'))
const localVariables = Object.values(data.variables)
const localVariableCollections = data.variableCollections

await mkdir(transformedDir, { recursive: true })

const fileContents = {}

for (const variable of localVariables) {
if (variable.remote) continue // Skip remote variables

const collection = localVariableCollections[variable.variableCollectionId]
if (collection) {
for (const mode of collection.modes) {
const fileName = `${mode.name}/${collection.name}.json`
const filePath = path.join(transformedDir, fileName)

if (!fileContents[filePath]) {
fileContents[filePath] = {}
}

let obj = fileContents[filePath]
for (const groupName of variable.name.split('/')) {
obj[groupName] = obj[groupName] || {}
obj = obj[groupName]
}

const token = {
type: tokenTypeFromVariable(variable),
value: tokenValueFromVariable(variable, mode.modeId, localVariables),
description: variable.description || '',
extensions: {
'com.figma': {
hiddenFromPublishing: variable.hiddenFromPublishing,
scopes: variable.scopes,
codeSyntax: variable.codeSyntax,
},
},
}

Object.assign(obj, token)
}
}
}

for (const [filePath, content] of Object.entries(fileContents)) {
const dir = path.dirname(filePath)
await mkdir(dir, { recursive: true })
await fs.writeFile(filePath, JSON.stringify(content, null, 2))
}

console.info('Variables transformed and saved to tmp/transformed-variables/')
}
3 changes: 3 additions & 0 deletions frontend/packages/figma-to-css-variables/biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["../../packages/configs/biome.jsonc"]
}
Loading

0 comments on commit eb1dfd5

Please sign in to comment.