diff --git a/.changeset/tiny-poets-doubt.md b/.changeset/tiny-poets-doubt.md new file mode 100644 index 0000000..db61a4a --- /dev/null +++ b/.changeset/tiny-poets-doubt.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': minor +--- + +BREAKING: add parser that extracts "italic" from fontWeight and adds it as a separate property on Typography tokens object values. diff --git a/src/parsers/add-font-styles.ts b/src/parsers/add-font-styles.ts new file mode 100644 index 0000000..9d3abb0 --- /dev/null +++ b/src/parsers/add-font-styles.ts @@ -0,0 +1,53 @@ +import { DeepKeyTokenMap, SingleToken, TokenTypographyValue } from '@tokens-studio/types'; +// @ts-expect-error no type exported for this function +import getReferences from 'style-dictionary/lib/utils/references/getReferences.js'; +// @ts-expect-error no type exported for this function +import usesReference from 'style-dictionary/lib/utils/references/usesReference.js'; + +function recurse( + slice: DeepKeyTokenMap, + boundGetRef: (ref: string) => Array>, +) { + for (const key in slice) { + const token = slice[key]; + const { type, value } = token; + if (type === 'typography') { + let fontWeight; + + if (usesReference(value)) { + let ref = { value: value } as SingleToken; + while (ref && ref.value && typeof ref.value === 'string' && usesReference(ref.value)) { + try { + ref = Object.fromEntries( + Object.entries(boundGetRef(ref.value)[0]).map(([k, v]) => [k, v]), + ) as SingleToken; + } catch (e) { + console.warn(`Warning: could not resolve reference ${ref.value}`); + return; + } + } + fontWeight = (ref.value as TokenTypographyValue).fontWeight; + (token as SingleToken).value = ref.value; + } else if (typeof value === 'object') { + fontWeight = value.fontWeight; + } + + // cast it to TokenTypographyValue now that we've resolved references all the way, we know it cannot be a string anymore. + const tokenValue = value as TokenTypographyValue; + if (fontWeight && fontWeight.toLowerCase().indexOf('italic') > -1) { + // @ts-expect-error fontStyle is not a property that exists on Typography Tokens, we just add it ourselves + tokenValue.fontStyle = 'italic'; + tokenValue.fontWeight = tokenValue.fontWeight?.replace(/italic$/i, '').trim(); + } + } else if (typeof token === 'object') { + recurse(token as unknown as DeepKeyTokenMap, boundGetRef); + } + } +} + +export function addFontStyles(dictionary: DeepKeyTokenMap): DeepKeyTokenMap { + const copy = { ...dictionary }; + const boundGetRef = getReferences.bind({ properties: copy }); + recurse(copy, boundGetRef); + return copy; +} diff --git a/src/parsers/expand-composites.ts b/src/parsers/expand-composites.ts index 2690b11..2d1cf6e 100644 --- a/src/parsers/expand-composites.ts +++ b/src/parsers/expand-composites.ts @@ -29,6 +29,7 @@ const typeMaps = { fontWeight: 'fontWeights', lineHeight: 'lineHeights', fontSize: 'fontSizes', + fontStyle: 'fontStyles', }, }; diff --git a/src/registerTransforms.ts b/src/registerTransforms.ts index e10e22f..6d98c54 100644 --- a/src/registerTransforms.ts +++ b/src/registerTransforms.ts @@ -15,6 +15,7 @@ import { TransformOptions } from './TransformOptions.js'; import { expandComposites } from './parsers/expand-composites.js'; import { excludeParentKeys } from './parsers/exclude-parent-keys.js'; import { transformOpacity } from './transformOpacity.js'; +import { addFontStyles } from './parsers/add-font-styles.js'; const isBrowser = typeof window === 'object'; @@ -59,7 +60,8 @@ export async function registerTransforms(sd: Core, transformOpts?: TransformOpti parse: ({ filePath, contents }) => { const obj = JSON.parse(contents); const excluded = excludeParentKeys(obj, transformOpts); - const expanded = expandComposites(excluded, filePath, transformOpts); + const withFontStyles = addFontStyles(excluded); + const expanded = expandComposites(withFontStyles, filePath, transformOpts); return expanded; }, }); diff --git a/test/spec/parsers/add-font-styles.spec.ts b/test/spec/parsers/add-font-styles.spec.ts new file mode 100644 index 0000000..ddc07b3 --- /dev/null +++ b/test/spec/parsers/add-font-styles.spec.ts @@ -0,0 +1,48 @@ +import { expect } from '@esm-bundle/chai'; +import { DeepKeyTokenMap } from '@tokens-studio/types'; +import { addFontStyles } from '../../../src/parsers/add-font-styles.js'; + +const tokensInput = { + foo: { + value: { + fontFamily: 'Arial', + fontWeight: 'Bold Italic', + lineHeight: '1.25', + fontSize: '26', + }, + type: 'typography', + }, + ref: { + value: '{foo}', + type: 'typography', + }, +}; + +const tokensOutput = { + foo: { + value: { + fontFamily: 'Arial', + fontWeight: 'Bold', + fontStyle: 'italic', + lineHeight: '1.25', + fontSize: '26', + }, + type: 'typography', + }, + ref: { + value: { + fontFamily: 'Arial', + fontWeight: 'Bold', + fontStyle: 'italic', + lineHeight: '1.25', + fontSize: '26', + }, + type: 'typography', + }, +}; + +describe('add font style', () => { + it.only(`should expand composition tokens by default`, () => { + expect(addFontStyles(tokensInput as DeepKeyTokenMap)).to.eql(tokensOutput); + }); +});