Skip to content

Instantly share code, notes, and snippets.

@yringler
Last active July 19, 2023 23:47
Show Gist options
  • Save yringler/cffebd0fd56db0ba9a630f0afc3e3d4f to your computer and use it in GitHub Desktop.
Save yringler/cffebd0fd56db0ba9a630f0afc3e3d4f to your computer and use it in GitHub Desktop.
const StyleDictionary = require('style-dictionary');
/**
* @description Some tokens are composites, defined with several other tokens. For example, typography. It is convenient to be able to have
* a single token which fully defines everything we need, but
* 1) Sometimes they can't. For example, css `type` can't define `letter-spacing`.
* 2) Sometimes we want to access a particular value separately. For example, the width of a border.
*
* This base class provides the foundation to extract a single expansion from a particular type of composite token.
* In the future, if we need to expand multiple components, we'll add that functionality.
*/
class TokenExpander {
/** @type {StyleDictionary.Dictionary} */
_dictionary;
/** @type {boolean} */
_outputReferences;
/**
* @description The suffix which should be added to any new tokens. For example, -width might be added to --border-large.
* @type {string}
*/
_suffix;
/**
* @description The token type which the expander applies to. For example, "border".
* @type {string}
*/
_type;
constructor(dictionary, outputReferences) {
this._dictionary = dictionary;
this._outputReferences = outputReferences;
}
/**
* @description Given a composite token, it returns all the css rules which are needed to describe it.
* @argument token {StyleDictionary.TransformedToken}
* @returns {string}
*/
getExpandedToken(token) {
if (token.type != this._type) {
return null;
}
const original = token.original;
/*
* A composite token can either be an object, which defines each part (font size, line height, etc).
* Or it might be an alias (a string) which references another token.
*/
// If it contains the data we need, use that data.
if (typeof original.value == 'object') {
return this._getCssRule(token);
} else {
// If it's a reference, get the value that it's referring to.
// Note that if outputReferences is true, instead of returning the source tokens value, it'll return a
// var(--...) based on the source tokens name.
if (!this._dictionary.usesReference(original.value)) {
throw "I thought there must be a reference.";
}
return this._getCssRule(this._dictionary.getReferences(original.value)[0]);
}
}
/**
* @description Converts the token to valid css. Eg `--token-name: 23px;`
* @private
* @argument token {StyleDictionary.TransformedToken}
* @returns {string}
*/
_getCssRule(token) {
let value = this.getValueToExpand(token.original);
// The value we're looking for doesn't necessarily exist. For example, not all typography tokens specify letter spacing.
if (!value) {
return null;
}
const name = ` --${token.name}-${this._suffix}`;
if (this._dictionary.usesReference(value)) {
const source = this._dictionary.getReferences(value)[0];
value = this._outputReferences ? `var(--${source.name})` : source.value;
}
return `${name}: ${value};`;
}
/**
* @description Get the value which will be used to create the new token. For example, the width of the border `value.width`.
* @protected
* @argument token {StyleDictionary.DesignToken}
*/
getValueToExpand(token) {
throw "Not implemented";
}
}
module.exports = {
TokenExpander
}
const { TokenExpander } = require("./token-expander");
class TypeExpander extends TokenExpander {
/** @inheritdoc */
_suffix = 'letter-spacing';
/** @inheritdoc */
_type = 'typography';
constructor(dictionary, outputReferences) {
super(dictionary, outputReferences);
}
/**
* @inheritdoc
* @argument token {StyleDictionary.DesignToken}
*/
getValueToExpand(token) {
return token.value?.letterSpacing || null;
}
}
module.exports = {
TypeExpander
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment