@nex-ui/system
Version:
A lightweight and performant styling library based on Emotion, focusing on component architecture and developer experience.
154 lines (151 loc) • 4.54 kB
JavaScript
import { mergeWith, isPlainObject, camelCase, isString, memoize, kebabCase } from '@nex-ui/utils';
import serialize from '@x1ngyu/serialize-javascript';
import { all } from 'known-css-properties';
function pathToTokenName(path) {
return path.join('.');
}
function isDecimalString(str) {
return /^\d+\.\d+$/.test(str);
}
function createCssVarName(prefix, path) {
return `--${prefix}-${path.map((k)=>{
if (isDecimalString(k)) {
return k.split('.').join('-');
}
return kebabCase(k);
}).join('-')}`;
}
function isResponsiveColor(value) {
return isPlainObject(value) && (value._light || value._dark || value._DEFAULT);
}
function isValidTokenValue(value) {
if (!isString(value) && typeof value !== 'number') {
return false;
}
return true;
}
function isValidSemanticTokenValue(value) {
if (!isString(value) && typeof value !== 'number' && !isResponsiveColor(value)) {
return false;
}
return true;
}
function isValidTokenCategory(category) {
if (!isString(category)) {
return false;
}
switch(category){
case 'colors':
case 'fontFamilies':
case 'fontSizes':
case 'fontWeights':
case 'sizes':
case 'spaces':
case 'lineHeights':
case 'borders':
case 'radii':
case 'breakpoints':
case 'shadows':
case 'transitions':
case 'borderWidths':
case 'zIndices':
return true;
default:
return false;
}
}
function isValidAliasValue(value) {
if (isString(value) || Array.isArray(value) && value.every(isString)) {
return true;
}
return false;
}
function isValidBreakpointValue(value) {
if (isString(value) && parseInt(value, 10) >= 0) {
return true;
}
return false;
}
function memoizeFn(fn) {
return memoize(fn, (...args)=>serialize(args));
}
function extractTokenPlaceholders(value) {
const regex = /\{(.*?)\}/g;
const matches = value.matchAll(regex);
return [
...matches
];
}
// TODO 明确 CSS 合并规则
function mergeRecipeConfigs(...args) {
const recipes = JSON.parse(JSON.stringify(args));
return mergeWith({}, ...recipes, (targetValue, srcValue, key)=>{
if (key === 'compoundVariants') {
if (targetValue === undefined) {
return srcValue;
}
if (Array.isArray(targetValue) && Array.isArray(srcValue)) {
return [
...targetValue,
...srcValue
];
}
}
if (typeof targetValue !== typeof srcValue || Array.isArray(targetValue) !== Array.isArray(srcValue) || isPlainObject(targetValue) !== isPlainObject(srcValue)) {
return srcValue;
}
});
}
const toExpression = (operator, ...operands)=>operands.map(String).join(` ${operator} `).replace(/calc/g, '');
const multiply = (...operands)=>`calc(${toExpression('*', ...operands)})`;
const negate = (x)=>{
const value = String(x);
if (value != null && !Number.isNaN(parseFloat(value))) {
if (Number(value) === 0) {
return '0';
}
return value.startsWith('-') ? value.slice(1) : `-${value}`;
}
return multiply(value, -1);
};
const ALL_CSS_PROPERTIES = new Set(all.map(camelCase));
const isCSSProperty = (key)=>{
// CSS variable
if (key.startsWith('--')) return true;
return ALL_CSS_PROPERTIES.has(key);
};
const isSelector = (key)=>{
// start with &
if (/&/.test(key)) {
return true;
}
// combinators
if (/^(?!.*@)(?!.*\()(\s*[>+~]\s*|[a-zA-Z]\s+[a-zA-Z])/.test(key)) {
return true;
}
// special rules
if (/^@(media|keyframes|supports|import|namespace|layer)/.test(key)) {
return true;
}
// id or class selector
if (/^[#.]/.test(key)) {
return true;
}
// attribute selector
if (/\[[^\]]*[=~|^$*]?[^\]]*\]/.test(key)) {
return true;
}
// multiple selectors
if (/,/.test(key)) {
return true;
}
// wildcard selector
if (/\*/.test(key)) {
return true;
}
if (isCSSProperty(key)) {
return false;
}
return true;
};
export { createCssVarName, extractTokenPlaceholders, isResponsiveColor, isSelector, isValidAliasValue, isValidBreakpointValue, isValidSemanticTokenValue, isValidTokenCategory, isValidTokenValue, memoizeFn, mergeRecipeConfigs, multiply, negate, pathToTokenName };