-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ffe-core): sync figma og kode, Figma Variables API
- Loading branch information
Showing
21 changed files
with
19,034 additions
and
3 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 |
---|---|---|
@@ -1,2 +1,3 @@ | ||
css/ | ||
gen-src/ | ||
less/colors-semantic.less |
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
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,126 @@ | ||
import { colorApproximatelyEqual, parseColor, rgbToHex } from './color'; | ||
|
||
describe('colorApproximatelyEqual', () => { | ||
it('compares by hex value', () => { | ||
expect( | ||
colorApproximatelyEqual({ r: 0, g: 0, b: 0 }, { r: 0, g: 0, b: 0 }), | ||
).toBe(true); | ||
expect( | ||
colorApproximatelyEqual( | ||
{ r: 0, g: 0, b: 0 }, | ||
{ r: 0, g: 0, b: 0, a: 1 }, | ||
), | ||
).toBe(true); | ||
expect( | ||
colorApproximatelyEqual( | ||
{ r: 0, g: 0, b: 0, a: 0.5 }, | ||
{ r: 0, g: 0, b: 0, a: 0.5 }, | ||
), | ||
).toBe(true); | ||
expect( | ||
colorApproximatelyEqual( | ||
{ r: 0, g: 0, b: 0 }, | ||
{ r: 0, g: 0, b: 0, a: 0 }, | ||
), | ||
).toBe(false); | ||
|
||
expect( | ||
colorApproximatelyEqual( | ||
{ r: 0, g: 0, b: 0 }, | ||
{ r: 0.001, g: 0, b: 0 }, | ||
), | ||
).toBe(true); | ||
expect( | ||
colorApproximatelyEqual( | ||
{ r: 0, g: 0, b: 0 }, | ||
{ r: 0.0028, g: 0, b: 0 }, | ||
), | ||
).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('parseColor', () => { | ||
it('parses hex values', () => { | ||
// 3-value syntax | ||
expect(parseColor('#000')).toEqual({ r: 0, g: 0, b: 0 }); | ||
expect(parseColor('#fff')).toEqual({ r: 1, g: 1, b: 1 }); | ||
expect(parseColor('#FFF')).toEqual({ r: 1, g: 1, b: 1 }); | ||
expect(parseColor('#f09')).toEqual({ r: 1, g: 0, b: 153 / 255 }); | ||
expect(parseColor('#F09')).toEqual({ r: 1, g: 0, b: 153 / 255 }); | ||
|
||
// 4-value syntax | ||
expect(parseColor('#0000')).toEqual({ r: 0, g: 0, b: 0, a: 0 }); | ||
expect(parseColor('#000F')).toEqual({ r: 0, g: 0, b: 0, a: 1 }); | ||
expect(parseColor('#f09a')).toEqual({ | ||
r: 1, | ||
g: 0, | ||
b: 153 / 255, | ||
a: 170 / 255, | ||
}); | ||
|
||
// 6-value syntax | ||
expect(parseColor('#000000')).toEqual({ r: 0, g: 0, b: 0 }); | ||
expect(parseColor('#ffffff')).toEqual({ r: 1, g: 1, b: 1 }); | ||
expect(parseColor('#FFFFFF')).toEqual({ r: 1, g: 1, b: 1 }); | ||
expect(parseColor('#ff0099')).toEqual({ r: 1, g: 0, b: 153 / 255 }); | ||
expect(parseColor('#FF0099')).toEqual({ r: 1, g: 0, b: 153 / 255 }); | ||
|
||
// 8-value syntax | ||
expect(parseColor('#00000000')).toEqual({ r: 0, g: 0, b: 0, a: 0 }); | ||
expect(parseColor('#00000080')).toEqual({ | ||
r: 0, | ||
g: 0, | ||
b: 0, | ||
a: 128 / 255, | ||
}); | ||
expect(parseColor('#000000ff')).toEqual({ r: 0, g: 0, b: 0, a: 1 }); | ||
expect(parseColor('#5EE0DCAB')).toEqual({ | ||
r: 0.3686274509803922, | ||
g: 0.8784313725490196, | ||
b: 0.8627450980392157, | ||
a: 0.6705882352941176, | ||
}); | ||
}); | ||
|
||
it('handles invalid hex values', () => { | ||
expect(() => parseColor('#')).toThrowError('Invalid color format'); | ||
expect(() => parseColor('#0')).toThrowError('Invalid color format'); | ||
expect(() => parseColor('#00')).toThrowError('Invalid color format'); | ||
expect(() => parseColor('#0000000')).toThrowError( | ||
'Invalid color format', | ||
); | ||
expect(() => parseColor('#000000000')).toThrowError( | ||
'Invalid color format', | ||
); | ||
expect(() => parseColor('#hhh')).toThrowError('Invalid color format'); | ||
}); | ||
}); | ||
|
||
describe('rgbToHex', () => { | ||
it('should convert rgb to hex', () => { | ||
expect(rgbToHex({ r: 1, g: 1, b: 1 })).toBe('#ffffff'); | ||
expect(rgbToHex({ r: 0, g: 0, b: 0 })).toBe('#000000'); | ||
expect(rgbToHex({ r: 0.5, g: 0.5, b: 0.5 })).toBe('#808080'); | ||
expect( | ||
rgbToHex({ | ||
r: 0.3686274509803922, | ||
g: 0.8784313725490196, | ||
b: 0.8627450980392157, | ||
}), | ||
).toBe('#5ee0dc'); | ||
}); | ||
|
||
it('should convert rgba to hex', () => { | ||
expect(rgbToHex({ r: 1, g: 1, b: 1, a: 1 })).toBe('#ffffff'); | ||
expect(rgbToHex({ r: 0, g: 0, b: 0, a: 0.5 })).toBe('#00000080'); | ||
expect(rgbToHex({ r: 0.5, g: 0.5, b: 0.5, a: 0.5 })).toBe('#80808080'); | ||
expect( | ||
rgbToHex({ | ||
r: 0.3686274509803922, | ||
g: 0.8784313725490196, | ||
b: 0.8627450980392157, | ||
a: 0, | ||
}), | ||
).toBe('#5ee0dc00'); | ||
}); | ||
}); |
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,52 @@ | ||
import { RGB, RGBA } from '@figma/rest-api-spec'; | ||
|
||
export function rgbToHex({ r, g, b, ...rest }: RGB | RGBA) { | ||
const a = 'a' in rest ? rest.a : 1; | ||
|
||
const toHex = (value: number) => { | ||
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) : ''}`.trim(); | ||
} | ||
|
||
/** | ||
* Compares two colors for approximate equality since converting between Figma RGBA objects (from 0 -> 1) and | ||
* hex colors can result in slight differences. | ||
*/ | ||
export function colorApproximatelyEqual( | ||
colorA: RGB | RGBA, | ||
colorB: RGB | RGBA, | ||
) { | ||
return rgbToHex(colorA) === rgbToHex(colorB); | ||
} | ||
|
||
export function parseColor(color: string): RGB | RGBA { | ||
const trimmedColor = color.trim(); | ||
const hexRegex = /^#([A-Fa-f0-9]{6})([A-Fa-f0-9]{2}){0,1}$/; | ||
const hexShorthandRegex = /^#([A-Fa-f0-9]{3})([A-Fa-f0-9]){0,1}$/; | ||
|
||
if (hexRegex.test(trimmedColor) || hexShorthandRegex.test(color)) { | ||
const hexValue = trimmedColor.substring(1); | ||
const expandedHex = | ||
hexValue.length === 3 || hexValue.length === 4 | ||
? hexValue | ||
.split('') | ||
.map(char => char + char) | ||
.join('') | ||
: hexValue; | ||
|
||
const alphaValue = | ||
expandedHex.length === 8 ? expandedHex.slice(6, 8) : undefined; | ||
|
||
return { | ||
r: parseInt(expandedHex.slice(0, 2), 16) / 255, | ||
g: parseInt(expandedHex.slice(2, 4), 16) / 255, | ||
b: parseInt(expandedHex.slice(4, 6), 16) / 255, | ||
...(alphaValue ? { a: parseInt(alphaValue, 16) / 255 } : {}), | ||
}; | ||
} | ||
throw new Error('Invalid color format'); | ||
} |
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,41 @@ | ||
import axios from 'axios'; | ||
import { | ||
GetLocalVariablesResponse, | ||
PostVariablesRequestBody, | ||
PostVariablesResponse, | ||
} from '@figma/rest-api-spec'; | ||
|
||
export default class FigmaApi { | ||
private baseUrl = 'https://api.figma.com'; | ||
private token: string; | ||
|
||
constructor(token: string) { | ||
this.token = token; | ||
} | ||
|
||
async getLocalVariables(fileKey: string) { | ||
const resp = await axios.request<GetLocalVariablesResponse>({ | ||
url: `${this.baseUrl}/v1/files/${fileKey}/variables/local`, | ||
headers: { | ||
Accept: '*/*', | ||
'X-Figma-Token': this.token, | ||
}, | ||
}); | ||
|
||
return resp.data; | ||
} | ||
|
||
async postVariables(fileKey: string, payload: PostVariablesRequestBody) { | ||
const resp = await axios.request<PostVariablesResponse>({ | ||
url: `${this.baseUrl}/v1/files/${fileKey}/variables`, | ||
method: 'POST', | ||
headers: { | ||
Accept: '*/*', | ||
'X-Figma-Token': this.token, | ||
}, | ||
data: payload, | ||
}); | ||
|
||
return resp.data; | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
packages/ffe-core/scripts/figma-api/sync_figma_to_tokens.ts
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,57 @@ | ||
import 'dotenv/config'; | ||
import * as fs from 'fs'; | ||
|
||
import FigmaApi from './figma_api'; | ||
|
||
import { green } from './utils'; | ||
import { tokenFilesFromLocalVariables } from './token_export'; | ||
|
||
/** | ||
* Usage: | ||
* | ||
* // Defaults to writing to the tokens_new directory | ||
* npm run sync-figma-to-tokens | ||
* | ||
* // Writes to the specified directory | ||
* npm run sync-figma-to-tokens -- --output directory_name | ||
*/ | ||
|
||
async function main() { | ||
if (!process.env.PERSONAL_ACCESS_TOKEN || !process.env.FILE_KEY) { | ||
throw new Error( | ||
'PERSONAL_ACCESS_TOKEN and FILE_KEY environemnt variables are required', | ||
); | ||
} | ||
const fileKey = process.env.FILE_KEY; | ||
|
||
const api = new FigmaApi(process.env.PERSONAL_ACCESS_TOKEN); | ||
const localVariables = await api.getLocalVariables(fileKey); | ||
|
||
const tokensFiles = tokenFilesFromLocalVariables(localVariables); | ||
|
||
let outputDir = './packages/ffe-core/tokens'; | ||
const outputArgIdx = process.argv.indexOf('--output'); | ||
if (outputArgIdx !== -1) { | ||
outputDir = process.argv[outputArgIdx + 1]; | ||
} | ||
|
||
if (!fs.existsSync(outputDir)) { | ||
fs.mkdirSync(outputDir); | ||
} | ||
|
||
Object.entries(tokensFiles).forEach(([fileName, fileContent]) => { | ||
fs.writeFileSync( | ||
`${outputDir}/${fileName}`, | ||
JSON.stringify(fileContent, null, 2), | ||
); | ||
console.log(`Wrote ${fileName}`); | ||
}); | ||
|
||
console.log( | ||
green( | ||
`✅ Tokens files have been written to the ${outputDir} directory`, | ||
), | ||
); | ||
} | ||
|
||
main(); |
Oops, something went wrong.