import { mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import process from "node:process"; import { fileURLToPath } from "node:url"; import prettier from "prettier"; import { eventDescriptionFor, profileFor } from "./showcase-profiles.mjs"; const exampleRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); const workspaceRoot = join(exampleRoot, "..", ".."); const referencePath = join(workspaceRoot, "packages", "ui", "component-reference.json"); const pagesDir = join(exampleRoot, "app", "pages"); const detailPagesDir = join(pagesDir, "components"); const layoutsDir = join(exampleRoot, "app", "layouts"); const reference = JSON.parse(readFileSync(referencePath, "utf8")); const interactiveOverlayComponents = new Set([ "ContextMenu", "Drawer", "Dropdown", "Modal", "Popover", "Tooltip", ]); const overlayVisibilityProps = new Set(["open", "defaultopen", "visible"]); function isInteractiveOverlay(component) { return interactiveOverlayComponents.has(component.name); } function overlayInteractionLabel(component) { switch (component.name) { case "ContextMenu": return "Right-click or use the trigger button"; case "Tooltip": return "Hover, focus, or click the trigger"; case "Drawer": return "Click the trigger to open the drawer"; case "Modal": return "Click the trigger to open the modal"; case "Dropdown": return "Click the trigger to open the menu"; case "Popover": return "Click the trigger to open the popover"; default: return "Interact with the trigger"; } } const displayName = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1 $2"); const baseSlugOf = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); const slugCounts = new Map(); for (const component of reference.components) { const slug = baseSlugOf(component.name); slugCounts.set(slug, (slugCounts.get(slug) ?? 0) + 1); } const slugOf = (name) => { const slug = baseSlugOf(name); if (slugCounts.get(slug) === 1) return slug; const component = reference.components.find((entry) => entry.name === name); return `${slug}-${component?.category ?? "component"}`; }; const titleCase = (value) => value .split("-") .map((word) => word[0].toUpperCase() + word.slice(1)) .join(" "); const identifier = (value) => value.replace(/(^|[^A-Za-z0-9_])([A-Za-z0-9])/g, (_, _sep, char) => char.toUpperCase()); const escapeText = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); // WRN uses braces for reactive expressions. Encode braces inside documentation // code so hydration keeps them as literal, copyable source text. /* * Code samples: each brace goes in its own element. * * The entity alone is not enough. It survives the compiler, but the BROWSER * decodes it back to a brace, and the client runtime then walks text nodes * looking for {mustache} and evaluates whatever it finds -- so a JSON sample * was silently eaten, leaving "columns=[, , , ]" on the page. A text node can * only be mistaken for a template when it holds a complete pair, so splitting * the braces into their own nodes makes samples immune. A reader sees no * difference. */ const escapeCode = (value) => escapeText(value).replaceAll("{", "{").replaceAll("}", "}"); /* * JSON for a textarea. Entities keep the braces away from the WRN compiler, * which would otherwise read them as interpolation and fail the page build, * while the browser decodes them so the reader still sees valid JSON. The * span trick used for code samples cannot be used here: a textarea shows its * markup literally. */ const escapeTextareaJson = (value) => escapeText(value).replaceAll("{", "{").replaceAll("}", "}"); const escapeAttribute = (value) => escapeText(value).replaceAll('"', """).replaceAll("'", "'"); function sampleItem(component, index, variation) { const number = index + 1; const labels = ["Primary workflow", "Secondary workflow", "Advanced workflow"]; return { id: `${slugOf(component.name)}-${variation}-${number}`, key: `${slugOf(component.name)}-${variation}-${number}`, value: index === 0 ? "primary" : "secondary", label: index === 0 ? labels[variation] : "Additional option", title: index === 0 ? labels[variation] : "Supporting example", description: variation === 0 ? "A clean default configuration for everyday product interfaces." : variation === 1 ? "A compact configuration designed for dense application layouts." : "A richer configuration with more supporting information and actions.", text: index === 0 ? "A configurable sample message." : "Another configurable message.", href: `#${slugOf(component.name)}-demo`, actionHref: `#${slugOf(component.name)}-demo`, actionLabel: variation === 2 ? "Explore workflow" : "View details", icon: variation === 0 ? "icon-[lucide--sparkles]" : variation === 1 ? "icon-[lucide--zap]" : "icon-[lucide--circle-check]", iconClass: variation === 0 ? "icon-[lucide--sparkles]" : variation === 1 ? "icon-[lucide--zap]" : "icon-[lucide--circle-check]", variant: variation === 0 ? "primary" : variation === 1 ? "secondary" : "success", status: variation === 2 ? "Complete" : "Active", time: variation === 0 ? "09:30" : variation === 1 ? "10:15" : "11:45", current: index === 0, selected: index === 0, checked: index === 0, disabled: variation === 2 && index === 1, outgoing: index === 1, open: true, number, count: number * (variation + 2) * 8, percentage: variation === 0 ? 72 : variation === 1 ? 48 : 91, color: variation === 0 ? "#7c3aed" : variation === 1 ? "#0284c7" : "#059669", target: "_self", ariaLabel: `Open ${labels[variation].toLowerCase()} ${number}`, items: [ { label: "Nested option A", value: "nested-a" }, { label: "Nested option B", value: "nested-b" }, ], links: [ { label: "Documentation", href: "#documentation" }, { label: "API reference", href: "#api-reference" }, ], values: ["Included", variation === 2 ? "Unlimited" : "Standard"], }; } function sampleArray(prop, component, variation) { const name = prop.name.toLowerCase(); if (name.includes("chartlabel") || name === "labels") return variation === 1 ? ["Q1", "Q2", "Q3", "Q4"] : ["Mon", "Tue", "Wed", "Thu", "Fri"]; if (name === "data" || name.includes("series")) return variation === 0 ? [18, 32, 27, 48, 64] : variation === 1 ? [64, 51, 43, 35] : [24, 46, 72, 88]; if (name === "pages") return [ { number: 1, href: "#page-1", current: variation === 0, ellipsis: false }, { number: 2, href: "#page-2", current: variation !== 0, ellipsis: false }, { number: 3, href: "#page-3", current: false, ellipsis: false }, ]; if (name === "plans") return [ { name: "Starter", label: "Starter", value: "starter" }, { name: "Pro", label: "Pro", value: "pro" }, { name: "Scale", label: "Scale", value: "scale" }, ]; if (name === "features") return [ { label: "Team members", values: ["5", "Unlimited", "Unlimited"] }, { label: "Support", values: ["Email", "Priority", "Dedicated"] }, { label: "Audit history", values: ["7 days", "90 days", "Custom"] }, ]; if (name === "logos") return ["Acme", "Northstar", "Vertex", "Orbit"].map((logo, index) => ({ name: logo, alt: `${logo} logo`, href: `#${slugOf(component.name)}-demo`, external: false, src: `/showcase-images/logo-${variation}-${index}.svg`, order: index, })); if (name.includes("navigationcolumns")) return [ { title: "Product", links: [ { label: "Overview", href: "#overview" }, { label: "Pricing", href: "#pricing" }, ], }, { title: "Resources", links: [ { label: "Guides", href: "#guides" }, { label: "Support", href: "#support" }, ], }, ]; if (name === "sections") return [ { label: "Product", value: "product", description: "Product navigation examples.", items: [ { label: "Platform", href: "#platform", description: "Explore the platform." }, { label: "Security", href: "#security", description: "Review security controls." }, ], }, { label: "Developers", value: "developers", description: "Developer resources.", items: [{ label: "Documentation", href: "#docs", description: "Read the guides." }], }, ]; if (name === "toasts") return [ { title: variation === 2 ? "Deployment complete" : "Changes saved", description: variation === 2 ? "The production release completed successfully." : "Your settings were saved successfully.", variant: "success", icon: "icon-[lucide--circle-check]", open: true, dismissible: true, dismissLabel: "Dismiss notification", }, ]; if (name === "stages") return [ { title: "Event received", description: "A customer event started the flow.", icon: "icon-[lucide--webhook]", }, { title: "Delivery", description: "Eligible channels are selected.", icon: "icon-[lucide--send]", items: [{ label: "Email" }, { label: "SMS" }], }, ]; return [sampleItem(component, 0, variation), sampleItem(component, 1, variation)]; } function defaultValue(prop) { if (prop.default === null) return undefined; if (prop.default === "true") return true; if (prop.default === "false") return false; if (/^-?\d+(?:\.\d+)?$/.test(prop.default ?? "")) return Number(prop.default); if (/^".*"$/.test(prop.default ?? "")) { try { return JSON.parse(prop.default); } catch { return undefined; } } return undefined; } function sampleValue(prop, component, variation) { const name = prop.name.toLowerCase(); if (component.name === "AdvancedSelect") { const configuration = advancedSelectConfiguration(variation); if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name]; return undefined; } if (component.name === "ComboBox") { const configuration = comboboxConfiguration(variation); if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name]; return undefined; } if (component.name === "PinInput") { const configuration = pinInputConfiguration(variation); if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name]; return undefined; } if (component.name === "StrongPassword") { const configuration = strongPasswordConfiguration(variation); if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name]; return undefined; } if (component.name === "TogglePassword") { const configuration = togglePasswordConfiguration(variation); if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name]; return undefined; } if (component.name === "InputNumber") { const configuration = inputNumberConfiguration(variation); if (Object.hasOwn(configuration, prop.name)) { return configuration[prop.name]; } return undefined; } const isArray = prop.default === "[]" || prop.type.includes("[]") || prop.type === "array"; const isObject = prop.default === "{}" || prop.type === "object"; if (isArray) return sampleArray(prop, component, variation); if (isObject) return sampleItem(component, 0, variation); if (name === "class") return `showcase-instance showcase-instance--${variation + 1}`; if (component.name === "Button") { const configurations = [ { label: "Save & Submit", as: "button", type: "submit", }, { label: "Learn more", as: "a", href: "#button-demo", variant: "outline", size: "sm", icon: "icon-[lucide--arrow-right]", iconPosition: "end", }, { label: "Upgrade plan", as: "button", variant: "secondary", size: "lg", pill: true }, { label: "Open settings", as: "button", ariaLabel: "Open settings", variant: "ghost", size: "icon", icon: "icon-[lucide--settings]", }, { label: "Delete project", as: "button", variant: "destructive", icon: "icon-[lucide--trash-2]", }, { label: "Read documentation", as: "a", href: "#button-demo", variant: "link" }, { label: "Publish", as: "button", loadingLabel: "Publishing…", loading: true }, { label: "Unavailable action", as: "button", disabled: true, variant: "outline" }, ]; const configuration = configurations[variation] ?? configurations[0]; if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name]; return undefined; } if (name === "controlclass") return variation === 2 ? "shadow-xl" : ""; if (prop.type === "boolean") { // Interactive overlays must render closed. A hard-coded open=true prop turns // the component into controlled mode, so its own click/hover handlers cannot // close it and multiple overlays block the complete showcase page. if (isInteractiveOverlay(component) && overlayVisibilityProps.has(name)) return false; if (name === "compact") return variation === 1; if (name === "fullwidth") return variation === 2; if (name === "loading" || name === "disabled") return false; if (name === "open" || name === "visible" || name === "dismissible" || name.startsWith("show")) return true; return defaultValue(prop) ?? variation === 2; } if (prop.type === "number") { if (name === "value") return 36 + variation * 24; if (name.includes("count") || name.includes("total") || name.includes("page")) return 3 + variation * 4; return defaultValue(prop) ?? variation + 1; } if (name.includes("arialabel")) return `${displayName(component.name)} ${variation + 1} example`; if (name === "title" || name.endsWith("title")) return variation === 0 ? `${displayName(component.name)} example` : variation === 1 ? `Compact ${displayName(component.name)}` : `Advanced ${displayName(component.name)}`; if (name.includes("description") || name === "summary") return variation === 0 ? `A polished default ${displayName(component.name).toLowerCase()} for a modern application.` : variation === 1 ? "A compact configuration for dashboards and dense product surfaces." : "A richer configuration demonstrating additional content and hierarchy."; if (name === "label" || name.endsWith("label")) return variation === 0 ? displayName(component.name) : variation === 1 ? "Compact example" : "Advanced example"; if (name.includes("placeholder")) return variation === 2 ? "Search by name, email, or identifier" : "Enter a sample value"; if (name.includes("email")) return variation === 2 ? "team@example.com" : "developer@example.com"; if (name.includes("href") || name === "action") { if (component.name === "Button" && variation === 0) return ""; return `#${slugOf(component.name)}-demo`; } if (name === "icon") return variation === 0 ? "" : variation === 1 ? "icon-[lucide--arrow-right]" : "icon-[lucide--trash-2]"; if (name === "variant") if (component.name === "Button") return variation === 0 ? "primary" : variation === 1 ? "secondary" : "danger"; if (name === "variant") return variation === 0 ? defaultValue(prop) || "primary" : variation === 1 ? "secondary" : "success"; if (name === "size") return variation === 0 ? defaultValue(prop) || "md" : variation === 1 ? "sm" : "lg"; if (name === "align") return variation === 1 ? "center" : variation === 2 ? "end" : "start"; if (name === "value") return `sample-${variation + 1}`; if (name.includes("count") || name.includes("total") || name.includes("page")) return undefined; if (prop.required) { return `${displayName(component.name)} sample`; } return undefined; } /* * Object props are hoisted into page state rather than written inline. * * A bare {...} attribute is read by the compiler as an interpolation, and * escaping the braces does not help either: prop coercion runs JSON.parse on * the raw attribute, which never sees the entities decoded, so the mount threw * and the demo silently rendered nothing. Binding to state is what the * playground already does, and it is the only form that survives both. * * Arrays start with [ and are unambiguous, so they stay inline. */ const hoistedObjects = []; function resetHoistedObjects() { hoistedObjects.length = 0; } function hoistObjectProp(value) { const name = `demo_obj_${hoistedObjects.length}`; // JSON.parse, not a bare object literal: the parser reads a leading brace // as the start of a block and rejects the rest of the page. const json = JSON.stringify(JSON.stringify(value)); hoistedObjects.push(` state ${name} = JSON.parse(${json})`); return name; } function hoistedObjectStates() { if (!hoistedObjects.length) return ""; // playgroundStates has no trailing newline, so lead with one. return "\n" + hoistedObjects.join("\n"); } function wrnAttribute(name, value) { if (typeof value === "object" && value !== null) { if (!Array.isArray(value)) return `${name}='{${hoistObjectProp(value)}}'`; return `${name}='${JSON.stringify(value).replaceAll("'", "\\u0027")}'`; } return `${name}="${escapeAttribute(String(value))}"`; } function componentAttributes(component, variation, overrides = {}) { const attributes = []; for (const prop of component.props) { const value = Object.hasOwn(overrides, prop.name) ? overrides[prop.name] : sampleValue(prop, component, variation); if (value === undefined) continue; attributes.push(wrnAttribute(prop.name, value)); } return attributes; } function configuredSlotMarkup(component, variation, overrides = {}) { const configured = overrides.__slots ?? {}; return component.slots .map((slot) => { const explicitlyConfigured = Object.hasOwn(configured, slot); if (slot !== "default" && !explicitlyConfigured) return ""; const custom = configured[slot]; const content = custom ?? (slot === "default" ? defaultSlotMarkup(component, variation) : ""); if (!content) return ""; return slot === "default" ? content : `
${content}
`; }) .join(""); } function componentMount(component, variation = 0, overrides = {}) { const attributes = componentAttributes(component, variation, overrides); const slotMarkup = configuredSlotMarkup(component, variation, overrides); const publicTag = /^[A-Z][A-Za-z0-9_]*$/.test(component.mount); if (publicTag) { const attributesSource = attributes.length ? ` ${attributes.join(" ")}` : ""; return slotMarkup ? `<${component.mount}${attributesSource}>${slotMarkup}` : `<${component.mount}${attributesSource} />`; } const attributesSource = attributes.length ? ` ${attributes.join(" ")}` : ""; return `
${slotMarkup}
`; } const structuralDefaultSlots = new Set([ "Accordion", "AspectRatio", "AutoGrid", "card", "Cluster", "CodeTabs", "Container", "ContentSection", "FilterBar", "ScrollArea", "Section", "SidebarLayout", "SplitLayout", "Stack", "Sticky", "StickyLayout", "Surface", "Typography", "VisuallyHidden", "Well", "WideContainer", ]); function defaultSlotMarkup(component, variation) { if (component.name === "Select") return ''; if (component.name === "Table") return "NameStatusProductionActiveStagingReady"; if (component.name === "Tooltip") return ""; if (!structuralDefaultSlots.has(component.name)) return ""; return `
${variation === 2 ? "Rich supporting content with additional context." : "Composable content area."}
`; } function indentSource(value, spaces = 2) { const indent = " ".repeat(spaces); return String(value) .trim() .split("\n") .map((line) => `${indent}${line}`) .join("\n"); } function sourceSnippet(component, variation, overrides = {}) { const attributes = component.props.flatMap((prop) => { const value = Object.hasOwn(overrides, prop.name) ? overrides[prop.name] : sampleValue(prop, component, variation); if (value === undefined || value === "" || prop.name === "class") return []; const declaredDefault = defaultValue(prop); if ( declaredDefault !== undefined && typeof value !== "object" && String(value) === String(declaredDefault) ) return []; if (typeof value === "object") { const serialized = JSON.stringify(value, null, 2) .replaceAll("'", "\\u0027") .replaceAll("\n", "\n "); return [`${prop.name}='${serialized}'`]; } return [`${prop.name}="${String(value).replaceAll('"', '\\"')}"`]; }); const formattedAttributes = attributes.length ? `\n ${attributes.join("\n ")}` : ""; const hasPublicComponentTag = /^[A-Z][A-Za-z0-9_]*$/.test(component.mount); const configured = overrides.__slots ?? {}; const slotParts = component.slots.flatMap((slot) => { const custom = configured[slot]; const fallback = slot === "default" ? defaultSlotMarkup(component, variation) : `
${displayName(slot)} slot
`; const content = custom ?? fallback; if (!content) return []; if (slot === "default") return [indentSource(content, 2)]; return [`
\n${indentSource(content, 4)}\n
`]; }); const slotMarkup = slotParts.length ? `\n${slotParts.join("\n")}\n` : ""; if (hasPublicComponentTag) { const source = slotMarkup ? `<${component.mount}${formattedAttributes}>${slotMarkup}` : formattedAttributes ? `<${component.mount}${formattedAttributes}\n/>` : `<${component.mount} />`; return escapeCode(source); } const mountAttribute = `data-component="${component.mount}"`; const source = slotMarkup ? `${slotMarkup}` : ``; return escapeCode(source); } function propsTable(component) { if (component.props.length === 0) return `
No props. Compose this component with slots.
`; return `
${component.props .map( (prop) => ``, ) .join("")}
PropTypeDefaultRequired
${escapeCode(prop.name)}${escapeText(prop.type)}${escapeCode(prop.default ?? "—")}${prop.required ? 'Yes' : "No"}
`; } function playgroundInitialValue(prop, component) { const profileValue = profileFor(component.name)?.playground?.[prop.name]; if (profileValue !== undefined) return profileValue; const sampled = sampleValue(prop, component, 0); return sampled === undefined ? (defaultValue(prop) ?? "") : sampled; } function playgroundValue(prop, component) { const value = playgroundInitialValue(prop, component); return typeof value === "object" ? JSON.stringify(value, null, 2) : String(value); } function playgroundStateName(prop) { return `playground_${prop.name.replace(/[^A-Za-z0-9_$]/g, "_")}`; } function isStructuredProp(prop) { return ( prop.default === "[]" || prop.default === "{}" || prop.type === "array" || prop.type === "object" || prop.type.includes("[]") ); } function playgroundStates(component) { return component.props .map((prop) => { const initialValue = playgroundInitialValue(prop, component); const query = `ctx.url.searchParams.get("pg_${prop.name}")`; if (prop.type === "boolean") { const initial = initialValue === true ? "true" : "false"; return ` state ${playgroundStateName(prop)} = (${query} ?? "${initial}") === "true"`; } if (prop.type === "number") { const initial = Number(initialValue || 0); return ` state ${playgroundStateName(prop)} = Number(${query} ?? ${JSON.stringify(String(initial))})`; } if (isStructuredProp(prop)) { const initial = JSON.stringify(initialValue ?? (prop.default === "{}" ? {} : [])); return ` state ${playgroundStateName(prop)} = JSON.parse(${query} ?? ${JSON.stringify(initial)})`; } return ` state ${playgroundStateName(prop)} = ${query} ?? ${JSON.stringify(String(initialValue ?? ""))}`; }) .join("\n"); } function playgroundMount(component) { const attributes = component.props.map((prop) => `${prop.name}='{${playgroundStateName(prop)}}'`); const profileSlots = profileFor(component.name)?.demos?.[0]?.slots ?? {}; const slotMarkup = configuredSlotMarkup(component, 0, { __slots: profileSlots }); const publicTag = /^[A-Z][A-Za-z0-9_]*$/.test(component.mount); if (publicTag) { const attributesSource = attributes.length ? ` ${attributes.join(" ")}` : ""; return slotMarkup ? `<${component.mount}${attributesSource}>${slotMarkup}` : `<${component.mount}${attributesSource} />`; } const attributesSource = attributes.length ? ` ${attributes.join(" ")}` : ""; return `
${slotMarkup}
`; } function playgroundControl(prop, component) { const value = playgroundValue(prop, component); const id = `playground-${slugOf(component.name)}-${prop.name}`; const label = escapeText(displayName(prop.name)); const meta = `${escapeText(prop.type)}${prop.required ? " · required" : ""}`; if (prop.type === "boolean") { return ``; } const structured = prop.default === "[]" || prop.default === "{}" || prop.type === "array" || prop.type === "object" || prop.type.includes("[]"); if (structured) { return ``; } const options = playgroundOptions(prop, component, value); if (options.length > 1) { return ``; } const inputType = prop.type === "number" ? "number" : prop.name.toLowerCase().includes("color") && /^#[0-9a-f]{6}$/i.test(value) ? "color" : "text"; return ``; } const commonPlaygroundOptions = { align: ["left", "center", "right", "start", "end", "stretch"], alignment: ["left", "center", "right", "start", "end", "stretch"], as: ["", "button", "a"], color: ["primary", "secondary", "success", "warning", "danger", "info"], direction: ["row", "column"], gap: ["none", "xs", "sm", "md", "lg", "xl", "2xl"], iconposition: ["start", "end", "left", "right"], imageposition: ["top", "left", "right"], layout: ["content", "split", "stacked", "balanced", "visual"], maxwidth: ["compact", "lg", "xl", "wide", "2xl", "full"], method: ["get", "post"], orientation: ["horizontal", "vertical"], placement: [ "top", "top-start", "top-end", "right", "right-start", "right-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end", ], position: ["top", "right", "bottom", "left", "start", "end", "center"], shape: ["round", "rounded", "pill"], size: ["default", "xs", "sm", "md", "lg", "xl", "icon", "icon-xs", "icon-sm", "icon-lg"], target: ["_self", "_blank", "_parent", "_top"], trigger: ["click", "hover", "focus", "manual"], visualposition: ["left", "right"], width: ["default", "compact", "wide", "full"], }; function playgroundOptions(prop, component, currentValue) { if (prop.type !== "string" || prop.name === "class" || prop.name === "controlClass") return []; const profileOptions = profileFor(component.name)?.options?.[prop.name] ?? []; const common = commonPlaygroundOptions[prop.name.toLowerCase()] ?? []; const componentSpecific = component.name === "Button" && prop.name === "type" ? ["button", "submit", "reset"] : []; const declared = [...profileOptions, ...componentSpecific, ...common]; // Free-form strings such as icon names, labels, URLs, IDs, and descriptions // must remain text inputs. Implementation comparisons are not a public enum. if (declared.length === 0) return []; const values = new Set(); if (typeof currentValue === "string" && currentValue.length <= 120) values.add(currentValue); const declaredDefault = defaultValue(prop); if (typeof declaredDefault === "string" && declaredDefault.length <= 120) values.add(declaredDefault); for (const option of declared) { if (typeof option === "string" && option.length <= 120) values.add(option); } return [...values]; } function playgroundSection(component) { const controls = component.props.map((prop) => playgroundControl(prop, component)).join(""); const publicTag = /^[A-Z][A-Za-z0-9_]*$/.test(component.mount); const eventNames = component.events.join(","); return `
Interactive playground

