release: WRNexusJS 0.6.0
This commit is contained in:
@@ -7,7 +7,6 @@ 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 uiComponentsPath = join(workspaceRoot, "packages", "ui", "components");
|
||||
const pagesDir = join(exampleRoot, "app", "pages");
|
||||
const detailPagesDir = join(pagesDir, "components");
|
||||
const layoutsDir = join(exampleRoot, "app", "layouts");
|
||||
@@ -74,7 +73,6 @@ const escapeText = (value) =>
|
||||
const escapeCode = (value) => escapeText(value).replaceAll("{", "{").replaceAll("}", "}");
|
||||
const escapeAttribute = (value) =>
|
||||
escapeText(value).replaceAll('"', """).replaceAll("'", "'");
|
||||
const jsonAttribute = (value) => escapeAttribute(JSON.stringify(value));
|
||||
|
||||
function sampleItem(component, index, variation) {
|
||||
const number = index + 1;
|
||||
@@ -532,9 +530,7 @@ function sourceSnippet(component, variation, overrides = {}) {
|
||||
const content = custom ?? fallback;
|
||||
if (!content) return [];
|
||||
if (slot === "default") return [indentSource(content, 2)];
|
||||
return [
|
||||
` <div data-slot="${slot}">\n${indentSource(content, 4)}\n </div>`,
|
||||
];
|
||||
return [` <div data-slot="${slot}">\n${indentSource(content, 4)}\n </div>`];
|
||||
});
|
||||
const slotMarkup = slotParts.length ? `\n${slotParts.join("\n")}\n` : "";
|
||||
|
||||
@@ -614,9 +610,7 @@ function playgroundStates(component) {
|
||||
}
|
||||
|
||||
function playgroundMount(component) {
|
||||
const attributes = component.props.map(
|
||||
(prop) => `${prop.name}='{${playgroundStateName(prop)}}'`,
|
||||
);
|
||||
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);
|
||||
@@ -682,7 +676,20 @@ const commonPlaygroundOptions = {
|
||||
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"],
|
||||
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"],
|
||||
@@ -698,9 +705,7 @@ function playgroundOptions(prop, component, currentValue) {
|
||||
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"]
|
||||
: [];
|
||||
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
|
||||
@@ -733,7 +738,7 @@ function playgroundSection(component) {
|
||||
<pre><code data-playground-code>${sourceSnippet(component, 0)}</code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
${component.events.length ? `<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect <code>event.detail</code>.</span></div>` : ""}
|
||||
${component.events.length ? `<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>` : ""}
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>${component.props.length} controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
@@ -1431,18 +1436,18 @@ function eventDocumentation(component) {
|
||||
)
|
||||
.join("");
|
||||
const declarativeAttributes = component.events
|
||||
.map((eventName) => ` @${eventName}='console.log(event.detail)'`)
|
||||
.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 = `const component = document.querySelector(${JSON.stringify(selector)})\n\n${component.events
|
||||
const listenerCode = `import { registerOutputHandler } from "@wrnexus/csr/outputs"\n\nconst component = document.querySelector(${JSON.stringify(selector)})\n\n${component.events
|
||||
.map(
|
||||
(eventName) =>
|
||||
`component?.addEventListener(${JSON.stringify(eventName)}, (event) => {\n console.log(${JSON.stringify(eventName)}, event.detail)\n})`,
|
||||
`if (component) registerOutputHandler(component, ${JSON.stringify(eventName)}, (payload) => {\n console.log(${JSON.stringify(eventName)}, payload)\n})`,
|
||||
)
|
||||
.join("\n\n")}`;
|
||||
|
||||
return `<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component events</span><h2>Respond to every public interaction</h2><p>Use declarative <code>@event</code> handlers in <code>.wrn</code> files or subscribe to the bubbling browser events from JavaScript.</p></div><div class="detail-events-grid"><div class="detail-events-list">${eventRows}</div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>${escapeCode(declarativeCode)}</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Browser listeners</span><small>.js</small></header><pre><code>${escapeCode(listenerCode)}</code></pre></section></div></div></section>`;
|
||||
return `<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list">${eventRows}</div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>${escapeCode(declarativeCode)}</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>${escapeCode(listenerCode)}</code></pre></section></div></div></section>`;
|
||||
}
|
||||
|
||||
function detailPage(component, categoryComponents) {
|
||||
@@ -1532,7 +1537,7 @@ ${playgroundStates(component)}
|
||||
<span class="showcase-eyebrow">${titleCase(component.category)} component</span>
|
||||
<h1>${escapeText(displayName(component.name))}</h1>
|
||||
<p>${escapeText(component.purpose)}</p>
|
||||
<div class="detail-badges"><span>${component.props.length} props</span><span>${component.slots.length} slots</span><span>${component.events.length} events</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>${component.props.length} props</span><span>${component.slots.length} slots</span><span>${component.events.length} outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -1544,7 +1549,7 @@ ${playgroundStates(component)}
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div>${propsTable(component)}</section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a>${uses.map((useCase, variation) => `<a href="#${slugOf(component.name)}-demo-${variation + 1}">${escapeText(useCase.title)}</a>`).join("")}${component.events.length ? '<a href="#events">Events</a>' : ""}<a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a>${uses.map((useCase, variation) => `<a href="#${slugOf(component.name)}-demo-${variation + 1}">${escapeText(useCase.title)}</a>`).join("")}${component.events.length ? '<a href="#events">Outputs</a>' : ""}<a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
@@ -1649,6 +1654,7 @@ layout Showcase {
|
||||
<aside class="docs-directory" data-docs-directory>
|
||||
<nav aria-label="Documentation directory">
|
||||
<section class="docs-directory-group docs-directory-guides"><strong>Getting Started</strong><div><a href="/docs">Introduction</a><a href="/installation">Installation</a><a href="/framework-guides">Framework guides</a><a href="/accessibility">Accessibility</a><a href="/resources">Resources</a></div></section>
|
||||
<section class="docs-directory-group docs-directory-guides"><strong>WRNexusJS v0.6</strong><div><a href="/v06-types">Types</a><a href="/v06-functions">Functions</a><a href="/v06-outputs">Outputs</a><a href="/v06-stores">Stores</a><a href="/v06-imports">Imports</a><a href="/v06-state">State</a><a href="/v06-runtime">Runtime</a><a href="/v06-migration">Migration</a></div></section>
|
||||
<section class="docs-directory-group docs-directory-guides"><strong>Customization</strong><div><a href="/dark-mode">Dark Mode</a><a href="/themes">Themes <small>New</small></a><a href="/colors">Colors <small>New</small></a><a href="/fonts">Fonts</a><a href="/sizing">Sizing</a></div></section>
|
||||
${directory}
|
||||
</nav>
|
||||
@@ -1839,6 +1845,148 @@ writeFileSync(
|
||||
"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 {
|
||||
<article class="page-docs guide-page">
|
||||
<nav class="docs-breadcrumbs" aria-label="Breadcrumb"><a href="/docs">Docs</a><span aria-hidden="true">/</span><span>WRNexusJS v0.6</span><span aria-hidden="true">/</span><span>${escapeText(title)}</span></nav>
|
||||
<header class="docs-intro"><span class="showcase-eyebrow">WRNexusJS v0.6</span><h1>${escapeText(title)}</h1><p>${escapeText(description)}</p></header>
|
||||
${sections.map(([heading, copy]) => `<section class="guide-section"><h2>${escapeText(heading)}</h2><p>${escapeText(copy)}</p></section>`).join("")}
|
||||
<nav class="guide-next"><a href="/framework-guides">Framework guides <span aria-hidden="true">→</span></a><a href="/v06-migration">Migration guide <span aria-hidden="true">→</span></a></nav>
|
||||
</article>
|
||||
}
|
||||
}
|
||||
`,
|
||||
"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";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user