fix(showcase): render object props, bridge the mega menu gap, make scrollspy testable
Object props never worked in generated demos, and my two earlier attempts each
traded one failure for another:
- a bare {...} attribute is read by the compiler as an interpolation, so it
parsed JSON as JavaScript and the page 500ed
- parenthesising it compiled, but prop coercion runs JSON.parse on the raw
attribute, so ({...}) threw and every demo rendered empty and silent
- entity-escaping the braces did not help either: the compiler hands the
attribute over without decoding, so JSON.parse still failed
They are now hoisted into page state and bound, which is what the playground
has always done. The state initialiser uses JSON.parse rather than an object
literal because the parser reads a leading brace as the start of a block.
Navbar gains a real profile: a brand, links, a two-column dropdown panel and
calls to action, instead of the generic scaffold samples that made every demo
look identical and showed no dropdown at all.
MegaMenu closed while the pointer travelled to it. The panel sits below the
trigger and that offset belongs to neither element, so crossing it fired
mouseleave on the root. A descendant now covers the gap.
Scrollspy could not be exercised at all: its links pointed at ids that did not
exist on the page. The demo now ships real sections, in page flow because the
runtime observes against the viewport.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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",
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user