-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui-kit): add Design Tokens synchronization automation from Figma…
… to UI Kit
- Loading branch information
Showing
6 changed files
with
294 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@propeldata/ui-kit': patch | ||
--- | ||
|
||
Added Design Tokens synchronization automation from Figma to UI Kit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
const fs = require('fs') | ||
const path = require('path') | ||
|
||
function kebabCaseToCamelCase(kebabStr) { | ||
return kebabStr | ||
.split('-') | ||
.map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1))) | ||
.join('') | ||
} | ||
|
||
function writeToFile(fileName, content) { | ||
try { | ||
fs.writeFileSync(`./src/themes/generated/${fileName}`, content) | ||
} catch (err) { | ||
console.error(err) | ||
} | ||
} | ||
|
||
const data = fs.readFileSync('./src/themes/variables.json') | ||
const variablesJSON = JSON.parse(data) | ||
|
||
if (!variablesJSON) { | ||
console.error('Error parsing JSON') | ||
return | ||
} | ||
|
||
const primitives = variablesJSON.collections.find(({ name }) => name === 'Primitives').modes[0].variables | ||
const tokens = variablesJSON.collections.find(({ name }) => name === 'Tokens').modes[0].variables | ||
|
||
const variables = tokens.map((token) => { | ||
const keys = token.name.split('/') | ||
const key = keys.at(keys.length - 1).toLowerCase() | ||
const primitiveValue = primitives.find((item) => item.name === token.value.name) | ||
let value = primitiveValue.value | ||
if (primitiveValue.type === 'number') { | ||
value = `${primitiveValue.value}px` | ||
} | ||
|
||
return { | ||
cssName: `--propel-${key}`, | ||
jsName: kebabCaseToCamelCase(key), | ||
type: primitiveValue.type === 'number' ? 'number' : 'string', | ||
value | ||
} | ||
}) | ||
|
||
// Define the directory path you want to create | ||
const dirPath = path.join(__dirname, '../src/themes/generated') | ||
// Check if the directory exists | ||
if (!fs.existsSync(dirPath)) { | ||
// Create the directory if it does not exist | ||
fs.mkdirSync(dirPath, { recursive: true }) | ||
} | ||
|
||
// Generate _tokens.scss | ||
writeToFile( | ||
'_tokens.scss', | ||
`// This file is generated automatically by scripts/parse-design-tokens.js. Do not edit manually.\n | ||
.tokens { | ||
${variables.map(({ cssName, value }) => ` ${cssName}: ${value};`).join('\n')} | ||
}\n` | ||
) | ||
|
||
// Generate theme.types.ts | ||
writeToFile( | ||
'theme.types.ts', | ||
`// This file is generated automatically by scripts/parse-design-tokens.js. Do not edit manually.\n | ||
export type ThemeTokenGeneratedProps = { | ||
${variables.map(({ jsName, type }) => ` ${jsName}?: ${type};`).join('\n')} | ||
} | ||
export type ThemeCSSTokenGeneratedProps = { | ||
${variables.map(({ cssName, type }) => ` '${cssName}'?: ${type};`).join('\n')} | ||
}\n` | ||
) | ||
|
||
// Generate themeTokens.ts | ||
writeToFile( | ||
'themeTokens.ts', | ||
`// This file is generated automatically by scripts/parse-design-tokens.js. Do not edit manually.\n | ||
import type { ThemeTokenGeneratedProps } from './theme.types' | ||
export const themeTokensGenerated: (keyof ThemeTokenGeneratedProps)[] = [ | ||
${variables.map(({ jsName }) => ` '${jsName}',`).join('\n')} | ||
]\n` | ||
) | ||
|
||
console.log('🎨 Desgin Tokens: parsed ok') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
const fs = require('fs') | ||
const path = require('path') | ||
|
||
// Function to list all css and scss files | ||
function getAllFiles(dirPath, type = 'current', arrayOfFiles = []) { | ||
const files = fs.readdirSync(dirPath) | ||
|
||
files.forEach((file) => { | ||
const fullPath = path.join(dirPath, file) | ||
if (fs.statSync(fullPath).isDirectory()) { | ||
if (type === 'current' && file !== 'themes') { | ||
arrayOfFiles = getAllFiles(fullPath, type, arrayOfFiles) | ||
} | ||
} else if (file.endsWith('.css') || (type === 'new' && file.endsWith('.scss'))) { | ||
arrayOfFiles.push(fullPath) | ||
} | ||
}) | ||
|
||
return arrayOfFiles | ||
} | ||
|
||
// Function to extract CSS variables | ||
function extractCSSVariables(file) { | ||
const fileContent = fs.readFileSync(file, 'utf8') | ||
const regex = /--propel-[\w-]+/g // Regex updated to match only variables starting with --propel- | ||
return fileContent.match(regex) || [] | ||
} | ||
|
||
function getCSSVariables(dirPath, type) { | ||
const files = getAllFiles(dirPath, type) | ||
const allVariables = [] | ||
|
||
files.forEach((file) => { | ||
const variables = extractCSSVariables(file) | ||
variables.forEach((variable) => { | ||
if (!allVariables.includes(variable)) { | ||
allVariables.push(variable) | ||
} | ||
}) | ||
}) | ||
|
||
return allVariables | ||
} | ||
|
||
const currentVariablesList = getCSSVariables('./src', 'current') | ||
const newVariablesList = getCSSVariables('./src/themes', 'new') | ||
|
||
const failedVariables = [] | ||
|
||
currentVariablesList.forEach((variable) => { | ||
if (!newVariablesList.includes(variable)) { | ||
failedVariables.push(`⛔️ Variable '${variable}' is not defined in Design Tokens list`) | ||
// console.log(`⛔️ Variable '${variable}' is not defined in Design Tokens list`) | ||
} | ||
}) | ||
|
||
if (failedVariables.length > 0) { | ||
console.error(failedVariables.join('\n')) | ||
throw new Error('🎨 Desgin Tokens: validation failed') | ||
} | ||
|
||
console.log('🎨 Desgin Tokens: validated ok') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
{ | ||
"version": "1.0.4", | ||
"metadata": {}, | ||
"collections": [ | ||
{ | ||
"name": "Primitives", | ||
"modes": [ | ||
{ | ||
"name": "Mode 1", | ||
"variables": [ | ||
{ | ||
"name": "color/blue/25", | ||
"type": "color", | ||
"isAlias": false, | ||
"value": "#F5FAFF" | ||
}, | ||
{ | ||
"name": "color/blue/50", | ||
"type": "color", | ||
"isAlias": false, | ||
"value": "#EFF8FF" | ||
}, | ||
{ | ||
"name": "spacing/0", | ||
"type": "number", | ||
"isAlias": false, | ||
"value": 0 | ||
}, | ||
{ | ||
"name": "spacing/0_5", | ||
"type": "number", | ||
"isAlias": false, | ||
"value": 2 | ||
}, | ||
{ | ||
"name": "spacing/1", | ||
"type": "number", | ||
"isAlias": false, | ||
"value": 4 | ||
}, | ||
{ | ||
"name": "color/warning/25", | ||
"type": "color", | ||
"isAlias": false, | ||
"value": "#FFFBFA" | ||
}, | ||
{ | ||
"name": "color/warning/50", | ||
"type": "color", | ||
"isAlias": false, | ||
"value": "#FEF3F2" | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
{ | ||
"name": "Tokens", | ||
"modes": [ | ||
{ | ||
"name": "Mode 1", | ||
"variables": [ | ||
{ | ||
"name": "text/text-primary", | ||
"type": "color", | ||
"isAlias": true, | ||
"value": { | ||
"collection": "Primitives", | ||
"name": "color/blue/25" | ||
} | ||
}, | ||
{ | ||
"name": "border/border-primary", | ||
"type": "color", | ||
"isAlias": true, | ||
"value": { | ||
"collection": "Primitives", | ||
"name": "color/warning/25" | ||
} | ||
}, | ||
{ | ||
"name": "border/border-secondary", | ||
"type": "color", | ||
"isAlias": true, | ||
"value": { | ||
"collection": "Primitives", | ||
"name": "color/warning/50" | ||
} | ||
}, | ||
{ | ||
"name": "text/text-secondary", | ||
"type": "color", | ||
"isAlias": true, | ||
"value": { | ||
"collection": "Primitives", | ||
"name": "color/blue/50" | ||
} | ||
}, | ||
{ | ||
"name": "spacing-none", | ||
"type": "number", | ||
"isAlias": true, | ||
"value": { | ||
"collection": "Primitives", | ||
"name": "spacing/0" | ||
} | ||
}, | ||
{ | ||
"name": "spacing-xxs", | ||
"type": "number", | ||
"isAlias": true, | ||
"value": { | ||
"collection": "Primitives", | ||
"name": "spacing/0_5" | ||
} | ||
}, | ||
{ | ||
"name": "spacing-xs", | ||
"type": "number", | ||
"isAlias": true, | ||
"value": { | ||
"collection": "Primitives", | ||
"name": "spacing/1" | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
} |