@tokens-studio/sd-transforms
Version:
Custom transforms for Style-Dictionary, to work with Design Tokens that are exported from Tokens Studio
167 lines (166 loc) • 6.94 kB
JavaScript
import { transformDimension } from './transformDimension.js';
import { transformHEXRGBaForCSS } from './css/transformHEXRGBa.js';
import { transformFontWeight } from './transformFontWeight.js';
import { transformLetterSpacingForCSS } from './css/transformLetterSpacing.js';
import { transformLineHeight } from './transformLineHeight.js';
import { transformTypographyForCompose } from './compose/transformTypography.js';
import { checkAndEvaluateMath } from './checkAndEvaluateMath.js';
import { mapDescriptionToComment } from './mapDescriptionToComment.js';
import { transformColorModifiers } from './color-modifiers/transformColorModifiers.js';
import { transformOpacity } from './transformOpacity.js';
import { parseTokens } from './preprocessors/parse-tokens.js';
import { transformShadow } from './css/transformShadow.js';
export const getTransforms = (transformOpts) => {
const agnosticTransforms = [
'ts/descriptionToComment',
'ts/resolveMath',
'ts/size/px',
'ts/opacity',
'ts/size/lineheight',
'ts/typography/fontWeight',
'ts/color/modifiers',
];
const platformTransforms = {
css: ['ts/color/css/hexrgba', 'ts/size/css/letterspacing', 'ts/shadow/innerShadow'],
compose: ['ts/typography/compose/shorthand'],
};
const platform = transformOpts?.platform ?? 'css';
return [...agnosticTransforms, ...(platformTransforms[platform] ?? [])];
};
/**
* typecasting since this will need to work in browser environment, so we cannot
* import style-dictionary as it depends on nodejs env
*/
export async function register(sd, transformOpts) {
sd.registerPreprocessor({
name: 'tokens-studio',
preprocessor: dictionary => {
return parseTokens(dictionary, transformOpts);
},
});
sd.registerTransform({
name: 'ts/descriptionToComment',
type: 'attribute',
filter: token => token.description,
transform: token => mapDescriptionToComment(token),
});
/**
* The transforms below are transitive transforms, because their values
* can contain references, e.g.:
* - rgba({color.r}, {color.g}, 0, 0)
* - {dimension.scale} * {spacing.sm}
* - { fontSize: "{foo}" }
* - { width: "{bar}" }
* - { blur: "{qux}" }
* or because the modifications have to be done on this specific token,
* after resolution, e.g. color modify or resolve math
*
* Any transforms that may need to occur after resolving of math therefore also
* need to be transitive... which means basically every transform we have.
*/
sd.registerTransform({
name: 'ts/resolveMath',
type: 'value',
transitive: true,
filter: token => ['string', 'object'].includes(typeof (token.$value ?? token.value)),
transform: (token, platformCfg) => checkAndEvaluateMath(token, platformCfg.mathFractionDigits),
});
sd.registerTransform({
name: 'ts/size/px',
type: 'value',
transitive: true,
filter: token => {
const type = token.$type ?? token.type;
return (typeof type === 'string' &&
['fontSize', 'dimension', 'typography', 'border', 'shadow'].includes(type));
},
transform: token => transformDimension(token),
});
sd.registerTransform({
name: 'ts/opacity',
type: 'value',
transitive: true,
filter: token => (token.$type ?? token.type) === 'opacity',
transform: token => transformOpacity(token.$value ?? token.value),
});
sd.registerTransform({
name: 'ts/size/css/letterspacing',
type: 'value',
transitive: true,
filter: token => {
const type = token.$type ?? token.type;
const originalType = token.$extensions?.['studio.tokens']?.originalType;
return (typeof type === 'string' &&
(['letterSpacing', 'typography'].includes(type) || originalType === 'letterSpacing'));
},
transform: token => transformLetterSpacingForCSS(token),
});
sd.registerTransform({
name: 'ts/size/lineheight',
type: 'value',
transitive: true,
filter: token => {
const type = token.$type ?? token.type;
return typeof type === 'string' && ['lineHeight', 'typography'].includes(type);
},
transform: token => transformLineHeight(token),
});
sd.registerTransform({
name: 'ts/typography/fontWeight',
type: 'value',
transitive: true,
filter: token => {
const type = token.$type ?? token.type;
return typeof type === 'string' && ['fontWeight', 'typography'].includes(type);
},
transform: token => transformFontWeight(token),
});
sd.registerTransform({
name: 'ts/shadow/innerShadow',
type: 'value',
transitive: true,
filter: token => (token.$type ?? token.type) === 'shadow',
transform: token => transformShadow(token.$value ?? token.value),
});
sd.registerTransform({
name: 'ts/typography/compose/shorthand',
type: 'value',
transitive: true,
filter: token => (token.$type ?? token.type) === 'typography',
transform: token => transformTypographyForCompose(token),
});
sd.registerTransform({
name: 'ts/color/css/hexrgba',
type: 'value',
transitive: true,
filter: token => {
const type = token.$type ?? token.type;
return typeof type === 'string' && ['color', 'shadow', 'border'].includes(type);
},
transform: token => transformHEXRGBaForCSS(token),
});
sd.registerTransform({
name: 'ts/color/modifiers',
type: 'value',
transitive: true,
filter: token => typeof (token.$value ?? token.value) === 'string' &&
(token.$type ?? token.type) === 'color' &&
token.$extensions &&
token.$extensions['studio.tokens']?.modify,
transform: token => transformColorModifiers(token, transformOpts?.['ts/color/modifiers']),
});
const includeBuiltinGroup = transformOpts?.withSDBuiltins ?? true;
// append `?? []` on the line below once we add more platforms which SD may not have a builtin transformGroup for
const builtinTransforms = sd.hooks.transformGroups[transformOpts?.platform ?? 'css'];
sd.registerTransformGroup({
name: transformOpts?.name ?? 'tokens-studio',
// add a default name transform, since this is almost always needed
// it's easy to override by users, adding their own "transforms"
transforms: [
...getTransforms(transformOpts),
// append the built-in style-dictionary transformGroup's transforms
...(includeBuiltinGroup ? builtinTransforms : []),
'name/camel',
],
});
}