@birdycom/omniacss 0.0.0-stage → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -3
- package/dist/oc-colors.css +4429 -0
- package/dist/oc-theme.css +69 -0
- package/dist/omnia.css +465436 -0
- package/dist/omnia.min.css +2 -0
- package/dist/omnia.starter.css +13461 -0
- package/dist/omnia.starter.min.css +1 -0
- package/dist/tokens/omnia.dtcg.json +1496 -0
- package/dist/tokens/omnia.tokens.json +580 -0
- package/dist/tokens/omnia_tokens.py +189 -0
- package/package.json +63 -6
- package/scss/base/_reset.scss +88 -0
- package/scss/config/omniacss.config.scss +114 -0
- package/scss/config/omniacss.fonts.scss +34 -0
- package/scss/functions/_functions.scss +407 -0
- package/scss/json/html-colors.json +1998 -0
- package/scss/json/omnia-colors.json +500 -0
- package/scss/json/ral-colors.json +3430 -0
- package/scss/layout/_media_query.scss +18 -0
- package/scss/omnia_colors.scss +7 -0
- package/scss/omnia_main.scss +143 -0
- package/scss/profiles/omnia_main.hsl.scss +12 -0
- package/scss/profiles/omnia_main.oklab.scss +12 -0
- package/scss/profiles/omnia_main.oklch.scss +12 -0
- package/scss/utilities/_accessibility.scss +42 -0
- package/scss/utilities/_alignment.scss +861 -0
- package/scss/utilities/_anchor_positioning.scss +65 -0
- package/scss/utilities/_animation_properties.scss +143 -0
- package/scss/utilities/_aspect.scss +73 -0
- package/scss/utilities/_backgrounds.scss +49 -0
- package/scss/utilities/_borders_extended.scss +97 -0
- package/scss/utilities/_borders_outlines.scss +161 -0
- package/scss/utilities/_clamp.scss +111 -0
- package/scss/utilities/_colors_html.scss +341 -0
- package/scss/utilities/_colors_mix.scss +84 -0
- package/scss/utilities/_colors_omnia.scss +315 -0
- package/scss/utilities/_colors_ral.scss +151 -0
- package/scss/utilities/_colors_tokens.scss +87 -0
- package/scss/utilities/_container.scss +58 -0
- package/scss/utilities/_containment.scss +82 -0
- package/scss/utilities/_corner_shape.scss +30 -0
- package/scss/utilities/_css_functions.scss +34 -0
- package/scss/utilities/_dark_mode.scss +117 -0
- package/scss/utilities/_display.scss +58 -0
- package/scss/utilities/_drop_shadow_colors.scss +26 -0
- package/scss/utilities/_effects.scss +50 -0
- package/scss/utilities/_effects_advanced.scss +102 -0
- package/scss/utilities/_experimental_css.scss +149 -0
- package/scss/utilities/_filters.scss +89 -0
- package/scss/utilities/_flexbox.scss +318 -0
- package/scss/utilities/_font_variants.scss +139 -0
- package/scss/utilities/_fonts.scss +276 -0
- package/scss/utilities/_gap.scss +104 -0
- package/scss/utilities/_gradient_stops.scss +48 -0
- package/scss/utilities/_gradients.scss +99 -0
- package/scss/utilities/_grid.scss +673 -0
- package/scss/utilities/_index.scss +22 -0
- package/scss/utilities/_interactivity.scss +198 -0
- package/scss/utilities/_layering.scss +24 -0
- package/scss/utilities/_layout_extras.scss +171 -0
- package/scss/utilities/_layout_flow.scss +210 -0
- package/scss/utilities/_mask_extras.scss +62 -0
- package/scss/utilities/_motion_transforms.scss +167 -0
- package/scss/utilities/_overflow.scss +41 -0
- package/scss/utilities/_position.scss +49 -0
- package/scss/utilities/_responsive.scss +272 -0
- package/scss/utilities/_scroll_animations.scss +80 -0
- package/scss/utilities/_scroll_spacing.scss +33 -0
- package/scss/utilities/_scrollbar.scss +50 -0
- package/scss/utilities/_shadow_tokens.scss +21 -0
- package/scss/utilities/_shapes.scss +72 -0
- package/scss/utilities/_sizing.scss +458 -0
- package/scss/utilities/_sizing_logical.scss +99 -0
- package/scss/utilities/_space_divide.scss +85 -0
- package/scss/utilities/_spacing_margin.scss +199 -0
- package/scss/utilities/_spacing_padding.scss +195 -0
- package/scss/utilities/_starting_style.scss +53 -0
- package/scss/utilities/_state_variants.scss +196 -0
- package/scss/utilities/_svg.scss +100 -0
- package/scss/utilities/_table.scss +46 -0
- package/scss/utilities/_table_advanced.scss +19 -0
- package/scss/utilities/_text.scss +1331 -0
- package/scss/utilities/_transforms_3d.scss +76 -0
- package/scss/utilities/_typography_advanced.scss +124 -0
- package/scss/utilities/_typography_modern.scss +83 -0
- package/scss/utilities/_view_transitions.scss +64 -0
- package/scss/utilities/_writing_modes.scss +72 -0
- package/scss/variables/_color_tone.scss +39 -0
- package/scss/variables/_hulp.scss +108 -0
- package/scss/variables/_mixins.scss +40 -0
- package/scss/variables/_modifiers.scss +32 -0
- package/scss/variables/_variables.scss +302 -0
- package/scss/variables/_variables_colors_html.scss +2019 -0
- package/scss/variables/_variables_colors_omnia.scss +465 -0
- package/scss/variables/_variables_colors_ral.scss +3472 -0
- package/scss/variables/_variables_effects.scss +109 -0
- package/scss/variables/_variables_flex_grid.scss +427 -0
- package/scss/variables/_variables_layout.scss +591 -0
- package/scss/variables/_variables_typography.scss +550 -0
- package/src/engine.mjs +258 -0
- package/src/postcss.mjs +43 -0
- package/src/starter-classes.mjs +124 -0
- package/src/tokens.mjs +377 -0
- package/src/variants.mjs +278 -0
- package/tokens/semantic.json +80 -0
package/src/engine.mjs
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// File: packages/core/src/engine.mjs
|
|
2
|
+
// On-demand OmniaCSS: scan content files for class names and keep only the CSS that is used.
|
|
3
|
+
// Map: candidates (scan) -> rule filter -> keyframes -> custom properties (tokens) -> empty at-rules -> layer
|
|
4
|
+
// Author: Serge Cools | Framework: OmniaCSS
|
|
5
|
+
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import postcss from "postcss";
|
|
10
|
+
import { esc, parseCandidate, rankOf, resolveVariants } from "./variants.mjs";
|
|
11
|
+
|
|
12
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
/** Default location of the full OmniaCSS build shipped with @birdycom/omniacss. */
|
|
15
|
+
export const DIST = path.resolve(here, "../dist");
|
|
16
|
+
|
|
17
|
+
/** Classes that are always kept (state classes toggled by scripts, theme hooks). */
|
|
18
|
+
export const ALWAYS_KEEP = ["dark", "is-open", "is-active", "is-expanded", "sr-only"];
|
|
19
|
+
|
|
20
|
+
const CLASS_IN_SELECTOR = /\.((?:\\.|[A-Za-z0-9_-])+)/g;
|
|
21
|
+
const unescape = (s) => s.replace(/\\(.)/g, "$1");
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Collect every token that could be a class name from the content files.
|
|
25
|
+
* Works for HTML, Blade, Vue, JSX/TSX, PHP, Markdown … (no framework-specific parsing needed).
|
|
26
|
+
* @param {string[]} patterns glob patterns, relative to cwd
|
|
27
|
+
* @param {string} [cwd]
|
|
28
|
+
* @returns {Promise<{ candidates: Set<string>, files: string[] }>}
|
|
29
|
+
*/
|
|
30
|
+
export async function collectCandidates(patterns, cwd = process.cwd()) {
|
|
31
|
+
const candidates = new Set(ALWAYS_KEEP);
|
|
32
|
+
const files = [];
|
|
33
|
+
for (const pattern of [].concat(patterns)) {
|
|
34
|
+
for await (const rel of fs.glob(pattern, { cwd, exclude: (p) => /node_modules|\.git/.test(p) })) {
|
|
35
|
+
const file = path.resolve(cwd, rel);
|
|
36
|
+
const stat = await fs.stat(file).catch(() => null);
|
|
37
|
+
if (!stat || !stat.isFile()) continue;
|
|
38
|
+
files.push(file);
|
|
39
|
+
addCandidates(await fs.readFile(file, "utf8"), candidates);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { candidates, files };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Split source text into class-like tokens (keeps `:`, `/`, `.` and `%` used in OmniaCSS class names). */
|
|
46
|
+
export function addCandidates(text, set = new Set()) {
|
|
47
|
+
const add = (raw) => {
|
|
48
|
+
if (!raw || raw.length > 120) return;
|
|
49
|
+
const token = raw.replace(/^[.:]+|[.:]+$/g, "");
|
|
50
|
+
if (token) set.add(token);
|
|
51
|
+
};
|
|
52
|
+
// Plain tokens (`cq-md:flex`, `w-perc-1/3`) ...
|
|
53
|
+
for (const raw of text.split(/[^A-Za-z0-9_\-:/.%@!]+/)) add(raw);
|
|
54
|
+
// ... and variant tokens with brackets (`has-[img]:flex`, `[&>*]:p-2`, `group-aria-[sort=asc]:x`).
|
|
55
|
+
for (const raw of text.split(/[^A-Za-z0-9_\-:/.%@!\[\]()=&>*~+,#$^|]+/)) if (/[\[\]()=&>*~+,#$^|]/.test(raw)) add(raw);
|
|
56
|
+
return set;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Classes a selector depends on; classes inside :not()/:where()/:is()/:has() are optional. */
|
|
60
|
+
function requiredClasses(selector) {
|
|
61
|
+
const stripped = selector.replace(/:(?:not|where|is|has)\((?:[^()]|\([^()]*\))*\)/g, "");
|
|
62
|
+
return [...stripped.matchAll(CLASS_IN_SELECTOR)].map((m) => unescape(m[1]));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate the used subset of OmniaCSS.
|
|
67
|
+
* @param {string} css full OmniaCSS (with tokens inlined)
|
|
68
|
+
* @param {Set<string>} candidates
|
|
69
|
+
* @param {{ layer?: string|false, keep?: string[], referencedBy?: string[] }} [options]
|
|
70
|
+
* referencedBy: other stylesheets (components, site CSS) whose var(--…) tokens must stay available.
|
|
71
|
+
*/
|
|
72
|
+
export function generate(css, candidates, options = {}) {
|
|
73
|
+
const keep = new Set([...(options.keep || []), ...ALWAYS_KEEP]);
|
|
74
|
+
const used = (c) => candidates.has(c) || keep.has(c);
|
|
75
|
+
const root = postcss.parse(css);
|
|
76
|
+
|
|
77
|
+
// 0. Variants: hover:, md:, group-hover:, has-[..]:, dark: … derived from the base utility's rules (before the filter removes them).
|
|
78
|
+
const variantRules = deriveVariantRules(root, candidates);
|
|
79
|
+
|
|
80
|
+
// 1. Rules: keep selectors whose classes are all used; selectors without classes (:root, html, *) stay.
|
|
81
|
+
root.walkRules((rule) => {
|
|
82
|
+
if (rule.parent?.type === "atrule" && /keyframes$/i.test(rule.parent.name)) return;
|
|
83
|
+
const selectors = rule.selectors.filter((sel) => requiredClasses(sel).every(used));
|
|
84
|
+
if (!selectors.length) rule.remove();
|
|
85
|
+
else if (selectors.length !== rule.selectors.length) rule.selectors = selectors;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
for (const node of variantRules) root.append(node);
|
|
89
|
+
|
|
90
|
+
// 2. Keyframes: only those referenced by a remaining animation.
|
|
91
|
+
const animationNames = new Set();
|
|
92
|
+
root.walkDecls(/^animation(-name)?$/, (decl) => decl.value.split(/[\s,]+/).forEach((n) => animationNames.add(n)));
|
|
93
|
+
root.walkAtRules(/keyframes$/i, (at) => {
|
|
94
|
+
if (!animationNames.has(at.params.trim())) at.remove();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 3. Custom properties: drop token definitions nobody references (repeat for var() chains).
|
|
98
|
+
// var(--…) in the content itself (inline styles, <style> blocks) counts as a reference too.
|
|
99
|
+
pruneCustomProperties(root, options.referencedBy || [], [...candidates].filter((c) => c.startsWith("--")));
|
|
100
|
+
|
|
101
|
+
// 4. Remove at-rules that became empty (@media, @supports, @container, @layer …).
|
|
102
|
+
let removed;
|
|
103
|
+
do {
|
|
104
|
+
removed = 0;
|
|
105
|
+
root.walkAtRules((at) => {
|
|
106
|
+
if (at.nodes && at.nodes.length === 0) { at.remove(); removed++; }
|
|
107
|
+
});
|
|
108
|
+
root.walkRules((rule) => {
|
|
109
|
+
if (rule.nodes.length === 0) { rule.remove(); removed++; }
|
|
110
|
+
});
|
|
111
|
+
} while (removed);
|
|
112
|
+
|
|
113
|
+
// @charset / @import must stay at the very top, outside any @layer block.
|
|
114
|
+
root.walkAtRules("charset", (at) => { at.remove(); });
|
|
115
|
+
const imports = [];
|
|
116
|
+
root.walkAtRules("import", (at) => { imports.push(at.toString() + ";"); at.remove(); });
|
|
117
|
+
|
|
118
|
+
let out = root.toString();
|
|
119
|
+
|
|
120
|
+
// Web font imports: keep only the families the remaining CSS actually uses (no render-blocking requests for unused fonts).
|
|
121
|
+
for (let i = imports.length - 1; i >= 0; i--) {
|
|
122
|
+
const m = imports[i].match(/fonts\.googleapis\.com\/css2\?([^"')]+)/);
|
|
123
|
+
if (!m) continue;
|
|
124
|
+
const params = m[1].split("&");
|
|
125
|
+
const families = params.filter((p) => p.startsWith("family=")).filter((p) => {
|
|
126
|
+
const name = decodeURIComponent(p.slice(7).split(":")[0]).replace(/\+/g, " ");
|
|
127
|
+
return out.includes(name);
|
|
128
|
+
});
|
|
129
|
+
if (!families.length) imports.splice(i, 1);
|
|
130
|
+
else imports[i] = `@import url("https://fonts.googleapis.com/css2?${[...families, ...params.filter((p) => !p.startsWith("family="))].join("&")}");`;
|
|
131
|
+
}
|
|
132
|
+
if (options.layer) {
|
|
133
|
+
out = `@layer ${options.layer} {\n${out}\n}\n`;
|
|
134
|
+
}
|
|
135
|
+
return (imports.length ? imports.join("\n") + "\n" : "") + out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** At-rule chain around a rule (outermost first), without keyframes. */
|
|
139
|
+
function atChain(rule) {
|
|
140
|
+
const chain = [];
|
|
141
|
+
for (let p = rule.parent; p && p.type === "atrule"; p = p.parent) chain.unshift({ name: p.name, params: p.params });
|
|
142
|
+
return chain;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Replace the (top-level) class `.base` in a selector by `target`. */
|
|
146
|
+
function replaceClass(sel, base, target) {
|
|
147
|
+
for (const m of sel.matchAll(CLASS_IN_SELECTOR)) {
|
|
148
|
+
if (unescape(m[1]) !== base) continue;
|
|
149
|
+
let depth = 0;
|
|
150
|
+
for (const ch of sel.slice(0, m.index)) { if (ch === "(") depth++; else if (ch === ")") depth--; }
|
|
151
|
+
if (depth === 0) return sel.slice(0, m.index) + target + sel.slice(m.index + m[0].length);
|
|
152
|
+
}
|
|
153
|
+
return sel;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function wrapAt(rules, chain) {
|
|
157
|
+
// @starting-style has to wrap the rule directly, so it goes innermost.
|
|
158
|
+
const ordered = [...chain.filter((a) => a.name !== "starting-style"), ...chain.filter((a) => a.name === "starting-style")];
|
|
159
|
+
let nodes = rules;
|
|
160
|
+
for (let i = ordered.length - 1; i >= 0; i--) nodes = [postcss.atRule({ name: ordered[i].name, params: ordered[i].params, nodes })];
|
|
161
|
+
return nodes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Variants on demand: for every candidate like `md:hover:bg-primary` whose base utility exists in the full CSS and
|
|
166
|
+
* whose full name does not, derive the rule from the base utility's declarations. Returns rule nodes in cascade order.
|
|
167
|
+
*/
|
|
168
|
+
export function deriveVariantRules(root, candidates) {
|
|
169
|
+
const parsed = [];
|
|
170
|
+
for (const token of candidates) {
|
|
171
|
+
if (!token.includes(":") && !token.startsWith("!") && !token.endsWith("!")) continue;
|
|
172
|
+
const p = parseCandidate(token);
|
|
173
|
+
if (!p) continue;
|
|
174
|
+
const branches = resolveVariants(p.variants);
|
|
175
|
+
if (branches) parsed.push({ ...p, branches });
|
|
176
|
+
}
|
|
177
|
+
if (!parsed.length) return [];
|
|
178
|
+
|
|
179
|
+
const wanted = new Set(parsed.map((p) => p.token));
|
|
180
|
+
const bases = new Set(parsed.map((p) => p.base));
|
|
181
|
+
const native = new Set();
|
|
182
|
+
const byBase = new Map();
|
|
183
|
+
root.walkRules((rule) => {
|
|
184
|
+
if (rule.parent?.type === "atrule" && /keyframes$/i.test(rule.parent.name)) return;
|
|
185
|
+
let chain;
|
|
186
|
+
for (const sel of rule.selectors) {
|
|
187
|
+
const classes = requiredClasses(sel);
|
|
188
|
+
for (const c of classes) if (wanted.has(c)) native.add(c);
|
|
189
|
+
if (classes.length === 1 && bases.has(classes[0])) {
|
|
190
|
+
chain ??= atChain(rule);
|
|
191
|
+
if (!byBase.has(classes[0])) byBase.set(classes[0], []);
|
|
192
|
+
byBase.get(classes[0]).push({ rule, sel, chain });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const out = [];
|
|
198
|
+
for (const p of parsed) {
|
|
199
|
+
if (native.has(p.token) || !byBase.has(p.base)) continue;
|
|
200
|
+
const baseHits = byBase.get(p.base);
|
|
201
|
+
const nodes = [];
|
|
202
|
+
for (const branch of p.branches) {
|
|
203
|
+
const target = branch.sel.replace(/&/g, `.${esc(p.token)}`);
|
|
204
|
+
const atRules = branch.at.map((a) => { const m = a.match(/^@([A-Za-z-]+)\s*([\s\S]*)$/); return { name: m[1], params: m[2] }; });
|
|
205
|
+
for (const hit of baseHits) {
|
|
206
|
+
const selector = replaceClass(hit.sel, p.base, target);
|
|
207
|
+
const rule = postcss.rule({ selector });
|
|
208
|
+
for (const d of hit.rule.nodes) if (d.type === "decl") rule.append(d.clone({ important: p.important || d.important }));
|
|
209
|
+
if (/::(?:before|after)$/.test(branch.sel) && !rule.some((d) => d.type === "decl" && d.prop === "content")) rule.prepend({ prop: "content", value: '""' });
|
|
210
|
+
if (!rule.nodes.length) continue;
|
|
211
|
+
nodes.push(...wrapAt([rule], [...hit.chain, ...atRules]));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (nodes.length) out.push({ rank: rankOf(p.variants), token: p.token, nodes });
|
|
215
|
+
}
|
|
216
|
+
out.sort((a, b) => a.rank - b.rank || (a.token < b.token ? -1 : 1));
|
|
217
|
+
return out.flatMap((o) => o.nodes);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function pruneCustomProperties(root, externalCss, contentVars = []) {
|
|
221
|
+
const external = new Set(contentVars);
|
|
222
|
+
for (const text of externalCss) for (const m of text.matchAll(/var\(\s*(--[A-Za-z0-9_-]+)/g)) external.add(m[1]);
|
|
223
|
+
for (let pass = 0; pass < 6; pass++) {
|
|
224
|
+
const referenced = new Set(external);
|
|
225
|
+
root.walkDecls((decl) => {
|
|
226
|
+
for (const m of decl.value.matchAll(/var\(\s*(--[A-Za-z0-9_-]+)/g)) referenced.add(m[1]);
|
|
227
|
+
});
|
|
228
|
+
// Registered properties (@property --x) count as referenced: they are part of the public API.
|
|
229
|
+
root.walkAtRules("property", (at) => { referenced.add(at.params.trim()); });
|
|
230
|
+
let removed = 0;
|
|
231
|
+
root.walkDecls(/^--/, (decl) => {
|
|
232
|
+
// Only prune global token definitions; component-scoped custom properties stay.
|
|
233
|
+
const sel = decl.parent?.selector || "";
|
|
234
|
+
if (!/^(:root|:host|html)\b/.test(sel)) return;
|
|
235
|
+
if (!referenced.has(decl.prop)) { decl.remove(); removed++; }
|
|
236
|
+
});
|
|
237
|
+
if (!removed) break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Read the full OmniaCSS build and inline the color tokens it imports.
|
|
243
|
+
* @param {{ dist?: string, minified?: boolean }} [options]
|
|
244
|
+
*/
|
|
245
|
+
export async function loadFullCss(options = {}) {
|
|
246
|
+
const dist = options.dist || DIST;
|
|
247
|
+
const main = await fs.readFile(path.join(dist, "omnia.css"), "utf8");
|
|
248
|
+
const tokens = await fs.readFile(path.join(dist, "oc-colors.css"), "utf8").catch(() => "");
|
|
249
|
+
return main.replace(/@import\s+url\("oc-colors\.css(?:\?[^"]*)?"\);?/, () => tokens);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** One call: scan content and return the used CSS plus some statistics. */
|
|
253
|
+
export async function buildCss({ content, cwd = process.cwd(), layer = "omnia", keep = [], dist, referencedBy = [] } = {}) {
|
|
254
|
+
if (!content || !content.length) throw new Error("omniacss: no content globs given (e.g. ['src/**/*.{html,vue,jsx,tsx,php}'])");
|
|
255
|
+
const [{ candidates, files }, full] = await Promise.all([collectCandidates(content, cwd), loadFullCss({ dist })]);
|
|
256
|
+
const css = generate(full, candidates, { layer, keep, referencedBy });
|
|
257
|
+
return { css, files, stats: { files: files.length, candidates: candidates.size, inputBytes: full.length, outputBytes: css.length } };
|
|
258
|
+
}
|
package/src/postcss.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// File: packages/core/src/postcss.mjs
|
|
2
|
+
// PostCSS plugin: replaces `@omnia;` (or `@omnia utilities;`) in your CSS with the OmniaCSS classes your
|
|
3
|
+
// project uses. Works in every PostCSS pipeline (Vite, Laravel Vite, Next.js, Astro, webpack …).
|
|
4
|
+
//
|
|
5
|
+
// // postcss.config.js
|
|
6
|
+
// import omniacss from "@birdycom/omniacss/postcss";
|
|
7
|
+
// export default { plugins: [omniacss({ content: ["resources/**/*.blade.php", "resources/js/**/*.{vue,tsx}"] })] };
|
|
8
|
+
//
|
|
9
|
+
// /* app.css */
|
|
10
|
+
// @omnia;
|
|
11
|
+
//
|
|
12
|
+
// Author: Serge Cools | Framework: OmniaCSS
|
|
13
|
+
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import postcss from "postcss";
|
|
16
|
+
import { buildCss } from "./engine.mjs";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{ content: string[], layer?: string|false, keep?: string[], cwd?: string }} options
|
|
20
|
+
*/
|
|
21
|
+
export default function omniacss(options = {}) {
|
|
22
|
+
return {
|
|
23
|
+
postcssPlugin: "omniacss",
|
|
24
|
+
async Once(root, { result }) {
|
|
25
|
+
const directives = [];
|
|
26
|
+
root.walkAtRules("omnia", (at) => directives.push(at));
|
|
27
|
+
if (!directives.length) return;
|
|
28
|
+
|
|
29
|
+
const cwd = options.cwd || process.cwd();
|
|
30
|
+
const { css, files } = await buildCss({ content: options.content, cwd, layer: options.layer ?? "omnia", keep: options.keep });
|
|
31
|
+
const generated = postcss.parse(css, { from: undefined });
|
|
32
|
+
|
|
33
|
+
directives[0].replaceWith(generated.nodes);
|
|
34
|
+
directives.slice(1).forEach((at) => at.remove());
|
|
35
|
+
|
|
36
|
+
// Let Vite & co. rebuild when a content file changes.
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
result.messages.push({ type: "dependency", plugin: "omniacss", file: path.resolve(file), parent: result.opts.from });
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
omniacss.postcss = true;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// File: packages/core/src/starter-classes.mjs
|
|
2
|
+
// Curated class list for the lightweight CDN bundle dist/omnia.starter.min.css.
|
|
3
|
+
// The build (scripts/build-packages.mjs) feeds this list through the on-demand engine (engine.mjs),
|
|
4
|
+
// so the CSS is generated from the full build, never written by hand.
|
|
5
|
+
// Edit the groups below to grow or shrink the bundle; names that do not exist are reported by the build.
|
|
6
|
+
// Author: Serge Cools | Framework: OmniaCSS
|
|
7
|
+
|
|
8
|
+
const list = (s) => s.trim().split(/\s+/);
|
|
9
|
+
const cross = (prefixes, values) => prefixes.flatMap((p) => values.map((v) => `${p}${v}`));
|
|
10
|
+
|
|
11
|
+
// --- Color: Omnia palette only -------------------------------------------------------------------
|
|
12
|
+
// Tokens (--color-{name}-{step}) are pruned automatically to the ones used below.
|
|
13
|
+
export const COLOR_NAMES = list(`red orange yellow lime green teal cyan blue indigo violet purple pink magenta brown gray black white`);
|
|
14
|
+
export const COLOR_STEPS = ["", "-10", "-20", "-30", "-40", "-50", "-60", "-70", "-80", "-90"]; // "" = the base tone (step 100)
|
|
15
|
+
export const NEUTRAL_STEPS = ["", "-2", "-10", "-20", "-30", "-40", "-50", "-60", "-70", "-80", "-90", "-95"]; // gray: full range for surfaces and text
|
|
16
|
+
|
|
17
|
+
const colorBase = COLOR_NAMES.flatMap((n) => (n === "gray" ? NEUTRAL_STEPS : n === "black" || n === "white" ? [""] : COLOR_STEPS).map((s) => n + s));
|
|
18
|
+
const colors = [
|
|
19
|
+
...cross(["text-", "bg-", "hover:text-", "hover:bg-", "focus:text-", "focus:bg-"], colorBase),
|
|
20
|
+
// border colors always carry an explicit step (border-red-100 = base tone); there is no bare border-red.
|
|
21
|
+
...cross(["border-"], colorBase.map((c) => (/-\d+$/.test(c) ? c : c + "-100"))),
|
|
22
|
+
...list("border-transparent border-current"),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// --- Spacing: padding / margin / gap -------------------------------------------------------------
|
|
26
|
+
export const SPACE = list(`0 px 0.5 1 1.5 2 2.5 3 4 5 6 7 8 9 10`);
|
|
27
|
+
const spacing = [
|
|
28
|
+
...cross(["p-", "pt-", "pr-", "pb-", "pl-", "ph-", "pv-", "m-", "mt-", "mr-", "mb-", "ml-", "mh-", "mv-"], SPACE),
|
|
29
|
+
...cross(["m-", "mt-", "mr-", "mb-", "ml-", "mh-", "mv-"], ["auto"]),
|
|
30
|
+
...cross(["gap-", "gap-x-", "gap-y-"], SPACE),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// --- Sizing --------------------------------------------------------------------------------------
|
|
34
|
+
const SIZE = list(`0 px 1 2 3 4 5 6 8 10 12 16 20 24 auto full screen min max fit perc-1/2 perc-1/3 perc-2/3 perc-1/4 perc-3/4`);
|
|
35
|
+
const sizing = [
|
|
36
|
+
...cross(["w-", "h-"], SIZE),
|
|
37
|
+
...cross(["min-w-", "min-h-"], list(`0 full screen min max fit`)),
|
|
38
|
+
...cross(["max-w-"], list(`none full screen min max fit xs sm md lg xl 2xl 3xl 4xl 5xl 6xl 7xl prose`)),
|
|
39
|
+
...cross(["max-h-"], list(`none full screen`)),
|
|
40
|
+
"container",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// --- Layout: display, flex, grid, position, overflow ---------------------------------------------
|
|
44
|
+
const layout = list(`
|
|
45
|
+
block inline inline-block inline-flex flex inline-grid grid contents none flow-root table
|
|
46
|
+
flex-row flex-col flex-row-reverse flex-col-reverse flex-wrap flex-nowrap flex-wrap-reverse
|
|
47
|
+
flex-1 flex-auto flex-initial flex-none grow nogrow shrink noshrink
|
|
48
|
+
justify-start justify-end justify-center justify-between justify-around justify-evenly
|
|
49
|
+
justify-items-start justify-items-center justify-items-end justify-self-center justify-self-end
|
|
50
|
+
align-start align-end align-center align-stretch align-baseline
|
|
51
|
+
align-items-start align-items-center align-items-end align-items-stretch align-items-baseline
|
|
52
|
+
align-self-auto align-self-start align-self-center align-self-end align-self-stretch
|
|
53
|
+
place-center order-first order-last
|
|
54
|
+
grid-flow-row grid-flow-col grid-flow-dense
|
|
55
|
+
static relative absolute fixed sticky isolate
|
|
56
|
+
float-left float-right float-none clear-both
|
|
57
|
+
visible invisible sr-only
|
|
58
|
+
box-border box-content
|
|
59
|
+
object-contain object-cover object-fill object-center
|
|
60
|
+
overflow-auto overflow-hidden overflow-clip overflow-visible overflow-scroll
|
|
61
|
+
overflow-x-auto overflow-x-hidden overflow-x-scroll overflow-y-auto overflow-y-hidden overflow-y-scroll
|
|
62
|
+
overscroll-contain overscroll-none
|
|
63
|
+
`);
|
|
64
|
+
const gridCols = [
|
|
65
|
+
...cross(["grid-cols-"], list(`1 2 3 4 5 6 7 8 9 10 11 12 none`)),
|
|
66
|
+
...cross(["grid-rows-"], list(`1 2 3 4 5 6 none`)),
|
|
67
|
+
...cross(["col-span-"], list(`1 2 3 4 5 6 7 8 9 10 11 12 full`)),
|
|
68
|
+
...cross(["row-span-"], list(`1 2 3 4 5 6 full`)),
|
|
69
|
+
...cross(["col-start-", "col-end-"], list(`1 2 3 4 5 6 7 13 auto`)),
|
|
70
|
+
];
|
|
71
|
+
const positions = [
|
|
72
|
+
...cross(["top-", "right-", "bottom-", "left-"], list(`0 1 2 4 8 auto full 1/2`)),
|
|
73
|
+
...cross(["z-"], list(`auto 0 10 20 30 40 50`)),
|
|
74
|
+
];
|
|
75
|
+
// Responsive variants: container-query based (cq-xs 20rem, cq-sm 24rem, cq-md 28rem, cq-lg 32rem, cq-xl 36rem).
|
|
76
|
+
const responsive = cross(
|
|
77
|
+
["cq-xs:", "cq-sm:", "cq-md:", "cq-lg:", "cq-xl:"],
|
|
78
|
+
list(`block hidden flex grid inline-flex flex-row flex-col grid-cols-1 grid-cols-2 grid-cols-3 grid-cols-4 grid-cols-5 grid-cols-6`),
|
|
79
|
+
);
|
|
80
|
+
const containers = list(`container-inline-size container-size container-normal`);
|
|
81
|
+
|
|
82
|
+
// --- Typography ----------------------------------------------------------------------------------
|
|
83
|
+
const typography = [
|
|
84
|
+
...cross(["font-"], list(`0.8 1 1.1 1.2 1.5 1.8 2 2.5 3 4 5 6 sansserif serif monospace`)),
|
|
85
|
+
...cross(["font-"], list(`thin light normal medium semibold bold extrabold black italic`)),
|
|
86
|
+
...cross(["lineheight-"], list(`1 1.2 1.5 1.7 2 none tight snug normal relaxed loose`)),
|
|
87
|
+
...cross(["letterspacing-"], list(`1 2 3 normal`)),
|
|
88
|
+
...list(`
|
|
89
|
+
text-left text-center text-right text-justify text-start text-end
|
|
90
|
+
uppercase lowercase capitalize normalcase underline overline line-through no-underline
|
|
91
|
+
truncate antialiased
|
|
92
|
+
whitespace-normal whitespace-nowrap whitespace-pre whitespace-prewrap
|
|
93
|
+
break-normal break-word wrap-break-word wrap-anywhere hyphens-auto
|
|
94
|
+
list-none list-disc list-decimal list-inside list-outside
|
|
95
|
+
line-clamp-1 line-clamp-2 line-clamp-3 line-clamp-4
|
|
96
|
+
vertical-baseline vertical-top vertical-middle vertical-bottom
|
|
97
|
+
`),
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
// --- Borders, radius, shadow, effects, motion ----------------------------------------------------
|
|
101
|
+
const borders = [
|
|
102
|
+
...list(`border border-none border-1 border-2 border-4 border-8 border-t border-r border-b border-l`),
|
|
103
|
+
...cross(["border-t", "border-r", "border-b", "border-l"], list(`1 2 4`)),
|
|
104
|
+
...list(`round round-none round-full round-t round-r round-b round-l round-tl round-tr round-bl round-br`),
|
|
105
|
+
...cross(["round-"], list(`1 2 3 4 5 6 8 10 12`)),
|
|
106
|
+
...list(`shadow-none shadow-sm shadow shadow-md shadow-lg shadow-xl shadow-2xl shadow-inner`),
|
|
107
|
+
...cross(["opacity-"], list(`0 10 25 50 75 100`)),
|
|
108
|
+
...list(`divider-h divider-v outline-none`),
|
|
109
|
+
];
|
|
110
|
+
const motion = [
|
|
111
|
+
...list(`transition-none transition-all transition-colors transition-opacity transition-shadow transition-transform`),
|
|
112
|
+
...cross(["duration-"], list(`75 100 150 200 300 500 700 1000`)),
|
|
113
|
+
...list(`ease-linear ease-in ease-out ease-in-out`),
|
|
114
|
+
...cross(["cursor-"], list(`auto default pointer wait text move help not-allowed grab grabbing`)),
|
|
115
|
+
...list(`pointer-events-none pointer-events-auto`),
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
/** Every class name of the starter bundle (unique). */
|
|
119
|
+
export const starterClasses = [...new Set([
|
|
120
|
+
...colors, ...spacing, ...sizing, ...layout, ...gridCols, ...positions, ...responsive, ...containers,
|
|
121
|
+
...typography, ...borders, ...motion,
|
|
122
|
+
])];
|
|
123
|
+
|
|
124
|
+
export default starterClasses;
|