Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bd4574b08 | ||
|
|
e32d83933e | ||
|
|
5ce2771718 | ||
|
|
68c7b96a9f | ||
|
|
6a9c48a207 | ||
|
|
9a03b7c8d4 |
+20
-2
@@ -1,11 +1,29 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "basic-app",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"packages/cli/src/index.ts",
|
||||
"dev",
|
||||
"examples/basic-app",
|
||||
"--port=3520"
|
||||
],
|
||||
"port": 3520
|
||||
},
|
||||
{
|
||||
"name": "component-showcase",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": ["run", "--cwd", "examples/component-showcase", "dev"],
|
||||
"port": 3000
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"packages/cli/src/index.ts",
|
||||
"dev",
|
||||
"examples/component-showcase",
|
||||
"--port=3400"
|
||||
],
|
||||
"port": 3400
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"packages/ui/components/Map.wrn": "7b8a0d5412a464fd7cab8977d816542c506d8c5dab8230a3c984c2caee407c91",
|
||||
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b",
|
||||
"packages/ui/components/Marquee.wrn": "b2b35eedf3297ba12ab3776385a9f3eab001027648d87a2a1968f576eee6c025",
|
||||
"packages/ui/components/MegaMenu.wrn": "48f4ced485df0e8de5217630c8bdcec0b2317bf04e6b279399f2c93d98d56995",
|
||||
"packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43",
|
||||
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
|
||||
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
|
||||
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
|
||||
@@ -85,11 +85,11 @@
|
||||
"packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
|
||||
"packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d",
|
||||
"packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686",
|
||||
"packages/ui/components/Stepper.wrn": "09f216a44f888421091bd1bce760a076927500a573550d5b2b10c4f27f608e92",
|
||||
"packages/ui/components/Stepper.wrn": "df9093e4222ef3250963a8c6fbfec74477242735d23eabe896c29731c54fc14d",
|
||||
"packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e",
|
||||
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336",
|
||||
"packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28",
|
||||
"packages/ui/components/Tabs.wrn": "4fa0c0854700532960043290700d7cf99663ec41692068101f0aae4c2b1a1181",
|
||||
"packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0",
|
||||
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19",
|
||||
"packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf",
|
||||
"packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695",
|
||||
@@ -110,6 +110,6 @@
|
||||
"packages/ui/components/progress.wrn": "ba6f4dfcc00f04f34e9533be675bb4a6200c3cdcd7d03fc597377e48520fdec7",
|
||||
"packages/ui/components/skeleton.wrn": "ceec8af147b8be08155500e378393ac7c72d819da61b62191c076c80e943d5a9",
|
||||
"packages/ui/components/spinner.wrn": "abc4ee3ede2e019289257e9009eee012c880a85839cc220fac6dbe1b780caf52",
|
||||
"packages/ui/ui.css": "f71641efb0c98b673af70c10dbcea8e693de258ce8620b4f84c64553ea70204e"
|
||||
"packages/ui/ui.css": "3138b86f269b6f7e67922ef2c56918444d562b11f4ae6f1fab98b63805cd78dc"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ component PublicLayout {
|
||||
<a href="/chat">{t:nav.chat}</a>
|
||||
<a href="/dashboard">{t:nav.dashboard}</a>
|
||||
<a href="/about">{t:nav.about}</a>
|
||||
<a href="/navigation">{t:nav.navigation}</a>
|
||||
<button type="button" data-wire-theme-toggle aria-label="Toggle color theme">Theme</button>
|
||||
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="en">EN</button>
|
||||
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="es">ES</button>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"ui": "UI",
|
||||
"chat": "Chat",
|
||||
"dashboard": "Dashboard",
|
||||
"about": "About"
|
||||
"about": "About",
|
||||
"navigation": "Navigation"
|
||||
},
|
||||
"home": {
|
||||
"title": "Hello from WrNexus",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"ui": "UI",
|
||||
"chat": "Chat",
|
||||
"dashboard": "Panel",
|
||||
"about": "Acerca de"
|
||||
"about": "Acerca de",
|
||||
"navigation": "Navegación"
|
||||
},
|
||||
"home": {
|
||||
"title": "Hola desde WrNexus",
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
// Navigation tour. Route: /navigation
|
||||
//
|
||||
// One page that wires every navigation component together the way a real
|
||||
// console would, rather than showing each in isolation:
|
||||
//
|
||||
// Navbar the top bar, with a dropdown built from nested children
|
||||
// MegaMenu a wide product panel beside it
|
||||
// Breadcrumb the trail under the bar
|
||||
// Sidebar the left rail, which becomes a Drawer on a phone
|
||||
// Tabs the main content, backed by a tab query parameter
|
||||
// Stepper a wizard inside one tab, gated on a checkbox
|
||||
// Pagination under the list in another tab
|
||||
// Scrollspy a contents rail that follows the reader down the article
|
||||
// Nav the secondary links in the footer
|
||||
//
|
||||
// Object props are held in state and bound. Passing one inline as an
|
||||
// attribute would put a brace at the start of the value, which the compiler
|
||||
// reads as an interpolation.
|
||||
page NavigationTour {
|
||||
layout = "public"
|
||||
|
||||
seo {
|
||||
title = "Navigation tour"
|
||||
description = "Every navigation component working together in one shell."
|
||||
}
|
||||
|
||||
state brand = { label: "Acme", description: "Console", href: "/navigation" }
|
||||
|
||||
state topLinks = [
|
||||
{ label: "Overview", href: "/navigation", value: "overview" },
|
||||
{
|
||||
label: "Workspace",
|
||||
value: "workspace",
|
||||
columns: 2,
|
||||
description: "Everything your team touches.",
|
||||
children: [
|
||||
{
|
||||
label: "Projects",
|
||||
children: [
|
||||
{ label: "Active", href: "/navigation", description: "In flight right now." },
|
||||
{ label: "Archived", href: "/navigation", description: "Kept for the record." }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "People",
|
||||
children: [
|
||||
{ label: "Members", href: "/navigation", description: "Who can sign in." },
|
||||
{ label: "Roles", href: "/navigation", description: "What they may do." }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ label: "Reports", href: "/navigation", value: "reports" }
|
||||
]
|
||||
|
||||
state topActions = [
|
||||
{ label: "Docs", href: "/about", variant: "link" },
|
||||
{ label: "New project", href: "/navigation", variant: "primary" }
|
||||
]
|
||||
|
||||
state megaColumns = [
|
||||
{
|
||||
heading: "Platform",
|
||||
items: [
|
||||
{ label: "Runtime", href: "/navigation", description: "The browser half.", icon: "icon-[lucide--cpu]" },
|
||||
{ label: "Compiler", href: "/navigation", description: "WRN to JavaScript.", icon: "icon-[lucide--hammer]" }
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "Tooling",
|
||||
items: [
|
||||
{ label: "CLI", href: "/navigation", description: "Scaffold and deploy.", icon: "icon-[lucide--terminal]" },
|
||||
{ label: "Editor", href: "/navigation", description: "Language server.", icon: "icon-[lucide--code]" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
state crumbs = [
|
||||
{ label: "Home", href: "/" },
|
||||
{ label: "Console", href: "/navigation" },
|
||||
{ label: "Navigation tour" }
|
||||
]
|
||||
|
||||
state sideItems = [
|
||||
{ label: "Overview", href: "#overview", value: "overview", icon: "icon-[lucide--gauge]" },
|
||||
{
|
||||
heading: "Build",
|
||||
items: [
|
||||
{ label: "Projects", href: "#projects", value: "projects", badge: "4" },
|
||||
{
|
||||
label: "Environments",
|
||||
value: "envs",
|
||||
items: [
|
||||
{ label: "Production", href: "#projects", value: "prod" },
|
||||
{ label: "Staging", href: "#projects", value: "staging" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "Account",
|
||||
items: [{ label: "Settings", href: "#settings", value: "settings", icon: "icon-[lucide--settings]" }]
|
||||
}
|
||||
]
|
||||
|
||||
state contentTabs = [
|
||||
{ label: "Article", value: "article", title: "Release notes" },
|
||||
{ label: "Wizard", value: "wizard", title: "Create a project" },
|
||||
{ label: "Members", value: "members", title: "Who can sign in" }
|
||||
]
|
||||
|
||||
state spyLinks = [
|
||||
{ label: "Overview", href: "#overview" },
|
||||
{ label: "Projects", href: "#projects" },
|
||||
{ label: "Settings", href: "#settings" }
|
||||
]
|
||||
|
||||
state wizardSteps = [
|
||||
{ label: "Details", title: "Name the project", content: "Pick something the team will recognise." },
|
||||
{ label: "Access", title: "Who can see it", content: "Invite people now or later." },
|
||||
{ label: "Review", title: "Check and create", content: "Nothing is created until you finish." }
|
||||
]
|
||||
|
||||
state footerLinks = [
|
||||
{ label: "Status", href: "/navigation", value: "status" },
|
||||
{ label: "Changelog", href: "/navigation", value: "changelog" },
|
||||
{ label: "Support", href: "/about", value: "support" }
|
||||
]
|
||||
|
||||
state members = [
|
||||
"A. Okafor", "R. Silva", "M. Chen", "J. Dubois", "P. Novak",
|
||||
"T. Ahmed", "L. Rossi", "K. Bauer", "S. Haddad", "N. Ito"
|
||||
]
|
||||
|
||||
// Tabs reports the selection and this page owns what is shown. Slot content
|
||||
// and component props are rendered once and do not track page state, so
|
||||
// anything that has to react lives out here in page scope.
|
||||
state section = "article"
|
||||
state sideActive = "overview"
|
||||
state wizardStep = 0
|
||||
// Not named page: that is the page keyword and shadows the runtime binding.
|
||||
state currentPage = 1
|
||||
state pageSize = 4
|
||||
state lastEvent = "Nothing yet. Interact with any control."
|
||||
|
||||
functions {
|
||||
client function onSideSelect(payload) {
|
||||
sideActive = payload.value || ""
|
||||
lastEvent = "Sidebar select: " + (payload.value || "(none)")
|
||||
}
|
||||
|
||||
client function onTabChange(payload) {
|
||||
section = payload.value
|
||||
lastEvent = "Tab change: " + payload.value
|
||||
}
|
||||
|
||||
// The stepper never validates anything itself. This page owns the
|
||||
// checkbox, so it decides when the step may advance.
|
||||
client function onWizardChange(payload) {
|
||||
wizardStep = payload.index
|
||||
lastEvent = "Wizard step: " + (payload.index + 1)
|
||||
}
|
||||
|
||||
client function onWizardFinish() {
|
||||
lastEvent = "Wizard finished. A real app would create the project here."
|
||||
}
|
||||
|
||||
client function onWizardSkip(payload) {
|
||||
wizardStep = payload.index
|
||||
lastEvent = "Wizard skipped to step " + (payload.index + 1)
|
||||
}
|
||||
|
||||
client function onPageChange(payload) {
|
||||
currentPage = payload.page
|
||||
lastEvent = "Page " + payload.page
|
||||
}
|
||||
|
||||
client function onSpyChange(payload) {
|
||||
lastEvent = "Reading: " + payload.label
|
||||
}
|
||||
|
||||
client function onMegaSelect(payload) {
|
||||
lastEvent = "Mega menu: " + payload.value
|
||||
}
|
||||
|
||||
client function onNavSelect(payload) {
|
||||
lastEvent = "Footer nav: " + payload.value
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<div class="tour">
|
||||
<Navbar
|
||||
brand={brand}
|
||||
items={topLinks}
|
||||
actions={topActions}
|
||||
active="overview"
|
||||
label="Primary navigation"
|
||||
mobileLabel="Toggle navigation"
|
||||
/>
|
||||
|
||||
<div class="tour__toolbar">
|
||||
<MegaMenu
|
||||
label="Products"
|
||||
icon="icon-[lucide--box]"
|
||||
columns={megaColumns}
|
||||
footer="Not sure where to start? Talk to us."
|
||||
@select="onMegaSelect(payload)"
|
||||
/>
|
||||
<Breadcrumb items={crumbs} />
|
||||
</div>
|
||||
|
||||
<div class="tour__shell">
|
||||
<aside class="tour__rail">
|
||||
<Sidebar
|
||||
items={sideItems}
|
||||
active={sideActive}
|
||||
label="Console"
|
||||
mobileLabel="Open navigation"
|
||||
drawerTitle="Console"
|
||||
@select="onSideSelect(payload)"
|
||||
/>
|
||||
</aside>
|
||||
|
||||
<main class="tour__main">
|
||||
<p class="tour__status" role="status">{lastEvent}</p>
|
||||
|
||||
<Tabs
|
||||
items={contentTabs}
|
||||
active="article"
|
||||
mode="url"
|
||||
param="tab"
|
||||
label="Console sections"
|
||||
@change="onTabChange(payload)"
|
||||
/>
|
||||
|
||||
<section class="tour__panel" data-show="section === 'article'">
|
||||
<section id="overview" class="tour__section">
|
||||
<h3>Overview</h3>
|
||||
<p>
|
||||
Scroll this column and the contents rail on the right follows along.
|
||||
The runtime observes these sections against the viewport and moves
|
||||
aria-current to the matching link.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="projects" class="tour__section">
|
||||
<h3>Projects</h3>
|
||||
<p>
|
||||
The rail on the left is the Sidebar. Below the tablet breakpoint it
|
||||
collapses behind a launcher and opens as a Drawer, which is where the
|
||||
focus trap and the body scroll lock come from.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="settings" class="tour__section">
|
||||
<h3>Settings</h3>
|
||||
<p>
|
||||
The tab you are reading is mirrored into the URL. Switch tabs and the
|
||||
address bar follows, then use the browser back button.
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="tour__panel" data-show="section === 'wizard'">
|
||||
<Stepper
|
||||
steps={wizardSteps}
|
||||
active={wizardStep}
|
||||
showPanel={true}
|
||||
controls={true}
|
||||
allowSkip={true}
|
||||
nextDisabled={false}
|
||||
backLabel="Back"
|
||||
nextLabel="Next"
|
||||
skipLabel="Skip"
|
||||
finishLabel="Create project"
|
||||
label="Create a project"
|
||||
@change="onWizardChange(payload)"
|
||||
@skip="onWizardSkip(payload)"
|
||||
@finish="onWizardFinish()"
|
||||
/>
|
||||
|
||||
<p class="tour__note">
|
||||
Back, Skip and Next each emit an output, shown in the status line
|
||||
above, and Next becomes Create project on the last step. The marker
|
||||
does not advance here: active is a component prop, and props are
|
||||
rendered once rather than tracking page state. Set nextDisabled to
|
||||
hold Next while a form is incomplete.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="tour__panel" data-show="section === 'members'">
|
||||
<p class="tour__note">
|
||||
Showing page {currentPage} of 3. Pagination reports the requested
|
||||
page through its change output and the page decides what to do with
|
||||
it; the counter above is bound to that state. Re-slicing the list
|
||||
itself would need the list re-rendered, which an each block does not
|
||||
do when state changes.
|
||||
</p>
|
||||
|
||||
<ul class="tour__members">
|
||||
{#each members as person}
|
||||
<li>{person}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<Pagination
|
||||
page={currentPage}
|
||||
pageSize={pageSize}
|
||||
total={members.length}
|
||||
variant="numbered"
|
||||
@change="onPageChange(payload)"
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<aside class="tour__toc">
|
||||
<Scrollspy
|
||||
items={spyLinks}
|
||||
heading="On this page"
|
||||
active="#overview"
|
||||
@change="onSpyChange(payload)"
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<footer class="tour__footer">
|
||||
<Nav
|
||||
items={footerLinks}
|
||||
active="status"
|
||||
label="Secondary"
|
||||
collapsible={false}
|
||||
@select="onNavSelect(payload)"
|
||||
/>
|
||||
</footer>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.tour {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.tour__toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Three columns: the rail, the reading column and the contents. The middle
|
||||
* column carries min-width 0 so a wide table or code block inside a tab
|
||||
* cannot push the whole shell sideways.
|
||||
*/
|
||||
.tour__shell {
|
||||
display: grid;
|
||||
grid-template-columns: 15rem minmax(0, 1fr) 12rem;
|
||||
gap: 1.5rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.tour__main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tour__rail,
|
||||
.tour__toc {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
}
|
||||
|
||||
.tour__status {
|
||||
margin: 0 0 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-sm);
|
||||
background: var(--wire-color-surface-soft);
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* Tall enough that scrolling actually moves between sections. */
|
||||
.tour__section {
|
||||
min-height: 60vh;
|
||||
padding-top: 0.5rem;
|
||||
scroll-margin-top: 1rem;
|
||||
}
|
||||
|
||||
.tour__section h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tour__section p {
|
||||
margin: 0;
|
||||
max-width: 46rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
|
||||
.tour__members {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
margin: 0 0 1rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tour__members li {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-sm);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.tour__footer {
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--wire-color-border);
|
||||
}
|
||||
|
||||
/* The rails fold away first; the reading column is what matters. */
|
||||
@media (max-width: 1023px) {
|
||||
.tour__shell {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.tour__rail,
|
||||
.tour__toc {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export interface Routes {
|
||||
"/language-tools": Record<string, never>;
|
||||
"/login": Record<string, never>;
|
||||
"/modal": Record<string, never>;
|
||||
"/navigation": Record<string, never>;
|
||||
"/partial-static": Record<string, never>;
|
||||
"/platform-showcase": Record<string, never>;
|
||||
"/reactive": Record<string, never>;
|
||||
@@ -32,6 +33,7 @@ export interface RouteNames {
|
||||
"language.tools": "/language-tools";
|
||||
"login": "/login";
|
||||
"modal": "/modal";
|
||||
"navigation": "/navigation";
|
||||
"partial.static": "/partial-static";
|
||||
"platform.showcase": "/platform-showcase";
|
||||
"reactive": "/reactive";
|
||||
@@ -121,6 +123,7 @@ export function route<N extends RouteName>(
|
||||
"language.tools": "/language-tools",
|
||||
"login": "/login",
|
||||
"modal": "/modal",
|
||||
"navigation": "/navigation",
|
||||
"partial.static": "/partial-static",
|
||||
"platform.showcase": "/platform-showcase",
|
||||
"reactive": "/reactive",
|
||||
|
||||
@@ -13,12 +13,21 @@
|
||||
|
||||
/* --- App theme layered on top of Tailwind's utilities & preflight --- */
|
||||
|
||||
/*
|
||||
* The app palette is derived from the theme tokens rather than fixed.
|
||||
*
|
||||
* These used to be hardcoded dark values. Wire UI surfaces follow the theme,
|
||||
* so switching to light turned the cards light while this text stayed light
|
||||
* too -- the sign-in form rendered at a contrast of about 1.1 and was
|
||||
* unreadable. Anything an app hardcodes here has to be themed as well, or it
|
||||
* only ever looks right in one mode.
|
||||
*/
|
||||
:root {
|
||||
--bg: #0b1020;
|
||||
--surface: #141a30;
|
||||
--text: #e7ecff;
|
||||
--muted: #9aa6d0;
|
||||
--brand: #6c8cff;
|
||||
--bg: var(--wire-color-bg);
|
||||
--surface: var(--wire-color-surface);
|
||||
--text: var(--wire-color-text);
|
||||
--muted: var(--wire-color-text-muted);
|
||||
--brand: var(--wire-color-primary);
|
||||
--radius: 10px;
|
||||
--maxw: 720px;
|
||||
}
|
||||
@@ -36,7 +45,12 @@ body {
|
||||
Roboto,
|
||||
sans-serif;
|
||||
color: var(--text);
|
||||
background: radial-gradient(1200px 600px at 50% -10%, #1b2750 0%, var(--bg) 60%);
|
||||
/* Tinted from the primary token so the wash follows the theme too. */
|
||||
background: radial-gradient(
|
||||
1200px 600px at 50% -10%,
|
||||
color-mix(in srgb, var(--wire-color-primary) 18%, var(--bg)) 0%,
|
||||
var(--bg) 60%
|
||||
);
|
||||
background-color: var(--bg);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ page NavbarDetail {
|
||||
state playground_maxWidth = ctx.url.searchParams.get("pg_maxWidth") ?? "full"
|
||||
state playground_mobileLabel = ctx.url.searchParams.get("pg_mobileLabel") ?? "Navbar"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
state demo_obj_0 = JSON.parse("{\"label\":\"Northwind\",\"description\":\"Console\",\"href\":\"/\"}")
|
||||
state demo_obj_1 = JSON.parse("{\"label\":\"Northwind\",\"icon\":\"icon-[lucide--box]\",\"href\":\"/\"}")
|
||||
state demo_obj_2 = JSON.parse("{\"label\":\"Acme\",\"href\":\"/\"}")
|
||||
seo {
|
||||
title = "Navbar"
|
||||
description = "Theme-aware, responsive navbar component."
|
||||
@@ -600,289 +603,54 @@ page NavbarDetail {
|
||||
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
|
||||
<article class="demo-case">
|
||||
<header class="demo-case-header">
|
||||
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
|
||||
<div><span class="demo-case-eyebrow">Recommended</span><h2>Brand, links and actions</h2><p>The everyday shape: a brand, a row of links with the current page marked, and calls to action on the right. Arrow keys move along the menu bar.</p></div>
|
||||
<span class="demo-case-number">01</span>
|
||||
</header>
|
||||
<div class="demo-workbench">
|
||||
<div id="navbar-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Navbar size="default" label="Navbar" topbarLabel="Navbar" brand='({"id":"navbar-0-1","key":"navbar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","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","Standard"]})' items='[{"id":"navbar-0-1","key":"navbar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","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","Standard"]},{"id":"navbar-0-2","key":"navbar-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","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","Standard"]}]' actions='[{"id":"navbar-0-1","key":"navbar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","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","Standard"]},{"id":"navbar-0-2","key":"navbar-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","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","Standard"]}]' sticky="false" openOnHover="false" mobileLabel="Navbar" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><Navbar size="default" label="Primary navigation" topbarLabel="Navbar" brand='{demo_obj_0}' items='[{"label":"Overview","href":"/","value":"overview"},{"label":"Projects","href":"/projects","value":"projects"},{"label":"Reports","href":"/reports","value":"reports"}]' actions='[{"label":"Sign in","href":"/login","variant":"link"},{"label":"Start free","href":"/signup","variant":"primary"}]' active="projects" sticky="false" openOnHover="false" mobileLabel="Navbar" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Production default usage">
|
||||
<section class="demo-code" aria-label="Brand, links and actions usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Navbar
|
||||
label="Navbar"
|
||||
topbarLabel="Navbar"
|
||||
brand='<span>{</span>
|
||||
"id": "navbar-0-1",
|
||||
"key": "navbar-0-1",
|
||||
"value": "primary",
|
||||
"label": "Primary workflow",
|
||||
"title": "Primary workflow",
|
||||
"description": "A clean default configuration for everyday product interfaces.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--sparkles]",
|
||||
"iconClass": "icon-[lucide--sparkles]",
|
||||
"variant": "primary",
|
||||
"status": "Active",
|
||||
"time": "09:30",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 16,
|
||||
"percentage": 72,
|
||||
"color": "#7c3aed",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open primary workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Northwind",
|
||||
"description": "Console",
|
||||
"href": "/"
|
||||
<span>}</span>'
|
||||
items='[
|
||||
<span>{</span>
|
||||
"id": "navbar-0-1",
|
||||
"key": "navbar-0-1",
|
||||
"value": "primary",
|
||||
"label": "Primary workflow",
|
||||
"title": "Primary workflow",
|
||||
"description": "A clean default configuration for everyday product interfaces.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--sparkles]",
|
||||
"iconClass": "icon-[lucide--sparkles]",
|
||||
"variant": "primary",
|
||||
"status": "Active",
|
||||
"time": "09:30",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 16,
|
||||
"percentage": 72,
|
||||
"color": "#7c3aed",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open primary workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Overview",
|
||||
"href": "/",
|
||||
"value": "overview"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"id": "navbar-0-2",
|
||||
"key": "navbar-0-2",
|
||||
"value": "secondary",
|
||||
"label": "Additional option",
|
||||
"title": "Supporting example",
|
||||
"description": "A clean default configuration for everyday product interfaces.",
|
||||
"text": "Another configurable message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--sparkles]",
|
||||
"iconClass": "icon-[lucide--sparkles]",
|
||||
"variant": "primary",
|
||||
"status": "Active",
|
||||
"time": "09:30",
|
||||
"current": false,
|
||||
"selected": false,
|
||||
"checked": false,
|
||||
"disabled": false,
|
||||
"outgoing": true,
|
||||
"open": true,
|
||||
"number": 2,
|
||||
"count": 32,
|
||||
"percentage": 72,
|
||||
"color": "#7c3aed",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open primary workflow 2",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Projects",
|
||||
"href": "/projects",
|
||||
"value": "projects"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Reports",
|
||||
"href": "/reports",
|
||||
"value": "reports"
|
||||
<span>}</span>
|
||||
]'
|
||||
actions='[
|
||||
<span>{</span>
|
||||
"id": "navbar-0-1",
|
||||
"key": "navbar-0-1",
|
||||
"value": "primary",
|
||||
"label": "Primary workflow",
|
||||
"title": "Primary workflow",
|
||||
"description": "A clean default configuration for everyday product interfaces.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--sparkles]",
|
||||
"iconClass": "icon-[lucide--sparkles]",
|
||||
"variant": "primary",
|
||||
"status": "Active",
|
||||
"time": "09:30",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 16,
|
||||
"percentage": 72,
|
||||
"color": "#7c3aed",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open primary workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Sign in",
|
||||
"href": "/login",
|
||||
"variant": "link"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"id": "navbar-0-2",
|
||||
"key": "navbar-0-2",
|
||||
"value": "secondary",
|
||||
"label": "Additional option",
|
||||
"title": "Supporting example",
|
||||
"description": "A clean default configuration for everyday product interfaces.",
|
||||
"text": "Another configurable message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--sparkles]",
|
||||
"iconClass": "icon-[lucide--sparkles]",
|
||||
"variant": "primary",
|
||||
"status": "Active",
|
||||
"time": "09:30",
|
||||
"current": false,
|
||||
"selected": false,
|
||||
"checked": false,
|
||||
"disabled": false,
|
||||
"outgoing": true,
|
||||
"open": true,
|
||||
"number": 2,
|
||||
"count": 32,
|
||||
"percentage": 72,
|
||||
"color": "#7c3aed",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open primary workflow 2",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Start free",
|
||||
"href": "/signup",
|
||||
"variant": "primary"
|
||||
<span>}</span>
|
||||
]'
|
||||
active="projects"
|
||||
mobileLabel="Navbar">
|
||||
<div data-slot="topbar">
|
||||
<div class="showcase-slot">topbar slot</div>
|
||||
@@ -896,290 +664,86 @@ page NavbarDetail {
|
||||
</article>
|
||||
<article class="demo-case">
|
||||
<header class="demo-case-header">
|
||||
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
|
||||
<div><span class="demo-case-eyebrow">Advanced</span><h2>Dropdown and mega panel</h2><p>An item carrying children opens a panel built from native details and summary, so it is keyboard operable without any extra script. Give a child its own children to get a titled column inside the panel.</p></div>
|
||||
<span class="demo-case-number">02</span>
|
||||
</header>
|
||||
<div class="demo-workbench">
|
||||
<div id="navbar-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Navbar size="sm" label="Compact example" topbarLabel="Compact example" brand='({"id":"navbar-1-1","key":"navbar-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","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","Standard"]})' items='[{"id":"navbar-1-1","key":"navbar-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","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","Standard"]},{"id":"navbar-1-2","key":"navbar-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","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","Standard"]}]' actions='[{"id":"navbar-1-1","key":"navbar-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","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","Standard"]},{"id":"navbar-1-2","key":"navbar-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","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","Standard"]}]' sticky="false" openOnHover="false" mobileLabel="Compact example" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><Navbar size="sm" label="Compact example" topbarLabel="Compact example" brand='{demo_obj_1}' items='[{"label":"Overview","href":"/","value":"overview"},{"label":"Products","value":"products","columns":2,"description":"Everything in the platform.","children":[{"label":"Platform","children":[{"label":"Runtime","href":"/runtime","description":"The browser half.","icon":"icon-[lucide--cpu]"},{"label":"Compiler","href":"/compiler","description":"WRN to JavaScript.","icon":"icon-[lucide--hammer]"}]},{"label":"Tooling","children":[{"label":"CLI","href":"/cli","description":"Scaffold and deploy."},{"label":"Editor","href":"/editor","description":"Language server."}]}]},{"label":"Pricing","href":"/pricing","value":"pricing"}]' actions='[{"label":"Book a demo","href":"/demo","variant":"primary"}]' active="overview" sticky="false" openOnHover="false" mobileLabel="Compact example" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Compact application usage">
|
||||
<section class="demo-code" aria-label="Dropdown and mega panel usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Navbar
|
||||
size="sm"
|
||||
label="Compact example"
|
||||
topbarLabel="Compact example"
|
||||
brand='<span>{</span>
|
||||
"id": "navbar-1-1",
|
||||
"key": "navbar-1-1",
|
||||
"value": "primary",
|
||||
"label": "Secondary workflow",
|
||||
"title": "Secondary workflow",
|
||||
"description": "A compact configuration designed for dense application layouts.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--zap]",
|
||||
"iconClass": "icon-[lucide--zap]",
|
||||
"variant": "secondary",
|
||||
"status": "Active",
|
||||
"time": "10:15",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 24,
|
||||
"percentage": 48,
|
||||
"color": "#0284c7",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open secondary workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Northwind",
|
||||
"icon": "icon-[lucide--box]",
|
||||
"href": "/"
|
||||
<span>}</span>'
|
||||
items='[
|
||||
<span>{</span>
|
||||
"id": "navbar-1-1",
|
||||
"key": "navbar-1-1",
|
||||
"value": "primary",
|
||||
"label": "Secondary workflow",
|
||||
"title": "Secondary workflow",
|
||||
"description": "A compact configuration designed for dense application layouts.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--zap]",
|
||||
"iconClass": "icon-[lucide--zap]",
|
||||
"variant": "secondary",
|
||||
"status": "Active",
|
||||
"time": "10:15",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 24,
|
||||
"percentage": 48,
|
||||
"color": "#0284c7",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open secondary workflow 1",
|
||||
"items": [
|
||||
"label": "Overview",
|
||||
"href": "/",
|
||||
"value": "overview"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Products",
|
||||
"value": "products",
|
||||
"columns": 2,
|
||||
"description": "Everything in the platform.",
|
||||
"children": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
"label": "Platform",
|
||||
"children": [
|
||||
<span>{</span>
|
||||
"label": "Runtime",
|
||||
"href": "/runtime",
|
||||
"description": "The browser half.",
|
||||
"icon": "icon-[lucide--cpu]"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Compiler",
|
||||
"href": "/compiler",
|
||||
"description": "WRN to JavaScript.",
|
||||
"icon": "icon-[lucide--hammer]"
|
||||
<span>}</span>
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
"label": "Tooling",
|
||||
"children": [
|
||||
<span>{</span>
|
||||
"label": "CLI",
|
||||
"href": "/cli",
|
||||
"description": "Scaffold and deploy."
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Editor",
|
||||
"href": "/editor",
|
||||
"description": "Language server."
|
||||
<span>}</span>
|
||||
]
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"id": "navbar-1-2",
|
||||
"key": "navbar-1-2",
|
||||
"value": "secondary",
|
||||
"label": "Additional option",
|
||||
"title": "Supporting example",
|
||||
"description": "A compact configuration designed for dense application layouts.",
|
||||
"text": "Another configurable message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--zap]",
|
||||
"iconClass": "icon-[lucide--zap]",
|
||||
"variant": "secondary",
|
||||
"status": "Active",
|
||||
"time": "10:15",
|
||||
"current": false,
|
||||
"selected": false,
|
||||
"checked": false,
|
||||
"disabled": false,
|
||||
"outgoing": true,
|
||||
"open": true,
|
||||
"number": 2,
|
||||
"count": 48,
|
||||
"percentage": 48,
|
||||
"color": "#0284c7",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open secondary workflow 2",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Pricing",
|
||||
"href": "/pricing",
|
||||
"value": "pricing"
|
||||
<span>}</span>
|
||||
]'
|
||||
actions='[
|
||||
<span>{</span>
|
||||
"id": "navbar-1-1",
|
||||
"key": "navbar-1-1",
|
||||
"value": "primary",
|
||||
"label": "Secondary workflow",
|
||||
"title": "Secondary workflow",
|
||||
"description": "A compact configuration designed for dense application layouts.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--zap]",
|
||||
"iconClass": "icon-[lucide--zap]",
|
||||
"variant": "secondary",
|
||||
"status": "Active",
|
||||
"time": "10:15",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 24,
|
||||
"percentage": 48,
|
||||
"color": "#0284c7",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open secondary workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"id": "navbar-1-2",
|
||||
"key": "navbar-1-2",
|
||||
"value": "secondary",
|
||||
"label": "Additional option",
|
||||
"title": "Supporting example",
|
||||
"description": "A compact configuration designed for dense application layouts.",
|
||||
"text": "Another configurable message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "View details",
|
||||
"icon": "icon-[lucide--zap]",
|
||||
"iconClass": "icon-[lucide--zap]",
|
||||
"variant": "secondary",
|
||||
"status": "Active",
|
||||
"time": "10:15",
|
||||
"current": false,
|
||||
"selected": false,
|
||||
"checked": false,
|
||||
"disabled": false,
|
||||
"outgoing": true,
|
||||
"open": true,
|
||||
"number": 2,
|
||||
"count": 48,
|
||||
"percentage": 48,
|
||||
"color": "#0284c7",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open secondary workflow 2",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
"label": "Book a demo",
|
||||
"href": "/demo",
|
||||
"variant": "primary"
|
||||
<span>}</span>
|
||||
]'
|
||||
active="overview"
|
||||
mobileLabel="Compact example">
|
||||
<div data-slot="topbar">
|
||||
<div class="showcase-slot">topbar slot</div>
|
||||
@@ -1193,291 +757,46 @@ page NavbarDetail {
|
||||
</article>
|
||||
<article class="demo-case">
|
||||
<header class="demo-case-header">
|
||||
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
|
||||
<div><span class="demo-case-eyebrow">Compact</span><h2>Sticky and compact</h2><p>Sticky keeps the bar pinned while the page scrolls. The toggle appears below the tablet breakpoint and the links collapse behind it.</p></div>
|
||||
<span class="demo-case-number">03</span>
|
||||
</header>
|
||||
<div class="demo-workbench">
|
||||
<div id="navbar-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Navbar size="lg" label="Advanced example" topbarLabel="Advanced example" brand='({"id":"navbar-2-1","key":"navbar-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","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","Unlimited"]})' items='[{"id":"navbar-2-1","key":"navbar-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","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","Unlimited"]},{"id":"navbar-2-2","key":"navbar-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","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","Unlimited"]}]' actions='[{"id":"navbar-2-1","key":"navbar-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","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","Unlimited"]},{"id":"navbar-2-2","key":"navbar-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","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","Unlimited"]}]' sticky="false" openOnHover="false" mobileLabel="Advanced example" class="showcase-instance showcase-instance--3" /></div>
|
||||
<div class="demo-render"><Navbar size="sm" label="Advanced example" topbarLabel="Advanced example" brand='{demo_obj_2}' items='[{"label":"Docs","href":"/docs","value":"docs"},{"label":"Support","href":"/support","value":"support"}]' actions='[{"label":"Dashboard","href":"/app","variant":"primary"}]' active="docs" sticky="true" openOnHover="false" mobileLabel="Toggle navigation" class="showcase-instance showcase-instance--3" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Rich configuration usage">
|
||||
<section class="demo-code" aria-label="Sticky and compact usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Navbar
|
||||
size="lg"
|
||||
size="sm"
|
||||
label="Advanced example"
|
||||
topbarLabel="Advanced example"
|
||||
brand='<span>{</span>
|
||||
"id": "navbar-2-1",
|
||||
"key": "navbar-2-1",
|
||||
"value": "primary",
|
||||
"label": "Advanced workflow",
|
||||
"title": "Advanced workflow",
|
||||
"description": "A richer configuration with more supporting information and actions.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "Explore workflow",
|
||||
"icon": "icon-[lucide--circle-check]",
|
||||
"iconClass": "icon-[lucide--circle-check]",
|
||||
"variant": "success",
|
||||
"status": "Complete",
|
||||
"time": "11:45",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 32,
|
||||
"percentage": 91,
|
||||
"color": "#059669",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open advanced workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
"label": "Acme",
|
||||
"href": "/"
|
||||
<span>}</span>'
|
||||
items='[
|
||||
<span>{</span>
|
||||
"id": "navbar-2-1",
|
||||
"key": "navbar-2-1",
|
||||
"value": "primary",
|
||||
"label": "Advanced workflow",
|
||||
"title": "Advanced workflow",
|
||||
"description": "A richer configuration with more supporting information and actions.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "Explore workflow",
|
||||
"icon": "icon-[lucide--circle-check]",
|
||||
"iconClass": "icon-[lucide--circle-check]",
|
||||
"variant": "success",
|
||||
"status": "Complete",
|
||||
"time": "11:45",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 32,
|
||||
"percentage": 91,
|
||||
"color": "#059669",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open advanced workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
"label": "Docs",
|
||||
"href": "/docs",
|
||||
"value": "docs"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"id": "navbar-2-2",
|
||||
"key": "navbar-2-2",
|
||||
"value": "secondary",
|
||||
"label": "Additional option",
|
||||
"title": "Supporting example",
|
||||
"description": "A richer configuration with more supporting information and actions.",
|
||||
"text": "Another configurable message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "Explore workflow",
|
||||
"icon": "icon-[lucide--circle-check]",
|
||||
"iconClass": "icon-[lucide--circle-check]",
|
||||
"variant": "success",
|
||||
"status": "Complete",
|
||||
"time": "11:45",
|
||||
"current": false,
|
||||
"selected": false,
|
||||
"checked": false,
|
||||
"disabled": true,
|
||||
"outgoing": true,
|
||||
"open": true,
|
||||
"number": 2,
|
||||
"count": 64,
|
||||
"percentage": 91,
|
||||
"color": "#059669",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open advanced workflow 2",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
"label": "Support",
|
||||
"href": "/support",
|
||||
"value": "support"
|
||||
<span>}</span>
|
||||
]'
|
||||
actions='[
|
||||
<span>{</span>
|
||||
"id": "navbar-2-1",
|
||||
"key": "navbar-2-1",
|
||||
"value": "primary",
|
||||
"label": "Advanced workflow",
|
||||
"title": "Advanced workflow",
|
||||
"description": "A richer configuration with more supporting information and actions.",
|
||||
"text": "A configurable sample message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "Explore workflow",
|
||||
"icon": "icon-[lucide--circle-check]",
|
||||
"iconClass": "icon-[lucide--circle-check]",
|
||||
"variant": "success",
|
||||
"status": "Complete",
|
||||
"time": "11:45",
|
||||
"current": true,
|
||||
"selected": true,
|
||||
"checked": true,
|
||||
"disabled": false,
|
||||
"outgoing": false,
|
||||
"open": true,
|
||||
"number": 1,
|
||||
"count": 32,
|
||||
"percentage": 91,
|
||||
"color": "#059669",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open advanced workflow 1",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"id": "navbar-2-2",
|
||||
"key": "navbar-2-2",
|
||||
"value": "secondary",
|
||||
"label": "Additional option",
|
||||
"title": "Supporting example",
|
||||
"description": "A richer configuration with more supporting information and actions.",
|
||||
"text": "Another configurable message.",
|
||||
"href": "#navbar-demo",
|
||||
"actionHref": "#navbar-demo",
|
||||
"actionLabel": "Explore workflow",
|
||||
"icon": "icon-[lucide--circle-check]",
|
||||
"iconClass": "icon-[lucide--circle-check]",
|
||||
"variant": "success",
|
||||
"status": "Complete",
|
||||
"time": "11:45",
|
||||
"current": false,
|
||||
"selected": false,
|
||||
"checked": false,
|
||||
"disabled": true,
|
||||
"outgoing": true,
|
||||
"open": true,
|
||||
"number": 2,
|
||||
"count": 64,
|
||||
"percentage": 91,
|
||||
"color": "#059669",
|
||||
"target": "_self",
|
||||
"ariaLabel": "Open advanced workflow 2",
|
||||
"items": [
|
||||
<span>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
"label": "Dashboard",
|
||||
"href": "/app",
|
||||
"variant": "primary"
|
||||
<span>}</span>
|
||||
]'
|
||||
mobileLabel="Advanced example">
|
||||
active="docs"
|
||||
sticky="true">
|
||||
<div data-slot="topbar">
|
||||
<div class="showcase-slot">topbar slot</div>
|
||||
</div>
|
||||
@@ -1520,7 +839,7 @@ if (component) registerOutputHandler(component, "action", (payload) => <span>
|
||||
<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><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Primary navigation"</code></td><td>No</td></tr><tr><td><code>topbarLabel</code></td><td>string</td><td><code>"Utility navigation"</code></td><td>No</td></tr><tr><td><code>brand</code></td><td>Record<string, unknown></td><td><code><span>{</span><span>}</span></code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>actions</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>sticky</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>openOnHover</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"full"</code></td><td>No</td></tr><tr><td><code>mobileLabel</code></td><td>string</td><td><code>"Toggle navigation"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#navbar-demo-1">Production default</a><a href="#navbar-demo-2">Compact application</a><a href="#navbar-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#navbar-demo-1">Brand, links and actions</a><a href="#navbar-demo-2">Dropdown and mega panel</a><a href="#navbar-demo-3">Sticky and compact</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
|
||||
@@ -266,16 +266,16 @@ page ScrollspyDetail {
|
||||
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
|
||||
<article class="demo-case">
|
||||
<header class="demo-case-header">
|
||||
<div><span class="demo-case-eyebrow">Recommended</span><h2>Table of contents</h2><p>Each href points at an element on the page. The runtime observes those elements and moves aria-current to the link for whichever one is in view, so the marker follows the reader without any component state.</p></div>
|
||||
<div><span class="demo-case-eyebrow">Recommended</span><h2>Follows the reader</h2><p>Scroll the page and the marker moves to whichever section is in view. The sections below are real page content, because the runtime observes against the viewport -- a table of contents inside a small scrolling box would have nothing to react to.</p></div>
|
||||
<span class="demo-case-number">01</span>
|
||||
</header>
|
||||
<div class="demo-workbench">
|
||||
<div id="scrollspy-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Scrollspy size="default" items='[{"label":"Overview","href":"#overview"},{"label":"Installation","href":"#installation"},{"label":"Usage","href":"#usage"},{"label":"API","href":"#api"}]' active="#overview" label="Scrollspy" heading="On this page" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><div class="showcase-spy-doc"><section id="overview" class="showcase-spy-section"><h3>Overview</h3><p>Scroll this page and watch the marker in the list move. The runtime observes these sections against the viewport, so the current link follows whatever you are reading.</p></section><section id="installation" class="showcase-spy-section"><h3>Installation</h3><p>Each link points at one of these ids. Nothing is wired up by hand -- the component only renders the links, and the runtime moves aria-current.</p></section><section id="usage" class="showcase-spy-section"><h3>Usage</h3><p>The active link is marked with aria-current="location", which is what a screen reader announces, and styled through data-active.</p></section><section id="api" class="showcase-spy-section"><h3>API</h3><p>Clicking a link jumps to its section and marks it immediately, so the control responds even before the scroll settles.</p></section></div><Scrollspy size="default" items='[{"label":"Overview","href":"#overview"},{"label":"Installation","href":"#installation"},{"label":"Usage","href":"#usage"},{"label":"API","href":"#api"}]' active="#overview" label="Scrollspy" heading="On this page" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Table of contents usage">
|
||||
<section class="demo-code" aria-label="Follows the reader usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Scrollspy
|
||||
items='[
|
||||
@@ -312,7 +312,7 @@ page ScrollspyDetail {
|
||||
<div id="scrollspy-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Scrollspy size="sm" items='[{"label":"Getting started","href":"#getting-started"},{"label":"Configuration","href":"#configuration"},{"label":"Troubleshooting","href":"#troubleshooting"}]' active="#configuration" label="Compact example" heading="Sections" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><Scrollspy size="sm" items='[{"label":"Overview","href":"#overview"},{"label":"Installation","href":"#installation"},{"label":"Usage","href":"#usage"}]' active="#installation" label="Compact example" heading="Sections" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Compact rail usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -320,19 +320,19 @@ page ScrollspyDetail {
|
||||
size="sm"
|
||||
items='[
|
||||
<span>{</span>
|
||||
"label": "Getting started",
|
||||
"href": "#getting-started"
|
||||
"label": "Overview",
|
||||
"href": "#overview"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Configuration",
|
||||
"href": "#configuration"
|
||||
"label": "Installation",
|
||||
"href": "#installation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Troubleshooting",
|
||||
"href": "#troubleshooting"
|
||||
"label": "Usage",
|
||||
"href": "#usage"
|
||||
<span>}</span>
|
||||
]'
|
||||
active="#configuration"
|
||||
active="#installation"
|
||||
label="Compact example"
|
||||
heading="Sections"
|
||||
/></code></pre>
|
||||
@@ -351,7 +351,7 @@ if (component) registerOutputHandler(component, "change", (payload) => <span>
|
||||
<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><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"On this page"</code></td><td>No</td></tr><tr><td><code>heading</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#scrollspy-demo-1">Table of contents</a><a href="#scrollspy-demo-2">Compact rail</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#scrollspy-demo-1">Follows the reader</a><a href="#scrollspy-demo-2">Compact rail</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
|
||||
@@ -8,6 +8,14 @@ page StepperDetail {
|
||||
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
|
||||
state playground_clickable = (ctx.url.searchParams.get("pg_clickable") ?? "false") === "true"
|
||||
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Stepper"
|
||||
state playground_showPanel = (ctx.url.searchParams.get("pg_showPanel") ?? "true") === "true"
|
||||
state playground_controls = (ctx.url.searchParams.get("pg_controls") ?? "false") === "true"
|
||||
state playground_allowSkip = (ctx.url.searchParams.get("pg_allowSkip") ?? "false") === "true"
|
||||
state playground_nextDisabled = (ctx.url.searchParams.get("pg_nextDisabled") ?? "false") === "true"
|
||||
state playground_backLabel = ctx.url.searchParams.get("pg_backLabel") ?? "Stepper"
|
||||
state playground_nextLabel = ctx.url.searchParams.get("pg_nextLabel") ?? "Stepper"
|
||||
state playground_skipLabel = ctx.url.searchParams.get("pg_skipLabel") ?? "Stepper"
|
||||
state playground_finishLabel = ctx.url.searchParams.get("pg_finishLabel") ?? "Stepper"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Stepper"
|
||||
@@ -22,16 +30,16 @@ page StepperDetail {
|
||||
<span class="showcase-eyebrow">Navigation component</span>
|
||||
<h1>Stepper</h1>
|
||||
<p>Theme-aware, responsive stepper component.</p>
|
||||
<div class="detail-badges"><span>8 props</span><span>2 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>16 props</span><span>3 slots</span><span>5 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>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Stepper" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="change">
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Stepper" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="change,back,next,skip,finish">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Stepper</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Stepper color='{playground_color}' size='{playground_size}' steps='{playground_steps}' active='{playground_active}' orientation='{playground_orientation}' clickable='{playground_clickable}' label='{playground_label}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Stepper color='{playground_color}' size='{playground_size}' steps='{playground_steps}' active='{playground_active}' orientation='{playground_orientation}' clickable='{playground_clickable}' label='{playground_label}' showPanel='{playground_showPanel}' controls='{playground_controls}' allowSkip='{playground_allowSkip}' nextDisabled='{playground_nextDisabled}' backLabel='{playground_backLabel}' nextLabel='{playground_nextLabel}' skipLabel='{playground_skipLabel}' finishLabel='{playground_finishLabel}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Stepper
|
||||
@@ -143,17 +151,25 @@ page StepperDetail {
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
label="Stepper">
|
||||
label="Stepper"
|
||||
showPanel="true"
|
||||
backLabel="Stepper"
|
||||
nextLabel="Stepper"
|
||||
skipLabel="Stepper"
|
||||
finishLabel="Stepper">
|
||||
<div data-slot="step-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">step-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
<div data-slot="panel-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">panel-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
</Stepper></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
<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>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<header><div><span>Component props</span><strong>16 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-stepper-color"><span><strong>color</strong><small>string</small></span><select id="playground-stepper-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-stepper-size"><span><strong>size</strong><small>string</small></span><select id="playground-stepper-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-stepper-steps"><span><strong>steps</strong><small>unknown[]</small></span><textarea id="playground-stepper-steps" name="pg_steps" rows="5" spellcheck="false" data-playground-json>[
|
||||
{
|
||||
"id": "stepper-0-1",
|
||||
@@ -261,7 +277,7 @@ page StepperDetail {
|
||||
"Standard"
|
||||
]
|
||||
}
|
||||
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-stepper-active"><span><strong>active</strong><small>number</small></span><input id="playground-stepper-active" name="pg_active" type="number" value="0" /></label><label class="playground-field" for="playground-stepper-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-stepper-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-toggle" for="playground-stepper-clickable"><span><strong>clickable</strong><small>boolean</small></span><input id="playground-stepper-clickable" name="pg_clickable" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-stepper-label"><span><strong>label</strong><small>string</small></span><input id="playground-stepper-label" name="pg_label" type="text" value="Stepper" /></label><label class="playground-field" for="playground-stepper-class"><span><strong>class</strong><small>string</small></span><input id="playground-stepper-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-stepper-active"><span><strong>active</strong><small>number</small></span><input id="playground-stepper-active" name="pg_active" type="number" value="0" /></label><label class="playground-field" for="playground-stepper-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-stepper-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-toggle" for="playground-stepper-clickable"><span><strong>clickable</strong><small>boolean</small></span><input id="playground-stepper-clickable" name="pg_clickable" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-stepper-label"><span><strong>label</strong><small>string</small></span><input id="playground-stepper-label" name="pg_label" type="text" value="Stepper" /></label><label class="playground-toggle" for="playground-stepper-showPanel"><span><strong>show Panel</strong><small>boolean</small></span><input id="playground-stepper-showPanel" name="pg_showPanel" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-stepper-controls"><span><strong>controls</strong><small>boolean</small></span><input id="playground-stepper-controls" name="pg_controls" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-stepper-allowSkip"><span><strong>allow Skip</strong><small>boolean</small></span><input id="playground-stepper-allowSkip" name="pg_allowSkip" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-stepper-nextDisabled"><span><strong>next Disabled</strong><small>boolean</small></span><input id="playground-stepper-nextDisabled" name="pg_nextDisabled" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-stepper-backLabel"><span><strong>back Label</strong><small>string</small></span><input id="playground-stepper-backLabel" name="pg_backLabel" type="text" value="Stepper" /></label><label class="playground-field" for="playground-stepper-nextLabel"><span><strong>next Label</strong><small>string</small></span><input id="playground-stepper-nextLabel" name="pg_nextLabel" type="text" value="Stepper" /></label><label class="playground-field" for="playground-stepper-skipLabel"><span><strong>skip Label</strong><small>string</small></span><input id="playground-stepper-skipLabel" name="pg_skipLabel" type="text" value="Stepper" /></label><label class="playground-field" for="playground-stepper-finishLabel"><span><strong>finish Label</strong><small>string</small></span><input id="playground-stepper-finishLabel" name="pg_finishLabel" type="text" value="Stepper" /></label><label class="playground-field" for="playground-stepper-class"><span><strong>class</strong><small>string</small></span><input id="playground-stepper-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -277,7 +293,7 @@ page StepperDetail {
|
||||
<div id="stepper-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Stepper size="default" steps='[{"label":"Account","description":"Your details"},{"label":"Billing","description":"Payment method"},{"label":"Confirm","description":"Review and submit"}]' active="1" clickable="false" label="Stepper" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><Stepper size="default" steps='[{"label":"Account","description":"Your details"},{"label":"Billing","description":"Payment method"},{"label":"Confirm","description":"Review and submit"}]' active="1" clickable="false" label="Stepper" showPanel="false" controls="false" allowSkip="false" nextDisabled="false" backLabel="Back" nextLabel="Next" skipLabel="Skip" finishLabel="Finish" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Horizontal progress usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -301,6 +317,9 @@ page StepperDetail {
|
||||
<div data-slot="step-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">step-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
<div data-slot="panel-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">panel-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
</Stepper></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -314,7 +333,7 @@ page StepperDetail {
|
||||
<div id="stepper-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Stepper size="sm" steps='[{"label":"Cloned","icon":"icon-[lucide--git-branch]"},{"label":"Built","icon":"icon-[lucide--hammer]"},{"label":"Deployed","icon":"icon-[lucide--rocket]"}]' active="2" orientation="vertical" clickable="false" label="Compact example" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><Stepper size="sm" steps='[{"label":"Cloned","icon":"icon-[lucide--git-branch]"},{"label":"Built","icon":"icon-[lucide--hammer]"},{"label":"Deployed","icon":"icon-[lucide--rocket]"}]' active="2" orientation="vertical" clickable="false" label="Compact example" showPanel="false" controls="false" allowSkip="false" nextDisabled="false" backLabel="Back" nextLabel="Next" skipLabel="Skip" finishLabel="Finish" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Vertical with icons usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -340,6 +359,97 @@ page StepperDetail {
|
||||
<div data-slot="step-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">step-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
<div data-slot="panel-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">panel-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
</Stepper></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
<article class="demo-case">
|
||||
<header class="demo-case-header">
|
||||
<div><span class="demo-case-eyebrow">Advanced</span><h2>Wizard with content and controls</h2><p>showPanel renders each step body and shows only the active one, the same contract Tabs uses. controls adds Back, Skip and Next, which becomes Finish on the last step.</p></div>
|
||||
<span class="demo-case-number">03</span>
|
||||
</header>
|
||||
<div class="demo-workbench">
|
||||
<div id="stepper-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Stepper size="lg" steps='[{"label":"Account","title":"Your details","content":"Name, email and a password."},{"label":"Billing","title":"How you pay","content":"Card or invoice."},{"label":"Confirm","title":"Review","content":"Check everything before submitting."}]' active="1" clickable="false" label="Advanced example" showPanel="true" controls="true" allowSkip="true" nextDisabled="false" backLabel="Back" nextLabel="Next" skipLabel="Skip" finishLabel="Finish" class="showcase-instance showcase-instance--3" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Wizard with content and controls usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Stepper
|
||||
size="lg"
|
||||
steps='[
|
||||
<span>{</span>
|
||||
"label": "Account",
|
||||
"title": "Your details",
|
||||
"content": "Name, email and a password."
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Billing",
|
||||
"title": "How you pay",
|
||||
"content": "Card or invoice."
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Confirm",
|
||||
"title": "Review",
|
||||
"content": "Check everything before submitting."
|
||||
<span>}</span>
|
||||
]'
|
||||
active="1"
|
||||
label="Advanced example"
|
||||
showPanel="true"
|
||||
controls="true"
|
||||
allowSkip="true">
|
||||
<div data-slot="step-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">step-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
<div data-slot="panel-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">panel-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
</Stepper></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
<article class="demo-case">
|
||||
<header class="demo-case-header">
|
||||
<div><span class="demo-case-eyebrow">Advanced</span><h2>Held until the step is valid</h2><p>The same wizard with nextDisabled set. The component never validates anything itself: the page owns the form, sets this while the step is incomplete, and clears it once the step passes.</p></div>
|
||||
<span class="demo-case-number">04</span>
|
||||
</header>
|
||||
<div class="demo-workbench">
|
||||
<div id="stepper-demo-4" class="demo-canvas demo-canvas--4">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Stepper size="lg" steps='[{"label":"Account","title":"Your details","content":"This step is not complete yet."},{"label":"Billing","title":"How you pay","content":"Card or invoice."}]' active="0" clickable="false" label="Advanced example" showPanel="true" controls="true" allowSkip="false" nextDisabled="true" backLabel="Back" nextLabel="Next" skipLabel="Skip" finishLabel="Finish" class="showcase-instance showcase-instance--4" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Held until the step is valid usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Stepper
|
||||
size="lg"
|
||||
steps='[
|
||||
<span>{</span>
|
||||
"label": "Account",
|
||||
"title": "Your details",
|
||||
"content": "This step is not complete yet."
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Billing",
|
||||
"title": "How you pay",
|
||||
"content": "Card or invoice."
|
||||
<span>}</span>
|
||||
]'
|
||||
label="Advanced example"
|
||||
showPanel="true"
|
||||
controls="true"
|
||||
nextDisabled="true">
|
||||
<div data-slot="step-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">step-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
<div data-slot="panel-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">panel-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
</Stepper></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -347,13 +457,13 @@ page StepperDetail {
|
||||
<article class="demo-case">
|
||||
<header class="demo-case-header">
|
||||
<div><span class="demo-case-eyebrow">Advanced</span><h2>Clickable steps</h2><p>With clickable the steps emit a change output and take arrow-key roving focus. Without it the stepper is read-only and stays out of the tab order.</p></div>
|
||||
<span class="demo-case-number">03</span>
|
||||
<span class="demo-case-number">05</span>
|
||||
</header>
|
||||
<div class="demo-workbench">
|
||||
<div id="stepper-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div id="stepper-demo-5" class="demo-canvas demo-canvas--5">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Stepper size="lg" steps='[{"label":"One"},{"label":"Two"},{"label":"Three"}]' active="0" clickable="true" label="Advanced example" class="showcase-instance showcase-instance--3" /></div>
|
||||
<div class="demo-render"><Stepper size="lg" steps='[{"label":"One"},{"label":"Two"},{"label":"Three"}]' active="0" clickable="true" label="Advanced example" showPanel="false" controls="false" allowSkip="false" nextDisabled="false" backLabel="Back" nextLabel="Next" skipLabel="Skip" finishLabel="Finish" class="showcase-instance showcase-instance--5" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Clickable steps usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -375,23 +485,46 @@ page StepperDetail {
|
||||
<div data-slot="step-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">step-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
<div data-slot="panel-<span>{</span>index<span>}</span>">
|
||||
<div class="showcase-slot">panel-<span>{</span>index<span>}</span> slot</div>
|
||||
</div>
|
||||
</Stepper></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
</article></div></section>
|
||||
<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"><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div></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><Stepper
|
||||
<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"><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@back</code><span>Fires when the component emits the back event.</span></div><div><code>@next</code><span>Fires when the component emits the next event.</span></div><div><code>@skip</code><span>Fires when the component emits the skip event.</span></div><div><code>@finish</code><span>Fires when the component emits the finish event.</span></div></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><Stepper
|
||||
@change='console.log(payload)'
|
||||
@back='console.log(payload)'
|
||||
@next='console.log(payload)'
|
||||
@skip='console.log(payload)'
|
||||
@finish='console.log(payload)'
|
||||
/></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>import <span>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"Stepper\"], [data-component=\"Stepper\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "back", (payload) => <span>{</span>
|
||||
console.log("back", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "next", (payload) => <span>{</span>
|
||||
console.log("next", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "skip", (payload) => <span>{</span>
|
||||
console.log("skip", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "finish", (payload) => <span>{</span>
|
||||
console.log("finish", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<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><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>steps</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>clickable</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Progress"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<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><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>steps</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>active</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>clickable</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Progress"</code></td><td>No</td></tr><tr><td><code>showPanel</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>controls</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>allowSkip</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>nextDisabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>backLabel</code></td><td>string</td><td><code>"Back"</code></td><td>No</td></tr><tr><td><code>nextLabel</code></td><td>string</td><td><code>"Next"</code></td><td>No</td></tr><tr><td><code>skipLabel</code></td><td>string</td><td><code>"Skip"</code></td><td>No</td></tr><tr><td><code>finishLabel</code></td><td>string</td><td><code>"Finish"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#stepper-demo-1">Horizontal progress</a><a href="#stepper-demo-2">Vertical with icons</a><a href="#stepper-demo-3">Clickable steps</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#stepper-demo-1">Horizontal progress</a><a href="#stepper-demo-2">Vertical with icons</a><a href="#stepper-demo-3">Wizard with content and controls</a><a href="#stepper-demo-4">Held until the step is valid</a><a href="#stepper-demo-5">Clickable steps</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
|
||||
@@ -17,11 +17,11 @@ page ComponentShowcase {
|
||||
<section class="home-product-grid"><article><span class="icon-[lucide--component] size-7"></span><strong>108 components</strong><p>Accessible primitives for every product surface.</p><a href="/base">Browse components →</a></article><article><span class="icon-[lucide--layout-template] size-7"></span><strong>Ready-made blocks</strong><p>Composable sections assembled from WRNexus UI.</p><a href="/block-library">Explore blocks →</a></article><article><span class="icon-[lucide--panels-top-left] size-7"></span><strong>Page templates</strong><p>Complete responsive pages ready to customize.</p><a href="/templates">View templates →</a></article></section>
|
||||
<section class="showcase-stats">
|
||||
<div class="showcase-stat"><strong>108</strong><span>Unique components</span></div>
|
||||
<div class="showcase-stat"><strong>419</strong><span>Live configurations</span></div>
|
||||
<div class="showcase-stat"><strong>421</strong><span>Live configurations</span></div>
|
||||
<div class="showcase-stat"><strong>0</strong><span>Duplicate implementations</span></div>
|
||||
<div class="showcase-stat"><strong>11</strong><span>Focused categories</span></div>
|
||||
</section>
|
||||
<section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>8 components · 112 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>27 components · 86 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>11 components · 33 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 45 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 28 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section>
|
||||
<section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>8 components · 112 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>27 components · 86 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>11 components · 33 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 45 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ page NavigationShowcase {
|
||||
<span class="showcase-eyebrow">Component category</span>
|
||||
<h1 class="showcase-title">Navigation</h1>
|
||||
<p class="showcase-description">10 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
|
||||
<div class="category-meta"><span>10 components</span><span>Multiple use cases</span><span>28 live configurations</span></div>
|
||||
<div class="category-meta"><span>10 components</span><span>Multiple use cases</span><span>30 live configurations</span></div>
|
||||
</header>
|
||||
<div class="catalog-grid">
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -56,7 +56,7 @@ page NavigationShowcase {
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Navbar size="default" label="Navbar" topbarLabel="Navbar" brand='({"id":"navbar-0-1","key":"navbar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","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","Standard"]})' items='[{"id":"navbar-0-1","key":"navbar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","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","Standard"]},{"id":"navbar-0-2","key":"navbar-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","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","Standard"]}]' actions='[{"id":"navbar-0-1","key":"navbar-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","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","Standard"]},{"id":"navbar-0-2","key":"navbar-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#navbar-demo","actionHref":"#navbar-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","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","Standard"]}]' sticky="false" openOnHover="false" mobileLabel="Navbar" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="catalog-card-stage"><Navbar size="default" label="Primary navigation" topbarLabel="Navbar" brand='{demo_obj_0}' items='[{"label":"Overview","href":"/","value":"overview"},{"label":"Projects","href":"/projects","value":"projects"},{"label":"Reports","href":"/reports","value":"reports"}]' actions='[{"label":"Sign in","href":"/login","variant":"link"},{"label":"Start free","href":"/signup","variant":"primary"}]' active="projects" sticky="false" openOnHover="false" mobileLabel="Navbar" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Navigation</span><h2>Navbar</h2><p>Theme-aware, responsive navbar component.</p></div>
|
||||
@@ -96,11 +96,11 @@ page NavigationShowcase {
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Stepper size="default" steps='[{"label":"Account","description":"Your details"},{"label":"Billing","description":"Payment method"},{"label":"Confirm","description":"Review and submit"}]' active="1" clickable="false" label="Stepper" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="catalog-card-stage"><Stepper size="default" steps='[{"label":"Account","description":"Your details"},{"label":"Billing","description":"Payment method"},{"label":"Confirm","description":"Review and submit"}]' active="1" clickable="false" label="Stepper" showPanel="false" controls="false" allowSkip="false" nextDisabled="false" backLabel="Back" nextLabel="Next" skipLabel="Skip" finishLabel="Finish" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Navigation</span><h2>Stepper</h2><p>Theme-aware, responsive stepper component.</p></div>
|
||||
<div class="catalog-card-footer"><span>8 props · 2 slots</span><a href="/components/stepper">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>16 props · 3 slots</span><a href="/components/stepper">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
|
||||
@@ -2925,3 +2925,38 @@ body {
|
||||
.demo-canvas:has([data-ui-component="Tooltip"], [data-ui-component="Popover"]) .demo-render {
|
||||
padding-block: 5rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scrollspy demo sections.
|
||||
*
|
||||
* Real page-flow content on purpose: the runtime observes sections against the
|
||||
* viewport, so a table of contents inside a small scrolling box would have
|
||||
* nothing to react to and the demo could not be tested at all.
|
||||
*/
|
||||
.showcase-spy-doc {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.showcase-spy-section {
|
||||
min-height: 42vh;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-md);
|
||||
background: var(--wire-color-surface-soft);
|
||||
scroll-margin-top: 5rem;
|
||||
}
|
||||
|
||||
.showcase-spy-section h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.showcase-spy-section p {
|
||||
margin: 0;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -424,16 +424,42 @@ function sampleValue(prop, component, variation) {
|
||||
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) {
|
||||
const expression = JSON.stringify(value).replaceAll("'", "\\u0027");
|
||||
/*
|
||||
* An object literal has to be parenthesised. A bare {...} at the start of
|
||||
* an attribute is read as an interpolation, so the compiler tried to parse
|
||||
* the JSON as JavaScript and stopped at the first colon. Arrays start with
|
||||
* [ and were never affected, which is why only object props broke.
|
||||
*/
|
||||
return `${name}='${Array.isArray(value) ? expression : `(${expression})`}'`;
|
||||
if (!Array.isArray(value)) return `${name}='{${hoistObjectProp(value)}}'`;
|
||||
return `${name}='${JSON.stringify(value).replaceAll("'", "\\u0027")}'`;
|
||||
}
|
||||
return `${name}="${escapeAttribute(String(value))}"`;
|
||||
}
|
||||
@@ -1475,6 +1501,8 @@ function eventDocumentation(component) {
|
||||
}
|
||||
|
||||
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];
|
||||
@@ -1552,7 +1580,7 @@ function detailPage(component, categoryComponents) {
|
||||
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)}
|
||||
${playgroundStates(component)}${hoistedObjectStates()}
|
||||
seo {
|
||||
title = "${escapeAttribute(displayName(component.name))}"
|
||||
description = "${escapeAttribute(component.purpose)}"
|
||||
|
||||
@@ -253,12 +253,99 @@ 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 = {
|
||||
Scrollspy: {
|
||||
Navbar: {
|
||||
demos: [
|
||||
standard(
|
||||
"Table of contents",
|
||||
"Each href points at an element on the page. The runtime observes those elements and moves aria-current to the link for whichever one is in view, so the marker follows the reader without any component state.",
|
||||
"Brand, links and actions",
|
||||
"The everyday shape: a brand, a row of links with the current page marked, and calls to action on the right. Arrow keys move along the menu bar.",
|
||||
{
|
||||
brand: { label: "Northwind", description: "Console", href: "/" },
|
||||
items: [
|
||||
{ label: "Overview", href: "/", value: "overview" },
|
||||
{ label: "Projects", href: "/projects", value: "projects" },
|
||||
{ label: "Reports", href: "/reports", value: "reports" },
|
||||
],
|
||||
actions: [
|
||||
{ label: "Sign in", href: "/login", variant: "link" },
|
||||
{ label: "Start free", href: "/signup", variant: "primary" },
|
||||
],
|
||||
active: "projects",
|
||||
label: "Primary navigation",
|
||||
},
|
||||
),
|
||||
advanced(
|
||||
"Dropdown and mega panel",
|
||||
"An item carrying children opens a panel built from native details and summary, so it is keyboard operable without any extra script. Give a child its own children to get a titled column inside the panel.",
|
||||
{
|
||||
brand: { label: "Northwind", icon: "icon-[lucide--box]", href: "/" },
|
||||
items: [
|
||||
{ label: "Overview", href: "/", value: "overview" },
|
||||
{
|
||||
label: "Products",
|
||||
value: "products",
|
||||
columns: 2,
|
||||
description: "Everything in the platform.",
|
||||
children: [
|
||||
{
|
||||
label: "Platform",
|
||||
children: [
|
||||
{
|
||||
label: "Runtime",
|
||||
href: "/runtime",
|
||||
description: "The browser half.",
|
||||
icon: "icon-[lucide--cpu]",
|
||||
},
|
||||
{
|
||||
label: "Compiler",
|
||||
href: "/compiler",
|
||||
description: "WRN to JavaScript.",
|
||||
icon: "icon-[lucide--hammer]",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Tooling",
|
||||
children: [
|
||||
{ label: "CLI", href: "/cli", description: "Scaffold and deploy." },
|
||||
{ label: "Editor", href: "/editor", description: "Language server." },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ label: "Pricing", href: "/pricing", value: "pricing" },
|
||||
],
|
||||
actions: [{ label: "Book a demo", href: "/demo", variant: "primary" }],
|
||||
active: "overview",
|
||||
},
|
||||
),
|
||||
compact(
|
||||
"Sticky and compact",
|
||||
"Sticky keeps the bar pinned while the page scrolls. The toggle appears below the tablet breakpoint and the links collapse behind it.",
|
||||
{
|
||||
brand: { label: "Acme", href: "/" },
|
||||
items: [
|
||||
{ label: "Docs", href: "/docs", value: "docs" },
|
||||
{ label: "Support", href: "/support", value: "support" },
|
||||
],
|
||||
actions: [{ label: "Dashboard", href: "/app", variant: "primary" }],
|
||||
active: "docs",
|
||||
sticky: true,
|
||||
size: "sm",
|
||||
mobileLabel: "Toggle navigation",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
Scrollspy: {
|
||||
demos: [
|
||||
{
|
||||
eyebrow: "Recommended",
|
||||
title: "Follows the reader",
|
||||
description:
|
||||
"Scroll the page and the marker moves to whichever section is in view. The sections below are real page content, because the runtime observes against the viewport -- a table of contents inside a small scrolling box would have nothing to react to.",
|
||||
before:
|
||||
'<div class="showcase-spy-doc"><section id="overview" class="showcase-spy-section"><h3>Overview</h3><p>Scroll this page and watch the marker in the list move. The runtime observes these sections against the viewport, so the current link follows whatever you are reading.</p></section><section id="installation" class="showcase-spy-section"><h3>Installation</h3><p>Each link points at one of these ids. Nothing is wired up by hand -- the component only renders the links, and the runtime moves aria-current.</p></section><section id="usage" class="showcase-spy-section"><h3>Usage</h3><p>The active link is marked with aria-current="location", which is what a screen reader announces, and styled through data-active.</p></section><section id="api" class="showcase-spy-section"><h3>API</h3><p>Clicking a link jumps to its section and marks it immediately, so the control responds even before the scroll settles.</p></section></div>',
|
||||
props: {
|
||||
heading: "On this page",
|
||||
items: [
|
||||
{ label: "Overview", href: "#overview" },
|
||||
@@ -268,7 +355,8 @@ export const componentProfiles = {
|
||||
],
|
||||
active: "#overview",
|
||||
},
|
||||
),
|
||||
slots: {},
|
||||
},
|
||||
advanced(
|
||||
"Compact rail",
|
||||
"On a phone the rail becomes a horizontal strip that scrolls, so a long contents list costs one line rather than a screenful.",
|
||||
@@ -276,11 +364,11 @@ export const componentProfiles = {
|
||||
heading: "Sections",
|
||||
size: "sm",
|
||||
items: [
|
||||
{ label: "Getting started", href: "#getting-started" },
|
||||
{ label: "Configuration", href: "#configuration" },
|
||||
{ label: "Troubleshooting", href: "#troubleshooting" },
|
||||
{ label: "Overview", href: "#overview" },
|
||||
{ label: "Installation", href: "#installation" },
|
||||
{ label: "Usage", href: "#usage" },
|
||||
],
|
||||
active: "#configuration",
|
||||
active: "#installation",
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -470,6 +558,15 @@ export const componentProfiles = {
|
||||
{ label: "Confirm", description: "Review and submit" },
|
||||
],
|
||||
active: 1,
|
||||
clickable: false,
|
||||
showPanel: false,
|
||||
controls: false,
|
||||
allowSkip: false,
|
||||
nextDisabled: false,
|
||||
backLabel: "Back",
|
||||
nextLabel: "Next",
|
||||
skipLabel: "Skip",
|
||||
finishLabel: "Finish",
|
||||
},
|
||||
),
|
||||
standard(
|
||||
@@ -483,6 +580,56 @@ export const componentProfiles = {
|
||||
],
|
||||
active: 2,
|
||||
orientation: "vertical",
|
||||
clickable: false,
|
||||
showPanel: false,
|
||||
controls: false,
|
||||
allowSkip: false,
|
||||
nextDisabled: false,
|
||||
backLabel: "Back",
|
||||
nextLabel: "Next",
|
||||
skipLabel: "Skip",
|
||||
finishLabel: "Finish",
|
||||
},
|
||||
),
|
||||
advanced(
|
||||
"Wizard with content and controls",
|
||||
"showPanel renders each step body and shows only the active one, the same contract Tabs uses. controls adds Back, Skip and Next, which becomes Finish on the last step.",
|
||||
{
|
||||
steps: [
|
||||
{ label: "Account", title: "Your details", content: "Name, email and a password." },
|
||||
{ label: "Billing", title: "How you pay", content: "Card or invoice." },
|
||||
{ label: "Confirm", title: "Review", content: "Check everything before submitting." },
|
||||
],
|
||||
active: 1,
|
||||
clickable: false,
|
||||
showPanel: true,
|
||||
controls: true,
|
||||
allowSkip: true,
|
||||
nextDisabled: false,
|
||||
backLabel: "Back",
|
||||
nextLabel: "Next",
|
||||
skipLabel: "Skip",
|
||||
finishLabel: "Finish",
|
||||
},
|
||||
),
|
||||
advanced(
|
||||
"Held until the step is valid",
|
||||
"The same wizard with nextDisabled set. The component never validates anything itself: the page owns the form, sets this while the step is incomplete, and clears it once the step passes.",
|
||||
{
|
||||
steps: [
|
||||
{ label: "Account", title: "Your details", content: "This step is not complete yet." },
|
||||
{ label: "Billing", title: "How you pay", content: "Card or invoice." },
|
||||
],
|
||||
active: 0,
|
||||
clickable: false,
|
||||
showPanel: true,
|
||||
controls: true,
|
||||
allowSkip: false,
|
||||
nextDisabled: true,
|
||||
backLabel: "Back",
|
||||
nextLabel: "Next",
|
||||
skipLabel: "Skip",
|
||||
finishLabel: "Finish",
|
||||
},
|
||||
),
|
||||
advanced(
|
||||
@@ -492,6 +639,14 @@ export const componentProfiles = {
|
||||
steps: [{ label: "One" }, { label: "Two" }, { label: "Three" }],
|
||||
active: 0,
|
||||
clickable: true,
|
||||
showPanel: false,
|
||||
controls: false,
|
||||
allowSkip: false,
|
||||
nextDisabled: false,
|
||||
backLabel: "Back",
|
||||
nextLabel: "Next",
|
||||
skipLabel: "Skip",
|
||||
finishLabel: "Finish",
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"generatedFrom": "packages/ui/component-reference.json",
|
||||
"componentCount": 108,
|
||||
"categoryCount": 11,
|
||||
"totalDemoCount": 419,
|
||||
"totalDemoCount": 421,
|
||||
"components": [
|
||||
{
|
||||
"name": "Accordion",
|
||||
@@ -850,7 +850,7 @@
|
||||
"propCount": 13,
|
||||
"slots": ["topbar", "actions"],
|
||||
"events": ["toggle", "open", "close", "select", "action"],
|
||||
"profiled": false
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
"name": "PageHeader",
|
||||
@@ -1110,10 +1110,10 @@
|
||||
"slug": "stepper",
|
||||
"category": "navigation",
|
||||
"purpose": "Theme-aware, responsive stepper component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 8,
|
||||
"slots": ["step-{index}", "default"],
|
||||
"events": ["change"],
|
||||
"demoCount": 5,
|
||||
"propCount": 16,
|
||||
"slots": ["step-{index}", "panel-{index}", "default"],
|
||||
"events": ["change", "back", "next", "skip", "finish"],
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2953,6 +2953,15 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
if (!dialog || !dialog.isConnected) return false;
|
||||
if (dialog.hasAttribute("hidden")) return false;
|
||||
if (dialog.closest('[data-show="false"]')) return false;
|
||||
/*
|
||||
* data-open is the signal Modal and Drawer actually publish, and it is the
|
||||
* only one Drawer can publish: its panel animates open, so it cannot be
|
||||
* toggled with data-show, which sets display:none. Treating every dialog
|
||||
* without a data-show="false" ancestor as open meant a closed Drawer held
|
||||
* the body scroll lock forever and the page could not be scrolled.
|
||||
*/
|
||||
var owner = dialog.closest("[data-open]");
|
||||
if (owner) return owner.getAttribute("data-open") === "true";
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3277,93 +3286,12 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
ensureBehaviorObserver();
|
||||
hydrateNavbarControllers(host);
|
||||
hydratePreferenceControllers(host);
|
||||
hydrateSidebarControllers(host);
|
||||
hydrateDropdownControllers(host);
|
||||
hydrateSelectControllers(host);
|
||||
hydratePinInputControllers(host);
|
||||
}
|
||||
|
||||
var navbarOutsideClickBound = false;
|
||||
var preferenceOutsideClickBound = false;
|
||||
var dropdownOutsideClickBound = false;
|
||||
|
||||
function hydrateSidebarControllers(root) {
|
||||
var host = root || document;
|
||||
var sidebars = [];
|
||||
if (host.nodeType === 1 && host.matches && host.matches(".wire-sidebar-shell")) {
|
||||
sidebars.push(host);
|
||||
}
|
||||
if (host.querySelectorAll) {
|
||||
Array.prototype.push.apply(sidebars, host.querySelectorAll(".wire-sidebar-shell"));
|
||||
}
|
||||
sidebars.forEach(function (sidebar) {
|
||||
var current = window.location.pathname.replace(/\/+$/, "") || "/";
|
||||
var best = null;
|
||||
var bestLength = -1;
|
||||
sidebar.querySelectorAll(".wire-sidebar-items a[href]").forEach(function (link) {
|
||||
var url;
|
||||
try { url = new URL(link.getAttribute("href"), window.location.origin); } catch (_) { return; }
|
||||
var path = url.pathname.replace(/\/+$/, "") || "/";
|
||||
var matches = path === current || (path !== "/" && current.indexOf(path + "/") === 0);
|
||||
link.classList.remove("is-active");
|
||||
if (!matches || path.length <= bestLength) return;
|
||||
best = link;
|
||||
bestLength = path.length;
|
||||
});
|
||||
sidebar.querySelectorAll(".wire-sidebar-items a[aria-current='page']").forEach(function (link) {
|
||||
if (link !== best) link.removeAttribute("aria-current");
|
||||
});
|
||||
sidebar.querySelectorAll(".wire-sidebar-group.has-active").forEach(function (group) {
|
||||
group.classList.remove("has-active");
|
||||
});
|
||||
if (best) {
|
||||
best.classList.add("is-active");
|
||||
best.setAttribute("aria-current", "page");
|
||||
var group = best.closest(".wire-sidebar-group");
|
||||
if (group) {
|
||||
group.setAttribute("open", "");
|
||||
group.classList.add("has-active");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function hydrateDropdownControllers(root) {
|
||||
var host = root || document;
|
||||
var dropdowns = [];
|
||||
if (host.nodeType === 1 && host.matches && host.matches("[data-wrn-dropdown]")) {
|
||||
dropdowns.push(host);
|
||||
}
|
||||
if (host.querySelectorAll) {
|
||||
Array.prototype.push.apply(dropdowns, host.querySelectorAll("[data-wrn-dropdown]"));
|
||||
}
|
||||
dropdowns.forEach(function (dropdown) {
|
||||
if (dropdown.dataset.wrnDropdownBound === "true") return;
|
||||
dropdown.dataset.wrnDropdownBound = "true";
|
||||
dropdown.addEventListener("toggle", function () {
|
||||
if (!dropdown.open) return;
|
||||
document.querySelectorAll("[data-wrn-dropdown][open]").forEach(function (candidate) {
|
||||
if (candidate !== dropdown) candidate.removeAttribute("open");
|
||||
});
|
||||
});
|
||||
});
|
||||
if (!dropdownOutsideClickBound) {
|
||||
dropdownOutsideClickBound = true;
|
||||
document.addEventListener("pointerdown", function (event) {
|
||||
document.querySelectorAll("[data-wrn-dropdown][open]").forEach(function (dropdown) {
|
||||
if (!dropdown.contains(event.target)) dropdown.removeAttribute("open");
|
||||
});
|
||||
});
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key !== "Escape") return;
|
||||
document.querySelectorAll("[data-wrn-dropdown][open]").forEach(function (dropdown) {
|
||||
dropdown.removeAttribute("open");
|
||||
var summary = dropdown.querySelector(":scope > summary");
|
||||
if (summary) summary.focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hydratePreferenceControllers(root) {
|
||||
var host = root || document;
|
||||
|
||||
@@ -1067,3 +1067,46 @@ test("json inside a textarea is left alone, not read as mustaches", () => {
|
||||
// evaluated {"id":"a-1","count":3} away and left it empty.
|
||||
expect(doc.querySelector("#editor")!.textContent).toBe('{"id":"a-1","count":3}');
|
||||
});
|
||||
|
||||
test("a closed drawer does not hold the body scroll lock", () => {
|
||||
const win = mount(
|
||||
`<div data-ui-component="Drawer" data-open="false">
|
||||
<div class="layer">
|
||||
<section role="dialog" aria-modal="true" tabindex="-1">
|
||||
<button id="inside">Inside</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>`,
|
||||
);
|
||||
const doc = win.document;
|
||||
/*
|
||||
* A drawer animates open, so its panel cannot be hidden with data-show --
|
||||
* display:none is not transitionable. It publishes data-open instead. Reading
|
||||
* only data-show made every closed drawer look open, which locked the body
|
||||
* and left the page unscrollable.
|
||||
*/
|
||||
expect(doc.body.style.overflow).not.toBe("hidden");
|
||||
|
||||
const outside = doc.querySelector("#inside") as unknown as HTMLElement;
|
||||
outside.focus();
|
||||
outside.dispatchEvent(keydown(win, "Tab"));
|
||||
// No trap either: focus is free to leave a closed dialog.
|
||||
expect(doc.activeElement!.id).toBe("inside");
|
||||
});
|
||||
|
||||
test("an open drawer locks the body scroll and traps Tab", () => {
|
||||
const win = mount(
|
||||
`<div data-ui-component="Drawer" data-open="true">
|
||||
<section role="dialog" aria-modal="true" tabindex="-1">
|
||||
<button id="first">First</button>
|
||||
<button id="last">Last</button>
|
||||
</section>
|
||||
</div>`,
|
||||
);
|
||||
const doc = win.document;
|
||||
expect(doc.body.style.overflow).toBe("hidden");
|
||||
const last = doc.querySelector("#last") as unknown as HTMLElement;
|
||||
last.focus();
|
||||
last.dispatchEvent(keydown(win, "Tab"));
|
||||
expect(doc.activeElement!.id).toBe("first");
|
||||
});
|
||||
|
||||
@@ -916,9 +916,9 @@ Theme-aware, responsive sidebar component.
|
||||
Theme-aware, responsive stepper component.
|
||||
|
||||
- Mount: `data-component="Stepper"`
|
||||
- Props: `color: string = "primary"`, `size: string = "default"`, `steps: unknown[] = []`, `active: number = 0`, `orientation: string = "horizontal"`, `clickable: boolean = false`, `label: string = "Progress"`, `class: string = ""`
|
||||
- Slots: `step-{index}`, `default`
|
||||
- Outputs: `change({ index: number; step: object })`
|
||||
- Props: `color: string = "primary"`, `size: string = "default"`, `steps: unknown[] = []`, `active: number = 0`, `orientation: string = "horizontal"`, `clickable: boolean = false`, `label: string = "Progress"`, `showPanel: boolean = false`, `controls: boolean = false`, `allowSkip: boolean = false`, `nextDisabled: boolean = false`, `backLabel: string = "Back"`, `nextLabel: string = "Next"`, `skipLabel: string = "Skip"`, `finishLabel: string = "Finish"`, `class: string = ""`
|
||||
- Slots: `step-{index}`, `panel-{index}`, `default`
|
||||
- Outputs: `change({ index: number; step: object })`, `back({ index: number; step: object })`, `next({ index: number; step: object })`, `skip({ index: number; step: object })`, `finish({ index: number; step: object })`
|
||||
|
||||
### Tabs
|
||||
|
||||
|
||||
@@ -11783,6 +11783,62 @@
|
||||
"default": "\"Progress\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "showPanel",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "controls",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "allowSkip",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "nextDisabled",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "backLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Back\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "nextLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Next\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "skipLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Skip\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "finishLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Finish\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -11791,14 +11847,30 @@
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"slots": ["step-{index}", "default"],
|
||||
"slots": ["step-{index}", "panel-{index}", "default"],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "change",
|
||||
"payloadType": "{ index: number; step: object }"
|
||||
},
|
||||
{
|
||||
"name": "back",
|
||||
"payloadType": "{ index: number; step: object }"
|
||||
},
|
||||
{
|
||||
"name": "next",
|
||||
"payloadType": "{ index: number; step: object }"
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"payloadType": "{ index: number; step: object }"
|
||||
},
|
||||
{
|
||||
"name": "finish",
|
||||
"payloadType": "{ index: number; step: object }"
|
||||
}
|
||||
],
|
||||
"events": ["change"],
|
||||
"events": ["change", "back", "next", "skip", "finish"],
|
||||
"source": "components/Stepper.wrn"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -214,6 +214,21 @@ component MegaMenu {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/*
|
||||
* The panel is offset below the trigger for looks, which used to close the
|
||||
* menu on the way to it: that offset is dead space belonging to neither
|
||||
* element, so crossing it fired mouseleave on the root. The bridge below
|
||||
* covers the gap with a descendant, so the pointer never actually leaves.
|
||||
*/
|
||||
.wire-mega__panel::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 100%;
|
||||
height: 0.6rem;
|
||||
}
|
||||
|
||||
.wire-mega__panel {
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
component Stepper {
|
||||
outputs {
|
||||
change(payload: { index: number; step: object })
|
||||
back(payload: { index: number; step: object })
|
||||
next(payload: { index: number; step: object })
|
||||
skip(payload: { index: number; step: object })
|
||||
finish(payload: { index: number; step: object })
|
||||
}
|
||||
|
||||
props {
|
||||
@@ -24,6 +28,19 @@ component Stepper {
|
||||
orientation: string = "horizontal"
|
||||
clickable: boolean = false
|
||||
label: string = "Progress"
|
||||
// Render each step body, showing only the active one -- same contract as
|
||||
// Tabs, so a wizard does not have to hand-roll the panel switching.
|
||||
showPanel: boolean = false
|
||||
controls: boolean = false
|
||||
allowSkip: boolean = false
|
||||
// Lets a form hold the step. The component never validates anything
|
||||
// itself: the page owns the form, so it sets this while the step is
|
||||
// incomplete and clears it once the step passes.
|
||||
nextDisabled: boolean = false
|
||||
backLabel: string = "Back"
|
||||
nextLabel: string = "Next"
|
||||
skipLabel: string = "Skip"
|
||||
finishLabel: string = "Finish"
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
@@ -63,6 +80,48 @@ component Stepper {
|
||||
return orientation === "vertical" ? "vertical" : "horizontal"
|
||||
}
|
||||
|
||||
shared function isActiveStep(index) {
|
||||
return index === activeIndex()
|
||||
}
|
||||
|
||||
shared function lastIndex() {
|
||||
return Math.max(0, stepList().length - 1)
|
||||
}
|
||||
|
||||
shared function onLastStep() {
|
||||
return activeIndex() >= lastIndex()
|
||||
}
|
||||
|
||||
shared function activeStep() {
|
||||
var list = stepList()
|
||||
return list.length ? list[activeIndex()] : {}
|
||||
}
|
||||
|
||||
client function goBack() {
|
||||
var target = Math.max(0, activeIndex() - 1)
|
||||
output.back({ index: target, step: stepList()[target] || {} })
|
||||
output.change({ index: target, step: stepList()[target] || {} })
|
||||
}
|
||||
|
||||
client function goNext() {
|
||||
if (nextDisabled) {
|
||||
return
|
||||
}
|
||||
if (onLastStep()) {
|
||||
output.finish({ index: activeIndex(), step: activeStep() })
|
||||
return
|
||||
}
|
||||
var target = Math.min(lastIndex(), activeIndex() + 1)
|
||||
output.next({ index: target, step: stepList()[target] || {} })
|
||||
output.change({ index: target, step: stepList()[target] || {} })
|
||||
}
|
||||
|
||||
client function goSkip() {
|
||||
var target = Math.min(lastIndex(), activeIndex() + 1)
|
||||
output.skip({ index: target, step: stepList()[target] || {} })
|
||||
output.change({ index: target, step: stepList()[target] || {} })
|
||||
}
|
||||
|
||||
client function selectStep(index, step) {
|
||||
if (!clickable) {
|
||||
return
|
||||
@@ -72,7 +131,7 @@ component Stepper {
|
||||
}
|
||||
|
||||
view {
|
||||
<ol
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Stepper"
|
||||
class='wire-stepper {class}'
|
||||
@@ -80,6 +139,9 @@ component Stepper {
|
||||
data-color='{color}'
|
||||
data-size='{size}'
|
||||
data-clickable='{clickable}'
|
||||
>
|
||||
<ol
|
||||
class="wire-stepper__list"
|
||||
data-wrn-roving='{rovingAxis()}'
|
||||
aria-label='{label}'
|
||||
>
|
||||
@@ -111,13 +173,62 @@ component Stepper {
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
<slot />
|
||||
</ol>
|
||||
|
||||
<div class="wire-stepper__panels" data-show="showPanel">
|
||||
{#each stepList() as step, index}
|
||||
<div
|
||||
class="wire-stepper__panel"
|
||||
data-show='isActiveStep(index)'
|
||||
aria-hidden='{index === activeIndex() ? "false" : "true"}'
|
||||
>
|
||||
<h4 class="wire-stepper__panel-title" data-show="step.title">{step.title}</h4>
|
||||
<p class="wire-stepper__panel-body" data-show="step.content">{step.content}</p>
|
||||
<slot name="panel-{index}"></slot>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="wire-stepper__controls" data-show="controls">
|
||||
<button
|
||||
type="button"
|
||||
class="wire-stepper__button-control wire-stepper__back"
|
||||
disabled='{activeIndex() === 0}'
|
||||
@click='goBack()'
|
||||
>
|
||||
{backLabel}
|
||||
</button>
|
||||
<span class="wire-stepper__controls-spacer"></span>
|
||||
<button
|
||||
type="button"
|
||||
class="wire-stepper__button-control wire-stepper__skip"
|
||||
data-show="allowSkip && !onLastStep()"
|
||||
@click='goSkip()'
|
||||
>
|
||||
{skipLabel}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="wire-stepper__button-control wire-stepper__next"
|
||||
data-primary="true"
|
||||
disabled='{nextDisabled}'
|
||||
@click='goNext()'
|
||||
>
|
||||
{onLastStep() ? finishLabel : nextLabel}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-stepper {
|
||||
--stepper-accent: var(--wire-color-primary);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.wire-stepper__list {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
@@ -150,7 +261,7 @@ component Stepper {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.wire-stepper[data-orientation="vertical"] {
|
||||
.wire-stepper[data-orientation="vertical"] .wire-stepper__list {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -241,9 +352,89 @@ component Stepper {
|
||||
color: var(--wire-color-text-muted);
|
||||
}
|
||||
|
||||
.wire-stepper__panels {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.wire-stepper__panel {
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-md);
|
||||
background: var(--wire-color-surface);
|
||||
animation: wire-stepper-in 200ms ease both;
|
||||
}
|
||||
|
||||
.wire-stepper__panel-title {
|
||||
margin: 0 0 0.35rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.wire-stepper__panel-body {
|
||||
margin: 0;
|
||||
color: var(--wire-color-text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.wire-stepper__controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.85rem;
|
||||
}
|
||||
|
||||
.wire-stepper__controls-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.wire-stepper__button-control {
|
||||
appearance: none;
|
||||
padding: 0.45rem 0.9rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-sm);
|
||||
background: var(--wire-color-surface);
|
||||
color: var(--wire-color-text);
|
||||
font: inherit;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wire-stepper__button-control:hover:not(:disabled) {
|
||||
background: var(--wire-color-surface-soft);
|
||||
}
|
||||
|
||||
.wire-stepper__button-control:focus-visible {
|
||||
outline: 2px solid var(--stepper-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.wire-stepper__button-control[data-primary="true"] {
|
||||
border-color: var(--stepper-accent);
|
||||
background: var(--stepper-accent);
|
||||
color: var(--wire-color-primary-contrast);
|
||||
}
|
||||
|
||||
/* A held step has to look held, or the button reads as broken. */
|
||||
.wire-stepper__button-control:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@keyframes wire-stepper-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(0.75rem);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* A horizontal stepper cannot stay side by side on a phone. */
|
||||
@media (max-width: 639px) {
|
||||
.wire-stepper {
|
||||
.wire-stepper__list {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ component Tabs {
|
||||
}
|
||||
|
||||
state activeValue = ""
|
||||
// Which way the panel should slide in. Set on every change so the animation
|
||||
// follows the direction of travel rather than always coming from one side.
|
||||
state slideFrom = "forward"
|
||||
|
||||
functions {
|
||||
shared function itemList() {
|
||||
@@ -80,6 +83,14 @@ component Tabs {
|
||||
return
|
||||
}
|
||||
var value = valueOf(item, index)
|
||||
var list = itemList()
|
||||
var previous = -1
|
||||
for (var scan = 0; scan < list.length; scan += 1) {
|
||||
if (valueOf(list[scan], scan) === currentValue()) {
|
||||
previous = scan
|
||||
}
|
||||
}
|
||||
slideFrom = previous > index ? "back" : "forward"
|
||||
activeValue = value
|
||||
|
||||
if (mode === "url" && window.history && window.history.pushState) {
|
||||
@@ -101,6 +112,7 @@ component Tabs {
|
||||
data-orientation='{orientation}'
|
||||
data-color='{color}'
|
||||
data-size='{size}'
|
||||
data-slide='{slideFrom}'
|
||||
data-mode='{mode}'
|
||||
data-param='{param}'
|
||||
>
|
||||
@@ -272,7 +284,16 @@ component Tabs {
|
||||
|
||||
.wire-tabs__panel {
|
||||
outline: none;
|
||||
animation: wire-tabs-in 200ms ease both;
|
||||
animation: wire-tabs-slide-forward 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.wire-tabs[data-slide="back"] .wire-tabs__panel {
|
||||
animation-name: wire-tabs-slide-back;
|
||||
}
|
||||
|
||||
/* The panels clip their own slide so it never widens the page. */
|
||||
.wire-tabs__panels {
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.wire-tabs__panel:focus-visible {
|
||||
@@ -297,10 +318,21 @@ component Tabs {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
@keyframes wire-tabs-in {
|
||||
@keyframes wire-tabs-slide-forward {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
transform: translateX(1.25rem);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wire-tabs-slide-back {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-1.25rem);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
|
||||
@@ -2522,7 +2522,10 @@ test("stepper marks complete, current and upcoming steps", async () => {
|
||||
expect(items[1]!.getAttribute("data-status")).toBe("current");
|
||||
expect(items[2]!.getAttribute("data-status")).toBe("upcoming");
|
||||
expect(items[1]!.getAttribute("aria-current")).toBe("step");
|
||||
expect(dom.querySelector(".wire-stepper")!.tagName.toLowerCase()).toBe("ol");
|
||||
// One root wraps the list, the panels and the controls; the steps
|
||||
// themselves are still an ordered list, which is what carries the semantics.
|
||||
expect(dom.querySelector(".wire-stepper")!.tagName.toLowerCase()).toBe("div");
|
||||
expect(dom.querySelector(".wire-stepper__list")!.tagName.toLowerCase()).toBe("ol");
|
||||
});
|
||||
|
||||
test("stepper renders vertically and clamps an out-of-range active index", async () => {
|
||||
@@ -2875,3 +2878,69 @@ test("breadcrumb marks the trail end without emitting an empty aria-current", as
|
||||
expect(dom.querySelector(".wire-breadcrumb")!.getAttribute("aria-label")).toBeTruthy();
|
||||
expect(dom.querySelectorAll(".wire-breadcrumb__item").length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
test("mega menu bridges the gap between its trigger and panel", async () => {
|
||||
const source = readFileSync(uiComponentPath("MegaMenu"), "utf8");
|
||||
/*
|
||||
* The panel is offset below the trigger, and that offset is dead space
|
||||
* belonging to neither element: travelling to the panel fired mouseleave on
|
||||
* the root and closed the menu before the pointer arrived. A descendant
|
||||
* covering the gap keeps the pointer inside the component.
|
||||
*/
|
||||
expect(source).toContain(".wire-mega__panel::before");
|
||||
expect(source).toMatch(/\.wire-mega__panel::before \{[^}]*bottom: 100%/s);
|
||||
});
|
||||
|
||||
test("stepper shows only the active step content and offers back, next and skip", async () => {
|
||||
const source = readFileSync(uiComponentPath("Stepper"), "utf8");
|
||||
const html = await renderComponent(source, {
|
||||
steps: [
|
||||
{ label: "Account", content: "ACCOUNT BODY" },
|
||||
{ label: "Billing", content: "BILLING BODY" },
|
||||
{ label: "Confirm", content: "CONFIRM BODY" },
|
||||
],
|
||||
active: 1,
|
||||
showPanel: true,
|
||||
controls: true,
|
||||
allowSkip: true,
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
|
||||
const panels = [...dom.querySelectorAll(".wire-stepper__panel")];
|
||||
expect(panels).toHaveLength(3);
|
||||
/*
|
||||
* Asserted through aria-hidden rather than data-show. data-show is emitted
|
||||
* verbatim for the client runtime to evaluate, so its server-rendered value
|
||||
* is not a reliable statement about which step is showing; aria-hidden is
|
||||
* interpolated at render and says exactly that.
|
||||
*/
|
||||
expect(panels.map((p) => p.getAttribute("aria-hidden"))).toEqual(["true", "false", "true"]);
|
||||
|
||||
expect(dom.querySelector(".wire-stepper__back")).not.toBeNull();
|
||||
expect(dom.querySelector(".wire-stepper__next")).not.toBeNull();
|
||||
expect(dom.querySelector(".wire-stepper__skip")).not.toBeNull();
|
||||
});
|
||||
|
||||
test("stepper back is disabled on the first step and next becomes finish on the last", async () => {
|
||||
const source = readFileSync(uiComponentPath("Stepper"), "utf8");
|
||||
const steps = [{ label: "One" }, { label: "Two" }];
|
||||
|
||||
const first = mountHtml(await renderComponent(source, { steps, active: 0, controls: true }));
|
||||
expect(first.querySelector(".wire-stepper__back")!.getAttribute("disabled")).not.toBeNull();
|
||||
expect(first.querySelector(".wire-stepper__next")!.textContent).toContain("Next");
|
||||
|
||||
const last = mountHtml(await renderComponent(source, { steps, active: 1, controls: true }));
|
||||
expect(last.querySelector(".wire-stepper__next")!.textContent).toContain("Finish");
|
||||
});
|
||||
|
||||
test("stepper next can be gated so a form can hold it until the step validates", async () => {
|
||||
const source = readFileSync(uiComponentPath("Stepper"), "utf8");
|
||||
const html = await renderComponent(source, {
|
||||
steps: [{ label: "One" }, { label: "Two" }],
|
||||
active: 0,
|
||||
controls: true,
|
||||
nextDisabled: true,
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
expect(dom.querySelector(".wire-stepper__next")!.getAttribute("disabled")).not.toBeNull();
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user