Skip to content

Commit ce80256

Browse files
add transition transformer
1 parent 6e97e23 commit ce80256

File tree

9 files changed

+206
-2
lines changed

9 files changed

+206
-2
lines changed

src/filters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export {isNumber} from './isNumber.js'
1212
export {isShadow} from './isShadow.js'
1313
export {isSource} from './isSource.js'
1414
export {isTypography} from './isTypography.js'
15+
export {isTransition} from './isTransition.js'

src/filters/isTransition.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {getMockToken} from '../test-utilities/index.js'
2+
import {isTransition} from './isTransition.js'
3+
4+
describe('Filter: isTransition', () => {
5+
it('returns true if $type property is `transition`', () => {
6+
expect(isTransition(getMockToken({$type: 'transition'}))).toStrictEqual(true)
7+
})
8+
9+
it('returns false if $type property is not `transition`', () => {
10+
expect(isTransition(getMockToken({$type: 'pumpkin'}))).toStrictEqual(false)
11+
})
12+
13+
it('returns false if $type property is missing', () => {
14+
expect(isTransition(getMockToken({alpha: 0.4}))).toStrictEqual(false)
15+
})
16+
17+
it('returns false if $type property is falsy', () => {
18+
expect(isTransition(getMockToken({$type: false}))).toStrictEqual(false)
19+
expect(isTransition(getMockToken({$type: undefined}))).toStrictEqual(false)
20+
expect(isTransition(getMockToken({$type: null}))).toStrictEqual(false)
21+
})
22+
})

src/filters/isTransition.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type {TransformedToken} from 'style-dictionary/types'
2+
3+
/**
4+
* @description Checks if token is of $type `transition`
5+
* @param token [TransformedToken](https://github.com/amzn/style-dictionary/blob/main/types/TransformedToken.d.ts)
6+
* @returns boolean
7+
*/
8+
export const isTransition = (token: TransformedToken): boolean => {
9+
return token.$type === 'transition'
10+
}

src/platforms/css.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const css: PlatformInitializer = (outputFile, prefix, buildPath, options)
3535
'shadow/css',
3636
'border/css',
3737
'typography/css',
38+
'transition/css',
3839
'fontFamily/css',
3940
'fontWeight/number',
4041
'gradient/css',

src/primerStyleDictionary.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
dimensionToRemPxArray,
2626
floatToPixel,
2727
floatToPixelUnitless,
28+
transitionToCss,
2829
} from './transformers/index.js'
2930
import {
3031
javascriptCommonJs,
@@ -152,6 +153,8 @@ PrimerStyleDictionary.registerTransform(borderToCss)
152153

153154
PrimerStyleDictionary.registerTransform(typographyToCss)
154155

156+
PrimerStyleDictionary.registerTransform(transitionToCss)
157+
155158
PrimerStyleDictionary.registerTransform(fontWeightToNumber)
156159

157160
PrimerStyleDictionary.registerTransform(fontFamilyToCss)

src/test-utilities/getMockToken.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ export const getMockToken = (
3636
[key: keyof TransformedToken]: unknown
3737
},
3838
options?: getMockTokenOptions,
39-
) => {
39+
): TransformedToken => {
4040
return {
4141
...removeProps(mockTokenDefaults, options?.remove),
4242
...valueOverrides,
43-
}
43+
} as TransformedToken
4444
}

