Files
WRNexusJS/examples/basic-app/app/pages/navigation.wrn
Clintchiz 2c960fc1dc
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s
refactor: migrate legacy wire namespace to wrn
2026-08-12 18:51:15 +05:30

439 lines
13 KiB
Plaintext

// Navigation tour. Route: /navigation
//
// One page that connects 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(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
background: var(--wrn-color-surface-soft);
color: var(--wrn-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(--wrn-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(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
font-size: 0.88rem;
}
.tour__footer {
padding-top: 1rem;
border-top: 1px solid var(--wrn-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;
}
}
}
}