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
@@ -0,0 +1,67 @@
|
||||
import { existsSync, unlinkSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
export async function generateCatalogPages({ exampleRoot, pagesDir, blockComponents, componentMount, sourceSnippet, demoConfiguration, slugOf, displayName, escapeCode, escapeText, resetHoistedObjects, hoistedObjectStates }) {
|
||||
const write = (path, text) => { mkdirSync(join(pagesDir, path.substring(0,path.lastIndexOf('/')) || '.'), {recursive:true}); writeFileSync(join(pagesDir,path), text); };
|
||||
const codePanel = (source, id="example-source") => `<section class="example-code-panel"><header><h2>Copy this example</h2><button type="button" data-copy-target="#${id}">Copy code</button></header><pre><code id="${id}">${escapeCode(source)}</code></pre></section>`;
|
||||
const page = (name,title,body) => `page ${name} {\n layout = "gallery"\n seo { title = "${title}" }\n view { ${body} }\n}\n`;
|
||||
const cards = entries => `<div class="example-catalog">${entries.map(e=>`<a class="example-card" href="${e.href}"><div class="example-card__art"><span class="icon-[lucide--${e.icon}]" aria-hidden="true"></span></div><div><h2>${e.title}</h2><p>${e.description}</p><strong>Open example →</strong></div></a>`).join('')}</div>`;
|
||||
const examples = [
|
||||
{slug:'workspace',title:'Workspace overview',icon:'layout-dashboard',description:'A complete dashboard with navigation, metrics, and service cards.'},
|
||||
{slug:'profile',title:'Profile & preferences',icon:'user-round',description:'A responsive settings form with grouped fields and save feedback.'},
|
||||
{slug:'sign-in',title:'Sign in',icon:'log-in',description:'A focused authentication page with a compact form.'},
|
||||
{slug:'scheduling',title:'Scheduling',icon:'calendar-days',description:'Date, date-and-time, and time controls in a practical form.'},
|
||||
{slug:'custom-modal',title:'Custom modal',icon:'panels-top-left',description:'Open and close a fully controlled dialog from your own buttons.'},
|
||||
{slug:'project-board',title:'Project workspace',icon:'list-checks',description:'Reorder priorities and write rich-text project updates.'},
|
||||
];
|
||||
const extra = {
|
||||
scheduling:`page SchedulingExample {\n view { <main class="example-form-page" style="max-width:42rem;margin:3rem auto;padding:1.5rem;display:grid;gap:1.5rem"><h1>Schedule a meeting</h1><p>Choose the date and time that work for your team.</p><DatePicker label="Meeting date" name="date" /><DatePicker type="datetime-local" label="Send reminder" name="reminder" value="2026-09-20T09:30" /><TimePicker label="Start time" name="time" value="09:30" /></main> }\n}`,
|
||||
'custom-modal':`page CustomModalExample {\n state dialogOpen: boolean = false\n view { <main class="example-form-page" style="max-width:42rem;margin:3rem auto;padding:1.5rem;display:grid;gap:1.5rem"><h1>Your own modal controls</h1><Button label="Invite a teammate" @click="dialogOpen = true" /><Modal open="{dialogOpen}" @close="dialogOpen = false" title="" description="" showFooter="true" cancelLabel="" confirmLabel=""><div data-slot="header"><h2>Invite a teammate</h2></div><Input label="Email address" type="email" name="invite-email" placeholder="name@company.com" /><div data-slot="footer"><Button label="Cancel" variant="secondary" @click="dialogOpen = false" /><Button label="Done" @click="dialogOpen = false" /></div></Modal></main> }\n}`,
|
||||
'project-board':`page ProjectBoardExample {\n state tasks = [{label:"Review designs"},{label:"Build the dashboard"},{label:"Test accessibility"}]\n view { <main class="example-form-page" style="max-width:42rem;margin:3rem auto;padding:1.5rem;display:grid;gap:1.5rem"><h1>Project workspace</h1><DragAndDrop title="Priorities" description="Drag a row or use its arrow buttons." items="{tasks}" /><WysiwygEditor title="Team update" value="Share what you are working on this week." /></main> }\n}`,
|
||||
};
|
||||
for (const e of examples) { const old = join(pagesDir,'templates',e.slug+'.wrn'); if(existsSync(old)) unlinkSync(old); }
|
||||
const css = readFileSync(join(exampleRoot,'app/styles/workspace-preview.css'),'utf8');
|
||||
for(const e of examples) {
|
||||
const source = extra[e.slug] || readFileSync(join(exampleRoot,'examples',e.slug+'.wrn'),'utf8');
|
||||
const at = source.lastIndexOf('\n }');
|
||||
let rendered;
|
||||
if(at>=0) rendered = source.slice(0,at)+codePanel(source)+`<details class="example-code-panel"><summary>Page styles · copy with the example</summary><button type="button" data-copy-target="#example-styles">Copy CSS</button><pre><code id="example-styles">${escapeCode(css)}</code></pre></details>`+source.slice(at);
|
||||
else rendered = source.replace(/}\s*}\s*$/,codePanel(source)+' } }');
|
||||
rendered = rendered.replace(/\bview\s*\{/, 'view {\n<nav class="example-toolbar"><a href="/preview">← All examples</a><a href="#example-source">View code</a></nav>');
|
||||
write(`preview/${e.slug}.wrn`, rendered);
|
||||
|
||||
}
|
||||
write('preview/index.wrn',page('PreviewIndex','Examples',`<header class="docs-intro"><span class="showcase-eyebrow">Ready to use</span><h1>Page examples</h1><p>Explore complete layouts, interact with the controls, and copy the source.</p></header>${cards(examples.map(e=>({...e,href:'/preview/'+e.slug})))}`));
|
||||
const templateGroups = [
|
||||
{slug:'dashboards',title:'Dashboards & admin',icon:'layout-dashboard',description:'Analytics and account operations with meaningful metrics.',heading:'Workspace performance',body:`<MetricCard title="Active accounts" value="1,284" /><Chart title="Weekly activity" type="bar" data='[{"label":"Mon","value":24},{"label":"Tue","value":38},{"label":"Wed","value":52}]' showLegend="false" /><Card title="Team access" description="Manage roles and permissions for your workspace." actionLabel="Manage team" actionHref="/preview/profile" />`},
|
||||
{slug:'commerce',title:'Commerce',icon:'shopping-bag',description:'A product collection with a working shopping bag.',heading:'Everyday essentials',body:`<p>Thoughtfully made tools for your desk.</p><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:1.25rem"><Card title="Studio notebook" description="Dot grid · recycled paper" footer="$18"><Button label="Add notebook" @click="bag = bag + 1" /></Card><Card title="Desk organizer" description="Solid oak · natural finish" footer="$42"><Button label="Add organizer" @click="bag = bag + 1" /></Card><Card title="Travel tumbler" description="Insulated · 350 ml" footer="$24"><Button label="Add tumbler" @click="bag = bag + 1" /></Card></div><p role="status">Shopping bag: {bag} items</p><Button label="Clear bag" variant="secondary" @click="bag = 0" />`},
|
||||
{slug:'marketing',title:'Marketing websites',icon:'sparkles',description:'A SaaS landing page with benefits, pricing, and a clear next step.',heading:'Make room for your best work.',body:`<p>One calm workspace for projects, notes, and the people behind them.</p><Button label="Explore the workspace" href="/preview/workspace" /><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:1.25rem"><Card title="Plan together" description="Turn your next idea into clear, shared priorities." /><Card title="Stay focused" description="Keep tasks, notes, and decisions in one place." /><Card title="Grow naturally" description="Start with your team. Add more space when you need it." /></div><Card title="Team plan" description="Everything your team needs to get started." footer="$19 / member / month" actionLabel="Get started" actionHref="/preview/sign-in" />`},
|
||||
{slug:'productivity',title:'Productivity',icon:'list-checks',description:'A planning workspace for priorities, deadlines, and notes.',heading:'Your week, in focus',body:`<DragAndDrop title="Priorities" items='[{"label":"Review the launch plan"},{"label":"Prepare customer interviews"},{"label":"Share the weekly update"}]' /><DatePicker label="Next milestone" name="milestone" /><WysiwygEditor title="Meeting notes" value="Capture decisions and next steps here." />`},
|
||||
];
|
||||
write('templates.wrn',page('Templates','Templates',`<header class="docs-intro"><span class="showcase-eyebrow">Start a product</span><h1>Templates for your next idea</h1><p>Four starting points with complete layouts and copyable source. Examples demonstrate controls; templates combine them into a product page.</p></header>${cards(templateGroups.map(e=>({...e,href:'/templates/'+e.slug})))}`));
|
||||
for (const t of templateGroups) {
|
||||
const source = `page ${t.slug.replace(/-/g,'')}Template {
|
||||
state bag: number = 0
|
||||
view { <main style="max-width:72rem;margin:3rem auto;padding:2rem;display:grid;gap:1.5rem"><h1>${t.heading}</h1>${t.body}</main> }
|
||||
}`;
|
||||
write(`templates/${t.slug}.wrn`, source.replace('view {', 'view {<nav class="example-toolbar"><a href="/templates">← All templates</a><a href="#example-source">View code</a></nav>').replace(/}\s*}\s*$/,codePanel(source)+' } }'));
|
||||
}
|
||||
const blockCards=[];
|
||||
for(const c of blockComponents) {
|
||||
const slug=slugOf(c.name); resetHoistedObjects();
|
||||
const config=demoConfiguration(c,0);
|
||||
const mount=componentMount(c,0,config);
|
||||
const states=hoistedObjectStates();
|
||||
const source=`page ${c.name}Example {${states}\n view {\n ${mount}\n }\n}`;
|
||||
write(`blocks/${slug}.wrn`, `page ${c.name}Block {\n layout = "gallery"\n ${states}\n seo { title = "${displayName(c.name)} block" }\n view { <header class="docs-intro"><a href="/block-library">← All blocks</a><h1>${displayName(c.name)}</h1><p>A ready-to-use section. Copy the complete example below.</p></header><section class="block-live-preview">${mount}</section>${codePanel(source)} }\n}`);
|
||||
blockCards.push({title:displayName(c.name),description:c.purpose || 'A complete reusable page section.',icon:'layout-template',href:'/blocks/'+slug});
|
||||
}
|
||||
write('block-library.wrn',page('BlockLibrary','UI blocks',`<header class="docs-intro"><span class="showcase-eyebrow">Composed sections</span><h1>Ready-to-use blocks</h1><p>Real sections with live previews and complete source code.</p></header>${cards(blockCards)}`));
|
||||
write('blocks/index.wrn',page('BlocksIndex','UI blocks',cards(blockCards)));
|
||||
// Update internal guides to point to the moved block documentation.
|
||||
for(const directory of [pagesDir,join(pagesDir,'components')]) for(const file of readdirSync(directory).filter(f=>f.endsWith('.wrn'))) {
|
||||
const path=join(directory,file);let text=readFileSync(path,'utf8');
|
||||
for(const c of blockComponents) text=text.replaceAll('/components/'+slugOf(c.name),'/blocks/'+slugOf(c.name));
|
||||
if (text !== readFileSync(path,"utf8")) writeFileSync(path,text);
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
@@ -253,6 +253,43 @@ const DATATABLE_ROWS =
|
||||
'[{"id": 1, "name": "Northwind", "plan": "Scale", "owner": "A. Okafor", "seats": 6, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 2, "name": "Acme Industrial", "plan": "Team", "owner": "R. Silva", "seats": 13, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 3, "name": "Globex", "plan": "Enterprise", "owner": "M. Chen", "seats": 20, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 4, "name": "Initech", "plan": "Starter", "owner": "J. Dubois", "seats": 27, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 5, "name": "Umbrella", "plan": "Scale", "owner": "P. Novak", "seats": 34, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 6, "name": "Stark Labs", "plan": "Team", "owner": "A. Okafor", "seats": 41, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 7, "name": "Wayne Foods", "plan": "Enterprise", "owner": "R. Silva", "seats": 48, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 8, "name": "Soylent", "plan": "Starter", "owner": "M. Chen", "seats": 55, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 9, "name": "Hooli", "plan": "Scale", "owner": "J. Dubois", "seats": 62, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 10, "name": "Vehement", "plan": "Team", "owner": "P. Novak", "seats": 69, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 11, "name": "Massive Dynamic", "plan": "Enterprise", "owner": "A. Okafor", "seats": 76, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 12, "name": "Cyberdyne", "plan": "Starter", "owner": "R. Silva", "seats": 83, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}, {"id": 13, "name": "Tyrell", "plan": "Scale", "owner": "M. Chen", "seats": 90, "status": "Active", "statusHtml": "<span class=\\"showcase-pill showcase-pill--success\\">Active</span>"}, {"id": 14, "name": "Aperture", "plan": "Team", "owner": "J. Dubois", "seats": 97, "status": "Trial", "statusHtml": "<span class=\\"showcase-pill showcase-pill--info\\">Trial</span>"}, {"id": 15, "name": "Black Mesa", "plan": "Enterprise", "owner": "P. Novak", "seats": 104, "status": "Past due", "statusHtml": "<span class=\\"showcase-pill showcase-pill--danger\\">Past due</span>"}]';
|
||||
|
||||
export const componentProfiles = {
|
||||
Accordion: {
|
||||
demos: [
|
||||
standard(
|
||||
"Plain disclosure rows",
|
||||
"Unframed questions with trailing chevrons and aligned answers.",
|
||||
{},
|
||||
),
|
||||
compact("Bordered group", "One shared border with leading indicators.", {}),
|
||||
advanced("Separate panels", "Individually framed rows; multiple answers can stay open.", {}),
|
||||
],
|
||||
},
|
||||
Alert: {
|
||||
demos: [
|
||||
standard("Success message", "A concise confirmation that can be dismissed.", {}),
|
||||
compact("Informational notice", "One useful message without unnecessary actions.", {}),
|
||||
advanced("Action required", "A warning with one relevant next step.", {}),
|
||||
],
|
||||
},
|
||||
Carousel: {
|
||||
demos: [
|
||||
standard(
|
||||
"Full-width, unframed",
|
||||
"An edge-to-edge carousel without a card, border, or outer padding.",
|
||||
{ framed: false },
|
||||
),
|
||||
compact(
|
||||
"Framed gallery",
|
||||
"Opt into a contained gallery when the surrounding layout needs it.",
|
||||
{ framed: true },
|
||||
),
|
||||
advanced("Draggable slides", "Move between slides with dragging, arrows, or pagination.", {
|
||||
framed: false,
|
||||
isDraggable: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
Navbar: {
|
||||
demos: [
|
||||
standard(
|
||||
@@ -1941,7 +1978,7 @@ export const componentProfiles = {
|
||||
},
|
||||
),
|
||||
],
|
||||
playground: { items: menuItems, open: false, defaultOpen: false, trigger: "click" },
|
||||
playground: { items: menuItems, open: false, defaultOpen: false, trigger: "contextmenu" },
|
||||
options: {
|
||||
trigger: ["contextmenu", "click", "both", "manual"],
|
||||
placement: ["pointer", "top-start", "top-end", "bottom-start", "bottom-end"],
|
||||
@@ -2411,13 +2448,13 @@ export const componentProfiles = {
|
||||
{ default: '<strong class="showcase-card-value">38</strong>' },
|
||||
),
|
||||
advanced(
|
||||
"Raised interactive card",
|
||||
"Use a stronger visual treatment for featured content.",
|
||||
"Featured resource",
|
||||
"Keep the description and next action together in a single surface.",
|
||||
{
|
||||
title: "Featured guide",
|
||||
description: "A complete guide to public safety and emergency readiness.",
|
||||
size: "spacious",
|
||||
variant: "raised",
|
||||
size: "lg",
|
||||
variant: "default",
|
||||
},
|
||||
{ default: '<a href="#guide" class="showcase-action">Read guide</a>' },
|
||||
),
|
||||
@@ -2903,6 +2940,30 @@ const genericEventDescriptions = {
|
||||
toggle: "Fires when the component changes between open and closed or on and off states.",
|
||||
};
|
||||
|
||||
|
||||
componentProfiles.Card.demos.push(
|
||||
standard("Image with footer", "A cover image, concise body, and separate footer.", {title:"Design your next chapter",description:"A practical guide to building thoughtful products.",imageSrc:"/showcase-images/green-hero.svg",imageAlt:"Green abstract cover",footer:"6 min read",actionLabel:"Read guide",actionHref:"#guide"}),
|
||||
compact("Without a header", "Body content with an optional footer; no reserved heading space.", {title:"",description:"Your workspace is ready. Invite your teammates to get started.",footer:"Updated just now"}, {default:""}),
|
||||
advanced("Image-only card", "A simple image surface without header or footer chrome.", {title:"",description:"",imageSrc:"/showcase-images/blue-mountain.svg",imageAlt:"Blue geometric landscape"}, {default:""}),
|
||||
);
|
||||
componentProfiles.Map = { demos:[
|
||||
standard("OpenStreetMap", "A real interactive geographic map. Set latitude, longitude and zoomLevel; no API key is needed.", {title:"Bengaluru",description:"Pan and zoom to explore the city",provider:"openstreetmap",latitude:12.9716,longitude:77.5946,zoomLevel:13,imageSrc:"",items:[]}),
|
||||
compact("Google Maps", "Pass your restricted Maps Embed API key and a place query to show Google Maps.", {title:"Google Maps",provider:"google",apiKey:"",query:"Bengaluru",imageSrc:"",items:[]}),
|
||||
], playground:{title:"Bengaluru",description:"Interactive geographic map",provider:"openstreetmap",latitude:12.9716,longitude:77.5946,zoomLevel:13,imageSrc:"",items:[]}, options:{provider:["openstreetmap","google"]} };
|
||||
componentProfiles.Rating = {demos:[standard("Whole stars", "Choose a rating from zero to five with step 1.",{title:"Rate your experience",value:3,step:1}),compact("Half stars", "Click a half star or use the precise slider and arrow keys.",{title:"Half-star rating",value:3.5,step:0.5}),advanced("Fine increments", "Use increments of 0.2 for a more precise rating.",{title:"Detailed score",value:4.2,step:0.2})]};
|
||||
componentProfiles.LegendIndicator = {demos:[standard("Chart legend", "Toggle a series and handle @toggle to update your chart.",{title:"Traffic sources",description:"Select a source to toggle its visibility.",items:[{label:"Direct",color:"#8b5cf6",value:"42%"},{label:"Search",color:"#14b8a6",value:"35%"},{label:"Referral",color:"#f59e0b",value:"23%"}]})]};
|
||||
componentProfiles.Chart = {demos:[standard("Bar chart", "Pass an array of label/value objects through data.",{title:"Weekly signups",type:"bar",data:[{label:"Mon",value:24},{label:"Tue",value:38},{label:"Wed",value:29},{label:"Thu",value:52},{label:"Fri",value:45}],items:[],showLegend:false}),compact("Line chart", "Use type=line to compare a trend over time.",{title:"Monthly revenue",type:"line",data:[{label:"Jan",value:20},{label:"Feb",value:35},{label:"Mar",value:28},{label:"Apr",value:55}],items:[],showLegend:false})],options:{type:["bar","line"]}};
|
||||
componentProfiles.DatePicker = { demos:[
|
||||
standard("Date picker", "An anchored calendar that does not move surrounding content.", {label:"Start date",name:"start-date",type:"date"}),
|
||||
compact("Date and time", "Choose a calendar date and a time in one field.", {label:"Schedule for",name:"schedule",type:"datetime-local",value:"2026-09-20T09:30"}),
|
||||
advanced("Disabled date", "A read-only scheduled date.", {label:"Scheduled date",readonly:true}),
|
||||
], options:{type:["date","datetime-local"]} };
|
||||
componentProfiles.TimePicker = { demos:[standard("Time picker", "Choose an hour and minute in an anchored panel.", {label:"Meeting time",name:"meeting-time",value:"09:30"})] };
|
||||
componentProfiles.Modal.demos.push(
|
||||
advanced("Custom header and footer", "Supply your own header, body, and footer actions.", {title:"",description:"",cancelLabel:"",confirmLabel:"",triggerLabel:"Open custom dialog"}, {header:'<h2>Invite a teammate</h2>',default:'<p>Give your teammate access to your workspace.</p>',footer:`<button type="button" class="showcase-action" data-modal-close="true">Done</button>`}),
|
||||
compact("Centered content", "Omit the standard header and footer for a fully custom composition.", {showHeader:false,showFooter:false,title:"",description:"",icon:"",size:"sm",triggerLabel:"Open centered dialog"}, {default:`<div style="text-align:center;padding:2rem"><h2>You are all set</h2><p>Your preferences have been saved.</p><button type="button" class="showcase-action" data-modal-close="true">Continue</button></div>`}),
|
||||
);
|
||||
|
||||
export function profileFor(name) {
|
||||
return componentProfiles[name] ?? null;
|
||||
}
|
||||
@@ -2912,3 +2973,9 @@ export function eventDescriptionFor(_componentName, eventName) {
|
||||
genericEventDescriptions[eventName] ?? `Fires when the component emits the ${eventName} event.`
|
||||
);
|
||||
}
|
||||
|
||||
componentProfiles.Card.demos.push(advanced("Custom slots", "Supply arbitrary header, body, and footer content. Use showHeader and showFooter to hide either region.", {title:"",description:"",showHeader:true,showFooter:true,footer:"",actionLabel:""}, {header:"<strong>Team invitation</strong>",default:'<Input label="Email" placeholder="name@company.com" />',footer:'<Button label="Send invitation" />'}));
|
||||
componentProfiles.RangeSlider = { demos: [compact("Fractional steps", "Use step=0.2 for precise increments with buttons, dragging, or arrow keys.", {label:"Scale",min:0,max:5,value:2.4,step:0.2})] };
|
||||
|
||||
|
||||
componentProfiles.FileInput = {demos:[standard("Choose a document", "Select a PDF or image. The selected filename replaces the placeholder.", {label:"Attachment",name:"attachment",value:"",accept:".pdf,image/*",helperText:"PDF or image",placeholder:"Choose a file"}),compact("Multiple attachments", "Select several files; each filename is shown in the control.",{label:"Attachments",name:"attachments",value:"",multiple:true,placeholder:"Choose files"}),advanced("Disabled upload", "A disabled control cannot open the file chooser.",{label:"Attachment",name:"disabled-file",value:"",disabled:true,placeholder:"Uploads unavailable"})]};
|
||||
Reference in new issue
Block a user