diff --git a/.changeset/ten-pots-applaud.md b/.changeset/ten-pots-applaud.md new file mode 100644 index 000000000..20c279831 --- /dev/null +++ b/.changeset/ten-pots-applaud.md @@ -0,0 +1,5 @@ +--- +'@primer/primitives': patch +--- + +Adding shadow tokens for figma diff --git a/scripts/buildPlatforms/buildFigma.ts b/scripts/buildPlatforms/buildFigma.ts index c4aaea488..598465728 100644 --- a/scripts/buildPlatforms/buildFigma.ts +++ b/scripts/buildPlatforms/buildFigma.ts @@ -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 * ----------------------------------- */ diff --git a/src/formats/jsonFigma.ts b/src/formats/jsonFigma.ts index bf3e13f19..257d792c5 100644 --- a/src/formats/jsonFigma.ts +++ b/src/formats/jsonFigma.ts @@ -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) @@ -28,33 +32,98 @@ const getFigmaType = (type: string): string => { throw new Error(`Invalid type: ${type}`) } +const shadowToVariables = ( + name: string, + values: Omit & {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[] = [] + // 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 diff --git a/src/platforms/figma.ts b/src/platforms/figma.ts index 62dc7f7aa..510ec2b19 100644 --- a/src/platforms/figma.ts +++ b/src/platforms/figma.ts @@ -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 @@ -41,6 +41,7 @@ export const figma: PlatformInitializer = (outputFile, prefix, buildPath, option format: `json/figma`, options: { outputReferences: true, + mode: options?.mode, }, }, ], diff --git a/src/schemas/shadowToken.ts b/src/schemas/shadowToken.ts index 7c7965299..7865017bd 100644 --- a/src/schemas/shadowToken.ts +++ b/src/schemas/shadowToken.ts @@ -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({ @@ -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() diff --git a/src/tokens/functional/shadow/dark.json5 b/src/tokens/functional/shadow/dark.json5 index 0c14424f1..57a1ef520 100644 --- a/src/tokens/functional/shadow/dark.json5 +++ b/src/tokens/functional/shadow/dark.json5 @@ -12,6 +12,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, button: { @@ -30,6 +37,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -48,6 +62,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -66,6 +87,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -84,6 +112,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -100,6 +135,13 @@ inset: true, }, $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, resting: { xsmall: { @@ -113,6 +155,13 @@ inset: false, }, $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, small: { $value: [ @@ -136,6 +185,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, medium: { $value: [ @@ -149,6 +205,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, }, floating: { @@ -180,6 +243,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, medium: { $value: [ @@ -225,6 +295,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, large: { $value: [ @@ -246,6 +323,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, xlarge: { $value: [ @@ -267,6 +351,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, }, }, diff --git a/src/tokens/functional/shadow/light.json5 b/src/tokens/functional/shadow/light.json5 index b76e5d154..16259a624 100644 --- a/src/tokens/functional/shadow/light.json5 +++ b/src/tokens/functional/shadow/light.json5 @@ -12,6 +12,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, button: { @@ -30,6 +37,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -48,6 +62,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -66,6 +87,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -84,6 +112,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'component shadow', + }, + }, }, }, }, @@ -100,6 +135,13 @@ inset: true, }, $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, resting: { xsmall: { @@ -113,6 +155,13 @@ inset: false, }, $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, small: { $value: [ @@ -136,6 +185,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, medium: { $value: [ @@ -149,6 +205,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, }, floating: { @@ -180,6 +243,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, medium: { $value: [ @@ -225,6 +295,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, large: { $value: [ @@ -246,6 +323,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, xlarge: { $value: [ @@ -267,6 +351,13 @@ }, ], $type: 'shadow', + $extensions: { + 'org.primer.figma': { + collection: 'mode', + mode: 'light', + group: 'shadow', + }, + }, }, }, },