Skip to content

Commit

Permalink
WIP transformation for shadow to figma (#809)
Browse files Browse the repository at this point in the history
* transformation for shadow to figma tokens
  • Loading branch information
lukasoppermann authored Feb 26, 2024
1 parent 11f00db commit 296bc3e
Show file tree
Hide file tree
Showing 7 changed files with 407 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-pots-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/primitives': patch
---

Adding shadow tokens for figma
112 changes: 112 additions & 0 deletions scripts/buildPlatforms/buildFigma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,118 @@ export const buildFigma = (buildOptions: ConfigGeneratorOptions): void => {
},
}).buildAllPlatforms()

/** -----------------------------------
* Shadow tokens
* ----------------------------------- */
const shadowFiles = [
{
name: 'light',
source: [`src/tokens/functional/shadow/light.json5`],
include: [
`src/tokens/base/color/light/light.json5`,
`src/tokens/functional/color/light/primitives-light.json5`,
`src/tokens/functional/color/light/patterns-light.json5`,
],
mode: 'light',
},
{
name: 'light-high-contrast',
source: [`src/tokens/functional/shadow/light.json5`],
include: [
`src/tokens/base/color/light/light.json5`,
`src/tokens/base/color/light/light.high-contrast.json5`,
`src/tokens/functional/color/light/primitives-light.json5`,
`src/tokens/functional/color/light/patterns-light.json5`,
],
mode: 'light high contrast',
},
{
name: 'light-colorblind',
source: [`src/tokens/functional/shadow/light.json5`],
include: [
`src/tokens/base/color/light/light.json5`,
`src/tokens/base/color/light/light.protanopia-deuteranopia.json5`,
`src/tokens/functional/color/light/primitives-light.json5`,
`src/tokens/functional/color/light/patterns-light.json5`,
],
mode: 'light colorblind',
},
{
name: 'light-tritanopia',
source: [`src/tokens/functional/shadow/light.json5`],
include: [
`src/tokens/base/color/light/light.json5`,
`src/tokens/base/color/light/light.tritanopia.json5`,
`src/tokens/functional/color/light/primitives-light.json5`,
`src/tokens/functional/color/light/patterns-light.json5`,
],
mode: 'light tritanopia',
},
{
name: 'dark',
source: [`src/tokens/functional/shadow/dark.json5`],
include: [
`src/tokens/base/color/dark/dark.json5`,
`src/tokens/functional/color/dark/primitives-dark.json5`,
`src/tokens/functional/color/dark/patterns-dark.json5`,
],
mode: 'dark',
},
{
name: 'dark-high-contrast',
source: [`src/tokens/functional/shadow/dark.json5`],
include: [
`src/tokens/base/color/dark/dark.json5`,
`src/tokens/base/color/dark/dark.high-contrast.json5`,
`src/tokens/functional/color/dark/primitives-dark.json5`,
`src/tokens/functional/color/dark/patterns-dark.json5`,
],
mode: 'dark high contrast',
},
{
name: 'dark-dimmed',
source: [`src/tokens/functional/shadow/dark.json5`],
include: [
`src/tokens/base/color/dark/dark.json5`,
`src/tokens/base/color/dark/dark.dimmed.json5`,
`src/tokens/functional/color/dark/primitives-dark.json5`,
`src/tokens/functional/color/dark/patterns-dark.json5`,
],
mode: 'dark dimmed',
},
{
name: 'dark-colorblind',
source: [`src/tokens/functional/shadow/dark.json5`],
include: [
`src/tokens/base/color/dark/dark.json5`,
`src/tokens/base/color/dark/dark.protanopia-deuteranopia.json5`,
`src/tokens/functional/color/dark/primitives-dark.json5`,
`src/tokens/functional/color/dark/patterns-dark.json5`,
],
mode: 'dark colorblind',
},
{
name: 'dark-tritanopia',
source: [`src/tokens/functional/shadow/dark.json5`],
include: [
`src/tokens/base/color/dark/dark.json5`,
`src/tokens/base/color/dark/dark.tritanopia.json5`,
`src/tokens/functional/color/dark/primitives-dark.json5`,
`src/tokens/functional/color/dark/patterns-dark.json5`,
],
mode: 'dark tritanopia',
},
]
//
for (const {name, source, include, mode} of shadowFiles) {
PrimerStyleDictionary.extend({
source,
include,
platforms: {
figma: figma(`figma/shadows/${name}.json`, buildOptions.prefix, buildOptions.buildPath, {mode}),
},
}).buildAllPlatforms()
}
/** -----------------------------------
* Create list of files
* ----------------------------------- */
Expand Down
105 changes: 87 additions & 18 deletions src/formats/jsonFigma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import StyleDictionary from 'style-dictionary'
import {format} from 'prettier'
import type {FormatterArguments} from 'style-dictionary/types/Format'
import {transformNamePathToFigma} from '../transformers/namePathToFigma'
import type {ShadowTokenValue} from '../types/ShadowTokenValue'
import {hexToRgbaFloat} from '../transformers/utilities/hexToRgbaFloat'
import type {RgbaFloat} from '../transformers/utilities/isRgbaFloat'
import {isRgbaFloat} from '../transformers/utilities/isRgbaFloat'
const {sortByReference} = StyleDictionary.formatHelpers

