diff --git a/.changeset/cool-windows-hang.md b/.changeset/cool-windows-hang.md new file mode 100644 index 0000000..d8a3dc3 --- /dev/null +++ b/.changeset/cool-windows-hang.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': minor +--- + +Add lineHeight and fontWeight types to the expandTypesMap. Even though DTCG spec does not yet recognize them, they really are special types and marking them as such enables transforms to target them specifically. diff --git a/.changeset/two-ads-confess.md b/.changeset/two-ads-confess.md new file mode 100644 index 0000000..f9521d1 --- /dev/null +++ b/.changeset/two-ads-confess.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': minor +--- + +Properly split fontWeight tokens that contain fontStyle into the parent group. For typography tokens this was correct but for fontWeight tokens, collision could occur between the token and its sibling tokens. diff --git a/package-lock.json b/package-lock.json index f92e899..1b242fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tokens-studio/sd-transforms", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tokens-studio/sd-transforms", - "version": "1.0.1", + "version": "1.1.0", "license": "MIT", "dependencies": { "@bundled-es-modules/deepmerge": "^4.3.1", @@ -45,7 +45,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "style-dictionary": "^4.0.0" + "style-dictionary": "^4.0.1" } }, "node_modules/@75lb/deep-merge": { @@ -145,29 +145,20 @@ } }, "node_modules/@bundled-es-modules/memfs": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/memfs/-/memfs-4.8.1.tgz", - "integrity": "sha512-9BodQuihWm3XJGKYuV/vXckK8Tkf9EDiT/au1NJeFUyBMe7EMYRtOqL9eLzrjqJSDJUFoGwQFHvraFHwR8cysQ==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/memfs/-/memfs-4.9.4.tgz", + "integrity": "sha512-1XyYPUaIHwEOdF19wYVLBtHJRr42Do+3ctht17cZOHwHf67vkmRNPlYDGY2kJps4RgE5+c7nEZmEzxxvb1NZWA==", "peer": true, "dependencies": { "assert": "^2.0.0", "buffer": "^6.0.3", "events": "^3.3.0", - "memfs": "^4.8.1", + "memfs": "^4.9.3", "path": "^0.12.7", - "stream": "^0.0.2", + "stream": "^0.0.3", "util": "^0.12.5" } }, - "node_modules/@bundled-es-modules/memfs/node_modules/stream": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz", - "integrity": "sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g==", - "peer": true, - "dependencies": { - "emitter-component": "^1.1.1" - } - }, "node_modules/@bundled-es-modules/postcss-calc-ast-parser": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@bundled-es-modules/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.6.tgz", @@ -3983,15 +3974,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "node_modules/emitter-component": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz", - "integrity": "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -9944,15 +9926,15 @@ } }, "node_modules/style-dictionary": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-dictionary/-/style-dictionary-4.0.0.tgz", - "integrity": "sha512-7UA+gdkNumWwKF8EIWIxvY2kNjNizeozA5P04c9EDZzP/bMAqTgf3GpwDNEFSv9QFAjQ/KXEhfyWZZDUoIKuLA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/style-dictionary/-/style-dictionary-4.0.1.tgz", + "integrity": "sha512-aZ2iouI0i0DIXk3QhCkwOeo5rQeuk5Ja0PhHo32/EXCNuay4jK4CZ+hQJW0Er0J74VWniR+qaeoWgjklcULxOQ==", "hasInstallScript": true, "peer": true, "dependencies": { "@bundled-es-modules/deepmerge": "^4.3.1", "@bundled-es-modules/glob": "^10.4.2", - "@bundled-es-modules/memfs": "^4.8.1", + "@bundled-es-modules/memfs": "^4.9.4", "@zip.js/zip.js": "^2.7.44", "chalk": "^5.3.0", "change-case": "^5.3.0", diff --git a/package.json b/package.json index fe9203f..2df3983 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "is-mergeable-object": "^1.1.1" }, "peerDependencies": { - "style-dictionary": "^4.0.0" + "style-dictionary": "^4.0.1" }, "devDependencies": { "@changesets/cli": "^2.27.6", diff --git a/src/index.ts b/src/index.ts index 3b2e6f6..1628a4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,10 @@ export const expandTypesMap = { paragraphIndent: 'dimension', textDecoration: 'other', textCase: 'other', + // even though this type does not yet exist in DTCG, it really should, since lineHeights can be both dimension or number + lineHeight: 'lineHeight', + // same as lineHeight except for fontWeight: recognized fontWeight keys (e.g. "regular") should be recognized as well as numbers + fontWeight: 'fontWeight', }, /** * boxShadow/x/y are not shadow/offsetX/offsetY here because the SD expand on global level diff --git a/src/preprocessors/add-font-styles.ts b/src/preprocessors/add-font-styles.ts index e6842c3..84156d3 100644 --- a/src/preprocessors/add-font-styles.ts +++ b/src/preprocessors/add-font-styles.ts @@ -66,15 +66,15 @@ function recurse( if (tokenType === 'typography') { const tokenTypographyValue = tokenValue as TokenTypographyValue & { fontStyle: string }; if (tokenTypographyValue.fontWeight === undefined) return; + const fontWeight = resolveFontWeight( `${tokenTypographyValue.fontWeight}`, refCopy, usesDtcg, ); const { weight, style } = splitWeightStyle(fontWeight, alwaysAddFontStyle); - - tokenTypographyValue.fontWeight = weight; if (style) { + tokenTypographyValue.fontWeight = weight; tokenTypographyValue.fontStyle = style; } } else if (tokenType === 'fontWeight') { @@ -82,13 +82,19 @@ function recurse( const fontWeight = resolveFontWeight(`${tokenFontWeightsValue}`, refCopy, usesDtcg); const { weight, style } = splitWeightStyle(fontWeight, alwaysAddFontStyle); - // since tokenFontWeightsValue is a primitive (string), we have to permutate the change directly - token[`${usesDtcg ? '$' : ''}value`] = weight; if (style) { - (slice as DeepKeyTokenMap)[`fontStyle`] = { - ...token, - [`${usesDtcg ? '$' : ''}type`]: 'fontStyle', - [`${usesDtcg ? '$' : ''}value`]: style, + // since tokenFontWeightsValue is a primitive (string), we have to permutate the change directly + (slice[key] as DeepKeyTokenMap) = { + weight: { + ...token, + [`${usesDtcg ? '$' : ''}type`]: 'fontWeight', + [`${usesDtcg ? '$' : ''}value`]: weight, + }, + style: { + ...token, + [`${usesDtcg ? '$' : ''}type`]: 'fontStyle', + [`${usesDtcg ? '$' : ''}value`]: style, + }, }; } } diff --git a/src/preprocessors/align-types.ts b/src/preprocessors/align-types.ts index c17157b..eea4a67 100644 --- a/src/preprocessors/align-types.ts +++ b/src/preprocessors/align-types.ts @@ -1,4 +1,5 @@ import { DeepKeyTokenMap, SingleToken, TokenTypes } from '@tokens-studio/types'; +import { typeDtcgDelegate } from 'style-dictionary/utils'; // TODO: composition tokens props also need the same types alignments.. // nested composition tokens are out of scope. @@ -86,7 +87,8 @@ function recurse(slice: DeepKeyTokenMap | SingleToken) { } export function alignTypes(dictionary: DeepKeyTokenMap): DeepKeyTokenMap { - const copy = structuredClone(dictionary); + // pre-emptively type dtcg delegate because otherwise we cannot align types properly here + const copy = typeDtcgDelegate(structuredClone(dictionary)); recurse(copy); return copy; } diff --git a/test/integration/cross-file-refs.test.ts b/test/integration/cross-file-refs.test.ts index 3b47834..7221a6d 100644 --- a/test/integration/cross-file-refs.test.ts +++ b/test/integration/cross-file-refs.test.ts @@ -32,7 +32,10 @@ let dict: StyleDictionary | undefined; describe('cross file references', () => { beforeEach(async () => { cleanup(dict); - dict = await init(cfg, { withSDBuiltins: false, expand: { typography: true } }); + dict = await init(cfg, { + withSDBuiltins: false, + expand: { typography: true }, + }); }); afterEach(async () => { @@ -67,14 +70,14 @@ describe('cross file references', () => { --sdTestTypographyTextFontSize: 25px; --sdTestTypographyTextLineHeight: 32px; --sdTestTypographyTextFontWeight: 700; - --sdWeight: 400; + --sdWeightWeight: 400; + --sdWeightStyle: italic; --sdTypoAliasFontWeight: 400; --sdTypoAliasFontStyle: italic; --sdTypo3FontFamily: Inter; --sdTypo3FontWeight: 800; --sdTypo3LineHeight: 1.5; --sdTypo3FontSize: 8px; - --sdFontStyle: italic; `); }); }); diff --git a/test/integration/expand-composition.test.ts b/test/integration/expand-composition.test.ts index 77968bc..2075c21 100644 --- a/test/integration/expand-composition.test.ts +++ b/test/integration/expand-composition.test.ts @@ -70,11 +70,12 @@ describe('expand composition tokens', () => { --sdCompositionFontSize: 96px; --sdCompositionFontFamily: Roboto; --sdCompositionFontWeight: 700; - --sdCompositionHeaderFontFamilies: Roboto; - --sdCompositionHeaderFontSizes: 96px; - --sdCompositionHeaderFontWeights: 700; + --sdCompositionHeaderFontFamily: Roboto; + --sdCompositionHeaderFontSize: 96px; + --sdCompositionHeaderFontWeight: 700; --sdTypography: italic 800 26px/1.25 Arial; - --sdFontWeightRef: 800; + --sdFontWeightRefWeight: 800; + --sdFontWeightRefStyle: italic; --sdBorder: 4px solid #FFFF00; --sdShadowSingle: inset 0 4px 10px 0 rgba(0,0,0,0.4); --sdShadowDouble: inset 0 4px 10px 0 rgba(0,0,0,0.4), 0 8px 12px 5px rgba(0,0,0,0.4); @@ -91,9 +92,9 @@ describe('expand composition tokens', () => { --sdCompositionFontSize: 96px; --sdCompositionFontFamily: Roboto; --sdCompositionFontWeight: 700; - --sdCompositionHeaderFontFamilies: Roboto; - --sdCompositionHeaderFontSizes: 96px; - --sdCompositionHeaderFontWeights: 700; + --sdCompositionHeaderFontFamily: Roboto; + --sdCompositionHeaderFontSize: 96px; + --sdCompositionHeaderFontWeight: 700; --sdTypographyFontFamily: Arial; --sdTypographyFontWeight: 800; --sdTypographyLineHeight: 1.25; @@ -104,27 +105,28 @@ describe('expand composition tokens', () => { --sdTypographyTextDecoration: none; --sdTypographyTextCase: none; --sdTypographyFontStyle: italic; - --sdFontWeightRef: 800; + --sdFontWeightRefWeight: 800; + --sdFontWeightRefStyle: italic; --sdBorderColor: #ffff00; --sdBorderWidth: 4px; --sdBorderStyle: solid; - --sdShadowSingleX: 0rem; - --sdShadowSingleY: 4px; --sdShadowSingleBlur: 10px; --sdShadowSingleSpread: 0rem; --sdShadowSingleColor: rgba(0, 0, 0, 0.4); --sdShadowSingleType: innerShadow; - --sdShadowDouble1X: 0rem; - --sdShadowDouble1Y: 4px; + --sdShadowSingleOffsetX: 0rem; + --sdShadowSingleOffsetY: 4px; --sdShadowDouble1Blur: 10px; --sdShadowDouble1Spread: 0rem; --sdShadowDouble1Color: rgba(0, 0, 0, 0.4); --sdShadowDouble1Type: innerShadow; - --sdShadowDouble2X: 0rem; - --sdShadowDouble2Y: 8px; + --sdShadowDouble1OffsetX: 0rem; + --sdShadowDouble1OffsetY: 4px; --sdShadowDouble2Blur: 12px; --sdShadowDouble2Spread: 5px; - --sdShadowDouble2Color: rgba(0, 0, 0, 0.4);`, + --sdShadowDouble2Color: rgba(0, 0, 0, 0.4); + --sdShadowDouble2OffsetX: 0rem; + --sdShadowDouble2OffsetY: 8px;`, ); }); @@ -159,17 +161,17 @@ describe('expand composition tokens', () => { const file = await promises.readFile(outputFilePath, 'utf-8'); expect(file).to.include( ` - --sdDeepRefShadowMulti1X: 0rem; - --sdDeepRefShadowMulti1Y: 4px; --sdDeepRefShadowMulti1Blur: 10px; --sdDeepRefShadowMulti1Spread: 0rem; --sdDeepRefShadowMulti1Color: rgba(0, 0, 0, 0.4); --sdDeepRefShadowMulti1Type: innerShadow; - --sdDeepRefShadowMulti2X: 0rem; - --sdDeepRefShadowMulti2Y: 8px; + --sdDeepRefShadowMulti1OffsetX: 0rem; + --sdDeepRefShadowMulti1OffsetY: 4px; --sdDeepRefShadowMulti2Blur: 12px; --sdDeepRefShadowMulti2Spread: 5px; - --sdDeepRefShadowMulti2Color: rgba(0, 0, 0, 0.4)`, + --sdDeepRefShadowMulti2Color: rgba(0, 0, 0, 0.4); + --sdDeepRefShadowMulti2OffsetX: 0rem; + --sdDeepRefShadowMulti2OffsetY: 8px;`, ); }); }); diff --git a/test/integration/tokens/expand-composition.tokens.json b/test/integration/tokens/expand-composition.tokens.json index fd1d13b..7510544 100644 --- a/test/integration/tokens/expand-composition.tokens.json +++ b/test/integration/tokens/expand-composition.tokens.json @@ -22,9 +22,9 @@ }, "header": { "value": { - "fontFamilies": "{composition.fontFamily}", - "fontSizes": "96", - "fontWeights": "{composition.fontWeight}" + "fontFamily": "{composition.fontFamily}", + "fontSize": "96", + "fontWeight": "{composition.fontWeight}" }, "type": "composition" } diff --git a/test/spec/preprocessors/add-font-styles.spec.ts b/test/spec/preprocessors/add-font-styles.spec.ts index 1c11e13..8175f9e 100644 --- a/test/spec/preprocessors/add-font-styles.spec.ts +++ b/test/spec/preprocessors/add-font-styles.spec.ts @@ -106,8 +106,13 @@ describe('add font style', () => { fw: { value: 'SemiBold Italic', type: 'fontWeight' }, }), ).to.eql({ - fw: { value: 'SemiBold', type: 'fontWeight' }, - fontStyle: { value: 'italic', type: 'fontStyle' }, + fw: { + weight: { + value: 'SemiBold', + type: 'fontWeight', + }, + style: { value: 'italic', type: 'fontStyle' }, + }, }); }); @@ -122,8 +127,10 @@ describe('add font style', () => { }), ).to.eql({ fw: { - weight: { $value: 'SemiBold', $type: 'fontWeight' }, - fontStyle: { $value: 'italic', $type: 'fontStyle' }, + weight: { + weight: { $value: 'SemiBold', $type: 'fontWeight' }, + style: { $value: 'italic', $type: 'fontStyle' }, + }, }, }); }); @@ -244,7 +251,7 @@ describe('add font style', () => { }); }); - it('handlestinvalid fontweight structures e.g. mixing token group / token, also handles DTCG format', () => { + it('handles invalid fontweight structures e.g. mixing token group / token, also handles DTCG format', () => { // @ts-expect-error aligned types already here const tokens = { foo: { @@ -277,7 +284,7 @@ describe('add font style', () => { thing: { $type: 'typography', $value: { - fontWeight: '700', + fontWeight: '{foo}', }, }, }); diff --git a/test/spec/register.spec.ts b/test/spec/register.spec.ts index 9a65fac..6e4d1cb 100644 --- a/test/spec/register.spec.ts +++ b/test/spec/register.spec.ts @@ -178,7 +178,7 @@ const tokens = { }, type: 'typography', }, - 'shadow-blur': { + blur: { value: '10', type: 'sizing', }, @@ -187,7 +187,7 @@ const tokens = { { x: '0', y: '4', - blur: '{shadow-blur}', + blur: '{blur}', spread: '0', color: 'rgba(0,0,0,0.4)', type: 'innerShadow', @@ -337,7 +337,7 @@ describe('register', () => { --fontSizesH6: 16px; --fontSizesBody: 16px; --heading6: 700 16px/1 'Arial Black', 'Suisse Int\\'l', sans-serif; - --shadowBlur: 10px; + --blur: 10px; --shadow: inset 0 4px 10px 0 rgba(0,0,0,0.4); --borderWidth: 5px; --border: 5px solid #000000; @@ -352,7 +352,7 @@ describe('register', () => { tokens, preprocessors: ['tokens-studio'], expand: { - include: ['border', 'boxShadow'], + include: ['border', 'shadow'], }, platforms: { compose: { @@ -386,6 +386,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.* object bar { + val blur = 10px val borderColor = #000000 val borderStyle = solid val borderWidth = 5px @@ -423,12 +424,11 @@ Arial Black, Suisse Int'l, sans-serif val lineHeightsHeading = 1.1 val opacity = 0.25 val shadowBlur = 10px - val shadowBlur = 10px val shadowColor = rgba(0,0,0,0.4) + val shadowOffsetX = 0 + val shadowOffsetY = 4px val shadowSpread = 0 val shadowType = innerShadow - val shadowX = 0 - val shadowY = 4 /** You can have multiple values in a single spacing token. Read more on these: https://docs.tokens.studio/available-tokens/spacing-tokens#multi-value-spacing-tokens */ val spacingMultiValue = 8px 64px val spacingSm = 8px