unocss-preset-utopia
Version:
UnoCSS preset that integrates Utopia calculations.
191 lines (181 loc) • 5.78 kB
JavaScript
;
const utils = require('@unocss/preset-mini/utils');
const defaultOptions = {
minWidth: 320,
maxWidth: 1240,
minSize: 18,
maxSize: 20,
minTypeScale: 1.2,
maxTypeScale: 1.25,
spaceValues: {
"3xs": 0.25,
"2xs": 0.5,
"xs": 0.75,
"s": 1,
"m": 1.5,
"l": 2,
"xl": 3,
"2xl": 4,
"3xl": 6
},
usesPresetMini: true
};
const fluidSizeValue = (from, to) => (options) => {
const { minSize, maxSize, minWidth, maxWidth } = options;
const stepMinSize = minSize * from;
const stepMaxSize = maxSize * (to ?? from);
const slope = (stepMaxSize - stepMinSize) / (maxWidth - minWidth);
const intersection = -1 * minWidth * slope + stepMinSize;
return `clamp(${(stepMinSize / 16).toFixed(2)}rem, calc(${(intersection / 16).toFixed(2)}rem + ${(slope * 100).toFixed(2)}vw), ${(stepMaxSize / 16).toFixed(2)}rem)`;
};
const fluidSpaceHandler = (match) => (options) => {
const sizeFactors = match.split("-").map((s) => options.spaceValues[s]).filter((v) => v !== void 0);
if (sizeFactors.length < 1)
return;
return fluidSizeValue(sizeFactors[0], sizeFactors[1])(options);
};
const fluidPropertyHandler = ({ property, includeDirection = true }) => (options) => ([, d, v]) => {
const cssVal = fluidSpaceHandler(v)(options);
if (!cssVal)
return;
if (includeDirection)
return utils.directionMap[d].map((i) => [`${property}${i}`, cssVal]);
return [[property, cssVal]];
};
const rulesWithMatcher = (regexes, matcher) => regexes.map((regex) => [regex, matcher]);
const paddingRegexes = [
/^pa?()-?fluid-?(-?.+)$/,
/^p-?([xy])-?fluid(?:-?(-?.+))?$/,
/^p-?([rltbse])-?fluid(?:-?(-?.+))?$/,
/^p-(block|inline)-?fluid(?:-(-?.+))?$/,
/^p-?([bi][se])-?fluid(?:-?(-?.+))?$/
];
const paddingRules = (options) => rulesWithMatcher(paddingRegexes, fluidPropertyHandler({ property: "padding" })(options));
const marginRegexes = [
/^ma?()-?fluid-?(-?.+)$/,
/^m-?([xy])-?fluid(?:-?(-?.+))?$/,
/^m-?([rltbse])-?fluid(?:-?(-?.+))?$/,
/^m-(block|inline)-?fluid(?:-(-?.+))?$/,
/^m-?([bi][se])-?fluid(?:-?(-?.+))?$/
];
const marginRules = (options) => rulesWithMatcher(marginRegexes, fluidPropertyHandler({ property: "margin" })(options));
const fluidSpaceBetweenHandler = (options) => (match) => {
const res = fluidPropertyHandler({ property: "margin", includeDirection: true })(options)(match);
const [, d] = match;
const results = res?.map(
([property, v]) => [property, property.endsWith("right") || property.endsWith("bottom") ? `calc(${v} * var(--un-space-${d}-reverse))` : `calc(${v} * calc(1 - var(--un-space-${d}-reverse)))`]
);
if (results) {
return [
[`--un-space-${d}-reverse`, 0],
...results
];
}
};
const spaceRegexes = [
/^space-?([xy])-?fluid-?(-?.+)$/,
/^space-(block|inline)-?fluid-(-?.+)$/
];
const spaceRules = (options) => [
...rulesWithMatcher(spaceRegexes, fluidSpaceBetweenHandler(options)),
[/^space-?([xy])-reverse$/, ([, d]) => ({ [`--un-space-${d}-reverse`]: 1 })],
[/^space-(block|inline)-reverse$/, ([, d]) => ({ [`--un-space-${d}-reverse`]: 1 })]
];
const gapDirections = {
"": "",
"x": "column-",
"y": "row-"
};
const fluidGapHandler = (options) => ([, d = "", s]) => {
const v = fluidSpaceHandler(s)(options);
if (v) {
return {
[`grid-${gapDirections[d]}gap`]: v,
[`${gapDirections[d]}gap`]: v
};
}
};
const gapRegexes = [
/^(?:flex-|grid-)?gap-?fluid-?()(.+)$/,
/^(?:flex-|grid-)?gap-([xy])-?fluid-?(.+)$/
];
const gapRules = (options) => rulesWithMatcher(gapRegexes, fluidGapHandler(options));
const sizeMapping = {
h: "height",
w: "width",
inline: "inline-size",
block: "block-size"
};
function getSizePropName(minmax, hw) {
return `${minmax || ""}${sizeMapping[hw]}`;
}
const fluidSizeHandler = (options) => ([, m, w, s]) => ({
[getSizePropName(m, w)]: fluidSpaceHandler(s)(options)
});
const sizeRegexes = [
/^(min-|max-)?([wh])-?fluid-?(.+)$/,
/^(min-|max-)?(block|inline)-fluid-?(.+)$/
];
const sizeRules = (options) => rulesWithMatcher(sizeRegexes, fluidSizeHandler(options));
const flexBasisRule = (options) => [
/^(?:flex-)?basis-?fluid-?(.+)$/,
([, d]) => ({ "flex-basis": fluidSpaceHandler(d)(options) })
];
const spacingRules = (options) => [
...paddingRules(options),
...marginRules(options),
...spaceRules(options),
...gapRules(options),
...sizeRules(options),
flexBasisRule(options)
];
const fluidTypeHandler = (scale) => (options) => {
const multiplier = parseFloat(scale);
if (Number.isNaN(multiplier))
return;
const from = options.minTypeScale ** multiplier;
const to = options.maxTypeScale ** multiplier;
return fluidSizeValue(from, to)(options);
};
const typographyRules = (options) => [
[/^text-fluid-?(.+)$/, ([, s]) => {
return [["font-size", fluidTypeHandler(s)(options)]];
}]
];
const cssPropertyRules = (options) => [
[/^\[([\w-]+)]-fluid-(type|space)-?(.+)$/, ([, prop, type, value]) => {
if (type === "type")
return { [prop]: fluidTypeHandler(value)(options) };
if (type === "space")
return { [prop]: fluidSpaceHandler(value)(options) };
}]
];
const variantSpace = (matcher) => {
if (/^space-?([xy])-?fluid-?(-?.+)$/.test(matcher)) {
return {
matcher,
selector: (input) => {
return `${input}>:not([hidden])~:not([hidden])`;
}
};
}
};
function presetUtopia(presetOptions) {
const options = {
...defaultOptions,
...presetOptions
};
return {
name: "unocss-preset-utopia",
rules: [
...typographyRules(options),
...spacingRules(options),
...cssPropertyRules(options)
],
options,
variants: [
...options.usesPresetMini ? [] : [variantSpace]
]
};
}
exports.presetUtopia = presetUtopia;