diff --git a/package-lock.json b/package-lock.json index 4590a6716..be1d13e1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@primer/primitives", - "version": "10.7.0", + "version": "11.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@primer/primitives", - "version": "10.7.0", + "version": "11.0.0", "license": "MIT", "devDependencies": { "@actions/core": "^1.11.1", diff --git a/src/formats/jsonFigma.ts b/src/formats/jsonFigma.ts index 5a276a994..0a266ed1a 100644 --- a/src/formats/jsonFigma.ts +++ b/src/formats/jsonFigma.ts @@ -7,6 +7,9 @@ import type {RgbaFloat} from '../transformers/utilities/isRgbaFloat.js' import {isRgbaFloat} from '../transformers/utilities/isRgbaFloat.js' import {getReferences, sortByReference} from 'style-dictionary/utils' +// Type for dimension value in new W3C object format +type DimensionValue = {value: number; unit: string} + const isReference = (string: string): boolean => /^\{([^\\]*)\}$/g.test(string) const getReference = (dictionary: Dictionary, refString: string, platform: PlatformConfig) => { @@ -32,19 +35,31 @@ const getFigmaType = (type: string): string => { const shadowToVariables = ( name: string, - values: Omit & {color: string | RgbaFloat}, + values: Omit & { + color: string | RgbaFloat + offsetX: DimensionValue + offsetY: DimensionValue + blur: DimensionValue + spread: DimensionValue + }, token: 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 floatValue = (property: 'offsetX' | 'offsetY' | 'blur' | 'spread') => { + const dimValue = values[property] + // New object format like {value: 1, unit: "px"} + const numValue = dimValue.value + + return { + name: `${name}/${property}`, + value: numValue, + type: 'FLOAT', + scopes: ['EFFECT_FLOAT'], + mode, + collection, + group, + } + } const {attributes} = token const {mode, collection, group} = attributes || {} diff --git a/src/platforms/styleLint.ts b/src/platforms/styleLint.ts index 212e3fa1d..af77bb6dc 100644 --- a/src/platforms/styleLint.ts +++ b/src/platforms/styleLint.ts @@ -9,7 +9,7 @@ export const styleLint: PlatformInitializer = (outputFile, prefix, buildPath, op transforms: [ 'name/pathToKebabCase', 'color/hex', - 'dimension/remPxArray', + 'dimension/pixel', 'shadow/css', 'border/css', 'typography/css', diff --git a/src/primerStyleDictionary.ts b/src/primerStyleDictionary.ts index e6dbaabda..541eb8934 100644 --- a/src/primerStyleDictionary.ts +++ b/src/primerStyleDictionary.ts @@ -6,6 +6,7 @@ import { colorToRgbaFloat, cubicBezierToCss, dimensionToRem, + dimensionToPixel, dimensionToPixelUnitless, durationToCss, figmaAttributes, @@ -123,6 +124,8 @@ PrimerStyleDictionary.registerTransform(floatToPixelUnitless) PrimerStyleDictionary.registerTransform(dimensionToRem) +PrimerStyleDictionary.registerTransform(dimensionToPixel) + PrimerStyleDictionary.registerTransform(dimensionToRemPxArray) PrimerStyleDictionary.registerTransform(dimensionToPixelUnitless) diff --git a/src/schemas/borderTokenSchema.test.ts b/src/schemas/borderTokenSchema.test.ts index 03a7792ca..efce3a063 100644 --- a/src/schemas/borderTokenSchema.test.ts +++ b/src/schemas/borderTokenSchema.test.ts @@ -3,7 +3,7 @@ import {borderToken, borderValue} from './borderToken.js' const validBorderValue = { color: '#333', style: 'solid', - width: '1px', + width: {value: 1, unit: 'px'}, } describe('Schema: borderValue', () => { diff --git a/src/schemas/dimensionTokenSchema.test.ts b/src/schemas/dimensionTokenSchema.test.ts index 09e9e870b..09746ad1f 100644 --- a/src/schemas/dimensionTokenSchema.test.ts +++ b/src/schemas/dimensionTokenSchema.test.ts @@ -2,15 +2,15 @@ import {dimensionToken} from './dimensionToken.js' describe('Schema: dimensionToken', () => { const validToken = { - $value: '1px', + $value: {value: 1, unit: 'px'}, $type: 'dimension', $description: 'a dimension token', } it('passes on valid values', () => { expect(dimensionToken.safeParse(validToken).success).toStrictEqual(true) - expect(dimensionToken.safeParse({...validToken, $value: '1em'}).success).toStrictEqual(true) - expect(dimensionToken.safeParse({...validToken, $value: '1rem'}).success).toStrictEqual(true) + expect(dimensionToken.safeParse({...validToken, $value: {value: 1, unit: 'em'}}).success).toStrictEqual(true) + expect(dimensionToken.safeParse({...validToken, $value: {value: 1, unit: 'rem'}}).success).toStrictEqual(true) }) it('fails on invalid type', () => { @@ -19,10 +19,13 @@ describe('Schema: dimensionToken', () => { it('fails on invalid value', () => { expect(dimensionToken.safeParse({...validToken, $value: 'wrong'}).success).toStrictEqual(false) + expect(dimensionToken.safeParse({...validToken, $value: '1px'}).success).toStrictEqual(false) expect(dimensionToken.safeParse({...validToken, $value: '1%'}).success).toStrictEqual(false) expect(dimensionToken.safeParse({...validToken, $value: undefined}).success).toStrictEqual(false) expect(dimensionToken.safeParse({...validToken, $value: ''}).success).toStrictEqual(false) expect(dimensionToken.safeParse({...validToken, $value: false}).success).toStrictEqual(false) expect(dimensionToken.safeParse({...validToken, $value: 1}).success).toStrictEqual(false) + expect(dimensionToken.safeParse({...validToken, $value: {value: 1, unit: '%'}}).success).toStrictEqual(false) + expect(dimensionToken.safeParse({...validToken, $value: {value: 'wrong', unit: 'px'}}).success).toStrictEqual(false) }) }) diff --git a/src/schemas/dimensionValue.ts b/src/schemas/dimensionValue.ts index b62733223..d05a5fc99 100644 --- a/src/schemas/dimensionValue.ts +++ b/src/schemas/dimensionValue.ts @@ -1,16 +1,6 @@ import {z} from 'zod' -import {schemaErrorMessage} from '../utilities/index.js' -export const dimensionValue = z.union([ - z.string().refine( - dim => /(^-?[0-9]+(px|rem)$|^-?[0-9]+\.?[0-9]*em$)/.test(dim), - val => ({ - message: schemaErrorMessage( - `Invalid dimension: "${val}"`, - `Dimension must be a string with a unit (px, rem or em) or 0`, - ), - }), - ), - z.literal('0'), - z.literal(0), -]) +export const dimensionValue = z.object({ + value: z.number(), + unit: z.enum(['px', 'rem', 'em']), +}) diff --git a/src/schemas/dimensionValueSchema.test.ts b/src/schemas/dimensionValueSchema.test.ts index fd0cd7ff7..8bbe48921 100644 --- a/src/schemas/dimensionValueSchema.test.ts +++ b/src/schemas/dimensionValueSchema.test.ts @@ -1,21 +1,31 @@ import {dimensionValue} from './dimensionValue.js' describe('Schema: dimensionValue', () => { - it('passes on valid values', () => { - expect(dimensionValue.safeParse('1px').success).toStrictEqual(true) - expect(dimensionValue.safeParse('-1px').success).toStrictEqual(true) - expect(dimensionValue.safeParse('1em').success).toStrictEqual(true) - expect(dimensionValue.safeParse('1rem').success).toStrictEqual(true) - expect(dimensionValue.safeParse('0').success).toStrictEqual(true) - expect(dimensionValue.safeParse(0).success).toStrictEqual(true) + it('passes on valid object values', () => { + expect(dimensionValue.safeParse({value: 1, unit: 'px'}).success).toStrictEqual(true) + expect(dimensionValue.safeParse({value: -1, unit: 'px'}).success).toStrictEqual(true) + expect(dimensionValue.safeParse({value: 16, unit: 'rem'}).success).toStrictEqual(true) + expect(dimensionValue.safeParse({value: 1.5, unit: 'em'}).success).toStrictEqual(true) + expect(dimensionValue.safeParse({value: 0, unit: 'px'}).success).toStrictEqual(true) }) - it('fails on invalid value', () => { + it('fails on invalid object values', () => { + expect(dimensionValue.safeParse({value: 1, unit: '%'}).success).toStrictEqual(false) + expect(dimensionValue.safeParse({value: 'small', unit: 'px'}).success).toStrictEqual(false) + expect(dimensionValue.safeParse({value: 1}).success).toStrictEqual(false) + expect(dimensionValue.safeParse({unit: 'px'}).success).toStrictEqual(false) + expect(dimensionValue.safeParse({}).success).toStrictEqual(false) + }) + + it('fails on invalid values', () => { + expect(dimensionValue.safeParse('1px').success).toStrictEqual(false) expect(dimensionValue.safeParse('1%').success).toStrictEqual(false) expect(dimensionValue.safeParse(1).success).toStrictEqual(false) expect(dimensionValue.safeParse('small').success).toStrictEqual(false) expect(dimensionValue.safeParse('').success).toStrictEqual(false) expect(dimensionValue.safeParse(false).success).toStrictEqual(false) expect(dimensionValue.safeParse(undefined).success).toStrictEqual(false) + expect(dimensionValue.safeParse('0').success).toStrictEqual(false) + expect(dimensionValue.safeParse(0).success).toStrictEqual(false) }) }) diff --git a/src/schemas/shadowTokenSchema.test.ts b/src/schemas/shadowTokenSchema.test.ts index 6b0760542..0e4536dce 100644 --- a/src/schemas/shadowTokenSchema.test.ts +++ b/src/schemas/shadowTokenSchema.test.ts @@ -3,10 +3,10 @@ import {shadowValue, shadowToken} from './shadowToken.js' const tokenValue = { color: '#000000', alpha: 0.5, - offsetX: '4px', - offsetY: '4px', - blur: '2px', - spread: '2px', + offsetX: {value: 4, unit: 'px'}, + offsetY: {value: 4, unit: 'px'}, + blur: {value: 2, unit: 'px'}, + spread: {value: 2, unit: 'px'}, inset: false, } @@ -15,8 +15,14 @@ describe('Schema: shadowValue', () => { expect(shadowValue.safeParse(tokenValue).success).toStrictEqual(true) // without inset expect( - shadowValue.safeParse({color: '#000000', alpha: 0.5, offsetX: '4px', offsetY: '4px', blur: '2px', spread: '2px'}) - .success, + shadowValue.safeParse({ + color: '#000000', + alpha: 0.5, + offsetX: {value: 4, unit: 'px'}, + offsetY: {value: 4, unit: 'px'}, + blur: {value: 2, unit: 'px'}, + spread: {value: 2, unit: 'px'}, + }).success, ).toStrictEqual(true) }) diff --git a/src/schemas/typographyTokenSchema.test.ts b/src/schemas/typographyTokenSchema.test.ts index cd9ed2bc7..7d9a0b712 100644 --- a/src/schemas/typographyTokenSchema.test.ts +++ b/src/schemas/typographyTokenSchema.test.ts @@ -2,8 +2,8 @@ import {typographyToken, typographyValue} from './typographyToken.js' describe('Schema: typographyToken', () => { const validValue = { - fontSize: '16px', - lineHeight: '24px', + fontSize: {value: 16, unit: 'px'}, + lineHeight: {value: 24, unit: 'px'}, fontWeight: 600, fontFamily: 'Helvetica', } @@ -31,14 +31,16 @@ describe('Schema: typographyToken', () => { }) it('it fails on invalid fontSize values', () => { - expect(typographyValue.safeParse({...validValue, fontSize: '100%'}).success).toStrictEqual(false) + expect(typographyValue.safeParse({...validValue, fontSize: {value: 100, unit: '%'}}).success).toStrictEqual(false) + expect(typographyValue.safeParse({...validValue, fontSize: '16px'}).success).toStrictEqual(false) expect(typographyValue.safeParse({...validValue, fontSize: '100'}).success).toStrictEqual(false) expect(typographyValue.safeParse({...validValue, fontSize: ''}).success).toStrictEqual(false) expect(typographyValue.safeParse({...validValue, fontSize: 10}).success).toStrictEqual(false) }) it('it fails on invalid lineHeight values', () => { - expect(typographyValue.safeParse({...validValue, lineHeight: '100%'}).success).toStrictEqual(false) + expect(typographyValue.safeParse({...validValue, lineHeight: {value: 100, unit: '%'}}).success).toStrictEqual(false) + expect(typographyValue.safeParse({...validValue, lineHeight: '24px'}).success).toStrictEqual(false) expect(typographyValue.safeParse({...validValue, lineHeight: '100'}).success).toStrictEqual(false) expect(typographyValue.safeParse({...validValue, lineHeight: ''}).success).toStrictEqual(false) expect(typographyValue.safeParse({...validValue, lineHeight: 10}).success).toStrictEqual(false) diff --git a/src/tokens/base/size/size.json5 b/src/tokens/base/size/size.json5 index f18cdd392..ceb57428c 100644 --- a/src/tokens/base/size/size.json5 +++ b/src/tokens/base/size/size.json5 @@ -2,7 +2,7 @@ "base": { "size": { "2": { - "$value": "2px", + "$value": {"value": 2, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -12,7 +12,7 @@ } }, "4": { - "$value": "4px", + "$value": {"value": 4, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -22,7 +22,7 @@ } }, "6": { - "$value": "6px", + "$value": {"value": 6, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -32,7 +32,7 @@ } }, "8": { - "$value": "8px", + "$value": {"value": 8, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -42,7 +42,7 @@ } }, "12": { - "$value": "12px", + "$value": {"value": 12, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -52,7 +52,7 @@ } }, "16": { - "$value": "16px", + "$value": {"value": 16, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -62,7 +62,7 @@ } }, "20": { - "$value": "20px", + "$value": {"value": 20, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -72,7 +72,7 @@ } }, "24": { - "$value": "24px", + "$value": {"value": 24, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -82,7 +82,7 @@ } }, "28": { - "$value": "28px", + "$value": {"value": 28, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -92,7 +92,7 @@ } }, "32": { - "$value": "32px", + "$value": {"value": 32, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -102,7 +102,7 @@ } }, "36": { - "$value": "36px", + "$value": {"value": 36, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -112,7 +112,7 @@ } }, "40": { - "$value": "40px", + "$value": {"value": 40, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -122,7 +122,7 @@ } }, "44": { - "$value": "44px", + "$value": {"value": 44, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -132,7 +132,7 @@ } }, "48": { - "$value": "48px", + "$value": {"value": 48, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -142,7 +142,7 @@ } }, "64": { - "$value": "64px", + "$value": {"value": 64, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -152,7 +152,7 @@ } }, "80": { - "$value": "80px", + "$value": {"value": 80, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -162,7 +162,7 @@ } }, "96": { - "$value": "96px", + "$value": {"value": 96, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -172,7 +172,7 @@ } }, "112": { - "$value": "112px", + "$value": {"value": 112, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -182,7 +182,7 @@ } }, "128": { - "$value": "128px", + "$value": {"value": 128, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { diff --git a/src/tokens/component/avatar.json5 b/src/tokens/component/avatar.json5 index 97a1d1e99..f197f3276 100644 --- a/src/tokens/component/avatar.json5 +++ b/src/tokens/component/avatar.json5 @@ -56,10 +56,10 @@ { color: '{base.color.neutral.0}', alpha: 0.8, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '2px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 2, unit: 'px' }, }, ], $type: 'shadow', @@ -74,10 +74,10 @@ { color: '{base.color.neutral.1}', alpha: 1, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '2px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 2, unit: 'px' }, }, ], }, diff --git a/src/tokens/component/button.json5 b/src/tokens/component/button.json5 index 80329521b..806d5d2df 100644 --- a/src/tokens/component/button.json5 +++ b/src/tokens/component/button.json5 @@ -142,10 +142,10 @@ { color: '{base.color.neutral.13}', alpha: 0.04, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, ], @@ -161,10 +161,10 @@ { color: '{base.color.transparent}', alpha: 0, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, ], @@ -528,10 +528,10 @@ { color: '{base.color.green.9}', alpha: 0.3, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, ], @@ -547,10 +547,10 @@ { color: '{base.color.blue.9}', alpha: 0.3, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, ], @@ -560,10 +560,10 @@ { color: '{base.color.blue.9}', alpha: 0.3, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, ], @@ -573,10 +573,10 @@ { color: '{base.color.transparent}', alpha: 0, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, ], @@ -1095,10 +1095,10 @@ { color: '{base.color.blue.9}', alpha: 0.2, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, ], @@ -1114,10 +1114,10 @@ { color: '{base.color.transparent}', alpha: 0, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, ], @@ -1451,10 +1451,10 @@ { color: '{base.color.red.9}', alpha: 0.2, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, ], @@ -1470,10 +1470,10 @@ { color: '{base.color.orange.9}', alpha: 0.2, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, ], @@ -1483,10 +1483,10 @@ { color: '{base.color.transparent}', alpha: 0, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, ], diff --git a/src/tokens/functional/border/border.json5 b/src/tokens/functional/border/border.json5 index c904d9132..e011990d5 100644 --- a/src/tokens/functional/border/border.json5 +++ b/src/tokens/functional/border/border.json5 @@ -4,7 +4,7 @@ $value: { color: '{focus.outlineColor}', style: 'solid', - width: '2px', + width: { value: 2, unit: 'px' }, }, $type: 'border', }, diff --git a/src/tokens/functional/shadow/shadow.json5 b/src/tokens/functional/shadow/shadow.json5 index cee07738d..8d2db2c17 100644 --- a/src/tokens/functional/shadow/shadow.json5 +++ b/src/tokens/functional/shadow/shadow.json5 @@ -4,10 +4,10 @@ $value: { color: '{base.color.neutral.13}', alpha: 0.04, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, $type: 'shadow', @@ -21,10 +21,10 @@ $value: { color: '{base.color.neutral.0}', alpha: 0.24, - offsetX: '0px', - offsetY: '1px', - blur: '0px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: true, }, }, @@ -36,10 +36,10 @@ $value: { color: '{base.color.neutral.13}', alpha: 0.06, - offsetX: '0px', - offsetY: '1px', - blur: '1px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 1, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, $type: 'shadow', @@ -53,10 +53,10 @@ $value: { color: '{base.color.neutral.0}', alpha: 0.8, - offsetX: '0px', - offsetY: '1px', - blur: '1px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 1, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, }, @@ -68,19 +68,19 @@ { color: '{base.color.neutral.13}', alpha: 0.06, - offsetX: '0px', - offsetY: '1px', - blur: '1px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 1, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, { color: '{base.color.neutral.13}', alpha: 0.06, - offsetX: '0px', - offsetY: '1px', - blur: '3px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 3, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, ], @@ -96,19 +96,19 @@ { color: '{base.color.neutral.0}', alpha: 0.6, - offsetX: '0px', - offsetY: '1px', - blur: '1px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 1, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, { color: '{base.color.neutral.0}', alpha: 0.6, - offsetX: '0px', - offsetY: '1px', - blur: '3px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 3, unit: 'px' }, + spread: { value: 0, unit: 'px' }, inset: false, }, ], @@ -121,18 +121,18 @@ { color: '{base.color.neutral.12}', alpha: 0.1, - offsetX: '0px', - offsetY: '1px', - blur: '1px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 1, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.12, - offsetX: '0px', - offsetY: '3px', - blur: '6px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 3, unit: 'px' }, + blur: { value: 6, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], $type: 'shadow', @@ -147,18 +147,18 @@ { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '1px', - blur: '1px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 1, unit: 'px' }, + blur: { value: 1, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.8, - offsetX: '0px', - offsetY: '3px', - blur: '6px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 3, unit: 'px' }, + blur: { value: 6, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], }, @@ -172,26 +172,26 @@ { color: '{overlay.borderColor}', alpha: 0.5, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.04, - offsetX: '0px', - offsetY: '6px', - blur: '12px', - spread: '-3px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 12, unit: 'px' }, + spread: { value: -3, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.12, - offsetX: '0px', - offsetY: '6px', - blur: '18px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 18, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], $type: 'shadow', @@ -206,26 +206,26 @@ { color: '{overlay.borderColor}', alpha: 1, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '6px', - blur: '12px', - spread: '-3px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 12, unit: 'px' }, + spread: { value: -3, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '6px', - blur: '18px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 18, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], }, @@ -237,42 +237,42 @@ { color: '{overlay.borderColor}', alpha: 0, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.08, - offsetX: '0px', - offsetY: '8px', - blur: '16px', - spread: '-4px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 8, unit: 'px' }, + blur: { value: 16, unit: 'px' }, + spread: { value: -4, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.08, - offsetX: '0px', - offsetY: '4px', - blur: '32px', - spread: '-4px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 4, unit: 'px' }, + blur: { value: 32, unit: 'px' }, + spread: { value: -4, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.08, - offsetX: '0px', - offsetY: '24px', - blur: '48px', - spread: '-12px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 24, unit: 'px' }, + blur: { value: 48, unit: 'px' }, + spread: { value: -12, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.08, - offsetX: '0px', - offsetY: '48px', - blur: '96px', - spread: '-24px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 48, unit: 'px' }, + blur: { value: 96, unit: 'px' }, + spread: { value: -24, unit: 'px' }, }, ], $type: 'shadow', @@ -287,42 +287,42 @@ { color: '{overlay.borderColor}', alpha: 1, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '8px', - blur: '16px', - spread: '-4px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 8, unit: 'px' }, + blur: { value: 16, unit: 'px' }, + spread: { value: -4, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '4px', - blur: '32px', - spread: '-4px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 4, unit: 'px' }, + blur: { value: 32, unit: 'px' }, + spread: { value: -4, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '24px', - blur: '48px', - spread: '-12px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 24, unit: 'px' }, + blur: { value: 48, unit: 'px' }, + spread: { value: -12, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '48px', - blur: '96px', - spread: '-24px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 48, unit: 'px' }, + blur: { value: 96, unit: 'px' }, + spread: { value: -24, unit: 'px' }, }, ], }, @@ -334,18 +334,18 @@ { color: '{overlay.borderColor}', alpha: 0, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.24, - offsetX: '0px', - offsetY: '40px', - blur: '80px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 40, unit: 'px' }, + blur: { value: 80, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], $type: 'shadow', @@ -360,18 +360,18 @@ { color: '{overlay.borderColor}', alpha: 1, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 1, - offsetX: '0px', - offsetY: '24px', - blur: '48px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 24, unit: 'px' }, + blur: { value: 48, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], }, @@ -383,18 +383,18 @@ { color: '{overlay.borderColor}', alpha: 0, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.32, - offsetX: '0px', - offsetY: '56px', - blur: '112px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 56, unit: 'px' }, + blur: { value: 112, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], $type: 'shadow', @@ -409,18 +409,18 @@ { color: '{overlay.borderColor}', alpha: 1, - offsetX: '0px', - offsetY: '0px', - blur: '0px', - spread: '1px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 0, unit: 'px' }, + blur: { value: 0, unit: 'px' }, + spread: { value: 1, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 1, - offsetX: '0px', - offsetY: '32px', - blur: '64px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 32, unit: 'px' }, + blur: { value: 64, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], }, @@ -432,18 +432,18 @@ { color: '{base.color.neutral.12}', alpha: 0.04, - offsetX: '0px', - offsetY: '6px', - blur: '12px', - spread: '-3px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 12, unit: 'px' }, + spread: { value: -3, unit: 'px' }, }, { color: '{base.color.neutral.12}', alpha: 0.12, - offsetX: '0px', - offsetY: '6px', - blur: '18px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 18, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], $type: 'shadow', @@ -455,18 +455,18 @@ { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '6px', - blur: '12px', - spread: '-3px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 12, unit: 'px' }, + spread: { value: -3, unit: 'px' }, }, { color: '{base.color.neutral.0}', alpha: 0.4, - offsetX: '0px', - offsetY: '6px', - blur: '18px', - spread: '0px', + offsetX: { value: 0, unit: 'px' }, + offsetY: { value: 6, unit: 'px' }, + blur: { value: 18, unit: 'px' }, + spread: { value: 0, unit: 'px' }, }, ], }, diff --git a/src/tokens/functional/size/border.json5 b/src/tokens/functional/size/border.json5 index 37700f6cf..a147c9b95 100644 --- a/src/tokens/functional/size/border.json5 +++ b/src/tokens/functional/size/border.json5 @@ -26,7 +26,7 @@ }, }, thin: { - $value: '1px', + $value: { value: 1, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -36,7 +36,7 @@ }, }, thick: { - $value: '2px', + $value: { value: 2, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -46,7 +46,7 @@ }, }, thicker: { - $value: '4px', + $value: { value: 4, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -58,7 +58,7 @@ }, borderRadius: { small: { - $value: '3px', + $value: { value: 3, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -71,7 +71,7 @@ }, }, medium: { - $value: '6px', + $value: { value: 6, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -84,7 +84,7 @@ }, }, large: { - $value: '12px', + $value: { value: 12, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -97,7 +97,7 @@ }, }, full: { - $value: '9999px', + $value: { value: 9999, unit: 'px' }, $type: 'dimension', $description: 'Use this border radius for pill shaped elements', $extensions: { @@ -127,7 +127,7 @@ outline: { focus: { offset: { - $value: '-2px', + $value: { value: -2, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -137,7 +137,7 @@ }, }, width: { - $value: '2px', + $value: { value: 2, unit: 'px' }, $type: 'dimension', $extensions: { 'org.primer.figma': { diff --git a/src/tokens/functional/size/breakpoints.json5 b/src/tokens/functional/size/breakpoints.json5 index e1458693a..b263ea220 100644 --- a/src/tokens/functional/size/breakpoints.json5 +++ b/src/tokens/functional/size/breakpoints.json5 @@ -1,7 +1,7 @@ { "breakpoint": { "xsmall": { - "$value": "320px", + "$value": {"value": 320, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -11,7 +11,7 @@ } }, "small": { - "$value": "544px", + "$value": {"value": 544, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -21,7 +21,7 @@ } }, "medium": { - "$value": "768px", + "$value": {"value": 768, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -31,7 +31,7 @@ } }, "large": { - "$value": "1012px", + "$value": {"value": 1012, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -41,7 +41,7 @@ } }, "xlarge": { - "$value": "1280px", + "$value": {"value": 1280, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -51,7 +51,7 @@ } }, "xxlarge": { - "$value": "1400px", + "$value": {"value": 1400, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { diff --git a/src/tokens/functional/size/size.json5 b/src/tokens/functional/size/size.json5 index 685831b9d..b203cff46 100644 --- a/src/tokens/functional/size/size.json5 +++ b/src/tokens/functional/size/size.json5 @@ -44,7 +44,7 @@ } }, "paddingBlock": { - "$value": "2px", + "$value": {"value": 2, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -182,7 +182,7 @@ } }, "paddingBlock": { - "$value": "6px", + "$value": {"value": 6, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -256,7 +256,7 @@ } }, "paddingBlock": { - "$value": "10px", + "$value": {"value": 10, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -320,7 +320,7 @@ } }, "paddingBlock": { - "$value": "14px", + "$value": {"value": 14, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -366,7 +366,7 @@ "spinner": { "strokeWidth": { "default": { - "$value": "2px", + "$value": {"value": 2, "unit": "px"}, "$type": "dimension" } }, @@ -580,7 +580,7 @@ "overlay": { "width": { "xsmall": { - "$value": "192px", + "$value": {"value": 192, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -590,7 +590,7 @@ } }, "small": { - "$value": "320px", + "$value": {"value": 320, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -600,7 +600,7 @@ } }, "medium": { - "$value": "480px", + "$value": {"value": 480, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -610,7 +610,7 @@ } }, "large": { - "$value": "640px", + "$value": {"value": 640, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -620,7 +620,7 @@ } }, "xlarge": { - "$value": "960px", + "$value": {"value": 960, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -632,7 +632,7 @@ }, "height": { "small": { - "$value": "256px", + "$value": {"value": 256, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -642,7 +642,7 @@ } }, "medium": { - "$value": "320px", + "$value": {"value": 320, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -652,7 +652,7 @@ } }, "large": { - "$value": "432px", + "$value": {"value": 432, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -662,7 +662,7 @@ } }, "xlarge": { - "$value": "600px", + "$value": {"value": 600, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { @@ -727,7 +727,7 @@ } }, "offset": { - "$value": "4px", + "$value": {"value": 4, "unit": "px"}, "$type": "dimension", "$extensions": { "org.primer.figma": { diff --git a/src/tokens/functional/typography/typography.json5 b/src/tokens/functional/typography/typography.json5 index bf7a9c038..7bd25e5ef 100644 --- a/src/tokens/functional/typography/typography.json5 +++ b/src/tokens/functional/typography/typography.json5 @@ -58,7 +58,7 @@ }, }, size: { - $value: '40px', + $value: {"value": 40, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -104,7 +104,7 @@ title: { size: { large: { - $value: '32px', + $value: {"value": 32, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -114,7 +114,7 @@ }, }, medium: { - $value: '20px', + $value: {"value": 20, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -124,7 +124,7 @@ }, }, small: { - $value: '16px', + $value: {"value": 16, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -242,7 +242,7 @@ }, subtitle: { size: { - $value: '20px', + $value: {"value": 20, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -288,7 +288,7 @@ body: { size: { large: { - $value: '16px', + $value: {"value": 16, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -298,7 +298,7 @@ }, }, medium: { - $value: '14px', + $value: {"value": 14, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -308,7 +308,7 @@ }, }, small: { - $value: '12px', + $value: {"value": 12, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -404,7 +404,7 @@ }, caption: { size: { - $value: '12px', + $value: {"value": 12, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -449,7 +449,7 @@ }, codeBlock: { size: { - $value: '13px', + $value: {"value": 13, "unit": "px"}, $type: 'dimension', $extensions: { 'org.primer.figma': { @@ -494,7 +494,7 @@ }, codeInline: { size: { - $value: '0.9285em', + $value: {"value": 0.9285, "unit": "em"}, $type: 'dimension', $extensions: { 'org.primer.figma': { diff --git a/src/transformers/borderToCss.test.ts b/src/transformers/borderToCss.test.ts index 47be3348a..134523dcd 100644 --- a/src/transformers/borderToCss.test.ts +++ b/src/transformers/borderToCss.test.ts @@ -2,7 +2,7 @@ import {getMockToken} from '../test-utilities/index.js' import {borderToCss} from './borderToCss.js' describe('Transformer: borderToCss', () => { - it('transforms `border` token to css border string', () => { + it('transforms `border` token to css border string with string width', () => { const input = getMockToken({ $value: { color: '#000000', @@ -15,8 +15,43 @@ describe('Transformer: borderToCss', () => { expect(borderToCss.transform(input, {}, {})).toStrictEqual(expectedOutput) }) + it('transforms `border` token to css border string with dimension object width', () => { + const input = getMockToken({ + $value: { + color: '#000000', + style: 'solid', + width: {value: 2, unit: 'px'}, + }, + }) + + const expectedOutput = '2px solid #000000' + expect(borderToCss.transform(input, {}, {})).toStrictEqual(expectedOutput) + }) + + it('transforms `border` token to css border string with array width from dimension/remPxArray', () => { + const input = getMockToken({ + $value: { + color: '#000000', + style: 'solid', + width: ['0.125rem', '2px'], // Array from dimension/remPxArray transformer + }, + }) + + const expectedOutput = '2px solid #000000' // Should use px value for styleLint + expect(borderToCss.transform(input, {}, {})).toStrictEqual(expectedOutput) + }) + + it('returns already transformed string values as-is', () => { + const input = getMockToken({ + $value: '1px solid #000000', + }) + + const expectedOutput = '1px solid #000000' + expect(borderToCss.transform(input, {}, {})).toStrictEqual(expectedOutput) + }) + it('throws an error when required values are missing', () => { - // missing blur + // missing width expect(() => borderToCss.transform( getMockToken({ @@ -30,7 +65,7 @@ describe('Transformer: borderToCss', () => { ), ).toThrowError() - // missing spread + // missing style expect(() => borderToCss.transform( getMockToken({ @@ -44,7 +79,7 @@ describe('Transformer: borderToCss', () => { ), ).toThrowError() - // missing offsets + // missing color expect(() => borderToCss.transform( getMockToken({ @@ -58,4 +93,20 @@ describe('Transformer: borderToCss', () => { ), ).toThrowError() }) + + it('throws an error for invalid width values', () => { + expect(() => + borderToCss.transform( + getMockToken({ + $value: { + color: '#000000', + style: 'solid', + width: 123, // Invalid: number instead of string or object + }, + }), + {}, + {}, + ), + ).toThrowError('Invalid width value') + }) }) diff --git a/src/transformers/borderToCss.ts b/src/transformers/borderToCss.ts index 9c0edf635..7fe8d519b 100644 --- a/src/transformers/borderToCss.ts +++ b/src/transformers/borderToCss.ts @@ -1,6 +1,7 @@ import type {Transform, TransformedToken} from 'style-dictionary/types' import {isBorder} from '../filters/isBorder.js' import type {BorderTokenValue} from '../types/borderTokenValue.js' +import {dimensionToString} from './utilities/dimensionToString.js' /** * checks if all required properties exist on shadow token @@ -13,6 +14,31 @@ const checkForBorderTokenProperties = (border: Record): border } return false } + +/** + * Converts width value to string, handling different formats + * @param width - The width value (string, dimension object, or array from dimension/remPxArray) + * @returns CSS width string + */ +const getWidthString = (width: unknown): string => { + // If it's already a string, return as is + if (typeof width === 'string') { + return width + } + + // If it's an array from dimension/remPxArray transformer, use the px value (second element) + if (Array.isArray(width) && width.length === 2) { + return width[1] // Return the px value for styleLint compatibility + } + + // If it's a dimension object, convert to string + if (typeof width === 'object' && width !== null && 'value' in width && 'unit' in width) { + return dimensionToString(width as {value: number; unit: string}) + } + + throw new Error(`Invalid width value: ${JSON.stringify(width)}`) +} + /** * @description converts w3c border tokens in css border string * @type valueTransformer — [StyleDictionary.ValueTransform](https://github.com/amzn/style-dictionary/blob/main/types/Transform.d.ts) @@ -36,7 +62,10 @@ export const borderToCss: Transform = { `Invalid border token property ${JSON.stringify(value)}. Must be an object with color, width and style properties.`, ) } + + const widthString = getWidthString(value.width) + /* width | style | color */ - return `${value.width} ${value.style} ${value.color}` + return `${widthString} ${value.style} ${value.color}` }, } diff --git a/src/transformers/dimensionToPixel.test.ts b/src/transformers/dimensionToPixel.test.ts new file mode 100644 index 000000000..5adb289bb --- /dev/null +++ b/src/transformers/dimensionToPixel.test.ts @@ -0,0 +1,78 @@ +import {dimensionToPixel} from './dimensionToPixel.js' +import {getMockToken} from '../test-utilities/index.js' + +describe('Transform: dimensionToPixel', () => { + it('transforms `px` dimension token', () => { + const input = [ + getMockToken({ + value: {value: 16, unit: 'px'}, + }), + ] + const expectedOutput = ['16px'] + expect(input.map(item => dimensionToPixel.transform(item, {basePxFontSize: 16}, {}))).toStrictEqual(expectedOutput) + }) + + it('transforms `rem` dimension token', () => { + const input = [ + getMockToken({ + value: {value: 1, unit: 'rem'}, + }), + ] + const expectedOutput = ['16px'] + expect(input.map(item => dimensionToPixel.transform(item, {basePxFontSize: 16}, {}))).toStrictEqual(expectedOutput) + }) + + it('transforms `em` dimension token', () => { + const input = [ + getMockToken({ + value: {value: 1.5, unit: 'em'}, + }), + ] + const expectedOutput = ['1.5em'] + expect(input.map(item => dimensionToPixel.transform(item, {basePxFontSize: 16}, {}))).toStrictEqual(expectedOutput) + }) + + it('transforms dimension token with `0` value', () => { + const input = [ + getMockToken({ + value: {value: 0, unit: 'px'}, + }), + ] + const expectedOutput = ['0'] + expect(input.map(item => dimensionToPixel.transform(item, {basePxFontSize: 16}, {}))).toStrictEqual(expectedOutput) + }) + + it('transforms dimension token with decimal value', () => { + const input = [ + getMockToken({ + value: {value: 1.5, unit: 'px'}, + }), + ] + const expectedOutput = ['1.5px'] + expect(input.map(item => dimensionToPixel.transform(item, {basePxFontSize: 16}, {}))).toStrictEqual(expectedOutput) + }) + + it('uses custom base font size', () => { + const input = [ + getMockToken({ + value: {value: 1, unit: 'rem'}, + }), + ] + const expectedOutput = ['20px'] + expect(input.map(item => dimensionToPixel.transform(item, {basePxFontSize: 20}, {}))).toStrictEqual(expectedOutput) + }) + + it('throws error for invalid dimension value', () => { + const input = getMockToken({ + value: 'invalid', + }) + expect(() => dimensionToPixel.transform(input, {basePxFontSize: 16}, {})).toThrow('Invalid dimension token') + }) + + it('throws error for unsupported unit', () => { + const input = getMockToken({ + value: {value: 16, unit: 'vh'}, + }) + expect(() => dimensionToPixel.transform(input, {basePxFontSize: 16}, {})).toThrow('Invalid dimension token') + }) +}) diff --git a/src/transformers/dimensionToPixel.ts b/src/transformers/dimensionToPixel.ts new file mode 100644 index 000000000..c68104c7b --- /dev/null +++ b/src/transformers/dimensionToPixel.ts @@ -0,0 +1,57 @@ +import {isDimension} from '../filters/index.js' +import type {Config, PlatformConfig, Transform, TransformedToken} from 'style-dictionary/types' + +/** + * @description base font size from options or 16 + * @param options + * @returns number + */ +const getBasePxFontSize = (options?: PlatformConfig): number => (options && options.basePxFontSize) || 16 + +/** + * @description converts dimension tokens value to `px` string, ignores `em` as they are relative to the font size of the parent element + * @type value transformer — [StyleDictionary.ValueTransform](https://github.com/amzn/style-dictionary/blob/main/types/Transform.d.ts) + * @matcher matches all tokens of $type `dimension` + * @transformer returns a `px` string + */ +export const dimensionToPixel: Transform = { + name: 'dimension/pixel', + type: 'value', + transitive: true, + filter: isDimension, + transform: (token: TransformedToken, config: PlatformConfig, options: Config) => { + const valueProp = options.usesDtcg ? '$value' : 'value' + const baseFont = getBasePxFontSize(config) + const dimensionValue = token[valueProp] as {value: number; unit: string} + + if (typeof dimensionValue !== 'object' || !('value' in dimensionValue) || !('unit' in dimensionValue)) { + throw new Error( + `Invalid dimension token: '${token.name}: ${JSON.stringify(token[valueProp])}' must be an object with value and unit properties \n`, + ) + } + + const {value, unit} = dimensionValue + + if (value === 0) { + return '0' + } + + if (unit === 'px') { + return `${value}px` + } + + if (unit === 'rem') { + // Convert rem to px + return `${value * baseFont}px` + } + + if (unit === 'em') { + // Keep em as is since it's relative + return `${value}em` + } + + throw new Error( + `Invalid dimension token: '${token.name}: ${JSON.stringify(token[valueProp])}' has unsupported unit '${unit}' \n`, + ) + }, +} diff --git a/src/transformers/dimensionToPixelUnitless.test.ts b/src/transformers/dimensionToPixelUnitless.test.ts index 25e124cc5..bba4e8ae5 100644 --- a/src/transformers/dimensionToPixelUnitless.test.ts +++ b/src/transformers/dimensionToPixelUnitless.test.ts @@ -2,33 +2,20 @@ import {getMockToken} from '../test-utilities/index.js' import {dimensionToPixelUnitless} from './dimensionToPixelUnitless.js' describe('Transformer: dimensionToPixelUnitless', () => { - it('transforms pixel string tokens', () => { + it('transforms pixel object tokens', () => { const input = [ getMockToken({ - value: '16px', + value: {value: 16, unit: 'px'}, }), ] const expectedOutput = [16] expect(input.map(item => dimensionToPixelUnitless.transform(item, {}, {}))).toStrictEqual(expectedOutput) }) - it('does not transforms number or number string', () => { - const input = [ - getMockToken({ - value: '16', - }), - getMockToken({ - value: 16, - }), - ] - const expectedOutput = ['16', 16] - expect(input.map(item => dimensionToPixelUnitless.transform(item, {}, {}))).toStrictEqual(expectedOutput) - }) - it('transforms rem', () => { const input = [ getMockToken({ - value: '1rem', + value: {value: 1, unit: 'rem'}, }), ] const expectedOutput = [16] @@ -38,7 +25,7 @@ describe('Transformer: dimensionToPixelUnitless', () => { it('transforms rem with custom basePxFontSize', () => { const input = [ getMockToken({ - value: '2rem', + value: {value: 2, unit: 'rem'}, }), ] const expectedOutput = [20] @@ -50,7 +37,7 @@ describe('Transformer: dimensionToPixelUnitless', () => { it('does not transforms em', () => { const input = [ getMockToken({ - value: '1em', + value: {value: 1, unit: 'em'}, }), ] const expectedOutput = ['1em'] @@ -60,13 +47,13 @@ describe('Transformer: dimensionToPixelUnitless', () => { it('transforms 0 to 0', () => { const input = [ getMockToken({ - value: '0rem', + value: {value: 0, unit: 'rem'}, }), getMockToken({ - value: '0px', + value: {value: 0, unit: 'px'}, }), getMockToken({ - value: '0', + value: {value: 0, unit: 'em'}, }), ] const expectedOutput = [0, 0, 0] @@ -78,6 +65,12 @@ describe('Transformer: dimensionToPixelUnitless', () => { getMockToken({ value: 'rem', }), + getMockToken({ + value: '16px', + }), + getMockToken({ + value: 16, + }), getMockToken({ value: '', }), @@ -87,7 +80,18 @@ describe('Transformer: dimensionToPixelUnitless', () => { getMockToken({ value: null, }), + getMockToken({ + value: {}, + }), + getMockToken({ + value: {value: 16}, + }), + getMockToken({ + value: {unit: 'px'}, + }), ] - expect(() => input.map(item => dimensionToPixelUnitless.transform(item, {}, {}))).toThrow() + for (const token of input) { + expect(() => dimensionToPixelUnitless.transform(token, {}, {})).toThrow() + } }) }) diff --git a/src/transformers/dimensionToPixelUnitless.ts b/src/transformers/dimensionToPixelUnitless.ts index f6b082743..62ca2a428 100644 --- a/src/transformers/dimensionToPixelUnitless.ts +++ b/src/transformers/dimensionToPixelUnitless.ts @@ -8,20 +8,6 @@ import type {PlatformConfig, Transform, TransformedToken, Config} from 'style-di */ const getBasePxFontSize = (options?: PlatformConfig): number => (options && options.basePxFontSize) || 16 -/** - * @description checks if token value has a specific unit - * @param value token value - * @param unit unit string like px or value - * @returns boolean - */ -const hasUnit = (value: string | number, unit: string): boolean => { - if (typeof value === 'number') { - return false - } - - return value.indexOf(unit) > -1 -} - /** * @description converts dimension tokens value to pixel value without unit, ignores `em` as they are relative to the font size of the parent element * @type value transformer — [StyleDictionary.ValueTransform](https://github.com/amzn/style-dictionary/blob/main/types/Transform.d.ts) @@ -36,25 +22,35 @@ export const dimensionToPixelUnitless: Transform = { transform: (token: TransformedToken, config: PlatformConfig, options: Config) => { const valueProp = options.usesDtcg ? '$value' : 'value' const baseFont = getBasePxFontSize(config) - const floatVal = parseFloat(token[valueProp]) - if (isNaN(floatVal)) { + const dimensionValue = token[valueProp] as {value: number; unit: string} + + if (typeof dimensionValue !== 'object' || !('value' in dimensionValue) || !('unit' in dimensionValue)) { throw new Error( - `Invalid dimension token: '${token.path.join('.')}: ${token[valueProp]}' is not valid and cannot be transform to 'float' \n`, + `Invalid dimension token: '${token.path.join('.')}: ${JSON.stringify(token[valueProp])}' must be an object with value and unit properties \n`, ) } - if (floatVal === 0) { + const {value, unit} = dimensionValue + + if (value === 0) { return 0 } - if (hasUnit(token[valueProp], 'rem')) { - return floatVal * baseFont + if (unit === 'px') { + return value + } + + if (unit === 'rem') { + return value * baseFont } - if (hasUnit(token[valueProp], 'px')) { - return floatVal + if (unit === 'em') { + // Return as string for em values (not converted) + return `${value}em` } - return token[valueProp] + throw new Error( + `Invalid dimension token: '${token.path.join('.')}: ${JSON.stringify(token[valueProp])}' has unsupported unit '${unit}' \n`, + ) }, } diff --git a/src/transformers/dimensionToRem.test.ts b/src/transformers/dimensionToRem.test.ts index 21c06bb89..b81ec2b08 100644 --- a/src/transformers/dimensionToRem.test.ts +++ b/src/transformers/dimensionToRem.test.ts @@ -2,33 +2,20 @@ import {getMockToken} from '../test-utilities/index.js' import {dimensionToRem} from './dimensionToRem.js' describe('Transformer: dimensionToRem', () => { - it('transforms pixel string tokens to rem', () => { + it('transforms pixel object tokens to rem', () => { const input = [ getMockToken({ - value: '16px', + value: {value: 16, unit: 'px'}, }), ] const expectedOutput = ['1rem'] expect(input.map(item => dimensionToRem.transform(item, {}, {}))).toStrictEqual(expectedOutput) }) - it('transforms number to rem', () => { - const input = [ - getMockToken({ - value: '16', - }), - getMockToken({ - value: 16, - }), - ] - const expectedOutput = ['1rem', '1rem'] - expect(input.map(item => dimensionToRem.transform(item, {}, {}))).toStrictEqual(expectedOutput) - }) - it('transforms rem to rem', () => { const input = [ getMockToken({ - value: '1rem', + value: {value: 1, unit: 'rem'}, }), ] const expectedOutput = ['1rem'] @@ -38,7 +25,7 @@ describe('Transformer: dimensionToRem', () => { it('does not transforms em to rem', () => { const input = [ getMockToken({ - value: '1em', + value: {value: 1, unit: 'em'}, }), ] const expectedOutput = ['1em'] @@ -48,13 +35,13 @@ describe('Transformer: dimensionToRem', () => { it('transforms 0 to 0', () => { const input = [ getMockToken({ - value: '0rem', + value: {value: 0, unit: 'rem'}, }), getMockToken({ - value: '0px', + value: {value: 0, unit: 'px'}, }), getMockToken({ - value: '0', + value: {value: 0, unit: 'em'}, }), ] const expectedOutput = ['0', '0', '0'] @@ -66,6 +53,12 @@ describe('Transformer: dimensionToRem', () => { getMockToken({ value: 'rem', }), + getMockToken({ + value: '16px', + }), + getMockToken({ + value: 16, + }), getMockToken({ value: '', }), @@ -75,7 +68,18 @@ describe('Transformer: dimensionToRem', () => { getMockToken({ value: null, }), + getMockToken({ + value: {}, + }), + getMockToken({ + value: {value: 16}, + }), + getMockToken({ + value: {unit: 'px'}, + }), ] - expect(() => input.map(item => dimensionToRem.transform(item, {}, {}))).toThrow() + for (const token of input) { + expect(() => dimensionToRem.transform(token, {}, {})).toThrow() + } }) }) diff --git a/src/transformers/dimensionToRem.ts b/src/transformers/dimensionToRem.ts index 5aaba8e70..de922df97 100644 --- a/src/transformers/dimensionToRem.ts +++ b/src/transformers/dimensionToRem.ts @@ -8,20 +8,6 @@ import type {Config, PlatformConfig, Transform, TransformedToken} from 'style-di */ const getBasePxFontSize = (options?: PlatformConfig): number => (options && options.basePxFontSize) || 16 -/** - * @description checks if token value has a specific unit - * @param value token value - * @param unit unit string like px or value - * @returns boolean - */ -const hasUnit = (value: string | number, unit: string): boolean => { - if (typeof value === 'number') { - return false - } - - return value.indexOf(unit) > -1 -} - /** * @description converts dimension tokens value to `rem`, ignores `em` as they are relative to the font size of the parent element * @type value transformer — [StyleDictionary.ValueTransform](https://github.com/amzn/style-dictionary/blob/main/types/Transform.d.ts) @@ -36,22 +22,31 @@ export const dimensionToRem: Transform = { transform: (token: TransformedToken, config: PlatformConfig, options: Config) => { const valueProp = options.usesDtcg ? '$value' : 'value' const baseFont = getBasePxFontSize(config) - const floatVal = parseFloat(token[valueProp]) + const dimensionValue = token[valueProp] as {value: number; unit: string} - if (isNaN(floatVal)) { + if (typeof dimensionValue !== 'object' || !('value' in dimensionValue) || !('unit' in dimensionValue)) { throw new Error( - `Invalid dimension token: '${token.name}: ${token[valueProp]}' is not valid and cannot be transform to 'rem' \n`, + `Invalid dimension token: '${token.name}: ${JSON.stringify(token[valueProp])}' must be an object with value and unit properties \n`, ) } - if (floatVal === 0) { + const {value, unit} = dimensionValue + + if (value === 0) { return '0' } - if (hasUnit(token[valueProp], 'rem') || hasUnit(token[valueProp], 'em')) { - return token[valueProp] + if (unit === 'rem' || unit === 'em') { + return `${value}${unit}` + } + + if (unit === 'px') { + // Convert px to rem + return `${value / baseFont}rem` } - return `${floatVal / baseFont}rem` + throw new Error( + `Invalid dimension token: '${token.name}: ${JSON.stringify(token[valueProp])}' has unsupported unit '${unit}' \n`, + ) }, } diff --git a/src/transformers/dimensionToRemPxArray.ts b/src/transformers/dimensionToRemPxArray.ts index f5e0966ea..045c42098 100644 --- a/src/transformers/dimensionToRemPxArray.ts +++ b/src/transformers/dimensionToRemPxArray.ts @@ -12,20 +12,6 @@ type SizeEm = '0' | `${number}em` */ const getBasePxFontSize = (options?: PlatformConfig): number => (options && options.basePxFontSize) || 16 -/** - * @description checks if token value has a specific unit - * @param value token value - * @param unit unit string like px or value - * @returns boolean - */ -const hasUnit = (value: string | number, unit: string): boolean => { - if (typeof value === 'number') { - return false - } - - return value.indexOf(unit) > -1 -} - /** * @description converts dimension tokens value to `rem`, ignores `em` as they are relative to the font size of the parent element * @type value transformer — [StyleDictionary.ValueTransform](https://github.com/amzn/style-dictionary/blob/main/types/Transform.d.ts) @@ -40,22 +26,35 @@ export const dimensionToRemPxArray: Transform = { transform: (token: TransformedToken, config: PlatformConfig, options: Config): [SizeRem | SizeEm, SizePx] => { const valueProp = options.usesDtcg ? '$value' : 'value' const baseFont = getBasePxFontSize(config) - const floatVal = parseFloat(token[valueProp]) + const dimensionValue = token[valueProp] as {value: number; unit: string} - if (isNaN(floatVal)) { + if (typeof dimensionValue !== 'object' || !('value' in dimensionValue) || !('unit' in dimensionValue)) { throw new Error( - `Invalid dimension token: '${token.name}: ${token[valueProp]}' is not valid and cannot be transform to 'rem' \n`, + `Invalid dimension token: '${token.name}: ${JSON.stringify(token[valueProp])}' must be an object with value and unit properties \n`, ) } - if (floatVal === 0) { + const {value, unit} = dimensionValue + + if (value === 0) { return ['0', '0'] } - if (hasUnit(token[valueProp], 'rem') || hasUnit(token[valueProp], 'em')) { - return [token.value, `${floatVal * baseFont}px`] + if (unit === 'rem') { + return [`${value}rem`, `${value * baseFont}px`] + } + + if (unit === 'em') { + return [`${value}em`, `${value * baseFont}px`] + } + + if (unit === 'px') { + // Convert px to rem and keep px + return [`${value / baseFont}rem`, `${value}px`] } - return [`${floatVal / baseFont}rem`, `${floatVal}px`] + throw new Error( + `Invalid dimension token: '${token.name}: ${JSON.stringify(token[valueProp])}' has unsupported unit '${unit}' \n`, + ) }, } diff --git a/src/transformers/index.ts b/src/transformers/index.ts index f84f2cfc6..c0aa5c064 100644 --- a/src/transformers/index.ts +++ b/src/transformers/index.ts @@ -8,6 +8,7 @@ export {floatToPixelUnitless} from './floatToPixel.js' export {gradientToCss} from './gradientToCss.js' export {dimensionToRem} from './dimensionToRem.js' export {dimensionToRemPxArray} from './dimensionToRemPxArray.js' +export {dimensionToPixel} from './dimensionToPixel.js' export {dimensionToPixelUnitless} from './dimensionToPixelUnitless.js' export {durationToCss} from './durationToCss.js' export {figmaAttributes} from './figmaAttributes.js' diff --git a/src/transformers/shadowToCss.test.ts b/src/transformers/shadowToCss.test.ts index a738b0e88..1eaac14ff 100644 --- a/src/transformers/shadowToCss.test.ts +++ b/src/transformers/shadowToCss.test.ts @@ -7,14 +7,14 @@ describe('Transformer: shadowToCss', () => { getMockToken({ $value: { color: '#000000', - offsetX: '0px', - offsetY: '2px', - blur: '1px', - spread: '0', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + blur: {value: 1, unit: 'px'}, + spread: {value: 0, unit: 'px'}, }, }), ] - const expectedOutput = ['0px 2px 1px 0 #000000'] + const expectedOutput = ['0px 2px 1px 0px #000000'] expect(input.map(item => shadowToCss.transform(item, {}, {}))).toStrictEqual(expectedOutput) }) @@ -23,20 +23,20 @@ describe('Transformer: shadowToCss', () => { getMockToken({ $value: { color: '#000000', - offsetX: '0px', - offsetY: '2px', - blur: '1px', - spread: '0px', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + blur: {value: 1, unit: 'px'}, + spread: {value: 0, unit: 'px'}, inset: true, }, }), getMockToken({ $value: { color: '#000000', - offsetX: '0px', - offsetY: '2px', - blur: '1px', - spread: '0px', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + blur: {value: 1, unit: 'px'}, + spread: {value: 0, unit: 'px'}, inset: false, }, }), @@ -52,9 +52,9 @@ describe('Transformer: shadowToCss', () => { getMockToken({ $value: { color: '#000000', - offsetX: '2px', - offsetY: '2px', - blur: '1px', + offsetX: {value: 2, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + spread: {value: 0, unit: 'px'}, }, }), {}, @@ -68,9 +68,9 @@ describe('Transformer: shadowToCss', () => { getMockToken({ $value: { color: '#000000', - offsetX: '2px', - offsetY: '2px', - blur: '1px', + offsetX: {value: 2, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + blur: {value: 1, unit: 'px'}, }, }), {}, @@ -84,9 +84,9 @@ describe('Transformer: shadowToCss', () => { getMockToken({ $value: { color: '#000000', - offsetX: '2px', - spread: '0px', - blur: '1px', + offsetX: {value: 2, unit: 'px'}, + spread: {value: 0, unit: 'px'}, + blur: {value: 1, unit: 'px'}, }, }), {}, @@ -99,9 +99,9 @@ describe('Transformer: shadowToCss', () => { getMockToken({ $value: { color: '#000000', - offsetY: '2px', - spread: '0px', - blur: '1px', + offsetY: {value: 2, unit: 'px'}, + spread: {value: 0, unit: 'px'}, + blur: {value: 1, unit: 'px'}, }, }), {}, @@ -113,10 +113,10 @@ describe('Transformer: shadowToCss', () => { shadowToCss.transform( getMockToken({ $value: { - offsetX: '0px', - offsetY: '2px', - spread: '0px', - blur: '1px', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + spread: {value: 0, unit: 'px'}, + blur: {value: 1, unit: 'px'}, }, }), {}, @@ -130,25 +130,25 @@ describe('Transformer: shadowToCss', () => { getMockToken({ $value: { color: '#000000', - offsetX: '0px', - offsetY: '2px', - blur: '1px', - spread: '0', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + blur: {value: 1, unit: 'px'}, + spread: {value: 0, unit: 'px'}, alpha: 0.5, }, }), getMockToken({ $value: { color: '#22222266', - offsetX: '0px', - offsetY: '2px', - blur: '1px', - spread: '0', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + blur: {value: 1, unit: 'px'}, + spread: {value: 0, unit: 'px'}, alpha: 0.5, }, }), ] - const expectedOutput = ['0px 2px 1px 0 #00000080', '0px 2px 1px 0 #22222280'] + const expectedOutput = ['0px 2px 1px 0px #00000080', '0px 2px 1px 0px #22222280'] expect(input.map(item => shadowToCss.transform(item, {}, {}))).toStrictEqual(expectedOutput) }) @@ -157,24 +157,40 @@ describe('Transformer: shadowToCss', () => { $value: [ { color: '#000000', - offsetX: '0px', - offsetY: '2px', - blur: '1px', - spread: '0', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 2, unit: 'px'}, + blur: {value: 1, unit: 'px'}, + spread: {value: 0, unit: 'px'}, alpha: 0.5, }, { color: '#22222266', - offsetX: '0px', - offsetY: '8px', - blur: '16px', - spread: '0', + offsetX: {value: 0, unit: 'px'}, + offsetY: {value: 8, unit: 'px'}, + blur: {value: 16, unit: 'px'}, + spread: {value: 0, unit: 'px'}, alpha: 0.2, }, ], }) - const expectedOutput = '0px 2px 1px 0 #00000080, 0px 8px 16px 0 #22222233' + const expectedOutput = '0px 2px 1px 0px #00000080, 0px 8px 16px 0px #22222233' expect(shadowToCss.transform(item, {}, {})).toStrictEqual(expectedOutput) }) + + it('maintains backward compatibility with string dimension values', () => { + const input = [ + getMockToken({ + $value: { + color: '#000000', + offsetX: '0px', + offsetY: '2px', + blur: '1px', + spread: '0px', + }, + }), + ] + const expectedOutput = ['0px 2px 1px 0px #000000'] + expect(input.map(item => shadowToCss.transform(item, {}, {}))).toStrictEqual(expectedOutput) + }) }) diff --git a/src/transformers/shadowToCss.ts b/src/transformers/shadowToCss.ts index e7ac6a1f0..eac66eb66 100644 --- a/src/transformers/shadowToCss.ts +++ b/src/transformers/shadowToCss.ts @@ -2,6 +2,7 @@ import {toHex} from 'color2k' import {isShadow} from '../filters/index.js' import {alpha} from './utilities/alpha.js' import {checkRequiredTokenProperties} from './utilities/checkRequiredTokenProperties.js' +import {dimensionToString} from './utilities/dimensionToString.js' import type {ShadowTokenValue} from '../types/shadowTokenValue.js' import {getTokenValue} from './utilities/getTokenValue.js' import type {PlatformConfig, Transform, TransformedToken} from 'style-dictionary/types' @@ -30,9 +31,11 @@ export const shadowToCss: Transform = { if (typeof shadow === 'string') return shadow checkRequiredTokenProperties(shadow, ['color', 'offsetX', 'offsetY', 'blur', 'spread']) /*css box shadow: inset? | offset-x | offset-y | blur-radius | spread-radius | color */ - return `${shadow.inset === true ? 'inset ' : ''}${shadow.offsetX} ${shadow.offsetY} ${shadow.blur} ${ - shadow.spread - } ${toHex(alpha(getTokenValue({...token, ...{[valueProp]: shadow}}, 'color'), shadow.alpha || 1, token, config))}` + return `${shadow.inset === true ? 'inset ' : ''}${dimensionToString(shadow.offsetX)} ${dimensionToString( + shadow.offsetY, + )} ${dimensionToString(shadow.blur)} ${dimensionToString( + shadow.spread, + )} ${toHex(alpha(getTokenValue({...token, ...{[valueProp]: shadow}}, 'color'), shadow.alpha || 1, token, config))}` }) .join(', ') }, diff --git a/src/transformers/utilities/dimensionToString.test.ts b/src/transformers/utilities/dimensionToString.test.ts new file mode 100644 index 000000000..12330a23e --- /dev/null +++ b/src/transformers/utilities/dimensionToString.test.ts @@ -0,0 +1,25 @@ +import {dimensionToString} from './dimensionToString.js' + +describe('Utility: dimensionToString', () => { + it('converts dimension object to string', () => { + expect(dimensionToString({value: 0, unit: 'px'})).toBe('0px') + expect(dimensionToString({value: 1, unit: 'px'})).toBe('1px') + expect(dimensionToString({value: 16, unit: 'px'})).toBe('16px') + expect(dimensionToString({value: 1.5, unit: 'rem'})).toBe('1.5rem') + expect(dimensionToString({value: 0, unit: 'em'})).toBe('0em') + }) + + it('handles string values for backward compatibility', () => { + expect(dimensionToString('0px')).toBe('0px') + expect(dimensionToString('1px')).toBe('1px') + expect(dimensionToString('16px')).toBe('16px') + expect(dimensionToString('1.5rem')).toBe('1.5rem') + }) + + it('throws error for invalid dimension values', () => { + expect(() => dimensionToString({value: 1} as {value: number; unit: string})).toThrowError() + expect(() => dimensionToString({unit: 'px'} as {value: number; unit: string})).toThrowError() + expect(() => dimensionToString(123 as unknown as string)).toThrowError() + expect(() => dimensionToString(null as unknown as string)).toThrowError() + }) +}) diff --git a/src/transformers/utilities/dimensionToString.ts b/src/transformers/utilities/dimensionToString.ts new file mode 100644 index 000000000..4ebd67838 --- /dev/null +++ b/src/transformers/utilities/dimensionToString.ts @@ -0,0 +1,19 @@ +/** + * Converts a dimension object with value and unit to a CSS string + * @param dimension - The dimension object with value and unit properties + * @returns CSS dimension string (e.g., "0px", "1rem") + */ +export const dimensionToString = (dimension: {value: number; unit: string} | string): string => { + // If it's already a string, return as is (backward compatibility) + if (typeof dimension === 'string') { + return dimension + } + + // Handle the new object format + if (typeof dimension === 'object' && 'value' in dimension && 'unit' in dimension) { + const {value, unit} = dimension + return `${value}${unit}` + } + + throw new Error(`Invalid dimension value: ${JSON.stringify(dimension)}`) +} diff --git a/src/types/borderTokenValue.d.ts b/src/types/borderTokenValue.d.ts index 4b2f75625..f6838e9e9 100644 --- a/src/types/borderTokenValue.d.ts +++ b/src/types/borderTokenValue.d.ts @@ -5,6 +5,6 @@ export type StrokeStyleString = 'solid' | 'dashed' | 'dotted' | 'double' | 'groove' | 'ridge' | 'outset' | 'inset' export type BorderTokenValue = { color: string - width: string + width: string | {value: number; unit: string} | [string, string] // Support string, dimension object, or array from transformers style: StrokeStyleString } diff --git a/src/types/shadowTokenValue.d.ts b/src/types/shadowTokenValue.d.ts index 89dbb5a57..9108c2696 100644 --- a/src/types/shadowTokenValue.d.ts +++ b/src/types/shadowTokenValue.d.ts @@ -4,10 +4,10 @@ */ export type ShadowTokenValue = { color: string - offsetX: string - offsetY: string - blur: string - spread: string + offsetX: string | {value: number; unit: string} + offsetY: string | {value: number; unit: string} + blur: string | {value: number; unit: string} + spread: string | {value: number; unit: string} // custom non w3c values inset?: boolean alpha?: number