src/transformers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ export {namePathToKebabCase} from './namePathToKebabCase.js'
2323
export {namePathToSlashNotation} from './namePathToSlashNotation.js'
2424
export {shadowToCss} from './shadowToCss.js'
2525
export {typographyToCss} from './typographyToCss.js'
26+
export {transitionToCss} from './transitionToCss.js'
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import {getMockToken} from '../test-utilities/getMockToken.js'
2+
import {transitionToCss} from './transitionToCss.js'
3+
4+
describe('transitionToCss', () => {
5+
it('should transform basic transition token to CSS', () => {
6+
const token = getMockToken({
7+
$value: {
8+
duration: '300ms',
9+
timing: [0.4, 0, 0.2, 1],
10+
},
11+
$type: 'transition',
12+
})
13+
14+
expect(transitionToCss.transform(token, {}, {})).toBe('300ms cubic-bezier(0.4, 0, 0.2, 1)')
15+
})
16+
17+
it('should transform transition token with delay to CSS', () => {
18+
const token = getMockToken({
19+
$value: {
20+
duration: '300ms',
21+
timing: [0.4, 0, 0.2, 1],
22+
delay: '100ms',
23+
},
24+
$type: 'transition',
25+
})
26+
27+
expect(transitionToCss.transform(token, {}, {})).toBe('300ms cubic-bezier(0.4, 0, 0.2, 1) 100ms')
28+
})
29+
30+
it('should transform transition token with resolved references', () => {
31+
const token = {
32+
$value: {
33+
duration: '300ms',
34+
timing: [0.4, 0, 0.2, 1],
35+
delay: '0ms',
36+
},
37+
$type: 'transition',
38+
}
39+
const resolvedRefs = {
40+
duration: '300ms',
41+
timing: [0.4, 0, 0.2, 1],
42+
delay: '0ms',
43+
}
44+
45+
expect(transitionToCss.transform(token, resolvedRefs)).toBe('300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms')
46+
})
47+
48+
it('should transform transition token with mixed values and references', () => {
49+
const token = getMockToken({
50+
$value: {
51+
duration: '{motion.duration.base.value}',
52+
timing: [0.4, 0, 0.2, 1],
53+
delay: '0ms',
54+
},
55+
$type: 'transition',
56+
})
57+
58+
expect(transitionToCss.transform(token, resolvedRefs)).toBe('300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms')
59+
})
60+
61+
it('should handle common timing function aliases', () => {
62+
const token = {
63+
$value: {
64+
duration: '300ms',
65+
timing: [0.4, 0, 1, 1],
66+
},
67+
$type: 'transition',
68+
}
69+
70+
expect(transitionToCss.transform(token, {}, {})).toBe('300ms ease-out')
71+
})
72+
73+
it('should handle direct reference token', () => {
74+
const token = {
75+
$value: '{motion.transition.standard}',
76+
$type: 'transition',
77+
}
78+
const resolvedRefs = {
79+
'motion.transition.standard': {
80+
duration: '300ms',
81+
timing: [0.4, 0, 0.2, 1],
82+
delay: '0ms',
83+
},
84+
}
85+
86+
expect(transitionToCss.transform(token, resolvedRefs)).toBe('300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms')
87+
})
88+
89+
it('should handle missing optional delay', () => {
90+
const token = {
91+
$value: {
92+
duration: '300ms',
93+
timing: [0.4, 0, 0.2, 1],
94+
},
95+
$type: 'transition',
96+
}
97+
98+
expect(transitionToCss.transform(token, {}, {})).toBe('300ms cubic-bezier(0.4, 0, 0.2, 1)')
99+
})
100+
101+
it('should throw error for invalid token type', () => {
102+
const token = {
103+
$value: {
104+
duration: '300ms',
105+
timing: [0.4, 0, 0.2, 1],
106+
},
107+
$type: 'invalid',
108+
}
109+
110+
expect(() => transitionToCss.transform(token, {}, {})).toThrow('Invalid token type: invalid')
111+
})
112+
113+
it('should throw error for missing required properties', () => {
114+
const token = {
115+
$value: {
116+
duration: '300ms',
117+
},
118+
$type: 'transition',
119+
}
120+
// @ts-expect-error: testing invalid token
121+
expect(() => transitionToCss.transform(token, {}, {})).toThrow('Missing required property: timing')
122+
})
123+
124+
it('should throw error for invalid timing value', () => {
125+
const token = {
126+
$value: {
127+
duration: '300ms',
128+
timing: [0.4, 0, 0.2], // Missing one value
129+
},
130+
$type: 'transition',
131+
}
132+
133+
expect(() => transitionToCss.transform(token, {}, {})).toThrow('Invalid timing value')
134+
})
135+
})

src/transformers/transitionToCss.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {isTransition} from '../filters/index.js'
2+
import {checkRequiredTokenProperties} from './utilities/checkRequiredTokenProperties.js'
3+
import {getTokenValue} from './utilities/getTokenValue.js'
4+
import type {Transform, TransformedToken} from 'style-dictionary/types'
5+
6+
/**
7+
* @description converts transition tokens to CSS transition string
8+
* @type value transformer — [StyleDictionary.ValueTransform](https://github.com/amzn/style-dictionary/blob/main/types/Transform.d.ts)
9+
* @matcher matches all tokens of $type `transition`
10+
* @transformer returns css transition `string`
11+
*/
12+
export const transitionToCss: Transform = {
13+
name: 'transition/css',
14+
type: 'value',
15+
transitive: true,
16+
filter: isTransition,
17+
transform: (token: TransformedToken) => {
18+
// extract value
19+
const value = getTokenValue(token)
20+
21+
// if value is a string, it's probably a reference, return as is
22+
if (typeof value === 'string') {
23+
return value
24+
}
25+
26+
// check required properties
27+
checkRequiredTokenProperties(value, ['duration', 'timingFunction'])
28+
29+
return `${value.duration} ${value.timingFunction} ${value.delay ? value.delay : ''}`.trim()
30+
},
31+
}

0 commit comments

Comments
 (0)