diff --git a/package.json b/package.json index 15503b9..6a77131 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@iconify-json/solar": "^1.1.9", "@types/node": "^20.12.12", "@unocss/cli": "^0.60.3", + "@unocss/rule-utils": "^0.60.4", "unocss": "^0.60.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27967fd..4983a09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ devDependencies: '@unocss/cli': specifier: ^0.60.3 version: 0.60.3 + '@unocss/rule-utils': + specifier: ^0.60.4 + version: 0.60.4 unocss: specifier: ^0.60.3 version: 0.60.3(postcss@8.4.38)(vite@5.2.11) @@ -847,6 +850,10 @@ packages: resolution: {integrity: sha512-4bBX1pavDl2DSCozEII7bxYGT0IkyO7kKlUuCtooTePWyLjf2F7essdzHkJ00EpNR64kkebR9V0lqBMBo07VPw==} dev: true + /@unocss/core@0.60.4: + resolution: {integrity: sha512-6tz8KTzC30oB0YikwRQoIpJ6Y6Dg+ZiK3NfCIsH+UX11bh2J2M53as2EL/5VQCqtiUn3YP0ZEzR2d1AWX78RCA==} + dev: true + /@unocss/extractor-arbitrary-variants@0.60.3: resolution: {integrity: sha512-PnwNwkeAHmbJbrf5XN0xQG1KT1VQEye8neYn5yz1MHnT8Cj2nqjrqoCRGLCLhpOUg3/MNj+bpiA7hGnFbXWaCQ==} dependencies: @@ -950,6 +957,14 @@ packages: magic-string: 0.30.10 dev: true + /@unocss/rule-utils@0.60.4: + resolution: {integrity: sha512-7qUN33NM4T/IwWavm9VIOCZ2+4hLBc0YUGxcMNTDZSFQRQLkWe3N5dOlgwKXtMyMKatZfbIRUKVDUgvEefoCTA==} + engines: {node: '>=14'} + dependencies: + '@unocss/core': 0.60.4 + magic-string: 0.30.10 + dev: true + /@unocss/scope@0.60.3: resolution: {integrity: sha512-uDUcBkFe8nRwNiU4YQyrOCjY7/+qFJI/Qr0eouMPOSEsQ6uIXQEWjykqUBJg2fvm0S2vbfBGO9tO/wCDIk5O3w==} dev: true diff --git a/uno-utils.js b/uno-utils.js new file mode 100644 index 0000000..7f10e3b --- /dev/null +++ b/uno-utils.js @@ -0,0 +1,143 @@ +/** +This file is licensed under the MIT license provided down below: + +MIT License + +Copyright (c) 2024-PRESENT Gustavo L. de Mello + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------- + +This file has source code modified from the UnoCSS repository, licensed under the MIT +license. A copy of the original licensed is provided here https://github.com/unocss/unocss/blob/main/LICENSE +and down below: + +MIT License + +Copyright (c) 2021-PRESENT Anthony Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + + +/** + * @param {string} str + * @param {string} [requiredType] + * + * @license MIT + * @author Anthony Fu + */ +function bracketWithType(str, requiredType) { + if (str && str.startsWith('[') && str.endsWith(']')) { + /** @type {string | undefined } */ + let base + /** @type {string | undefined } */ + let hintedType + + const bracketTypeRe = /^\[(color|length|size|position|quoted|string):/i + const match = str.match(bracketTypeRe) + if (!match) { + base = str.slice(1, -1) + } + else { + if (!requiredType) + hintedType = match[1] + base = str.slice(match[0].length, -1) + } + + if (!base) + return + + // test/preset-attributify.test.ts > fixture5 + if (base === '=""') + return + + if (base.startsWith('--')) + base = `var(${base})` + + let curly = 0 + for (const i of base) { + if (i === '[') { + curly += 1 + } + else if (i === ']') { + curly -= 1 + if (curly < 0) + return + } + } + if (curly) + return + + switch (hintedType) { + case 'string': return base + .replace(/(^|[^\\])_/g, '$1 ') + .replace(/\\_/g, '_') + + case 'quoted': return base + .replace(/(^|[^\\])_/g, '$1 ') + .replace(/\\_/g, '_') + .replace(/(["\\])/g, '\\$1') + .replace(/^(.+)$/, '"$1"') + } + + return base + .replace(/(url\(.*?\))/g, v => v.replace(/_/g, '\\_')) + .replace(/(^|[^\\])_/g, '$1 ') + .replace(/\\_/g, '_') + .replace(/(?:calc|clamp|max|min)\((.*)/g, (match) => { + /** @type {string[]} */ + const vars = [] + return match + .replace(/var\((--.+?)[,)]/g, (match, g1) => { + vars.push(g1) + return match.replace(g1, '--un-calc') + }) + .replace(/(-?\d*\.?\d(?!-\d.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, '$1 $2 ') + .replace(/--un-calc/g, () => /** @type {string} */(vars.shift())) + }) + } +} + +/** + * @param {string} str + * + * @license MIT + * @author Anthony Fu + */ +export function bracket(str) { + return bracketWithType(str) +} diff --git a/uno.config.js b/uno.config.js index dfeb095..6596714 100644 --- a/uno.config.js +++ b/uno.config.js @@ -7,6 +7,95 @@ import { transformerDirectives, transformerVariantGroup, } from 'unocss'; +import { definePreset } from 'unocss'; +import { variantGetParameter } from '@unocss/rule-utils'; + +import * as utils from './uno-utils.js'; + +const presetContainers = definePreset( + /** + * @param {{ containers?: Record}} [options={}] + */ + (options = {}) => { + const defaultContainers = { + 'xs': '20rem', + 'sm': '24rem', + 'md': '28rem', + 'lg': '32rem', + 'xl': '36rem', + '2xl': '42rem', + '3xl': '48rem', + '4xl': '56rem', + '5xl': '64rem', + '6xl': '72rem', + '7xl': '80rem', + }; + options.containers = { + ...defaultContainers, + ...Object.fromEntries(Object.entries(defaultContainers).map(e => [`h-${e[0]}`, e[1]])), + ...Object.fromEntries(Object.entries(defaultContainers).map(e => [`w-${e[0]}`, e[1]])), + ...options.containers, + }; + /** @type {import('unocss').Preset} */ + const preset = { + name: 'preset-containers', + rules: [ + [/^@container(?:\/(\w+))?(?:-(normal|size))?$/, ([, l, v]) => { + return { + 'container-type': v ?? 'inline-size', + 'container-name': l, + } + }], + ], + variants: [ + { + name: '@', + match(matcher, ctx) { + if (matcher.startsWith('@container')) { + return matcher + } + const variant = variantGetParameter('@', matcher, ctx.generator.config.separators) + if (variant) { + const [match, rest, label] = variant; + const unit = utils.bracket(match); + console.log(match, rest, label) + + /** @type {string | undefined } */ + let container; + if (unit?.startsWith("h:")) { + container = `(min-height: ${unit.replace('h:', '')})` + } else if (unit?.startsWith("w:") || unit) { + container = `(min-width: ${unit.replace('w:', '')})` + } else { + /** @type {string | number} */ + const size = options.containers?.[match] ?? ''; + container = + `(${match.startsWith('h-') ? 'min-height' : 'min-width'}: ` + + `${typeof size === 'number' ? `${size}px` : size})` + } + + if (!container) { + return + } + + let order = (label ? 1000 : 2000) + Object.keys(options.containers ?? {}).indexOf(match); + + return { + matcher: rest, + handle: (input, next) => next({ + ...input, + parent: `${input.parent ? `${input.parent} $$` : ''}@container${label ? ` ${label} ` : ''}${container}`, + parentOrder: order, + }), + } + } + }, + multiPass: true, + } + ], + } + return preset; + }) export default defineConfig({ theme: { @@ -16,6 +105,8 @@ export default defineConfig({ 'light-gray': '#9c9c9c', 'gray': '#383838', 'dark-gray': '#0c0c0c', + 'mauve': '#cba6f7', + 'yellow': '#f9e2af', }, }, cli: { @@ -28,6 +119,7 @@ export default defineConfig({ }, }, presets: [ + presetContainers(), presetIcons(), presetTypography(), presetUno(),