Skip to content

Commit 6806030

Browse files
make it work for shadow and transition as well
1 parent 4946c47 commit 6806030

File tree

3 files changed

+234
-12
lines changed

3 files changed

+234
-12
lines changed

src/formats/utilities/createPropertyFormatterWithRef.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
import type {Dictionary, FormattingOptions, OutputReferences, TransformedToken} from 'style-dictionary/types'
1414
import {getReferences, usesReferences} from 'style-dictionary/utils'
15+
import {processCompositeTokenReferences} from './processCompositeTokenReferences.js'
1516

1617
/**
1718
* @typedef {import('../../../types/DesignToken.d.ts').TransformedToken} TransformedToken
@@ -191,18 +192,20 @@ export default function createPropertyFormatterWithRef({
191192
value = originalValue
192193
} else {
193194
if (token.$type === 'border') {
194-
const transformedValues = value.split(' ')
195-
value = ['width', 'style', 'color']
196-
.map((prop, index) => {
197-
if (
198-
originalValue[prop].startsWith('{') &&
199-
refs.find(ref => ref.path.join('.') === originalValue[prop].replace(/[{}]/g, ''))?.isSource === true
200-
) {
201-
return originalValue[prop]
202-
}
203-
return transformedValues[index]
204-
})
205-
.join(' ')
195+
value = processCompositeTokenReferences(value, originalValue, ['width', 'style', 'color'], refs)
196+
}
197+
if (token.$type === 'shadow') {
198+
value = processCompositeTokenReferences(
199+
value,
200+
originalValue,
201+
['offsetX', 'offsetY', 'blur', 'spread', 'color'],
202+
refs,
203+
['inset'],
204+
)
205+
}
206+
// add if clause for transition tokens
207+
if (token.$type === 'transition') {
208+
value = processCompositeTokenReferences(value, originalValue, ['duration', 'timingFunction', 'delay'], refs)
206209
}
207210
}
208211
/* eslint-disable-next-line github/array-foreach */
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import {processCompositeTokenReferences} from './processCompositeTokenReferences.js'
2+
3+
describe('processCompositeTokenReferences', () => {
4+
// ... existing code ...
5+
6+
test('should handle composite border token references', () => {
7+
const value = '1px solid #000000'
8+
const originalValue = {
9+
width: '{border.width.small}',
10+
style: '{border.style.solid}',
11+
color: '{color.black}',
12+
}
13+
const properties = ['width', 'style', 'color']
14+
const refs = [
15+
{path: ['border', 'width', 'small'], isSource: true},
16+
{path: ['border', 'style', 'solid'], isSource: true},
17+
{path: ['color', 'black'], isSource: true},
18+
]
19+
const sanitizeValue: string[] = []
20+
21+
const result = processCompositeTokenReferences(value, originalValue, properties, refs, sanitizeValue)
22+
expect(result).toBe('{border.width.small} {border.style.solid} {color.black}')
23+
})
24+
25+
test('should handle composite border token with mixed references', () => {
26+
const value = '1px solid #000000'
27+
const originalValue = {
28+
width: '{border.width.small}',
29+
style: 'solid',
30+
color: '{color.black}',
31+
}
32+
const properties = ['width', 'style', 'color']
33+
const refs = [
34+
{path: ['border', 'width', 'small'], isSource: true},
35+
{path: ['color', 'black'], isSource: true},
36+
]
37+
const sanitizeValue = ['px']
38+
39+
const result = processCompositeTokenReferences(value, originalValue, properties, refs, sanitizeValue)
40+
expect(result).toBe('{border.width.small} solid {color.black}')
41+
})
42+
43+
test('should handle composite border token with invalid references', () => {
44+
const value = '1px solid #000000'
45+
const originalValue = {
46+
width: '{border.width.small}',
47+
style: '{border.style.solid}',
48+
color: '{color.black}',
49+
}
50+
const properties = ['width', 'style', 'color']
51+
const refs = [
52+
{path: ['border', 'width', 'small'], isSource: false},
53+
{path: ['border', 'style', 'solid'], isSource: true},
54+
{path: ['color', 'black'], isSource: false},
55+
]
56+
const sanitizeValue = ['px']
57+
58+
const result = processCompositeTokenReferences(value, originalValue, properties, refs, sanitizeValue)
59+
expect(result).toBe('1 {border.style.solid} #000000')
60+
})
61+
62+
test('should handle composite shadow token references', () => {
63+
const value = '0px 4px 6px -1px rgba(0, 0, 0, 0.1)'
64+
const originalValue = {
65+
offsetX: '{shadow.offsetX.none}',
66+
offsetY: '{shadow.offsetY.small}',
67+
blur: '{shadow.blur.medium}',
68+
spread: '{shadow.spread.small}',
69+
color: '{color.black.alpha10}',
70+
}
71+
const properties = ['offsetX', 'offsetY', 'blur', 'spread', 'color']
72+
const refs = [
73+
{path: ['shadow', 'offsetX', 'none'], isSource: true},
74+
{path: ['shadow', 'offsetY', 'small'], isSource: true},
75+
{path: ['shadow', 'blur', 'medium'], isSource: true},
76+
{path: ['shadow', 'spread', 'small'], isSource: true},
77+
{path: ['color', 'black', 'alpha10'], isSource: true},
78+
]
79+
const sanitizeValue: string[] = []
80+
81+
const result = processCompositeTokenReferences(value, originalValue, properties, refs, sanitizeValue)
82+
expect(result).toBe(
83+
'{shadow.offsetX.none} {shadow.offsetY.small} {shadow.blur.medium} {shadow.spread.small} {color.black.alpha10}',
84+
)
85+
})
86+
87+
test('should handle composite shadow token with mixed references', () => {
88+
const value = 'inset 0px 4px 6px -1px rgba(0, 0, 0, 0.1)'
89+
const originalValue = {
90+
inset: true,
91+
offsetX: '{shadow.offsetX.none}',
92+
offsetY: '4px',
93+
blur: '{shadow.blur.medium}',
94+
spread: '-1px',
95+
color: '{color.black.alpha10}',
96+
}
97+
98+
const properties = ['offsetX', 'offsetY', 'blur', 'spread', 'color']
99+
const refs = [
100+
{path: ['shadow', 'offsetX', 'none'], isSource: true},
101+
{path: ['shadow', 'blur', 'medium'], isSource: true},
102+
{path: ['color', 'black', 'alpha10'], isSource: true},
103+
]
104+
const sanitizeValue = ['inset']
105+
106+
const result = processCompositeTokenReferences(value, originalValue, properties, refs, sanitizeValue)
107+
expect(result).toBe('{shadow.offsetX.none} 4px {shadow.blur.medium} -1px {color.black.alpha10}')
108+
})
109+
110+
test('should handle composite shadow token with invalid references', () => {
111+
const value = '0px 4px 6px -1px rgba(0, 0, 0, 0.1)'
112+
const originalValue = {
113+
offsetX: '{shadow.offsetX.none}',
114+
offsetY: '{shadow.offsetY.small}',
115+
blur: '{shadow.blur.medium}',
116+
spread: '{shadow.spread.small}',
117+
color: '{color.black.alpha10}',
118+
}
119+
const properties = ['offsetX', 'offsetY', 'blur', 'spread', 'color']
120+
const refs = [
121+
{path: ['shadow', 'offsetX', 'none'], isSource: false},
122+
{path: ['shadow', 'offsetY', 'small'], isSource: true},
123+
{path: ['shadow', 'blur', 'medium'], isSource: false},
124+
{path: ['shadow', 'spread', 'small'], isSource: true},
125+
{path: ['color', 'black', 'alpha10'], isSource: false},
126+
]
127+
128+
const result = processCompositeTokenReferences(value, originalValue, properties, refs)
129+
expect(result).toBe('0px {shadow.offsetY.small} 6px {shadow.spread.small} rgba(0, 0, 0, 0.1)')
130+
})
131+
132+
test('should handle complex shadow token with multiple values', () => {
133+
const value = '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -1px rgba(0, 0, 0, 0.06)'
134+
const originalValue = {
135+
shadow1: '{shadow.medium}',
136+
shadow2: '{shadow.small}',
137+
}
138+
const properties = ['shadow1', 'shadow2']
139+
const refs = [
140+
{path: ['shadow', 'medium'], isSource: true},
141+
{path: ['shadow', 'small'], isSource: true},
142+
]
143+
144+
const result = processCompositeTokenReferences(value, originalValue, properties, refs)
145+
expect(result).toBe('{shadow.medium} {shadow.small}')
146+
})
147+
148+
test('should not split rgb or rgba values', () => {
149+
const value = '0px 4px 6px -1px rgb(0, 0, 0)'
150+
const originalValue = {
151+
offsetX: '{shadow.offsetX.none}',
152+
offsetY: '{shadow.offsetY.small}',
153+
blur: '{shadow.blur.medium}',
154+
spread: '{shadow.spread.small}',
155+
color: '{color.black}',
156+
}
157+
const properties = ['offsetX', 'offsetY', 'blur', 'spread', 'color']
158+
const refs = [
159+
{path: ['shadow', 'offsetX', 'none'], isSource: false},
160+
{path: ['shadow', 'offsetY', 'small'], isSource: true},
161+
{path: ['shadow', 'blur', 'medium'], isSource: false},
162+
{path: ['shadow', 'spread', 'small'], isSource: true},
163+
{path: ['color', 'black'], isSource: false},
164+
]
165+
166+
const result = processCompositeTokenReferences(value, originalValue, properties, refs)
167+
expect(result).toBe('0px {shadow.offsetY.small} 6px {shadow.spread.small} rgb(0, 0, 0)')
168+
})
169+
})
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export function processCompositeTokenReferences(
2+
value: string,
3+
originalValue: Record<string, string | boolean>,
4+
properties: string[],
5+
refs: Array<{path: string[]; isSource: boolean}>,
6+
sanitizeValue: string[] = [],
7+
): string {
8+
// Copy the value to avoid mutating it
9+
const sanitizedValue = removeStringsFromString(value, sanitizeValue) // Remove the sanitized value from the original value
10+
const transformedValues = sanitizedValue.split(/(?<!,)\s+/)
11+
12+
// Map over the properties and process each one
13+
return properties
14+
.map((prop, index) => {
15+
if (
16+
typeof originalValue[prop] === 'string' &&
17+
originalValue[prop].startsWith('{') && // Check if the original value is a reference
18+
refs.find(ref => ref.path.join('.') === (originalValue[prop] as string).replace(/[{}]/g, ''))?.isSource === true // Validate the reference
19+
) {
20+
return originalValue[prop] // Return the valid reference
21+
}
22+
return transformedValues[index] // Otherwise, return the default value
23+
})
24+
.join(' ') // Join the processed values back into a single string
25+
}
26+
27+
/**
28+
* Removes any string from the given array from the input string.
29+
*
30+
* @param input - The input string to process.
31+
* @param stringsToRemove - An array of strings to remove from the input string.
32+
* @returns The resulting string with the specified strings removed.
33+
*/
34+
export function removeStringsFromString(input: string, stringsToRemove: string[]): string {
35+
// Create a regex pattern to match any of the strings in the array
36+
const pattern = new RegExp(stringsToRemove.map(str => escapeRegExp(str)).join('|'), 'g')
37+
38+
// Replace the matching strings with an empty string
39+
return input.replace(pattern, '').trim()
40+
}
41+
42+
/**
43+
* Escapes special characters in a string for use in a regular expression.
44+
*
45+
* @param str - The string to escape.
46+
* @returns The escaped string.
47+
*/
48+
function escapeRegExp(str: string): string {
49+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escapes special regex characters
50+
}

0 commit comments

Comments
 (0)