Configure ${escapeText(displayName(component.name))}

Change any prop and inspect the server-rendered component immediately.

Live preview
${playgroundMount(component)}
Component code
${sourceSnippet(component, 0)}
${component.events.length ? `
Event outputInteract with the preview to inspect the typed payload.
` : ""}
Component props${component.props.length} controls
${controls || '

This component has no declared props. Its slot content remains composable.

'}
`; } const defaultUses = [ { eyebrow: "Recommended", title: "Production default", description: "Balanced spacing, hierarchy, and content for the most common product workflow.", }, { eyebrow: "Dense UI", title: "Compact application", description: "A tighter variation for dashboards, side panels, tables, and operational interfaces.", }, { eyebrow: "Extended", title: "Rich configuration", description: "A more expressive variation using additional data, stronger emphasis, and optional states.", }, ]; const buttonUses = [ { eyebrow: "Default", title: "Primary action", description: "The standard high-emphasis action for forms and product workflows.", }, { eyebrow: "Outline · Small", title: "Secondary navigation", description: "A compact link button with an end icon and restrained emphasis.", }, { eyebrow: "Secondary · Large · Pill", title: "Prominent rounded action", description: "A large secondary action using the optional pill shape.", }, { eyebrow: "Ghost · Icon", title: "Accessible icon action", description: "An icon-only control with its accessible name supplied through props.", }, { eyebrow: "Destructive", title: "Dangerous action", description: "A destructive treatment for irreversible or high-risk operations.", }, { eyebrow: "Link", title: "Inline action", description: "A low-chrome action that behaves and reads like an inline link.", }, { eyebrow: "Loading", title: "Pending action", description: "The button owns its busy state, loading label, spinner, and disabled behavior.", }, { eyebrow: "Disabled", title: "Unavailable action", description: "A visibly and semantically disabled control.", }, ]; const advancedSelectTitles = [ "Default advanced select", "Grouped options", "Fixed-position dropdown", "Placeholder with icon", "Allow empty selection", "Multiple selection", "Multiple selection with optgroups", "Multiple selection with option templates", "Multiple selection with counter", "Multiple selection with conditional counter", "Counter with option templates", "Searchable dropdown", "Minimum search length", "Search result limit", "Scroll to selected option", "Search match mode", "Search labels and descriptions", "Tag-style selection", "Disabled tag selection", "Option template with icons", "Option template with avatars", "Option template with color indicators", "Advanced select sizes", "Disabled advanced select", "Inside a modal", "Validation states", "Dynamic validation styling", "Add or remove options", "Add or remove options in multiple mode", "Set a single value programmatically", "Set multiple values programmatically", "Remote data source", "Remote data source (multiple)", "Remote data tags", "Option templates with remote data", "Conditional counter with remote data", "Avatar template with remote data", "Preselected values", "Infinite scroll", "Inside overflow-hidden containers", "Destroy and reinitialize", ]; const advancedSelectUses = advancedSelectTitles.map((title, index) => ({ eyebrow: index < 11 ? "Selection" : index < 23 ? "Search & templates" : "Advanced behavior", title, description: `Live ${title.toLowerCase()} configuration with copyable component code.`, })); function advancedSelectConfiguration(variation) { const title = advancedSelectTitles[variation] ?? advancedSelectTitles[0]; const lower = title.toLowerCase(); const options = [ { value: "design", label: "Design", description: "Product and visual design", icon: "icon-[lucide--palette]", color: "#8b5cf6", avatar: "/showcase-images/avatar-design.svg", }, { value: "engineering", label: "Engineering", description: "Platform and application engineering", icon: "icon-[lucide--code-2]", color: "#0ea5e9", avatar: "/showcase-images/avatar-engineering.svg", }, { value: "growth", label: "Growth", description: "Marketing and customer growth", icon: "icon-[lucide--trending-up]", color: "#10b981", avatar: "/showcase-images/avatar-growth.svg", }, { value: "support", label: "Support", description: "Customer operations", disabled: false }, ]; const multiple = lower.includes("multiple") || lower.includes("tags"); const remote = lower.includes("remote") || lower.includes("infinite"); const grouped = lower.includes("group"); return { label: title, name: `advanced-select-${variation + 1}`, value: multiple || lower.includes("placeholder") ? "" : lower.includes("preselected") ? "engineering" : "design", values: multiple ? ["design", "engineering"] : [], options: grouped ? [] : options, groups: grouped ? [ { label: "Product", options: options.slice(0, 2) }, { label: "Business", options: options.slice(2) }, ] : [], placeholder: lower.includes("placeholder") ? "Choose a workspace" : "Choose a team", placeholderIcon: lower.includes("placeholder") ? "icon-[lucide--building-2]" : "", multiple, searchable: lower.includes("search") || remote || variation === 0, allowEmpty: lower.includes("empty"), tags: lower.includes("tag"), disabled: lower.includes("disabled"), required: lower.includes("validation"), invalid: false, validationMessage: "", minSearchLength: lower.includes("minimum") ? 3 : remote && !lower.includes("infinite") ? 2 : 0, searchResultLimit: lower.includes("result limit") ? 3 : 0, searchMode: lower.includes("match mode") ? "startsWith" : "contains", showCounter: lower.includes("counter"), maxSelections: lower.includes("conditional") ? 3 : 0, optionTemplate: lower.includes("avatar") ? "avatar" : lower.includes("color") ? "color" : lower.includes("icon") || lower.includes("template") ? "icon" : "default", fixed: lower.includes("fixed") || lower.includes("overflow"), remote, remoteUrl: remote ? "/api/teams" : "", infinite: lower.includes("infinite"), hasMore: lower.includes("infinite"), size: lower.includes("sizes") ? "lg" : "default", }; } const comboboxTitles = [ "Default autocomplete", "Local HTML options", "Grouped suggestions", "Minimum search length", "Search result limit", "Starts-with matching", "Search labels and descriptions", "Suggestions with icons", "Suggestions with avatars", "Suggestions with color indicators", "Clearable value", "Custom text values", "Preselected value", "Disabled combobox", "Validation state", "Remote data with automatic preload", "Remote search API", "Remote query parameters", "Infinite remote suggestions", "Fixed-position suggestions", "JavaScript methods", ]; const comboboxUses = comboboxTitles.map((title, index) => ({ eyebrow: index < 7 ? "Autocomplete" : index < 15 ? "Templates & states" : "Remote & API", title, description: `Live ${title.toLowerCase()} combobox with editable autocomplete and copyable component code.`, })); function comboboxConfiguration(variation) { const title = comboboxTitles[variation] ?? comboboxTitles[0]; const lower = title.toLowerCase(); const options = [ { value: "design", label: "Design", description: "Product and visual design", icon: "icon-[lucide--palette]", color: "#8b5cf6", avatar: "/showcase-images/avatar-design.svg", }, { value: "engineering", label: "Engineering", description: "Platform and application engineering", icon: "icon-[lucide--code-2]", color: "#0ea5e9", avatar: "/showcase-images/avatar-engineering.svg", }, { value: "growth", label: "Growth", description: "Marketing and customer growth", icon: "icon-[lucide--trending-up]", color: "#10b981", avatar: "/showcase-images/avatar-growth.svg", }, { value: "support", label: "Support", description: "Customer operations", icon: "icon-[lucide--life-buoy]", color: "#f59e0b", }, ]; const remote = lower.includes("remote") || lower.includes("infinite") || lower.includes("query parameter"); const grouped = lower.includes("grouped"); return { label: title, name: `combobox-${variation + 1}`, value: lower.includes("preselected") ? "engineering" : "", options: grouped ? [] : options, groups: grouped ? [ { label: "Product", options: options.slice(0, 2) }, { label: "Business", options: options.slice(2) }, ] : [], placeholder: remote ? "Search remote teams" : "Search teams", allowCustomValue: lower.includes("custom"), clearable: true, disabled: lower.includes("disabled"), required: lower.includes("validation"), invalid: false, validationMessage: "", minSearchLength: lower.includes("minimum") ? 3 : remote ? 2 : 0, searchResultLimit: lower.includes("result limit") ? 2 : 0, searchMode: lower.includes("starts-with") ? "startsWith" : "contains", optionTemplate: lower.includes("avatar") ? "avatar" : lower.includes("color") ? "color" : lower.includes("icon") ? "icon" : "default", fixed: lower.includes("fixed"), remote, remoteUrl: remote ? "/api/teams" : "", remoteQueryParam: "q", remoteAutoLoad: true, infinite: lower.includes("infinite"), hasMore: lower.includes("infinite"), }; } const pinInputTitles = [ "Default four-digit PIN", "Different lengths", "Pre-filled verification code", "Masked security PIN", "Alphanumeric regex", "Grouped code with separator", "Paste from clipboard", "Disabled PIN input", "Read-only code", "Validation state", "Supporting help text", "Large PIN cells", "Custom placeholder", "Automatic form submission", "Completion events", "JavaScript methods", ]; const pinInputUses = pinInputTitles.map((title, index) => ({ eyebrow: index < 7 ? "Entry behavior" : index < 13 ? "States & design" : "Automation", title, description: `Live ${title.toLowerCase()} example with keyboard navigation, filtering, and copyable component code.`, })); function pinInputConfiguration(variation) { const title = pinInputTitles[variation] ?? pinInputTitles[0]; const lower = title.toLowerCase(); const fourDigits = lower.includes("four-digit") || lower.includes("security pin"); return { label: title, name: `pin-input-${variation + 1}`, value: lower.includes("pre-filled") ? "482915" : lower.includes("read-only") ? "739204" : "", length: fourDigits ? 4 : 6, pattern: lower.includes("alphanumeric") ? "[A-Z0-9]" : "[0-9]", inputMode: lower.includes("alphanumeric") ? "text" : "numeric", placeholder: lower.includes("custom placeholder") ? "•" : "○", masked: lower.includes("masked"), disabled: lower.includes("disabled"), readonly: lower.includes("read-only"), required: lower.includes("validation"), invalid: false, validationMessage: "", allowPaste: true, separator: lower.includes("separator") ? "–" : "", groupSize: lower.includes("separator") ? 3 : 0, helpText: lower.includes("help text") ? "Enter the code sent to your registered email address." : "", autoSubmit: lower.includes("automatic"), size: lower.includes("large") ? "lg" : "default", }; } const strongPasswordTitles = [ "Live strength meter", "Requirements and hint text", "Requirements in a popover", "Custom special characters", "Optional requirements", "Disabled password strength", ]; const strongPasswordUses = strongPasswordTitles.map((title, index) => ({ eyebrow: index < 3 ? "Strength feedback" : "Configuration", title, description: `Live ${title.toLowerCase()} example with accessible scoring feedback and copyable component code.`, })); function strongPasswordConfiguration(variation) { const title = strongPasswordTitles[variation] ?? strongPasswordTitles[0]; const lower = title.toLowerCase(); return { label: title, name: `strong-password-${variation + 1}`, value: lower.includes("live") ? "Northstar!2026" : "", placeholder: "Create a strong password", autocomplete: "new-password", minLength: 8, specialCharactersSet: lower.includes("custom") ? "@#_-." : "!@#$%^&*()_+-=[]{}|;:,.<>?", requireLowercase: true, requireUppercase: true, requireNumber: true, requireSpecialCharacter: !lower.includes("optional"), showRequirements: !lower.includes("live"), presentation: lower.includes("popover") ? "popover" : "inline", hintText: lower.includes("hint") ? "Use a unique password you do not use for another account." : "", disabled: lower.includes("disabled"), readonly: false, required: false, invalid: false, validationMessage: "", }; } const togglePasswordTitles = [ "Inline visibility button", "Checkbox-controlled toggle", "Synchronized password fields", "Without visibility toggle", "Supporting help text", "Required validation", "Disabled password", "Read-only password", "Large password field", "Toggle events", ]; const togglePasswordUses = togglePasswordTitles.map((title, index) => ({ eyebrow: index < 4 ? "Visibility" : index < 8 ? "Forms & states" : "Design & events", title, description: `Live ${title.toLowerCase()} example with accessible visibility controls and copyable component code.`, })); function togglePasswordConfiguration(variation) { const title = togglePasswordTitles[variation] ?? togglePasswordTitles[0]; const lower = title.toLowerCase(); const checkbox = lower.includes("checkbox"); const synchronized = lower.includes("synchronized"); return { label: synchronized ? "Passwords" : lower.includes("inline") ? "Password" : title, name: `toggle-password-${variation + 1}`, value: lower.includes("inline") || checkbox || lower.includes("read-only") ? "Northstar!2026" : "", placeholder: "Enter your password", autocomplete: "current-password", minlength: lower.includes("validation") ? "8" : "", maxlength: "128", pattern: "(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}", fields: synchronized ? [ { label: "New password", name: "new-password", placeholder: "Enter new password", autocomplete: "new-password", }, { label: "Current password", name: "current-password", value: "Northstar!2026", autocomplete: "current-password", }, ] : [], visible: false, toggleable: !lower.includes("without"), toggleMode: checkbox ? "checkbox" : "button", checkboxLabel: "Show password", disabled: lower.includes("disabled"), readonly: lower.includes("read-only"), required: lower.includes("validation"), helpText: lower.includes("help text") ? "Use at least eight characters and avoid passwords used on other sites." : "", validationMessage: "", invalid: false, size: lower.includes("large") ? "lg" : "default", }; } const inputNumberUses = [ { eyebrow: "Basic usage", title: "Default controls", description: "Use standard decrement and increment buttons alongside a numeric input field.", }, { eyebrow: "Basic usage", title: "Labeled input style", description: "Add supporting label text and rounded controls for a more form-like number input.", }, { eyebrow: "Layout", title: "Vertical buttons", description: "Stack the increment and decrement buttons vertically beside the field.", }, { eyebrow: "Layout", title: "Horizontal buttons", description: "Place the increment and decrement buttons on opposite sides of the value.", }, { eyebrow: "Sizing", title: "Compact size", description: "Use a smaller compact control when the available space is limited.", }, { eyebrow: "Commerce", title: "Seat quantity selector", description: "Pair the control with pricing information for seat counts or plan add-ons.", }, { eyebrow: "States", title: "Disabled input", description: "Disable manual entry while keeping supported quantity controls available.", }, { eyebrow: "States", title: "Disabled buttons", description: "Disable increment and decrement controls while leaving the current value visible.", }, { eyebrow: "Behaviour", title: "Custom step size", description: "Set a custom step so every button press changes the value by two.", }, { eyebrow: "Limits", title: "Negative values", description: "Use the min option to permit controlled negative values.", }, { eyebrow: "Limits", title: "Maximum limit", description: "Set an upper limit and disable incrementing when that limit is reached.", }, { eyebrow: "Validation", title: "Validation states", description: "Communicate whether the current numerical value is valid.", }, ]; function inputNumberConfiguration(variation) { const configurations = [ { name: "default-quantity", value: 1, min: 0, max: 99, step: 1, variant: "default", }, { name: "labeled-quantity", label: "Select quantity", value: 1, min: 1, max: 25, step: 1, variant: "labeled", }, { name: "vertical-quantity", label: "Select quantity", value: 1, min: 0, max: 20, variant: "vertical", }, { name: "horizontal-quantity", value: 1, min: 0, max: 20, variant: "horizontal", }, { name: "compact-quantity", value: 0, min: 0, max: 10, variant: "compact", size: "sm", }, { name: "additional-seats", label: "Additional seats", description: "$39 monthly", value: 0, min: 0, max: 50, variant: "seat", }, { name: "disabled-input", value: 10, inputDisabled: true, }, { name: "disabled-buttons", value: 10, buttonsDisabled: true, }, { name: "custom-step", value: 0, min: 0, max: 20, step: 2, }, { name: "negative-values", value: -10, min: -100, max: 100, }, { name: "maximum-limit", value: 10, min: 0, max: 10, help: "The maximum value is 10.", }, { name: "invalid-quantity", value: 10, min: 0, max: 5, error: "Out of limit", }, ]; return { color: "primary", size: "md", ...configurations[variation], }; } function demoUses(component) { const profile = profileFor(component.name); if (profile?.demos?.length) { // `before` is carried through: it is markup rendered beside the mount for // host components that have no slot to put controls in. return profile.demos.map(({ eyebrow, title, description, before }) => ({ eyebrow, title, description, before, })); } switch (component.name) { case "Button": return buttonUses; case "AdvancedSelect": return advancedSelectUses; case "ComboBox": return comboboxUses; case "InputNumber": return inputNumberUses; case "PinInput": return pinInputUses; case "StrongPassword": return strongPasswordUses; case "TogglePassword": return togglePasswordUses; default: return defaultUses; } } function demoConfiguration(component, variation) { const demo = profileFor(component.name)?.demos?.[variation]; if (!demo) return {}; return { ...(demo.props ?? {}), __slots: demo.slots ?? {}, }; } function eventDocumentation(component) { if (!component.events.length) return ""; const eventRows = component.events .map( (eventName) => `
@${escapeCode(eventName)}${escapeText(eventDescriptionFor(component.name, eventName))}
`, ) .join(""); const declarativeAttributes = component.events .map((eventName) => ` @${eventName}='console.log(payload)'`) .join("\n"); const declarativeCode = `<${component.mount}\n${declarativeAttributes}\n/>`; const selector = `[data-ui-component="${component.name}"], [data-component="${component.mount}"]`; const listenerCode = `import { registerOutputHandler } from "@wrnexus/csr/outputs"\n\nconst component = document.querySelector(${JSON.stringify(selector)})\n\n${component.events .map( (eventName) => `if (component) registerOutputHandler(component, ${JSON.stringify(eventName)}, (payload) => {\n console.log(${JSON.stringify(eventName)}, payload)\n})`, ) .join("\n\n")}`; return `
Component outputs

Receive every typed component output

Use declarative output handlers in .wrn files or register a direct output handler from JavaScript. The canonical API exposes payload and does not require event.detail.

${eventRows}
Declarative handlers.wrn
${escapeCode(declarativeCode)}
Direct output handlers.js
${escapeCode(listenerCode)}
`; } function detailPage(component, categoryComponents) { // Names are per page; without this they accumulate across the whole run. resetHoistedObjects(); const index = categoryComponents.findIndex((item) => item.name === component.name); const previous = categoryComponents[index - 1]; const next = categoryComponents[index + 1]; const uses = demoUses(component); const demos = uses .map((useCase, variation) => { const configuredDemo = demoConfiguration(component, variation); const isPinLengthDemo = component.name === "PinInput" && variation === 1; const pinLengths = [3, 5, 7]; const mount = isPinLengthDemo ? `
${pinLengths .map((length) => componentMount(component, variation, { ...configuredDemo, label: `${length}-digit code`, name: `pin-length-${length}`, length, }), ) .join("")}
` : componentMount(component, variation, configuredDemo); const snippet = isPinLengthDemo ? pinLengths .map((length) => sourceSnippet(component, variation, { ...configuredDemo, label: `${length}-digit code`, name: `pinLength${length}`, length, }), ) .join("\n\n") : sourceSnippet(component, variation, configuredDemo); const validationSchema = component.name === "AdvancedSelect" && variation === 25 ? "advanced-select-validation" : component.name === "AdvancedSelect" && variation === 26 ? "advanced-select-dynamic-validation" : component.name === "ComboBox" && variation === 14 ? "combobox-validation" : component.name === "PinInput" && variation === 9 ? "pin-input-validation" : component.name === "TogglePassword" && variation === 5 ? "toggle-password-validation" : ""; const preview = validationSchema ? `
${mount}
` : mount; // `before` renders markup NEXT TO the mount rather than inside its slot. // Host-style components (Toaster) have no slot at all: what a reader // needs to see is the controls that drive them, and slot content would // simply be dropped. const beforeMarkup = useCase.before ?? ""; return `
${useCase.eyebrow}

${useCase.title}

${useCase.description}

0${variation + 1}
Live preview
${isInteractiveOverlay(component) ? `

${escapeText(overlayInteractionLabel(component))}

` : ""}
${beforeMarkup}${preview}
Component usage.wrn
${snippet}
`; }) .join(""); return `// Generated by scripts/generate-showcase.mjs. Do not edit directly. page ${component.name.replace(/[^A-Za-z0-9_]/g, "")}Detail { layout = "showcase" ${playgroundStates(component)}${hoistedObjectStates()} seo { title = "${escapeAttribute(displayName(component.name))}" description = "${escapeAttribute(component.purpose)}" } view {
${titleCase(component.category)} component

${escapeText(displayName(component.name))}

${escapeText(component.purpose)}

${component.props.length} props${component.slots.length} slots${component.events.length} outputsTheme readyResponsive
${playgroundSection(component)}
Live examples

Designed for real product surfaces

Compare configurations and resize the browser to check responsive behavior.

${demos}
${eventDocumentation(component)}
Component API

Props and configuration

All content and behavior shown above is supplied through these props and slots.

${propsTable(component)}
} } `; } function categoryPage(category, components) { const liveDemoCount = components.reduce( (total, component) => total + demoUses(component).length, 0, ); const cards = components .map( (component) => `
${componentMount(component, 0, demoConfiguration(component, 0))}${isInteractiveOverlay(component) ? `

${escapeText(overlayInteractionLabel(component))}

` : ""}
${titleCase(category)}

