@birdycom/omniaui 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 +21 -3
- package/bin/lib/add.mjs +126 -0
- package/bin/lib/catalog.mjs +169 -0
- package/bin/lib/project.mjs +90 -0
- package/bin/omniaui.mjs +70 -0
- package/dist/code-block.css +163 -0
- package/dist/components-extra.css +916 -0
- package/dist/components.css +10171 -0
- package/dist/components.min.css +1 -0
- package/dist/data-table.d.ts +9 -0
- package/dist/data-table.js +505 -0
- package/dist/docs-ui.css +267 -0
- package/dist/oc-theme.css +69 -0
- package/dist/parts/.gitkeep +0 -0
- package/dist/parts/app-accordion-accent.css +60 -0
- package/dist/parts/app-alpha-tabs.css +68 -0
- package/dist/parts/app-busy-overlay.css +25 -0
- package/dist/parts/app-column-menu.css +85 -0
- package/dist/parts/app-confirm-dialog.css +70 -0
- package/dist/parts/app-cookie-consent.css +63 -0
- package/dist/parts/app-data-table.css +174 -0
- package/dist/parts/app-greeting.css +47 -0
- package/dist/parts/app-list-toolbar.css +81 -0
- package/dist/parts/app-page-header.css +54 -0
- package/dist/parts/app-severity-card.css +91 -0
- package/dist/parts/app-sidebar-dark.css +15 -0
- package/dist/parts/app-sortable-row.css +52 -0
- package/dist/parts/app-stat-card.css +68 -0
- package/dist/parts/app-table-actions.css +71 -0
- package/dist/parts/app-table-compact.css +54 -0
- package/dist/parts/app-top-progress.css +68 -0
- package/dist/parts/app-tree.css +104 -0
- package/dist/parts/app-upgrade-banner.css +90 -0
- package/dist/parts/app-version-banner.css +80 -0
- package/dist/parts/data-chart-line.css +167 -0
- package/dist/parts/forms-affix-input.css +75 -0
- package/dist/parts/forms-barcode-input.css +75 -0
- package/dist/parts/forms-chip-filter.css +91 -0
- package/dist/parts/forms-color-select.css +97 -0
- package/dist/parts/forms-decimal-input.css +51 -0
- package/dist/parts/forms-filter-counts.css +94 -0
- package/dist/parts/forms-language-select.css +89 -0
- package/dist/parts/forms-number-input.css +98 -0
- package/dist/parts/forms-password-requirements.css +55 -0
- package/dist/parts/forms-phone-input.css +85 -0
- package/dist/parts/forms-time-input.css +74 -0
- package/dist/parts/forms-toggle-field.css +69 -0
- package/dist/parts/media-art.css +21 -0
- package/dist/parts/media-attachment.css +160 -0
- package/dist/parts/media-browser-frame.css +73 -0
- package/dist/parts/media-card.css +83 -0
- package/dist/parts/media-chat-composer.css +90 -0
- package/dist/parts/media-doc-viewer.css +151 -0
- package/dist/parts/media-emoji-picker.css +88 -0
- package/dist/parts/media-entity-card.css +52 -0
- package/dist/parts/media-flip-card.css +78 -0
- package/dist/parts/media-image-zoom.css +68 -0
- package/dist/parts/media-lightbox.css +131 -0
- package/dist/parts/media-message-thread.css +136 -0
- package/dist/parts/media-message.css +160 -0
- package/dist/parts/media-row.css +78 -0
- package/dist/parts/media-stack-card.css +82 -0
- package/dist/parts/media-terminal-log.css +76 -0
- package/dist/parts/media-thumb.css +71 -0
- package/dist/parts/site-fact-list.css +25 -0
- package/dist/parts/site-link-arrow.css +34 -0
- package/dist/parts/site-marquee.css +34 -0
- package/dist/parts/site-next-teaser.css +75 -0
- package/dist/parts/site-price-card.css +91 -0
- package/dist/parts/site-product-mark.css +66 -0
- package/dist/parts/site-project-card.css +111 -0
- package/dist/parts/site-prose.css +124 -0
- package/dist/parts/site-stack-list.css +48 -0
- package/dist/parts/site-timeline.css +54 -0
- package/dist/showcase.css +404 -0
- package/manifest.json +1108 -0
- package/package.json +69 -6
- package/snippets/accordion-accent.html +24 -0
- package/snippets/accordion.html +11 -0
- package/snippets/affix-input.html +30 -0
- package/snippets/alert-dialog.html +22 -0
- package/snippets/alert.html +5 -0
- package/snippets/alpha-tabs.html +37 -0
- package/snippets/aspect-ratio.html +15 -0
- package/snippets/attachment.html +43 -0
- package/snippets/avatar.html +6 -0
- package/snippets/back-link.html +5 -0
- package/snippets/badge.html +5 -0
- package/snippets/barcode-input.html +18 -0
- package/snippets/breadcrumb.html +6 -0
- package/snippets/browser-frame.html +18 -0
- package/snippets/busy-overlay.html +31 -0
- package/snippets/button-group.html +21 -0
- package/snippets/button.html +8 -0
- package/snippets/calendar.html +43 -0
- package/snippets/card.html +17 -0
- package/snippets/carousel.html +21 -0
- package/snippets/chart-line.html +46 -0
- package/snippets/chart.html +27 -0
- package/snippets/chat-composer.html +21 -0
- package/snippets/checkbox.html +5 -0
- package/snippets/checklist.html +23 -0
- package/snippets/chip-filter.html +14 -0
- package/snippets/code-window.html +20 -0
- package/snippets/collapsible.html +19 -0
- package/snippets/color-field.html +13 -0
- package/snippets/color-select.html +18 -0
- package/snippets/column-menu.html +38 -0
- package/snippets/combobox.html +19 -0
- package/snippets/command.html +24 -0
- package/snippets/confirm-dialog.html +32 -0
- package/snippets/context-menu.html +25 -0
- package/snippets/cookie-consent.html +70 -0
- package/snippets/copy-field.html +19 -0
- package/snippets/counter.html +2 -0
- package/snippets/data-table.html +151 -0
- package/snippets/date-picker.html +32 -0
- package/snippets/decimal-input.html +20 -0
- package/snippets/definition-list.html +20 -0
- package/snippets/demo-panel.html +18 -0
- package/snippets/doc-viewer.html +58 -0
- package/snippets/drawer.html +30 -0
- package/snippets/dropdown-menu.html +27 -0
- package/snippets/dropdown.html +9 -0
- package/snippets/dropzone.html +13 -0
- package/snippets/emoji-picker.html +51 -0
- package/snippets/empty-state.html +17 -0
- package/snippets/entity-card.html +26 -0
- package/snippets/expandable-table.html +26 -0
- package/snippets/fact-list.html +14 -0
- package/snippets/filter-counts.html +30 -0
- package/snippets/flip-card.html +22 -0
- package/snippets/form-field.html +6 -0
- package/snippets/greeting.html +22 -0
- package/snippets/heading.html +14 -0
- package/snippets/hover-card.html +18 -0
- package/snippets/image-zoom.html +30 -0
- package/snippets/input-group.html +10 -0
- package/snippets/input.html +5 -0
- package/snippets/item.html +26 -0
- package/snippets/kbd.html +13 -0
- package/snippets/label.html +9 -0
- package/snippets/language-select.html +26 -0
- package/snippets/launcher-card.html +14 -0
- package/snippets/lightbox.html +50 -0
- package/snippets/link-arrow.html +10 -0
- package/snippets/list-toolbar.html +33 -0
- package/snippets/marquee.html +15 -0
- package/snippets/media-card.html +28 -0
- package/snippets/media-row.html +21 -0
- package/snippets/media-thumb.html +12 -0
- package/snippets/menubar.html +30 -0
- package/snippets/message-thread.html +123 -0
- package/snippets/message.html +51 -0
- package/snippets/modal.html +15 -0
- package/snippets/native-select.html +15 -0
- package/snippets/navmenu.html +23 -0
- package/snippets/next-teaser.html +15 -0
- package/snippets/number-input.html +27 -0
- package/snippets/otp-input.html +9 -0
- package/snippets/page-header.html +24 -0
- package/snippets/pagination.html +6 -0
- package/snippets/password-input.html +24 -0
- package/snippets/password-requirements.html +30 -0
- package/snippets/phone-input.html +22 -0
- package/snippets/popover.html +15 -0
- package/snippets/price-card.html +34 -0
- package/snippets/product-mark.html +13 -0
- package/snippets/progress.html +4 -0
- package/snippets/project-card.html +23 -0
- package/snippets/prose.html +54 -0
- package/snippets/radio.html +17 -0
- package/snippets/recovery-codes.html +20 -0
- package/snippets/resizable.html +16 -0
- package/snippets/scroll-area.html +20 -0
- package/snippets/search.html +8 -0
- package/snippets/segmented.html +11 -0
- package/snippets/select.html +7 -0
- package/snippets/separator.html +15 -0
- package/snippets/severity-card.html +35 -0
- package/snippets/sheet.html +32 -0
- package/snippets/sidebar-docs.html +24 -0
- package/snippets/sidebar.html +55 -0
- package/snippets/site-footer.html +6 -0
- package/snippets/site-header.html +26 -0
- package/snippets/skeleton.html +4 -0
- package/snippets/slider.html +17 -0
- package/snippets/sortable-row.html +31 -0
- package/snippets/spinner.html +14 -0
- package/snippets/stack-card.html +17 -0
- package/snippets/stack-list.html +16 -0
- package/snippets/stat-card.html +24 -0
- package/snippets/stepper.html +13 -0
- package/snippets/switch.html +6 -0
- package/snippets/table-actions.html +39 -0
- package/snippets/table-compact.html +24 -0
- package/snippets/table.html +29 -0
- package/snippets/tabs.html +9 -0
- package/snippets/tag-input.html +60 -0
- package/snippets/terminal-log.html +23 -0
- package/snippets/textarea.html +3 -0
- package/snippets/theme-switch.html +6 -0
- package/snippets/time-input.html +26 -0
- package/snippets/timeline.html +28 -0
- package/snippets/toast-stack.html +10 -0
- package/snippets/toast.html +36 -0
- package/snippets/toc.html +13 -0
- package/snippets/toggle-field.html +20 -0
- package/snippets/toggle.html +22 -0
- package/snippets/tooltip.html +4 -0
- package/snippets/top-progress.html +12 -0
- package/snippets/tree.html +38 -0
- package/snippets/upgrade-banner.html +28 -0
- package/snippets/user.html +15 -0
- package/snippets/version-banner.html +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Serge Cools (Birdycom)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @birdycom/omniaui
|
|
2
|
+
|
|
3
|
+
CSS-first components for OmniaCSS. Native HTML does the behaviour — **no JavaScript required**:
|
|
4
|
+
|
|
5
|
+
| Component | Markup |
|
|
6
|
+
|-----------|--------|
|
|
7
|
+
| Modal | `<dialog class="oc-modal">` + `command="show-modal" commandfor="id"` |
|
|
8
|
+
| Dropdown | `<div popover class="oc-dropdown-menu">` + `popovertarget` (anchor positioned) |
|
|
9
|
+
| Accordion | `<details class="oc-accordion-item" name="group">` |
|
|
10
|
+
| Tabs | `.oc-tabset` with radio inputs (`:has()`) |
|
|
11
|
+
| Theme switch | `.oc-theme-switch` (system / light / dark, `light-dark()` tokens) |
|
|
12
|
+
| Counter | `.oc-counter` (`@property`, optional start on scroll) |
|
|
13
|
+
|
|
14
|
+
Plus buttons, cards, badges, alerts, forms, avatar, progress, breadcrumb, pagination, skeleton, tooltip and the Code-block.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @birdycom/omniaui # import "@birdycom/omniaui"
|
|
18
|
+
npx omniacss add component modal # copy a snippet into your project
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
MIT
|
package/bin/lib/add.mjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// File: packages/ui/bin/lib/add.mjs
|
|
2
|
+
// The "add" and "list" commands of the omniaui CLI: copy components/blocks into a project (you own the code).
|
|
3
|
+
// Map: copyFiles -> importLine -> cmdAdd -> cmdList
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { catalog, closure, findEntries, INSTALL_HINT, proBlockNames } from "./catalog.mjs";
|
|
8
|
+
import { cwd, defaultDir, ensureContentGlob, exists, pascal, posix, readJson, requireFromProject, resolveFramework } from "./project.mjs";
|
|
9
|
+
|
|
10
|
+
/** Copied Blade files call each other as <x-oc.name>, matching the folder they land in. */
|
|
11
|
+
const rewriteBlade = (text) => text.replace(/(<\/?)x-oc::/g, "$1x-oc.");
|
|
12
|
+
|
|
13
|
+
async function copyFiles(root, files, targetDir, fw, flags) {
|
|
14
|
+
let copied = 0;
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
// vue/react sources live in src/; keep their folder layout below it so relative imports stay valid
|
|
17
|
+
const rel = fw === "blade" ? file : file.replace(/^src\//, "");
|
|
18
|
+
const target = path.resolve(cwd, targetDir, rel);
|
|
19
|
+
const shown = posix(path.relative(cwd, target));
|
|
20
|
+
if (!flags.force && (await exists(target))) { console.log(` skip ${shown} (exists, use --force)`); continue; }
|
|
21
|
+
if (!flags["dry-run"]) {
|
|
22
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
23
|
+
if (fw === "blade") await fs.writeFile(target, rewriteBlade(await fs.readFile(path.join(root, file), "utf8")));
|
|
24
|
+
else await fs.copyFile(path.join(root, file), target);
|
|
25
|
+
}
|
|
26
|
+
console.log(` ${flags["dry-run"] ? "would copy" : "copied "} ${shown}`);
|
|
27
|
+
copied++;
|
|
28
|
+
}
|
|
29
|
+
return copied;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** css flavour: copy the one HTML snippet straight into the target folder (no sub-folders). */
|
|
33
|
+
async function copySnippet(entry, dir, flags) {
|
|
34
|
+
const target = path.resolve(cwd, dir, path.basename(entry.files[0]));
|
|
35
|
+
const shown = posix(path.relative(cwd, target));
|
|
36
|
+
if (!flags.force && (await exists(target))) { console.log(` skip ${shown} (exists, use --force)`); return 0; }
|
|
37
|
+
if (!flags["dry-run"]) {
|
|
38
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
39
|
+
await fs.copyFile(path.join(entry.root, entry.files[0]), target);
|
|
40
|
+
}
|
|
41
|
+
console.log(` ${flags["dry-run"] ? "would copy" : "copied "} ${shown}`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The line a user pastes to use the npm package (or Blade tag) instead of copying code. */
|
|
46
|
+
async function importLine(fw, root, entry) {
|
|
47
|
+
if (fw === "vue") {
|
|
48
|
+
const names = (await readJson(path.join(root, "manifest.json"))).items
|
|
49
|
+
.filter((i) => i.kind === entry.kind && i.snippet && path.basename(i.snippet, ".html") === entry.name).map((i) => i.name);
|
|
50
|
+
return `import { ${names.join(", ")} } from "@birdycom/omniaui-vue";`;
|
|
51
|
+
}
|
|
52
|
+
if (fw === "react") {
|
|
53
|
+
const names = [];
|
|
54
|
+
for (const file of entry.files) {
|
|
55
|
+
for (const m of (await fs.readFile(path.join(root, file), "utf8")).matchAll(/export\s+(?:const|function|class)\s+([A-Z]\w*)/g)) names.push(m[1]);
|
|
56
|
+
}
|
|
57
|
+
return `import { ${[...new Set(names)].join(", ")} } from "@birdycom/omniaui-react";`;
|
|
58
|
+
}
|
|
59
|
+
if (fw === "blade") return `<x-oc::${entry.kind === "block" ? "block." : ""}${entry.name} />`;
|
|
60
|
+
return entry.kind === "block" ? `@birdycom/omniaui-blocks/blocks/${entry.name}.html` : `@birdycom/omniaui/snippets/${entry.name}.html`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function cmdAdd(args, flags) {
|
|
64
|
+
const names = [...args];
|
|
65
|
+
let kind = flags.kind;
|
|
66
|
+
let legacyKind; // `omniaui add block <name>` keeps working
|
|
67
|
+
if (["block", "component", "blocks", "components"].includes(names[0]) && names.length > 1) legacyKind = kind = names.shift().replace(/s$/, "");
|
|
68
|
+
if (kind && !["component", "block"].includes(kind)) throw new Error(`Unknown kind "${kind}". Use component or block.`);
|
|
69
|
+
if (!names.length && !flags.all) throw new Error("Usage: omniaui add <name...> [-f css|vue|react|blade] [--kind component|block] [--dir dir] [--force] [--dry-run] [--import]");
|
|
70
|
+
|
|
71
|
+
const fw = await resolveFramework(flags);
|
|
72
|
+
const cat = await catalog(fw);
|
|
73
|
+
const selected = [];
|
|
74
|
+
if (flags.all) selected.push(...cat.entries.filter((e) => e.kind === (kind || "component")));
|
|
75
|
+
for (const input of names) {
|
|
76
|
+
const hits = findEntries(cat.entries, input, kind);
|
|
77
|
+
if (!hits.length && (await proBlockNames()).includes(input.replace(/^block-/, ""))) {
|
|
78
|
+
throw new Error(fw === "css"
|
|
79
|
+
? `"${input}" is an OmniaUI Pro block. Install @birdycom/omniaui-pro (licensed) to add it.`
|
|
80
|
+
: `"${input}" is an OmniaUI Pro block: use it from @birdycom/omniaui-pro-${fw === "blade" ? "laravel" : fw} (licensed) instead of copying.`);
|
|
81
|
+
}
|
|
82
|
+
if (!hits.length) throw new Error(`Unknown ${kind || "component or block"} "${input}" for ${fw}. Run "omniaui list -f ${fw}" to see the options.`);
|
|
83
|
+
if (hits.length > 1) throw new Error(`"${input}" exists as component and block. Pass --kind component or --kind block.`);
|
|
84
|
+
selected.push(hits[0]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (flags.import) {
|
|
88
|
+
for (const entry of selected) console.log(await importLine(fw, cat.root, entry));
|
|
89
|
+
if (fw === "vue" || fw === "react") {
|
|
90
|
+
try { requireFromProject.resolve(`@birdycom/omniaui-${fw}/package.json`); } catch { console.log(`Package not installed in this project. Install it with: ${INSTALL_HINT[fw]}`); }
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const dir = flags.dir || flags.to || (await defaultDir(fw, legacyKind));
|
|
96
|
+
console.log(`${fw} → ${dir}${flags["dry-run"] ? " (dry run)" : ""}`);
|
|
97
|
+
let total = 0;
|
|
98
|
+
for (const entry of selected) {
|
|
99
|
+
console.log(`${entry.kind} "${entry.name}"`);
|
|
100
|
+
if (fw === "css") total += await copySnippet(entry, dir, flags);
|
|
101
|
+
else total += await copyFiles(cat.root, await closure(fw, cat.root, entry, cat.entries), dir, fw, flags);
|
|
102
|
+
}
|
|
103
|
+
if (flags["dry-run"]) { console.log(`\n${total} file(s) would be copied.`); return; }
|
|
104
|
+
const hasBlock = selected.some((e) => e.kind === "block");
|
|
105
|
+
const glob = { css: "**/*.html", vue: "**/*.vue", react: "**/*.tsx", blade: "**/*.blade.php" }[fw];
|
|
106
|
+
await ensureContentGlob(`${posix(path.normalize(dir)).replace(/\/$/, "")}/${glob}`, hasBlock);
|
|
107
|
+
const hasPro = selected.some((e) => e.pro);
|
|
108
|
+
console.log(`\n${total} file(s) copied. Styles come from @birdycom/omniaui${hasBlock ? " and @birdycom/omniaui-blocks" : ""}${hasPro ? " and @birdycom/omniaui-pro" : ""} ("omniacss build" includes them).`);
|
|
109
|
+
if (fw === "vue" || fw === "react") console.log(`These files need ${fw} in your project (${fw === "vue" ? "3.4+" : "18+"}).`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function cmdList(what, flags = {}) {
|
|
113
|
+
const fw = await resolveFramework(flags);
|
|
114
|
+
const kinds = flags.kind ? [flags.kind] : what ? [what.replace(/s$/, "")] : ["component", "block"];
|
|
115
|
+
const cat = await catalog(fw);
|
|
116
|
+
console.log(`framework: ${fw}`);
|
|
117
|
+
for (const kind of kinds) {
|
|
118
|
+
console.log(`\n${kind}s`);
|
|
119
|
+
for (const e of cat.entries.filter((x) => x.kind === kind)) {
|
|
120
|
+
const label = fw === "vue" ? `Oc${kind === "block" ? "Block" : ""}${pascal(e.name)}`
|
|
121
|
+
: fw === "react" ? path.basename(e.files[0], ".tsx")
|
|
122
|
+
: fw === "blade" ? `<x-oc::${kind === "block" ? "block." : ""}${e.name}>` : "";
|
|
123
|
+
console.log(` ${e.name.padEnd(18)} ${label.padEnd(fw === "css" ? 0 : 30)}${e.description || ""}`.trimEnd());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// File: packages/ui/bin/lib/catalog.mjs
|
|
2
|
+
// What can be added, per flavour (css | vue | react | blade), and which files an entry needs.
|
|
3
|
+
// Map: registry (snippet manifests) -> flavourRoot -> catalog -> closure (internal imports) -> findEntries
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { binDir, cwd, exists, kebab, pkgDir, posix, readJson } from "./project.mjs";
|
|
8
|
+
|
|
9
|
+
export const INSTALL_HINT = { vue: "npm i @birdycom/omniaui-vue", react: "npm i @birdycom/omniaui-react", blade: "composer require birdycom/omniaui-laravel", block: "npm i @birdycom/omniaui-blocks" };
|
|
10
|
+
|
|
11
|
+
// Package roots inside the OmniaUI monorepo (used when the package is not installed, e.g. while developing).
|
|
12
|
+
const uiRoot = path.resolve(binDir, "..");
|
|
13
|
+
const monoPackages = path.resolve(binDir, "../..");
|
|
14
|
+
const monoBlade = path.resolve(binDir, "../../../php-packages/omniaui-laravel/resources/views/components");
|
|
15
|
+
|
|
16
|
+
/** Folder of a package, or a monorepo fallback folder, or null. */
|
|
17
|
+
async function findPkg(name, fallback) {
|
|
18
|
+
try { return pkgDir(name); } catch { /* not installed */ }
|
|
19
|
+
return (await exists(path.join(fallback, "package.json"))) ? fallback : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const registryCache = {};
|
|
23
|
+
/** Snippet registry: { dir, manifest } of @birdycom/omniaui (components) or @birdycom/omniaui-blocks (blocks). */
|
|
24
|
+
export async function registry(kind) {
|
|
25
|
+
if (!registryCache[kind]) {
|
|
26
|
+
const dir = kind === "block" ? await findPkg("@birdycom/omniaui-blocks", path.join(monoPackages, "blocks")) : uiRoot;
|
|
27
|
+
if (!dir) throw new Error(`Cannot find the blocks. Install them with: ${INSTALL_HINT.block}`);
|
|
28
|
+
registryCache[kind] = { dir, manifest: await readJson(path.join(dir, "manifest.json")) };
|
|
29
|
+
}
|
|
30
|
+
return registryCache[kind];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** OmniaUI Pro snippets ({ dir, manifest }) when @birdycom/omniaui-pro is installed, else null. */
|
|
34
|
+
async function proRegistry() {
|
|
35
|
+
try {
|
|
36
|
+
const dir = pkgDir("@birdycom/omniaui-pro");
|
|
37
|
+
return { dir, manifest: await readJson(path.join(dir, "manifest.json")) };
|
|
38
|
+
} catch { return null; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Names of the OmniaUI Pro blocks (pro.json in @birdycom/omniaui-blocks), for a hint when one is asked for without Pro. */
|
|
42
|
+
export async function proBlockNames() {
|
|
43
|
+
try { return (await readJson(path.join((await registry("block")).dir, "pro.json"))).items.map((i) => i.name); } catch { return []; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Root folder holding the sources of a flavour (installed package first, monorepo second). Returns null when missing. */
|
|
47
|
+
async function flavourRoot(fw) {
|
|
48
|
+
if (fw === "vue" || fw === "react") return findPkg(`@birdycom/omniaui-${fw}`, path.join(monoPackages, fw));
|
|
49
|
+
if (fw === "blade") {
|
|
50
|
+
for (const base of [path.join(cwd, "vendor/birdycom/omniaui-laravel/resources/views/components"), monoBlade]) {
|
|
51
|
+
if (await exists(base)) return base;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function walk(dir, base = dir) {
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const e of await fs.readdir(dir, { withFileTypes: true })) {
|
|
60
|
+
const full = path.join(dir, e.name);
|
|
61
|
+
if (e.isDirectory()) out.push(...(await walk(full, base)));
|
|
62
|
+
else out.push(posix(path.relative(base, full)));
|
|
63
|
+
}
|
|
64
|
+
return out.sort();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Snippet name of a vue manifest item (sub-parts share the snippet of their parent). */
|
|
68
|
+
const vueSnippetName = (i) => (i.snippet ? path.basename(i.snippet, ".html") : kebab(i.name.replace(/^Oc(Block)?/, "")));
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Catalog of one flavour: { root, entries: [{ kind, name, files (relative to root), description }] }.
|
|
72
|
+
* An entry groups the files that belong together (Accordion + AccordionItem, accordion.blade.php + accordion/).
|
|
73
|
+
*/
|
|
74
|
+
export async function catalog(fw) {
|
|
75
|
+
const describe = {};
|
|
76
|
+
const regs = {};
|
|
77
|
+
for (const kind of ["component", "block"]) {
|
|
78
|
+
// Blocks are optional (separate package): without them, only components are listed.
|
|
79
|
+
try { regs[kind] = await registry(kind); } catch (err) { if (kind === "component") throw err; regs[kind] = null; }
|
|
80
|
+
for (const i of regs[kind]?.manifest.items || []) describe[`${kind}:${i.name}`] = i;
|
|
81
|
+
}
|
|
82
|
+
if (fw === "css") {
|
|
83
|
+
const entries = [];
|
|
84
|
+
for (const kind of ["component", "block"]) {
|
|
85
|
+
for (const i of regs[kind]?.manifest.items || []) entries.push({ kind, name: i.name, files: [i.file], description: i.description, root: regs[kind].dir });
|
|
86
|
+
}
|
|
87
|
+
const pro = await proRegistry();
|
|
88
|
+
for (const i of pro?.manifest.items || []) entries.push({ kind: "block", name: i.name, files: [i.file], description: i.description, root: pro.dir, pro: true });
|
|
89
|
+
return { root: null, entries };
|
|
90
|
+
}
|
|
91
|
+
const root = await flavourRoot(fw);
|
|
92
|
+
if (!root) throw new Error(`Cannot find the ${fw} sources. Install them with: ${INSTALL_HINT[fw]}`);
|
|
93
|
+
const groups = new Map();
|
|
94
|
+
const put = (kind, name, file) => {
|
|
95
|
+
const key = `${kind}:${name}`;
|
|
96
|
+
if (!groups.has(key)) groups.set(key, { kind, name, files: [], description: describe[key]?.description });
|
|
97
|
+
groups.get(key).files.push(file);
|
|
98
|
+
};
|
|
99
|
+
if (fw === "blade") {
|
|
100
|
+
for (const file of await walk(root)) {
|
|
101
|
+
if (!file.endsWith(".blade.php")) continue;
|
|
102
|
+
const segs = file.slice(0, -".blade.php".length).split("/");
|
|
103
|
+
if (segs[0] === "block" && segs.length > 1) put("block", segs[1], file);
|
|
104
|
+
else put("component", segs[0], file);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
const manifest = await readJson(path.join(root, "manifest.json"));
|
|
108
|
+
// react: one entry per name; vue: parts that share an HTML snippet belong to one entry
|
|
109
|
+
for (const i of manifest.items) put(i.kind, fw === "vue" ? vueSnippetName(i) : i.name, i.file);
|
|
110
|
+
}
|
|
111
|
+
return { root, entries: [...groups.values()].sort((a, b) => a.name.localeCompare(b.name)) };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const EXT = ["", ".ts", ".tsx", ".vue", "/index.ts", "/index.tsx"];
|
|
115
|
+
/** Local files a vue/react source imports (relative specifiers only). */
|
|
116
|
+
async function localImports(root, file) {
|
|
117
|
+
const text = await fs.readFile(path.join(root, file), "utf8");
|
|
118
|
+
const found = [];
|
|
119
|
+
for (const m of text.matchAll(/(?:from|import)\s*["'](\.{1,2}\/[^"']+)["']/g)) {
|
|
120
|
+
const base = path.posix.normalize(path.posix.join(path.posix.dirname(file), m[1]));
|
|
121
|
+
for (const ext of EXT) {
|
|
122
|
+
if (path.extname(base + ext) && (await fs.stat(path.join(root, base + ext)).catch(() => null))?.isFile()) { found.push(base + ext); break; }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return found;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Blade components a file uses, as files: <x-oc::button> -> button.blade.php, <x-oc::block.hero> -> block/hero.blade.php. */
|
|
129
|
+
async function bladeUses(root, file) {
|
|
130
|
+
const text = await fs.readFile(path.join(root, file), "utf8");
|
|
131
|
+
const found = new Set();
|
|
132
|
+
for (const m of text.matchAll(/<x-oc::([\w.-]+)/g)) {
|
|
133
|
+
const rel = `${m[1].replace(/\./g, "/")}.blade.php`;
|
|
134
|
+
if (await exists(path.join(root, rel))) found.add(rel);
|
|
135
|
+
}
|
|
136
|
+
return [...found];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** All files an entry needs: its own files plus internal dependencies (utils, hooks, composables, sub-components). */
|
|
140
|
+
export async function closure(fw, root, entry, entries) {
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
const queue = [...entry.files];
|
|
143
|
+
while (queue.length) {
|
|
144
|
+
const file = queue.shift();
|
|
145
|
+
if (seen.has(file)) continue;
|
|
146
|
+
seen.add(file);
|
|
147
|
+
const deps = fw === "blade" ? await bladeUses(root, file) : await localImports(root, file);
|
|
148
|
+
for (const dep of deps) {
|
|
149
|
+
queue.push(dep);
|
|
150
|
+
// blade: pull in the whole entry (root file + sub-parts like accordion/item)
|
|
151
|
+
if (fw === "blade") {
|
|
152
|
+
const owner = entries.find((e) => e.files.includes(dep));
|
|
153
|
+
if (owner) queue.push(...owner.files);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return [...seen].sort();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Find the entries for a user-typed name ("button", "OcButton", "app-shell"). */
|
|
161
|
+
export function findEntries(entries, input, kind) {
|
|
162
|
+
let name = kebab(input);
|
|
163
|
+
if (name.startsWith("oc-")) name = name.slice(3);
|
|
164
|
+
if (name.startsWith("block-") && !kind) {
|
|
165
|
+
const hit = entries.filter((e) => e.kind === "block" && e.name === name.slice(6));
|
|
166
|
+
if (hit.length) return hit;
|
|
167
|
+
}
|
|
168
|
+
return entries.filter((e) => e.name === name && (!kind || e.kind === kind));
|
|
169
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// File: packages/ui/bin/lib/project.mjs
|
|
2
|
+
// Project helpers for the omniaui CLI: paths, package resolving, config, framework detection, content globs.
|
|
3
|
+
// Map: cwd + resolve -> config (omnia.config.json) -> detectFramework / resolveFramework -> defaultDir -> ensureContentGlob
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
export const cwd = process.cwd();
|
|
11
|
+
export const CONFIG = "omnia.config.json";
|
|
12
|
+
export const FRAMEWORKS = ["css", "vue", "react", "blade"];
|
|
13
|
+
// Folder of the omniaui CLI (packages/ui/bin); the package root is one level up.
|
|
14
|
+
export const binDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
export const requireFromProject = createRequire(path.join(cwd, "package.json"));
|
|
16
|
+
const requireFromCli = createRequire(path.join(binDir, "omniaui.mjs"));
|
|
17
|
+
|
|
18
|
+
export const exists = async (p) => !!(await fs.stat(p).catch(() => null));
|
|
19
|
+
export const posix = (p) => p.split(path.sep).join("/");
|
|
20
|
+
export const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
|
|
21
|
+
export const pascal = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
22
|
+
|
|
23
|
+
export async function readJson(file) {
|
|
24
|
+
return JSON.parse(await fs.readFile(file, "utf8"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Folder of an installed package: the project's own install first, then next to the CLI. Throws when missing. */
|
|
28
|
+
export function pkgDir(name) {
|
|
29
|
+
for (const req of [requireFromProject, requireFromCli]) {
|
|
30
|
+
try { return path.dirname(req.resolve(`${name}/package.json`)); } catch { /* try next */ }
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Cannot find ${name}. Install it with: npm install ${name}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** omnia.config.json of the project ({} when there is none). */
|
|
36
|
+
export async function loadConfig() {
|
|
37
|
+
try { return await readJson(path.join(cwd, CONFIG)); } catch { return {}; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Detect the flavour from package.json / composer.json: vue, react, blade (Laravel) or css. */
|
|
41
|
+
export async function detectFramework() {
|
|
42
|
+
let pkg = {};
|
|
43
|
+
try { pkg = await readJson(path.join(cwd, "package.json")); } catch { /* no package.json */ }
|
|
44
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
45
|
+
if (deps.vue || deps.nuxt) return "vue";
|
|
46
|
+
if (deps.react || deps.next) return "react";
|
|
47
|
+
let laravel = await exists(path.join(cwd, "artisan"));
|
|
48
|
+
if (!laravel) {
|
|
49
|
+
try { const c = await readJson(path.join(cwd, "composer.json")); laravel = !!{ ...c.require, ...c["require-dev"] }["laravel/framework"]; } catch { /* no composer.json */ }
|
|
50
|
+
}
|
|
51
|
+
return laravel ? "blade" : "css";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Pick the flavour: --framework flag, then config `framework`, then detection. */
|
|
55
|
+
export async function resolveFramework(flags) {
|
|
56
|
+
const config = await loadConfig();
|
|
57
|
+
const fw = String(flags.framework || config.framework || (await detectFramework())).toLowerCase();
|
|
58
|
+
if (!FRAMEWORKS.includes(fw)) throw new Error(`Unknown framework "${fw}". Use one of: ${FRAMEWORKS.join(", ")}.`);
|
|
59
|
+
return fw;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Default target folder per flavour (Laravel projects get Laravel paths). */
|
|
63
|
+
export async function defaultDir(fw, legacyKind) {
|
|
64
|
+
const laravel = await exists(path.join(cwd, "artisan"));
|
|
65
|
+
if (fw === "blade") return "resources/views/components/oc";
|
|
66
|
+
if (fw === "css") return legacyKind ? `omnia/${legacyKind}s` : laravel ? "resources/views/oc-snippets" : "oc-snippets";
|
|
67
|
+
return laravel || (await exists(path.join(cwd, "resources/js"))) ? "resources/js/components/oc" : "src/components/oc";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Does a content glob already cover a sample path? (tiny matcher: **, *, {a,b}) */
|
|
71
|
+
function globCovers(glob, sample) {
|
|
72
|
+
const re = glob
|
|
73
|
+
.replace(/[.+^$()|[\]\\]/g, "\\$&")
|
|
74
|
+
.replace(/\{([^}]+)\}/g, (_, g) => `(${g.split(",").join("|")})`)
|
|
75
|
+
.replace(/\*\*\//g, "\u0000").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\u0000/g, "(?:.*/)?");
|
|
76
|
+
return new RegExp(`^${re}$`).test(sample);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Add a glob to omnia.config.json so "omniacss build" scans copied files (only when a config exists). */
|
|
80
|
+
export async function ensureContentGlob(glob, enableBlocks) {
|
|
81
|
+
const file = path.join(cwd, CONFIG);
|
|
82
|
+
let config;
|
|
83
|
+
try { config = await readJson(file); } catch { return; }
|
|
84
|
+
let changed = false;
|
|
85
|
+
if (enableBlocks && !config.blocks) { config.blocks = true; changed = true; console.log(`Enabled "blocks" in ${CONFIG}`); }
|
|
86
|
+
const sample = glob.replace("**/", "x/");
|
|
87
|
+
const covered = (config.content || []).some((g) => g === glob || globCovers(g, sample));
|
|
88
|
+
if (!covered) { config.content = [...(config.content || []), glob]; changed = true; console.log(`Added "${glob}" to ${CONFIG} content`); }
|
|
89
|
+
if (changed) await fs.writeFile(file, JSON.stringify(config, null, 2) + "\n");
|
|
90
|
+
}
|
package/bin/omniaui.mjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// File: packages/ui/bin/omniaui.mjs
|
|
3
|
+
// OmniaUI CLI — copy components and blocks into your project (css | vue | react | blade), list the registry.
|
|
4
|
+
// Map: args -> commands (add | list | help | version) -> lib/add.mjs (lib/catalog.mjs, lib/project.mjs)
|
|
5
|
+
// Author: Serge Cools | Framework: OmniaUI (on OmniaCSS)
|
|
6
|
+
//
|
|
7
|
+
// These commands used to be "omniacss add/list" (moved here in October 2026). The CSS build stays in
|
|
8
|
+
// the omniacss CLI: "npx omniacss build" (package @birdycom/omniacss-cli).
|
|
9
|
+
|
|
10
|
+
import { parseArgs } from "node:util";
|
|
11
|
+
import { cmdAdd, cmdList } from "./lib/add.mjs";
|
|
12
|
+
import { readJson } from "./lib/project.mjs";
|
|
13
|
+
|
|
14
|
+
const HELP = `
|
|
15
|
+
OmniaUI CLI
|
|
16
|
+
|
|
17
|
+
omniaui add <name...> [-f css|vue|react|blade] [--kind component|block]
|
|
18
|
+
copy components/blocks into your project (you own the code)
|
|
19
|
+
omniaui add <name...> -f vue --import print the import line for the npm package instead
|
|
20
|
+
omniaui list [blocks|components] [-f vue] show what can be added in a flavour
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
-f, --framework <fw> css | vue | react | blade (default: "framework" in omnia.config.json, else detected
|
|
24
|
+
from package.json / composer.json, else css)
|
|
25
|
+
-k, --kind <kind> component | block (needed when a name exists as both)
|
|
26
|
+
--dir <dir> target folder (alias --to); defaults per flavour:
|
|
27
|
+
vue/react: resources/js/components/oc (Laravel) or src/components/oc,
|
|
28
|
+
css: resources/views/oc-snippets (Laravel) or oc-snippets,
|
|
29
|
+
blade: resources/views/components/oc
|
|
30
|
+
--all add every component (or block with --kind block)
|
|
31
|
+
--force overwrite existing files
|
|
32
|
+
--dry-run show what would be copied
|
|
33
|
+
--import print the import line for the npm package, copy nothing
|
|
34
|
+
|
|
35
|
+
Blocks need @birdycom/omniaui-blocks, wrappers @birdycom/omniaui-vue / -react, Blade birdycom/omniaui-laravel.
|
|
36
|
+
Build only the CSS you use with the OmniaCSS CLI: npx omniacss build
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const { values, positionals } = parseArgs({
|
|
41
|
+
allowPositionals: true,
|
|
42
|
+
options: {
|
|
43
|
+
to: { type: "string" },
|
|
44
|
+
dir: { type: "string" },
|
|
45
|
+
framework: { type: "string", short: "f" },
|
|
46
|
+
kind: { type: "string", short: "k" },
|
|
47
|
+
all: { type: "boolean" },
|
|
48
|
+
force: { type: "boolean" },
|
|
49
|
+
"dry-run": { type: "boolean" },
|
|
50
|
+
import: { type: "boolean" },
|
|
51
|
+
help: { type: "boolean", short: "h" },
|
|
52
|
+
version: { type: "boolean", short: "v" },
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const [command, ...rest] = positionals;
|
|
56
|
+
if (values.version) {
|
|
57
|
+
console.log((await readJson(new URL("../package.json", import.meta.url))).version);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
switch (command) {
|
|
61
|
+
case "add": return cmdAdd(rest, values);
|
|
62
|
+
case "list": return cmdList(rest[0], values);
|
|
63
|
+
default: console.log(HELP);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch((err) => {
|
|
68
|
+
console.error(`omniaui: ${err.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/* File: packages/ui/src/code-block.css — Code-block component (line numbers + Omnia syntax theme) */
|
|
2
|
+
/* Map: pre.code base -> CSS variables -> token spans (.hl-tag, .hl-attr, .hl-string, etc.) */
|
|
3
|
+
/*
|
|
4
|
+
* Shared code-block and syntax-highlight styles.
|
|
5
|
+
* Used by: docs (via oc-docs-shell.css), UI blocks, UI components.
|
|
6
|
+
* Load this file wherever you show <pre class="code"><code>...</code></pre>
|
|
7
|
+
* with syntax spans (.hl-tag, .hl-attr, .hl-string, .hl-keyword, etc.).
|
|
8
|
+
*
|
|
9
|
+
* Theme: Birdycom Omnia Dark — colors from src/json/omniathememain.json.
|
|
10
|
+
* To refresh from theme: npm run code-syntax-theme (updates :root in this file).
|
|
11
|
+
* Per-language mapping: docs/code-syntax-theme.html. Supports HTML, CSS, PHP, JS, JSON, Python.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/* ========== Theme variables (Birdycom Omnia Dark) ========== */
|
|
15
|
+
:root {
|
|
16
|
+
--oc-syntax-bg: var(--color-black-30); /* oklch(22% 0 none) */
|
|
17
|
+
--oc-syntax-bg-preview: var(--color-shallowblue-60); /* oklch(16.6694200968% 0.0414311339 269.5585332855deg) */
|
|
18
|
+
--oc-syntax-fg: var(--color-silver-60); /* oklch(75.9328463574% 0.008 none) */
|
|
19
|
+
--oc-syntax-comment: var(--color-purple-2); /* oklch(74.5202011783% 0.1586314015 328.363415175deg) */
|
|
20
|
+
--oc-syntax-string: var(--color-green-2); /* oklch(78.869080581% 0.1450237607 142.4953450414deg) */
|
|
21
|
+
--oc-syntax-keyword: var(--color-purple-2); /* oklch(74.5202011783% 0.1586314015 328.363415175deg) */
|
|
22
|
+
--oc-syntax-variable: var(--color-skycyan-30); /* oklch(86.5517470794% 0.0931746865 230.317904058deg) */
|
|
23
|
+
--oc-syntax-function: var(--color-skycyan-60); /* oklch(77.7931310956% 0.1072512965 230.317904058deg) */
|
|
24
|
+
--oc-syntax-number: var(--color-peru-40); /* oklch(72.3245616775% 0.1153837684 62.1815775778deg) */
|
|
25
|
+
--oc-syntax-operator: var(--color-red-50); /* oklch(65.0278042086% 0.2473759717 29.2338802796deg) */
|
|
26
|
+
--oc-syntax-constant: var(--color-skycyan-70); /* oklch(72.8276120895% 0.1132257599 230.317904058deg) */
|
|
27
|
+
--oc-syntax-storage: var(--color-green-2); /* oklch(78.869080581% 0.1450237607 142.4953450414deg) */
|
|
28
|
+
--oc-syntax-tag: var(--color-brown-10); /* oklch(67.7979779433% 0.1404981732 25.5621058937deg) */
|
|
29
|
+
--oc-syntax-attr: var(--color-skycyan-70); /* oklch(72.8276120895% 0.1132257599 230.317904058deg) */
|
|
30
|
+
--oc-syntax-prop: var(--color-green-2); /* oklch(78.869080581% 0.1450237607 142.4953450414deg) */
|
|
31
|
+
--oc-syntax-value: var(--color-teal-10); /* oklch(71.6735992853% 0.0815848415 194.768959898deg) */
|
|
32
|
+
--oc-syntax-cls: var(--color-wheat-70); /* oklch(79.977339078% 0.0750386695 83.0327715335deg) */
|
|
33
|
+
--oc-syntax-id: var(--color-skycyan-70); /* oklch(72.8276120895% 0.1132257599 230.317904058deg) */
|
|
34
|
+
--oc-syntax-unit: var(--color-peru-40); /* oklch(72.3245616775% 0.1153837684 62.1815775778deg) */
|
|
35
|
+
--oc-syntax-key: var(--color-brown-10); /* oklch(67.7979779433% 0.1404981732 25.5621058937deg) */
|
|
36
|
+
|
|
37
|
+
/* oc-code-block: line numbers and tab size (match editor: 2 spaces) */
|
|
38
|
+
--oc-code-block-tab-size: 2;
|
|
39
|
+
--oc-code-block-line-number-color: oklch(from var(--color-white-2) l c h / 0.25);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ========== oc-code-block wrapper (line numbers + tab-size) ========== */
|
|
43
|
+
/* The Code-block always uses the dark Omnia syntax theme, also on light pages:
|
|
44
|
+
color-scheme: dark makes every light-dark() value inside it resolve to the dark variant. */
|
|
45
|
+
.oc-code-block,
|
|
46
|
+
pre.code-preview {
|
|
47
|
+
color-scheme: dark;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.oc-code-block {
|
|
51
|
+
display: grid;
|
|
52
|
+
grid-template-columns: auto 1fr;
|
|
53
|
+
margin: 0 0 14px;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
border: 1px solid var(--oc-border, var(--color-twilightblue-40));
|
|
56
|
+
background: var(--oc-syntax-bg);
|
|
57
|
+
border-radius: 8px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.oc-code-block__gutter {
|
|
61
|
+
margin: 0;
|
|
62
|
+
padding: 14px 12px 14px 16px;
|
|
63
|
+
font-size: 0.8rem;
|
|
64
|
+
line-height: 1.6;
|
|
65
|
+
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Consolas, monospace;
|
|
66
|
+
color: var(--oc-code-block-line-number-color);
|
|
67
|
+
background: var(--oc-syntax-bg);
|
|
68
|
+
text-align: right;
|
|
69
|
+
user-select: none;
|
|
70
|
+
white-space: pre;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.oc-code-block pre.code-preview {
|
|
74
|
+
margin: 0;
|
|
75
|
+
border: none;
|
|
76
|
+
border-radius: 0;
|
|
77
|
+
tab-size: var(--oc-code-block-tab-size, 2);
|
|
78
|
+
padding-left: 12px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Scrollbars: same on .oc-code-block pre.code (inherited from pre.code rules below) */
|
|
82
|
+
|
|
83
|
+
/* ========== Base block: <pre class="code"> ========== */
|
|
84
|
+
pre.code-preview {
|
|
85
|
+
position: relative;
|
|
86
|
+
margin: 0 0 14px;
|
|
87
|
+
overflow-x: auto;
|
|
88
|
+
border: 1px solid var(--oc-border, var(--color-twilightblue-40));
|
|
89
|
+
background: var(--oc-syntax-bg-preview);
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
padding: 14px 16px;
|
|
92
|
+
color: var(--oc-syntax-fg);
|
|
93
|
+
font-size: 0.8rem;
|
|
94
|
+
line-height: 1.6;
|
|
95
|
+
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Consolas, monospace;
|
|
96
|
+
white-space: pre;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
/* Code inhoud: spaties en newlines behouden, regels onder elkaar, monospace voor uitlijning */
|
|
101
|
+
pre.code-preview code {
|
|
102
|
+
display: block;
|
|
103
|
+
white-space: pre;
|
|
104
|
+
font-family: inherit;
|
|
105
|
+
font-size: inherit;
|
|
106
|
+
line-height: inherit;
|
|
107
|
+
}
|
|
108
|
+
/* WebKit (Chrome, Edge, Safari): same as docs sidebar */
|
|
109
|
+
pre.code-preview::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
110
|
+
pre.code-preview::-webkit-scrollbar-track { background: transparent; }
|
|
111
|
+
pre.code-preview::-webkit-scrollbar-thumb {
|
|
112
|
+
background: oklch(from var(--color-vividblue-50) l c h / 0.18);
|
|
113
|
+
border-radius: 4px;
|
|
114
|
+
border: 2px solid transparent;
|
|
115
|
+
background-clip: content-box;
|
|
116
|
+
}
|
|
117
|
+
pre.code-preview::-webkit-scrollbar-thumb:hover {
|
|
118
|
+
background: oklch(from var(--color-vividblue-50) l c h / 0.35);
|
|
119
|
+
background-clip: content-box;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ========== HTML ========== */
|
|
123
|
+
pre.code-preview .tag,
|
|
124
|
+
pre.code-preview .hl-tag { color: var(--oc-syntax-tag); }
|
|
125
|
+
|
|
126
|
+
pre.code-preview .attr,
|
|
127
|
+
pre.code-preview .hl-attr { color: var(--oc-syntax-attr); }
|
|
128
|
+
|
|
129
|
+
pre.code-preview .val,
|
|
130
|
+
pre.code-preview .hl-val,
|
|
131
|
+
pre.code-preview .hl-string { color: var(--oc-syntax-string); }
|
|
132
|
+
|
|
133
|
+
pre.code-preview .cmt,
|
|
134
|
+
pre.code-preview .hl-comment { color: var(--oc-syntax-comment); font-style: italic; }
|
|
135
|
+
|
|
136
|
+
/* ========== CSS ========== */
|
|
137
|
+
pre.code-preview .prop,
|
|
138
|
+
pre.code-preview .hl-prop { color: var(--oc-syntax-prop); }
|
|
139
|
+
|
|
140
|
+
pre.code-preview .hl-value { color: var(--oc-syntax-value); }
|
|
141
|
+
|
|
142
|
+
pre.code-preview .cls { color: var(--oc-syntax-cls); }
|
|
143
|
+
|
|
144
|
+
pre.code-preview .hl-id { color: var(--oc-syntax-id); }
|
|
145
|
+
|
|
146
|
+
pre.code-preview .hl-unit { color: var(--oc-syntax-unit); }
|
|
147
|
+
|
|
148
|
+
/* ========== Generic (PHP, JS, Python, JSON, etc.) ========== */
|
|
149
|
+
pre.code-preview .hl-keyword { color: var(--oc-syntax-keyword); }
|
|
150
|
+
|
|
151
|
+
pre.code-preview .hl-variable { color: var(--oc-syntax-variable); }
|
|
152
|
+
|
|
153
|
+
pre.code-preview .hl-function { color: var(--oc-syntax-function); }
|
|
154
|
+
|
|
155
|
+
pre.code-preview .hl-number { color: var(--oc-syntax-number); }
|
|
156
|
+
|
|
157
|
+
pre.code-preview .hl-operator { color: var(--oc-syntax-operator); }
|
|
158
|
+
|
|
159
|
+
pre.code-preview .hl-constant { color: var(--oc-syntax-constant); }
|
|
160
|
+
|
|
161
|
+
pre.code-preview .hl-storage { color: var(--oc-syntax-storage); }
|
|
162
|
+
|
|
163
|
+
pre.code-preview .hl-key { color: var(--oc-syntax-key); }
|