const isReference = (string: string): boolean => /^\{([^\\]*)\}$/g.test(string)
Expand All @@ -28,33 +32,98 @@ const getFigmaType = (type: string): string => {
throw new Error(`Invalid type: ${type}`)
}

const shadowToVariables = (
name: string,
values: Omit<ShadowTokenValue, 'color'> & {color: string | RgbaFloat},
token: StyleDictionary.TransformedToken,
) => {
// floatValue
const floatValue = (property: 'offsetX' | 'offsetY' | 'blur' | 'spread') => ({
name: `${name}/${property}`,
value: parseInt(values[property].replace('px', '')),
type: 'FLOAT',
scopes: ['EFFECT_FLOAT'],
mode,
collection,
group,
})

const {attributes} = token
const {mode, collection, group} = attributes || {}

return [
floatValue('offsetX'),
floatValue('offsetY'),
floatValue('blur'),
floatValue('spread'),
{
name: `${name}/color`,
value: isRgbaFloat(values.color)
? {...values.color, ...(values.alpha ? {a: values.alpha} : {})}
: hexToRgbaFloat(values.color, values.alpha),
type: 'COLOR',
scopes: ['EFFECT_COLOR'],
mode,
collection,
group,
},
]
}

/**
* @description Converts `StyleDictionary.dictionary.tokens` to javascript esm (javascript export statement)
* @param arguments [FormatterArguments](https://github.com/amzn/style-dictionary/blob/main/types/Format.d.ts)
* @returns formatted `string`
*/
export const jsonFigma: StyleDictionary.Formatter = ({dictionary, file: _file, platform}: FormatterArguments) => {
// sort tokens by reference
const tokens = dictionary.allTokens.sort(sortByReference(dictionary)).map(token => {
// array to store tokens in
const tokens: Record<string, unknown>[] = []
// loop through tokens sorted by reference
for (const token of dictionary.allTokens.sort(sortByReference(dictionary))) {
// deconstruct token
const {attributes, value, $type, comment: description, original, alpha, mix} = token
const {mode, collection, scopes, group, codeSyntax} = attributes || {}

return {
name: token.name,
value,
type: getFigmaType($type),
alpha,
isMix: mix ? true : undefined,
description,
refId: [collection, token.name].filter(Boolean).join('/'),
reference: getReference(dictionary, original.value, platform),
collection,
mode,
group,
scopes,
codeSyntax,
// shadows need to be specifically dealt with
if ($type === 'shadow') {
const shadowValues = !Array.isArray(value) ? [value] : value
// if only one set of shadow values is present
if (shadowValues.length === 1) {
tokens.push(
...shadowToVariables(token.name, shadowValues[0], {
...token,
...(platform.mode ? {mode: platform.mode} : {}),
}),
)
} else {
// if multiple shadow sets values are present we need loop through them and add the index to the name
for (const [index, stepValue] of shadowValues.entries()) {
tokens.push(
...shadowToVariables(`${token.name}/${index + 1}`, stepValue, {
...token,
...(platform.mode ? {mode: platform.mode} : {}),
}),
)
}
}
// other tokens just get added to tokens array
} else {
tokens.push({
name: token.name,
value,
type: getFigmaType($type),
alpha,
isMix: mix ? true : undefined,
description,
refId: [collection, token.name].filter(Boolean).join('/'),
reference: getReference(dictionary, original.value, platform),
collection,
mode,
group,
scopes,
codeSyntax,
})
}
})
}
// add file header and convert output
const output = JSON.stringify(tokens, null, 2)
// return prettified
Expand Down
3 changes: 2 additions & 1 deletion src/platforms/figma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {PlatformInitializer} from '~/src/types/PlatformInitializer'
import {isSource} from '~/src/filters'

const validFigmaToken = (token: StyleDictionary.TransformedToken) => {
const validTypes = ['color', 'dimension']
const validTypes = ['color', 'dimension', 'shadow']
// is a siource token, not an included one
if (!isSource(token)) return false
// has a collection attribute
Expand Down Expand Up @@ -41,6 +41,7 @@ export const figma: PlatformInitializer = (outputFile, prefix, buildPath, option
format: `json/figma`,
options: {
outputReferences: true,
mode: options?.mode,
},
},
],
Expand Down
19 changes: 19 additions & 0 deletions src/schemas/shadowToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {colorHexValue} from './colorHexValue'
import {alphaValue} from './alphaValue'
import {dimensionValue} from './dimensionValue'
import {tokenType} from './tokenType'
import {collection, mode} from './collections'

export const shadowValue = z
.object({
Expand All @@ -22,5 +23,23 @@ export const shadowToken = baseToken
.extend({
$value: z.union([shadowValue, z.array(shadowValue), referenceValue]),
$type: tokenType('shadow'),
$extensions: z
.object({
'org.primer.figma': z.object({
collection: collection(['mode']).optional(),
mode: mode([
'light',
'dark',
'dark dimmed',
'light high contrast',
'dark high contrast',
'light colorblind',
'dark colorblind',
'light tritanopia',
'dark tritanopia',
]).optional(),
}),
})
.optional(),
})
.strict()
Loading

0 comments on commit 296bc3e

Please sign in to comment.