${escapeText(displayName(component.name))}

${escapeText(component.purpose)}

`, ) .join(""); return `// Generated by scripts/generate-showcase.mjs. Do not edit directly. page ${identifier(category)}Showcase { layout = "showcase" seo { title = "${titleCase(category)} components" description = "${components.length} ${category} components from @wrnexus/ui." } view {
Component category

${titleCase(category)}

${components.length} unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.

${components.length} componentsMultiple use cases${liveDemoCount} live configurations
${cards}
} } `; } const categories = Object.groupBy(reference.components, (component) => component.category); const categoryEntries = Object.entries(categories).sort(([left], [right]) => left.localeCompare(right), ); const totalDemoCount = reference.components.reduce( (total, component) => total + demoUses(component).length, 0, ); function showcaseLayout() { const directory = categoryEntries .map( ([category, components]) => `
${titleCase(category)}
${components .map( (component) => `${escapeText(displayName(component.name))}`, ) .join("")}
`, ) .join(""); return `// Generated by scripts/generate-showcase.mjs. Do not edit directly. layout Showcase { view {
WRNexus UI
} } `; } function documentLayout() { return `// Generated by scripts/generate-showcase.mjs. Do not edit directly. layout Document { props { cookies = {} theme = "light" language = "en" url = "" pathname = "/" } view {
} } `; } mkdirSync(pagesDir, { recursive: true }); mkdirSync(detailPagesDir, { recursive: true }); mkdirSync(layoutsDir, { recursive: true }); writeFileSync(join(layoutsDir, "showcase.wrn"), showcaseLayout(), "utf8"); writeFileSync(join(layoutsDir, "document.wrn"), documentLayout(), "utf8"); for (const file of readdirSync(pagesDir).filter((entry) => entry.endsWith(".wrn"))) { unlinkSync(join(pagesDir, file)); } for (const file of readdirSync(detailPagesDir).filter((entry) => entry.endsWith(".wrn"))) { unlinkSync(join(detailPagesDir, file)); } for (const [category, components] of categoryEntries) { writeFileSync(join(pagesDir, `${category}.wrn`), categoryPage(category, components), "utf8"); for (const component of components) { writeFileSync( join(detailPagesDir, `${slugOf(component.name)}.wrn`), detailPage(component, components), "utf8", ); } } const showcaseManifest = { generatedFrom: "packages/ui/component-reference.json", componentCount: reference.count, categoryCount: categoryEntries.length, totalDemoCount, components: reference.components.map((component) => ({ name: component.name, mount: component.mount, slug: slugOf(component.name), category: component.category, purpose: component.purpose, demoCount: demoUses(component).length, propCount: component.props.length, slots: component.slots, events: component.events, profiled: Boolean(profileFor(component.name)), })), }; const formattedManifest = await prettier.format(`${JSON.stringify(showcaseManifest, null, 2)}\n`, { parser: "json", printWidth: 100, endOfLine: "lf", }); writeFileSync(join(exampleRoot, "showcase-manifest.json"), formattedManifest, "utf8"); const categoryCards = categoryEntries .map( ([category, components], index) => `0${index + 1}
${titleCase(category)}

