Refresh UI components and showcase; release updated framework packages
This commit is contained in:
1 parent
d800228585
commit
c77f5e1cec
293 files changed
+22544
-35778
No files matched your search
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import prettier from "prettier";
|
||||
import { generateCatalogPages } from "./generate-catalog-pages.mjs";
|
||||
import { eventDescriptionFor, profileFor } from "./showcase-profiles.mjs";
|
||||
|
||||
const exampleRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
@@ -12,6 +13,12 @@ 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 excludedNames = new Set(["AuthForm", "AuthSplitLayout", "PortalDashboard", "PublicPageShell"]);
|
||||
const blockNames = new Set(["CTASection","Hero","HeroActions","SplitHero","FeatureCard","FeatureIconCard","FeatureGrid","MarketingSectionHeader","PageHeader","SectionHeader","AnnouncementBar","Footer","MetricGrid","StatsBar"]);
|
||||
const blockComponents = reference.components.filter(component => blockNames.has(component.name));
|
||||
reference.components = reference.components.filter(component => !excludedNames.has(component.name) && !blockNames.has(component.name));
|
||||
reference.count = reference.components.length;
|
||||
|
||||
|
||||
const interactiveOverlayComponents = new Set([
|
||||
"ContextMenu",
|
||||
@@ -56,7 +63,7 @@ for (const component of reference.components) {
|
||||
}
|
||||
const slugOf = (name) => {
|
||||
const slug = baseSlugOf(name);
|
||||
if (slugCounts.get(slug) === 1) return slug;
|
||||
if (!slugCounts.has(slug) || slugCounts.get(slug) === 1) return slug;
|
||||
const component = reference.components.find((entry) => entry.name === name);
|
||||
return `${slug}-${component?.category ?? "component"}`;
|
||||
};
|
||||
@@ -263,7 +270,7 @@ function defaultValue(prop) {
|
||||
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 ?? "")) {
|
||||
if (/^".*"$/.test(prop.default ?? "") || (prop.default ?? "").startsWith("[")) {
|
||||
try {
|
||||
return JSON.parse(prop.default);
|
||||
} catch {
|
||||
@@ -275,6 +282,216 @@ function defaultValue(prop) {
|
||||
|
||||
function sampleValue(prop, component, variation) {
|
||||
const name = prop.name.toLowerCase();
|
||||
const curated = {
|
||||
ChatBubble: { title:"", description:"", showMetadata:true, showAvatars:false, items:[{text:"Hi Maya, is the new dashboard ready to review?",direction:"incoming",timestamp:"10:24"},{text:"Yes! I have shared the latest version with you.",direction:"outgoing",timestamp:"10:25",status:"Delivered"},{text:"Thanks, I will take a look this afternoon.",direction:"incoming",timestamp:"10:26"}] },
|
||||
FileUploadProgress: { label:"Upload progress",fileName:"brand-guidelines.pdf",fileSize:"4.8 MB",uploadedSize:variation === 0 ? "2.4 MB" : "",value:variation === 2 ? 100 : 50,status:["uploading","error","complete"][variation % 3] },
|
||||
ListGroup: {title:"Workspace",value:"inbox",items:[{value:"inbox",label:"Inbox",description:"Messages that need your attention",icon:"icon-[lucide--inbox]",badge:"8"},{value:"projects",label:"Projects",description:"Your team's active work",icon:"icon-[lucide--folder]"},{value:"archive",label:"Archive",description:"Completed conversations",icon:"icon-[lucide--archive]"}]},
|
||||
Rating: {title:"How was your experience?",value:variation === 1 ? 3 : 4,readonly:variation === 2},
|
||||
StyledIcon: {icon:["icon-[lucide--sparkles]","icon-[lucide--check]","icon-[lucide--shield-check]"][variation % 3],label:"Verified",color:variation === 1 ? "success" : "primary",variant:variation === 2 ? "outline" : "default",size:variation === 1 ? "sm" : "default"},
|
||||
TreeView: {title:"Project files",defaultExpanded:["src"],items:[{value:"src",label:"src",children:[{value:"pages",label:"pages",children:[{value:"home",label:"index.wrn"}]},{value:"styles",label:"styles.css"}]},{value:"readme",label:"README.md"}]},
|
||||
InputGroup: {name:"workspace-url",label:"Workspace address",placeholder:"your-team",startText:variation === 0 ? "https://" : "",endText:variation === 0 ? ".example.com" : "",icon:variation === 1 ? "icon-[lucide--search]" : "",actionLabel:variation === 1 ? "Search" : "",value:""},
|
||||
RangeSlider: {name:"volume",label:"Volume",value:40,showBounds:true,showValue:true,marks:[]},
|
||||
Chart: {title:"Weekly activity",description:"Completed tasks this week",items:[{label:"Mon",value:24},{label:"Tue",value:38},{label:"Wed",value:29},{label:"Thu",value:52},{label:"Fri",value:45}],height:220,showLegend:variation === 2},
|
||||
Clipboard: {title:"",description:"",label:"Copy",value:"npm install @wrnexus/ui",iconOnly:variation !== 0},
|
||||
Confetti: {label:"Celebrate milestone",count:40,duration:1400},
|
||||
DataMap: {title:"Activity by team",description:"Select a tile to inspect its activity score.",items:[{label:"Design",value:32},{label:"Engineering",value:84},{label:"Support",value:56},{label:"Sales",value:45},{label:"Marketing",value:68},{label:"Operations",value:20}]},
|
||||
DragAndDrop: {title:"Project priorities",description:"Drag to reorder, or use the arrow buttons.",items:[{label:"Review designs"},{label:"Build the dashboard"},{label:"Test accessibility"},{label:"Prepare release notes"}]},
|
||||
WysiwygEditor: {title:"Project update",description:"",value:"Share progress, decisions, and next steps with your team.",readonly:variation === 2},
|
||||
BackToTop: {label:"Back to top"},
|
||||
|
||||
Pagination: {
|
||||
label: "Pagination",
|
||||
previousLabel: "Previous",
|
||||
nextLabel: "Next",
|
||||
hrefTemplate: "",
|
||||
},
|
||||
Avatar: {
|
||||
name: "Aisha Khan",
|
||||
initials: "AK",
|
||||
alt: "Aisha Khan",
|
||||
tooltip: "Aisha Khan · Product designer",
|
||||
description: variation === 2 ? "Product designer" : "",
|
||||
size: variation === 1 ? "sm" : "md",
|
||||
status: variation === 2 ? "online" : "",
|
||||
},
|
||||
ColorPicker: {
|
||||
name: "brand-color",
|
||||
label: "Brand color",
|
||||
value: ["#8b5cf6", "#14b8a6", "#3b82f6"][variation % 3],
|
||||
},
|
||||
Kbd: { label: ["⌘ K", "Esc", "Ctrl + Enter"][variation % 3] },
|
||||
|
||||
Alert: {
|
||||
title: ["Changes saved", "Scheduled maintenance", "Payment needs attention"][variation % 3],
|
||||
description: [
|
||||
"Your workspace settings are up to date.",
|
||||
"The dashboard will be unavailable Sunday, 02:00–02:30 UTC.",
|
||||
"Update your payment method to keep your subscription active.",
|
||||
][variation % 3],
|
||||
color: ["success", "info", "warning"][variation % 3],
|
||||
variant: "soft",
|
||||
shadow: "none",
|
||||
showIcon: true,
|
||||
icon: ["icon-[lucide--circle-check]", "icon-[lucide--info]", "icon-[lucide--triangle-alert]"][
|
||||
variation % 3
|
||||
],
|
||||
dismissible: true,
|
||||
actionLabel: variation === 2 ? "Update payment" : "",
|
||||
actionHref: variation === 2 ? "#payment" : "",
|
||||
},
|
||||
AvatarGroup: {
|
||||
items: [
|
||||
{ name: "Aisha Khan", initials: "AK", tooltip: "Aisha Khan · Product designer" },
|
||||
{ name: "Daniel Lee", initials: "DL", tooltip: "Daniel Lee · Engineer" },
|
||||
{ name: "Maya Patel", initials: "MP", tooltip: "Maya Patel · Product manager" },
|
||||
{ name: "Oliver Reed", initials: "OR", tooltip: "Oliver Reed · Support" },
|
||||
{ name: "Sofia Chen", initials: "SC", tooltip: "Sofia Chen · Operations" },
|
||||
],
|
||||
showTooltips: true,
|
||||
maxVisible: 3,
|
||||
size: variation === 1 ? "sm" : "md",
|
||||
layout: variation === 2 ? "grid" : "stack",
|
||||
},
|
||||
ButtonGroup: {
|
||||
items: [
|
||||
{ label: "Day", value: "day" },
|
||||
{ label: "Week", value: "week" },
|
||||
{ label: "Month", value: "month" },
|
||||
],
|
||||
value: "week",
|
||||
selectable: true,
|
||||
variant: "default",
|
||||
size: variation === 1 ? "sm" : "md",
|
||||
attached: variation !== 2,
|
||||
},
|
||||
Card: {
|
||||
title: "Team workspace",
|
||||
description: "One place for your projects, files, and conversations.",
|
||||
size: "md",
|
||||
variant: "default",
|
||||
items: [],
|
||||
dismissible: false,
|
||||
},
|
||||
Carousel: {
|
||||
items: [
|
||||
{
|
||||
title: "A space to create",
|
||||
description: "Bring your next idea to life.",
|
||||
imageSrc: "/showcase-images/green-hero.svg",
|
||||
},
|
||||
{
|
||||
title: "Made for teamwork",
|
||||
description: "Keep everyone moving together.",
|
||||
imageSrc: "/showcase-images/blue-mountain.svg",
|
||||
},
|
||||
{
|
||||
title: "Room to grow",
|
||||
description: "Build something that matters.",
|
||||
imageSrc: "/showcase-images/violet-circle.svg",
|
||||
},
|
||||
],
|
||||
framed: variation === 1,
|
||||
showPagination: true,
|
||||
showCounter: true,
|
||||
ariaLabel: "Product highlights",
|
||||
},
|
||||
}[component.name];
|
||||
if (curated) return Object.hasOwn(curated, prop.name) ? curated[prop.name] : defaultValue(prop);
|
||||
if (component.name === "Accordion") {
|
||||
const configuration = {
|
||||
items: [
|
||||
{
|
||||
value: "workspace",
|
||||
label: "How do I create a workspace?",
|
||||
content:
|
||||
"Choose Create workspace, add a name, and invite your team. You can update the details any time in Settings.",
|
||||
},
|
||||
{
|
||||
value: "billing",
|
||||
label: "Can I change my plan later?",
|
||||
content: "Yes. Open Billing to compare plans and choose the one that suits your team.",
|
||||
},
|
||||
{
|
||||
value: "security",
|
||||
label: "How is my account protected?",
|
||||
content:
|
||||
"Enable two-step verification and add a recovery method to keep your account secure.",
|
||||
},
|
||||
],
|
||||
defaultOpen: ["workspace"],
|
||||
indicator: variation === 1 ? "plus" : "chevron",
|
||||
indicatorPosition: variation === 1 ? "start" : "end",
|
||||
bordered: variation !== 0,
|
||||
separated: variation === 2,
|
||||
multiple: variation === 2,
|
||||
};
|
||||
if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name];
|
||||
}
|
||||
if (component.name === "ToggleCount") {
|
||||
const configuration = {
|
||||
value: "monthly",
|
||||
firstValue: "monthly",
|
||||
secondValue: "annual",
|
||||
firstLabel: "Monthly",
|
||||
secondLabel: "Yearly",
|
||||
firstValueKey: "monthly",
|
||||
secondValueKey: "annual",
|
||||
items: [
|
||||
{
|
||||
title: "Starter",
|
||||
label: "Starter",
|
||||
description: "For individuals getting started.",
|
||||
monthly: 12,
|
||||
annual: 120,
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
label: "Team",
|
||||
description: "For teams working together.",
|
||||
monthly: 29,
|
||||
annual: 290,
|
||||
},
|
||||
],
|
||||
};
|
||||
if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name];
|
||||
}
|
||||
if (component.name === "Badge") {
|
||||
const configuration = {
|
||||
label: ["Active", "In review", "Verified"][variation % 3],
|
||||
variant: ["soft", "outline", "solid"][variation % 3],
|
||||
color: ["success", "warning", "primary"][variation % 3],
|
||||
icon: variation === 2 ? "icon-[lucide--check]" : "",
|
||||
size: "sm",
|
||||
anchorLabel: "",
|
||||
anchorIcon: "",
|
||||
dismissible: variation === 2,
|
||||
dismissLabel: "Remove badge",
|
||||
dotLabel: "Status",
|
||||
};
|
||||
if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name];
|
||||
}
|
||||
if (component.name === "TimePicker" && ["hours", "minutes"].includes(prop.name))
|
||||
return defaultValue(prop);
|
||||
if (component.name === "TimePicker" && name === "value") return "09:30";
|
||||
if (component.name === "DatePicker") {
|
||||
if (name === "months")
|
||||
return [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
].map((label, index) => ({ label, value: String(index + 1).padStart(2, "0") }));
|
||||
if (name === "days") return Array.from({ length: 31 }, (_, index) => index + 1);
|
||||
if (name === "years") return [2024, 2025, 2026, 2027, 2028, 2029, 2030];
|
||||
}
|
||||
if (component.name === "AdvancedSelect") {
|
||||
const configuration = advancedSelectConfiguration(variation);
|
||||
if (Object.hasOwn(configuration, prop.name)) return configuration[prop.name];
|
||||
@@ -417,6 +634,7 @@ function sampleValue(prop, component, variation) {
|
||||
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" && component.name === "DatePicker") return `2026-09-${20 + variation}`;
|
||||
if (name === "value") return `sample-${variation + 1}`;
|
||||
if (name.includes("count") || name.includes("total") || name.includes("page")) return undefined;
|
||||
if (prop.required) {
|
||||
@@ -479,6 +697,8 @@ function componentAttributes(component, variation, overrides = {}) {
|
||||
|
||||
function configuredSlotMarkup(component, variation, overrides = {}) {
|
||||
const configured = overrides.__slots ?? {};
|
||||
if (component.name === "LayoutSplitter" && !Object.keys(configured).length)
|
||||
return '<div data-slot="start"><h3>Project files</h3><p>Drag the divider to resize these panels.</p></div><div data-slot="end"><h3>Editor</h3><p>Your selected file appears here.</p></div>';
|
||||
return component.slots
|
||||
.map((slot) => {
|
||||
const explicitlyConfigured = Object.hasOwn(configured, slot);
|
||||
@@ -552,11 +772,21 @@ function indentSource(value, spaces = 2) {
|
||||
}
|
||||
|
||||
function sourceSnippet(component, variation, overrides = {}) {
|
||||
const declarations = [];
|
||||
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 [];
|
||||
if (prop.type.includes("[]")) {
|
||||
let data = value;
|
||||
if (typeof data === "string") { try { data = JSON.parse(data); } catch {} }
|
||||
if (Array.isArray(data)) {
|
||||
declarations.push(` state ${prop.name} = ${JSON.stringify(data, null, 2)}`);
|
||||
return [`${prop.name}="{${prop.name}}"`];
|
||||
}
|
||||
}
|
||||
|
||||
const declaredDefault = defaultValue(prop);
|
||||
if (
|
||||
declaredDefault !== undefined &&
|
||||
@@ -594,7 +824,7 @@ function sourceSnippet(component, variation, overrides = {}) {
|
||||
: formattedAttributes
|
||||
? `<${component.mount}${formattedAttributes}\n/>`
|
||||
: `<${component.mount} />`;
|
||||
return escapeCode(source);
|
||||
return escapeCode(declarations.length ? `page ${identifier(component.name)}Example {\n${declarations.join("\n")}\n view {\n${indentSource(source,4)}\n }\n}`.replaceAll("\\n", "\n") : source);
|
||||
}
|
||||
|
||||
const mountAttribute = `data-component="${component.mount}"`;
|
||||
@@ -633,6 +863,7 @@ function playgroundStateName(prop) {
|
||||
|
||||
function isStructuredProp(prop) {
|
||||
return (
|
||||
(prop.default ?? "").startsWith("[") ||
|
||||
prop.default === "[]" ||
|
||||
prop.default === "{}" ||
|
||||
prop.type === "array" ||
|
||||
@@ -655,7 +886,10 @@ function playgroundStates(component) {
|
||||
return ` state ${playgroundStateName(prop)} = Number(${query} ?? ${JSON.stringify(String(initial))})`;
|
||||
}
|
||||
if (isStructuredProp(prop)) {
|
||||
const initial = JSON.stringify(initialValue ?? (prop.default === "{}" ? {} : []));
|
||||
const initial =
|
||||
typeof initialValue === "string" && initialValue.trim()
|
||||
? initialValue
|
||||
: JSON.stringify(initialValue || (prop.default === "{}" ? {} : []));
|
||||
return ` state ${playgroundStateName(prop)} = JSON.parse(${query} ?? ${JSON.stringify(initial)})`;
|
||||
}
|
||||
return ` state ${playgroundStateName(prop)} = ${query} ?? ${JSON.stringify(String(initialValue ?? ""))}`;
|
||||
@@ -689,6 +923,7 @@ function playgroundControl(prop, component) {
|
||||
}
|
||||
|
||||
const structured =
|
||||
(prop.default ?? "").startsWith("[") ||
|
||||
prop.default === "[]" ||
|
||||
prop.default === "{}" ||
|
||||
prop.type === "array" ||
|
||||
@@ -1661,7 +1896,7 @@ page ${identifier(category)}Showcase {
|
||||
|
||||
const categories = Object.groupBy(reference.components, (component) => component.category);
|
||||
const categoryEntries = Object.entries(categories).sort(([left], [right]) =>
|
||||
left.localeCompare(right),
|
||||
left === "advanced-forms" ? 1 : right === "advanced-forms" ? -1 : left.localeCompare(right),
|
||||
);
|
||||
const totalDemoCount = reference.components.reduce(
|
||||
(total, component) => total + demoUses(component).length,
|
||||
@@ -1695,7 +1930,7 @@ layout Showcase {
|
||||
<label class="docs-search"><span class="icon-[lucide--search] size-4" aria-hidden="true"></span><input type="search" placeholder="Search documentation..." data-docs-search /><kbd>⌘ K</kbd></label>
|
||||
</div>
|
||||
<div class="showcase-header-end">
|
||||
<nav aria-label="Primary navigation" class="showcase-nav"><a href="/docs">Docs</a><a href="/block-library">Blocks</a><a href="/templates">Templates</a></nav>
|
||||
<nav aria-label="Primary navigation" class="showcase-nav"><a href="/preview">Examples</a><a href="/docs">Docs</a><a href="/block-library">Blocks</a><a href="/templates">Templates</a></nav>
|
||||
<button type="button" class="showcase-theme" data-design-panel-toggle aria-label="Customize design"><span class="icon-[lucide--palette] size-5" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -1712,11 +1947,10 @@ 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>
|
||||
<p class="docs-directory-empty" data-docs-empty>No components found.</p>
|
||||
<p class="docs-directory-empty" data-docs-empty hidden>No components found.</p>
|
||||
</aside>
|
||||
<main class="showcase-main"><slot /></main>
|
||||
</div>
|
||||
@@ -1779,6 +2013,8 @@ for (const [category, components] of categoryEntries) {
|
||||
|
||||
const showcaseManifest = {
|
||||
generatedFrom: "packages/ui/component-reference.json",
|
||||
removed: [...excludedNames],
|
||||
blocks: blockComponents.map(component => ({ name: component.name, slug: slugOf(component.name), path: `/blocks/${slugOf(component.name)}` })),
|
||||
componentCount: reference.count,
|
||||
categoryCount: categoryEntries.length,
|
||||
totalDemoCount,
|
||||
@@ -2048,6 +2284,17 @@ page V06${identifier(title)}Guide {
|
||||
);
|
||||
}
|
||||
|
||||
const practicalGuides = {
|
||||
installation: {heading:"Render your first component", copy:"Import the shared UI stylesheet in your application entry. In a WRN page, use registered components directly. Keep user-editable values in page state and listen to typed outputs.", code:'import "@wrnexus/ui/ui.css"\n\npage Welcome {\n view { <Button label="Get started" href="/preview" /> }\n}'},
|
||||
"framework-guides": {heading:"Connect a field to page state",copy:"Props supply initial configuration. Output handlers receive payload directly. Use payload.value to update your page state and display the committed result.",code:'page NameForm {\n state name: string = ""\n view { <Input label="Your name" value="{name}" @input="name = payload.value" /><p>Hello {name}</p> }\n}'},
|
||||
accessibility: {heading:"Label controls and explain errors",copy:"Give every control a visible label. Use helperText for instructions and error for actionable feedback. Test Tab, Shift+Tab, Enter and Escape; focus must remain visible and follow the reading order.",code:'<Input label="Email address" type="email" name="email" required="true" helperText="Use your work email." error="Enter a valid email address." />\n<Button label="Save changes" disabled="true" />'},
|
||||
"dark-mode": {heading:"Use semantic colors in both modes",copy:"Use shared surface, text, border and focus tokens instead of hard-coded white and black. Check disabled, hover, selected and error states in both themes using the appearance control.",code:'.panel { background: var(--wrn-color-surface); color: var(--wrn-color-text); border: 1px solid var(--wrn-color-border); }'},
|
||||
themes: {heading:"Keep theme overrides in one place",copy:"Override design tokens at your application root so all components inherit the same appearance. Start with radius and brand color, then review overlays, forms and navigation together.",code:':root { --wrn-radius: 8px; --wrn-color-primary: #7c3aed; }\n<Button label="Primary action" color="primary" />'},
|
||||
colors: {heading:"Choose color by meaning",copy:"Use primary for the main action, danger for destructive actions and success for completion. Always include text or an icon so color is not the only signal.",code:'<Button label="Save" color="primary" />\n<Button label="Delete record" color="danger" />\n<Badge label="Active" color="success" />'},
|
||||
fonts: {heading:"Set one application font",copy:"Components inherit the shared font family. Supply a font stack once; use font weights and spacing to establish hierarchy instead of enlarging every label.",code:':root { --wrn-font-sans: "Inter", system-ui, sans-serif; }\nbody { font-family: var(--wrn-font-sans); }'},
|
||||
sizing: {heading:"Match density to the task",copy:"Use compact controls for dense toolbars and tables, and the default size for forms. Keep related controls at the same size and retain usable touch targets on small screens.",code:'<Button label="Filter" size="sm" />\n<Input label="Project name" size="default" />\n<RangeSlider label="Scale" min="0" max="5" step="0.2" value="2.4" />'},
|
||||
resources: {heading:"Choose the right starting point",copy:"Component pages document individual controls. Blocks combine controls into sections. Examples demonstrate complete workflows. Templates provide product-oriented starting pages with source you can adapt.",code:'// DataTable accepts arrays directly through bindings.\nstate rows = [{ id: 1, name: "Northwind" }]\nstate columns = [{ key: "name", label: "Account" }]\n<DataTable rows="{rows}" columns="{columns}" />'}
|
||||
};
|
||||
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";
|
||||
@@ -2100,6 +2347,7 @@ for (const [slug, title, description, code] of docsSections) {
|
||||
<nav class="docs-breadcrumbs" aria-label="Breadcrumb"><a href="/docs">Docs</a><span aria-hidden="true">/</span><span>${escapeText(title)}</span></nav>
|
||||
<header class="docs-intro"><span class="showcase-eyebrow">${section}</span><h1>${escapeText(title)}</h1><p>${escapeText(description)}</p></header>
|
||||
${examples.map(([heading, copy]) => `<section class="guide-section"><h2>${escapeText(heading)}</h2><p>${escapeText(copy)}</p></section>`).join("")}
|
||||
${practicalGuides[slug] ? `<section class="guide-section"><h2>${escapeText(practicalGuides[slug].heading)}</h2><p>${escapeText(practicalGuides[slug].copy)}</p><pre><code>${escapeCode(practicalGuides[slug].code)}</code></pre><h3>Verify the result</h3><p>Open the matching component page, change its options, and try the keyboard controls. Copy the demonstrated configuration into your page, then replace sample content with your data.</p></section>` : ""}
|
||||
${code ? `<section class="guide-section"><h2>Quick start</h2><pre><code>${escapeCode(code)}</code></pre></section>` : ""}
|
||||
<nav class="guide-next"><a href="/components/button">Explore components <span aria-hidden="true">→</span></a><a href="/block-library">Browse blocks <span aria-hidden="true">→</span></a></nav>
|
||||
</article>
|
||||
@@ -2220,3 +2468,5 @@ writeFileSync(
|
||||
process.stdout.write(
|
||||
`Generated ${reference.count} component detail pages, ${totalDemoCount} live demos, and ${categoryEntries.length} category pages.\n`,
|
||||
);
|
||||
|
||||
await generateCatalogPages({ exampleRoot, pagesDir, blockComponents, componentMount, sourceSnippet, demoConfiguration, slugOf, displayName, escapeCode, escapeText, resetHoistedObjects, hoistedObjectStates });
|
||||
Reference in new issue
Block a user