Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
/* Should be adjusted based on different tokenization */
import { colord } from 'colord';
import { from } from 'nearest-color';
import tokens from 'your-tokens';
function getType(key) {
if (key.toLowerCase().includes('color')) {
return 'color';
}
if (['fontSize', 'lineHeight', 'fontWeight', 'fontFamily'].includes(key)) {
return 'typography';
}
}
function getPairs(type) {
return Object.entries(tokens[type]);
}
function filterPairs({ pairs, key }) {
return pairs.filter(([k]) => k.includes(key));
}
function getValue([_, value]) {
return value.value;
}
function pxToRem(value) {
return `${parseFloat(value).toFixed(3) / 16}rem`;
}
function normalizeFontSize(value) {
if (value.includes('rem')) {
return value;
}
return pxToRem(value);
}
function getNearestValue({ key, value, type }) {
const pairs = getPairs(type);
if (type === 'color') {
const hex = colord(value).toHex();
const pairs = getPairs(type);
const colors = Object.fromEntries(pairs.map(([key, value]) => [key, value.value]));
const result = from(colors)(hex).value;
const [token] = pairs.find(([_, value]) => value.value === result);
return { token, value: result };
}
if (type === 'typography' && key === 'fontSize') {
const fontSizes = filterPairs({ pairs, key: 'text' }).map(([_, v]) =>
normalizeFontSize(v.value)
);
const goal = normalizeFontSize(value).replace('rem', '');
const result = fontSizes.reduce((prev, curr) => {
return Math.abs(curr - goal) <
Math.abs(prev - goal)
? curr
: prev;
});
const [token] = pairs.find(([_, value]) => value.value === result);
return { token, value: result }
}
}
function match({ key, value, type }) {
const pairs = getPairs(type);
if (type === 'color') {
return {
isInvalid: !pairs.map(getValue).includes(value),
nearestValue: getNearestValue({ key, value, type }),
};
}
if (type === 'typography') {
if (key === 'fontSize') {
const fontSizes = filterPairs({ pairs, key: 'text' }).map(([_, v]) =>
normalizeFontSize(v.value)
);
return {
isInvalid: !fontSizes.includes(normalizeFontSize(value)),
nearestValue: getNearestValue({ key, value, type }),
};
}
if (key === 'fontFamily') {
const fonts = filterPairs({ pairs, key: 'font-family' }).map(getValue);
return {
isInvalid: !fonts.some((x) => x.includes(value)),
nearestValue: null,
};
}
}
}
function create(context) {
return {
Property(node) {
const key = node.key.name;
const value = node.value.value;
const type = getType(key);
const { isInvalid, nearestValue } = match({ key, value, type });
if (isInvalid) {
context.report({
node,
message: `Unofficial ${type}. ${
nearestValue ? `Use tokens.${type}['${nearestValue.token}'] (${nearestValue.value})?` : ''
}`,
});
}
},
};
}
export default {
rules: {
'no-unofficial-styles': {
create,
},
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment