UNPKG

unocss-preset-utopia

Version:

UnoCSS preset that integrates Utopia calculations.

191 lines (181 loc) 5.78 kB
'use strict'; 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;