${components.length} components · ${components.reduce((total, component) => total + demoUses(component).length, 0)} live demos

`, ) .join(""); writeFileSync( join(pagesDir, "index.wrn"), `// Generated by scripts/generate-showcase.mjs. Do not edit directly. page ComponentShowcase { layout = "showcase" seo { title = "WRNexus component system" description = "Explore every unique @wrnexus/ui component through live, production-quality examples." } view {
The UI foundation for WRNexus

Build modern products
at framework speed.

A complete, accessible component system with ${reference.count} primitives, ready-made blocks, page templates, responsive behavior, and deeply configurable design tokens.

$bun add @wrnexus/ui
${reference.count}Unique components
${totalDemoCount}Live configurations
0Duplicate implementations
${categoryEntries.length}Focused categories
Explore the system

Everything your product needs

${categoryCards}
} } `, "utf8", ); const docsSections = [ [ "installation", "Installation", "Install the package and import the shared stylesheet once in your application.", "bun add @wrnexus/ui", ], [ "framework-guides", "Framework guides", "WRNexus components use native HTML semantics and framework component mounts.", ], [ "accessibility", "Accessibility", "Keyboard interaction, visible focus states, labels, and reduced-motion support ship by default.", ], [ "dark-mode", "Dark mode", "Choose light, dark, or system mode. The selection persists and updates every preview.", ], [ "themes", "Themes", "Theme presets control radius, surface treatment, borders, shadows, and motion.", ], [ "colors", "Colors", "Semantic primary, secondary, success, warning, danger, and info tokens remain available in every palette.", ], [ "fonts", "Fonts", "Select a font family globally while components continue to inherit typography correctly.", ], [ "sizing", "Sizing", "Compact through large scales adjust the complete component system consistently.", ], [ "resources", "Resources", "Use the component API pages, blocks, and templates as production starting points.", ], ]; writeFileSync( join(pagesDir, "docs.wrn"), `page Documentation { layout = "showcase" seo { title = "Documentation" description = "Install, configure, and use WRNexus UI." } view {
Getting started

Build with WRNexus UI

Configure one design system and use it across components, blocks, and complete page templates.

Explore the documentation

Every Getting Started and Customization topic now has its own page. Choose a guide from the sidebar to continue.

} } `, "utf8", ); const v06Guides = [ [ "types", "Types", "TypeScript contracts for props, state, outputs, imported application types, and generated declarations.", [ [ "Application types", "Load ambient app/types/global.d.ts and explicitly import types from app/types/*.ts.", ], [ "Typed contracts", "Validate component props, state assignments, function parameters, returns, and output payloads.", ], [ "Generated declarations", "Generate component, output, store, layout, and server-call declarations under dist/types.", ], ], ], [ "functions", "Functions", "Classify behavior for the browser, server, or both runtimes from one functions block.", [ ["Client", "Compile browser-only functions into CSP-safe client modules."], [ "Server", "Keep server-only functions out of browser artifacts and expose typed RPC calls when referenced.", ], ["Shared", "Emit deterministic shared helpers to both compilation targets."], ], ], [ "outputs", "Outputs", "Use typed callable outputs for child-to-parent communication without application-level $emit or event.detail.", [ ["Declare", "Define zero- or one-payload output signatures in an outputs block."], ["Invoke", "Call output.name(payload) from client functions."], ["Consume", "Receive the typed payload local in parent component listeners."], ], ], [ "stores", "Stores", "Create request-safe global stores and route-scoped page stores with typed actions and computed state.", [ ["Global stores", "Survive CSR navigation while remaining isolated for every SSR request."], [ "Page stores", "Share state with route descendants and dispose it when navigation leaves the route.", ], ["Persistence", "Persist include-only safe fields to memory, session, or local storage."], ], ], [ "imports", "Imports", "Declare component, layout, store, package, and type dependencies explicitly while retaining compatibility mode.", [ ["Application imports", "Resolve @/ aliases to the active application directory."], ["Package imports", "Use named imports for components exported by @wrnexus/ui."], [ "Compatibility", "Run legacy, compatible, or explicit import modes during project migration.", ], ], ], [ "state", "State", "Use individual or grouped typed state declarations with shared, client-only, and server-only scopes.", [ [ "Shared state", "Render during SSR and safely hydrate serializable values into the browser.", ], ["Client state", "Keep browser-only interaction state out of server artifacts."], ["Server state", "Keep sensitive request-only state out of hydration data."], ], ], [ "runtime", "Runtime", "Split server and browser artifacts, hydrate only interactive components, and preserve compatible state during HMR.", [ [ "Partial hydration", "Load client code using load, idle, visible, interaction, media, or none strategies.", ], [ "RPC", "Validate browser-to-server inputs and outputs with same-origin and CSRF-aware defaults.", ], ["HMR", "Replace functions and preserve compatible component and store state."], ], ], [ "migration", "Migration", "Upgrade older projects through idempotent v0.6 migrations with backups, dry runs, and review reports.", [ [ "Legacy outputs", "Convert @event declarations, $emit calls, and event.detail listeners to outputs and payload.", ], [ "Explicit dependencies", "Add component and layout imports only when resolution is unambiguous.", ], [ "Safety", "Parse and type-check changed files and roll back when migration validation fails.", ], ], ], ]; for (const [slug, title, description, sections] of v06Guides) { writeFileSync( join(pagesDir, `v06-${slug}.wrn`), `// Generated by scripts/generate-showcase.mjs. Do not edit directly. page V06${identifier(title)}Guide { layout = "showcase" seo { title = "WRNexusJS v0.6 ${escapeAttribute(title)}" description = "${escapeAttribute(description)}" } view {
WRNexusJS v0.6

