/** * @wrnexus/ui — the Wire UI component library. * * Components are `.wrn` files under `components/`, auto-discovered by the * framework (the router scans this directory in addition to the app's own * `app/components`). Mount them in any page with `data-component=""`. * Their styles live in a single themeable stylesheet, `ui.css`, served once at * `/__wrnexus/ui.css` — every class uses `var(--wire-*)` theme tokens. * * Override, in increasing order of power: * 1. theme tokens (change `--wire-color-primary`, etc.) * 2. redefine a `.wire-*` class in your own CSS (loaded after ui.css) * 3. pass a `class` prop (appended to the component root) * 4. `wrnexus eject ` to copy the component into `app/components` and own it */ import { readdirSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const packageRoot = join(here, ".."); let cachedCss: string | undefined; let cachedComponentFiles: Map | undefined; /** Absolute path to the directory of Wire UI component `.wrn` files. */ export function uiComponentsDir(): string { return join(packageRoot, "components"); } /** Absolute path to the Wire UI stylesheet. */ export function uiCssPath(): string { return join(packageRoot, "ui.css"); } /** The Wire UI stylesheet contents (all `.wire-*` classes, themed via tokens). */ export function uiCss(): string { return (cachedCss ??= readFileSync(uiCssPath(), "utf8")); } function uiComponentFiles(): Map { if (cachedComponentFiles) return cachedComponentFiles; const files = new Map(); const componentFiles = readdirSync(uiComponentsDir()).filter((entry) => entry.endsWith(".wrn")); for (const file of componentFiles) { const path = join(uiComponentsDir(), file); const source = readFileSync(path, "utf8"); const declaration = /^\s*component\s+([A-Za-z][A-Za-z0-9_]*)\b/m.exec(source); if (!declaration) { throw new Error(`UI component source does not declare a component: ${path}`); } const name = declaration[1]!; const previous = files.get(name); if (previous) { throw new Error(`Duplicate UI component declaration '${name}' in ${previous} and ${path}`); } files.set(name, path); } cachedComponentFiles = files; return files; } /** Names declared by the bundled components, independent of filename casing. */ export function uiComponentNames(): string[] { return [...uiComponentFiles().keys()].sort((left, right) => left.localeCompare(right)); } /** Absolute path to a bundled component by its declared component name. */ export function uiComponentPath(name: string): string { const files = uiComponentFiles(); const exact = files.get(name); if (exact) return exact; const normalized = name.toLowerCase(); const matches = [...files.entries()].filter( ([componentName]) => componentName.toLowerCase() === normalized, ); if (matches.length === 1) return matches[0]![1]; throw new Error(`Unknown WRNexus UI component '${name}'.`); } export { uiComponentReference, findUiComponent, auditUiComponents } from "./metadata.ts"; export type { UiComponentMetadata, UiComponentReference } from "./metadata.ts";