UNPKG

frutjam

Version:

CSS-only Tailwind CSS v4 component library, also usable with UnoCSS or no build step at all. 65+ accessible UI components, zero JavaScript, WCAG AA guaranteed. Free MCP server for AI editors included.

78 lines (70 loc) • 3.23 kB
import { symbols } from "@unocss/core" // Generated by scripts/build-unocss.mjs from the same objects the Tailwind // plugin ships, so the two cannot describe different components. import { rules as tokenRules, preflight, base, components } from "../../dist/unocss/map.js" /** * Frutjam as an UnoCSS preset. * * Every class the Tailwind plugin generates is available here too, so * `class="btn btn-error"` works, and with presetAttributify so does * `btn="error xl"`. Only what you use is emitted. * * Bodies go through symbols.body rather than a plain object, because several * components nest, and symbols.body takes nested CSS while leaving variants * intact. That is what keeps `md:btn-error` and `hover:card-primary` working. * * @param {object} [options] * @param {boolean} [options.preflight=true] Emit Frutjam's tokens, reset and * themes. Turn off if you already * import frutjam/css/base yourself. * @param {string} [options.prefix=''] Matches the Tailwind plugin's * prefix option, so `fj` gives * fj-btn and fj-btn="error". * @param {string} [options.layer='frutjam'] Cascade layer for the components. */ export function presetFrutjam(options = {}) { const { preflight: withPreflight = true, prefix = "", layer = "frutjam" } = options const p = prefix ? `${prefix}-` : "" const named = (token) => `${p}${token}` const componentAttrs = new Set(components.map(named)) /* * `btn="error xl"` expands to btn-error and btn-xl and never to a bare btn, * so the component itself would be missing. Asking for it here, at extraction * time, keeps it free of whatever variant the attribute carried: with * `btn="md:error"` the button must exist at every width and only the colour * belongs inside the media query. */ const attributeBaseExtractor = { name: "frutjam-attribute-base", order: -1, extract({ code }) { const found = new Set() for (const match of code.matchAll(/(?:^|[\s"'`])([a-z][a-z0-9-]*)\s*=\s*["']/g)) { if (componentAttrs.has(match[1])) found.add(match[1]) } return found }, } // Static rules rather than one catch-all regex: UnoCSS can look these up // directly instead of running a pattern against every token it sees. const rules = Object.entries(tokenRules).map(([token, body]) => { const css = { [symbols.body]: body } // A component answers to its class and to its attribute. UnoCSS merges the // two into one selector list when both appear, so this costs nothing. if (componentAttrs.has(named(token))) { css[symbols.selector] = (selector) => `${selector},[${named(token)}]` } return [named(token), css, { layer }] }) const preflights = [{ layer, getCSS: () => preflight }] if (withPreflight) preflights.unshift({ layer, getCSS: () => base }) return { name: "frutjam", layers: { [layer]: -1 }, extractors: [attributeBaseExtractor], rules, preflights, autocomplete: { templates: Object.keys(tokenRules).map(named) }, } } export default presetFrutjam