${escapeText(title)}

${escapeText(description)}

${sections.map(([heading, copy]) => `

${escapeText(heading)}

${escapeText(copy)}

`).join("")}
} } `, "utf8", ); } const customizationGuides = new Set(["dark-mode", "themes", "colors", "fonts", "sizing"]); for (const [slug, title, description, code] of docsSections) { const section = customizationGuides.has(slug) ? "Customization" : "Getting Started"; const examples = slug === "colors" ? [ ["Brand colors", "Primary and secondary tokens communicate brand hierarchy."], [ "Status colors", "Success, warning, danger, and info preserve their semantic meaning in every palette.", ], [ "Component override", 'Pass color="success" or another semantic color to any component.', ], ] : slug === "sizing" ? [ ["Component size", "Every component accepts xs, sm, default, md, lg, and xl."], [ "Global density", "Compact, default, comfortable, and large scale the complete interface.", ], ["Responsive sizing", "Component sizing cooperates with responsive breakpoints."], ] : slug === "themes" ? [ ["Default", "Balanced surfaces, borders, elevation, and radius."], ["Soft and sharp", "Choose friendly rounded surfaces or dense operational styling."], ["Glass", "Use translucent layered surfaces while retaining semantic tokens."], ] : [ ["Overview", description], ["Configuration", "Use the design switcher in the header to test this setting live."], [ "Component behavior", "The setting is inherited by components, blocks, templates, and playground previews.", ], ]; writeFileSync( join(pagesDir, `${slug}.wrn`), `page ${identifier(slug)}Guide { layout = "showcase" seo { title = "${escapeAttribute(title)}" description = "${escapeAttribute(description)}" } view {
${section}

${escapeText(title)}

${escapeText(description)}

${examples.map(([heading, copy]) => `

${escapeText(heading)}

${escapeText(copy)}

`).join("")} ${code ? `

Quick start

${escapeCode(code)}
` : ""}
} } `, "utf8", ); } const blockCards = [ [ "Hero sections", "Marketing", "layout-template", "Hero, actions, product proof, and visual stage.", ], [ "Feature grids", "Marketing", "grid-2x2", "Responsive benefits and product capability sections.", ], ["Pricing sections", "Marketing", "badge-dollar-sign", "Tier cards, comparison grids, and CTAs."], [ "Application shells", "Application UI", "panel-left", "Headers, sidebars, content, and command areas.", ], [ "Dashboard stats", "Application UI", "chart-no-axes-combined", "Metrics, trends, filters, and summaries.", ], ["Checkout panels", "Ecommerce", "shopping-cart", "Cart, address, payment, and order summary."], [ "Article layouts", "Blog & Articles", "newspaper", "Editorial headers, content, author, and sharing.", ], [ "Authentication", "Application UI", "shield-check", "Sign in, registration, recovery, and verification.", ], ]; // Keep /blocks for the generated Blocks component category. writeFileSync( join(pagesDir, "block-library.wrn"), `page BlockLibrary { layout = "showcase" seo { title = "UI blocks" description = "Complete sections composed from WRNexus UI components." } view {
Composable sections

Ready-to-use UI blocks

Complete responsive sections composed exclusively from the components in this library.

${blockCards.map(([title, category, icon, description], index) => ``).join("")}
} } `, "utf8", ); const templates = [ [ "Admin dashboard", "Dashboard & Admin", "31 pages", "Analytics, teams, settings, billing, and operational workflows.", ], [ "Commerce storefront", "Ecommerce", "18 pages", "Catalog, product, cart, checkout, and customer account pages.", ], [ "SaaS website", "Websites", "12 pages", "Landing, pricing, about, blog, contact, and legal pages.", ], [ "Support center", "Support", "9 pages", "Knowledge base, ticketing, status, and customer help experiences.", ], [ "Finance workspace", "Finance & Payments", "14 pages", "Accounts, transactions, reporting, transfers, and controls.", ], ]; writeFileSync( join(pagesDir, "templates.wrn"), `page Templates { layout = "showcase" seo { title = "Page templates" description = "Complete pages assembled from WRNexus UI blocks." } view {
Complete products

Production-ready templates

Responsive page systems assembled from WRNexus blocks and components.

${templates.map(([title, category, count], index) => `
${count}

${title}

${category} template with consistent navigation, content, forms, and responsive behavior.

Preview
`).join("")}
} } `, "utf8", ); process.stdout.write( `Generated ${reference.count} component detail pages, ${totalDemoCount} live demos, and ${categoryEntries.length} category pages.\n`, );