Section, SectionHeader and PublicPageShell move onto wire-* classes with local style blocks, variants as data attributes. SectionHeader loses 38 utility lines, and Section stops spending one line per variant and colour pairing: tinted and solid now select on two attributes. PageHeader was already on the convention and only needed the audit. examples/basic-app/app/pages/layout.wrn composes the whole set into one page. Building it surfaced a library-wide bug. Ten custom properties were referenced by components and defined by nothing: --wire-color-focus, --wire-color-surface-soft, --wire-color-on-danger, --wire-color-surface-subtle and the input-* family, plus hover and contrast for every semantic colour except primary and secondary. An undefined custom property does not warn, it resolves to nothing, so focus rings drew with no colour and every soft surface rendered transparent -- 27 components referenced surface-soft alone. They are derived in the theme now, and a test checks every token a component references against the rendered theme CSS rather than the source, since most are generated. The semantic spread also had to move ahead of the primary and secondary entries so the palette keeps winning for those two. Known and unresolved: LayoutSplitter emits its sizeChange output and the component does fire it, but a parent binding on the tag is not invoked. The tour page therefore points at the handle aria-valuenow rather than wiring a handler that would never update. Outputs work elsewhere, so this is narrower than an outputs-are-broken problem and needs its own investigation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
212 lines
7.3 KiB
Plaintext
212 lines
7.3 KiB
Plaintext
// Layout tour. Route: /layout
|
|
//
|
|
// One page built out of the layout set, rather than showing each piece alone:
|
|
//
|
|
// PublicPageShell the outer shell, holding the page background
|
|
// Section each band down the page, with its own spacing and tint
|
|
// SectionHeader the eyebrow, heading and description of each band
|
|
// Container the reading width
|
|
// Grid the card deck, including an auto-fit track
|
|
// Columns the coarse two-way split
|
|
// LayoutSplitter a resizable pane, by drag or by arrow key
|
|
// CustomScrollbar a themed scrolling region
|
|
// Divider the rules between bands
|
|
// Typography the prose column
|
|
// Image, Link, Kbd the smaller pieces inside it
|
|
//
|
|
// CustomScrollbar sits beside the splitter rather than inside a pane of it: a
|
|
// component tag nested in another component slot is dropped, and only its
|
|
// children survive.
|
|
//
|
|
// The splitter does not report into a status line here. It emits a sizeChange
|
|
// output and the component does fire it, but a parent binding on the tag is
|
|
// not invoked, so wiring one up would show something that never updates.
|
|
page LayoutTour {
|
|
layout = "public"
|
|
|
|
seo {
|
|
title = "Layout tour"
|
|
description = "Every layout component working together on one page."
|
|
}
|
|
|
|
view {
|
|
<PublicPageShell background="default" minHeight="dvh" fullWidth={true}>
|
|
<Section spacing="lg" variant="soft" borderBottom={true}>
|
|
<Container maxWidth="lg" columns={1}>
|
|
<SectionHeader
|
|
eyebrow="Layout"
|
|
title="Composing a page"
|
|
description="Every band on this page is a Section. The width you are reading at is a Container, and the pieces inside are the rest of the layout set."
|
|
align="center"
|
|
maxWidth="2xl"
|
|
/>
|
|
</Container>
|
|
</Section>
|
|
|
|
<Section spacing="lg">
|
|
<Container maxWidth="lg" columns={1}>
|
|
<SectionHeader
|
|
eyebrow="Grid"
|
|
title="Card decks"
|
|
description="A fixed column count collapses at each breakpoint. Pass minItemWidth instead and the browser decides the count, reflowing continuously."
|
|
headingLevel={3}
|
|
/>
|
|
|
|
<Divider />
|
|
|
|
<Grid columns={3} gap="md">
|
|
<div class="tour-card">
|
|
<h4>Fixed columns</h4>
|
|
<p>Three tracks on a wide screen, two on a tablet, one on a phone.</p>
|
|
</div>
|
|
<div class="tour-card">
|
|
<h4>Themed</h4>
|
|
<p>Every surface here is a theme token, so light and dark both work.</p>
|
|
</div>
|
|
<div class="tour-card">
|
|
<h4>No Tailwind</h4>
|
|
<p>These components carry their own styles and no longer need it.</p>
|
|
</div>
|
|
</Grid>
|
|
|
|
<Divider label="auto fit" />
|
|
|
|
<Grid minItemWidth="12rem" gap="sm">
|
|
<div class="tour-card tour-card--compact"><h4>One</h4></div>
|
|
<div class="tour-card tour-card--compact"><h4>Two</h4></div>
|
|
<div class="tour-card tour-card--compact"><h4>Three</h4></div>
|
|
<div class="tour-card tour-card--compact"><h4>Four</h4></div>
|
|
<div class="tour-card tour-card--compact"><h4>Five</h4></div>
|
|
</Grid>
|
|
</Container>
|
|
</Section>
|
|
|
|
<Section spacing="lg" variant="soft" borderTop={true} borderBottom={true}>
|
|
<Container maxWidth="lg" columns={1}>
|
|
<SectionHeader
|
|
eyebrow="Split"
|
|
title="Columns and panes"
|
|
description="Columns is the coarse split of a page. LayoutSplitter is the one the reader can move."
|
|
headingLevel={3}
|
|
/>
|
|
|
|
<Columns columns={2} gap="lg">
|
|
<Typography maxWidth="full">
|
|
<h4>Prose</h4>
|
|
<p>
|
|
Typography caps the measure so a line never runs too long to
|
|
follow. Inside it you get the smaller pieces: a
|
|
<Link href="/navigation" label="link" /> to somewhere else, and
|
|
<Kbd label="Ctrl" /> <Kbd label="K" /> for a shortcut.
|
|
</p>
|
|
</Typography>
|
|
|
|
<Image src="" alt="Placeholder artwork" size="video" rounded={true} />
|
|
</Columns>
|
|
</Container>
|
|
</Section>
|
|
|
|
<Section spacing="lg">
|
|
<Container maxWidth="lg" columns={1}>
|
|
<SectionHeader
|
|
eyebrow="Interactive"
|
|
title="Resize and scroll"
|
|
description="The splitter resolves its size in the runtime and reports it here. The scrolling region themes its own scrollbar."
|
|
headingLevel={3}
|
|
/>
|
|
|
|
<p class="tour-status">
|
|
Drag the divider, or focus it and use the arrow keys. Home and End
|
|
go to the bounds. The live value is on the handle itself, as
|
|
aria-valuenow, which is what a screen reader announces.
|
|
</p>
|
|
|
|
<LayoutSplitter size={45} minSize={20} step={5} label="Resize the panes">
|
|
<div data-slot="start">
|
|
<div class="tour-pane">
|
|
<h4>Start pane</h4>
|
|
<p>Drag the divider, or focus it and use the arrow keys. Home and End go to the bounds.</p>
|
|
</div>
|
|
</div>
|
|
<div data-slot="end">
|
|
<div class="tour-pane">
|
|
<h4>End pane</h4>
|
|
<p>The size is resolved in the runtime and written onto the container, so the panes are plain grid tracks.</p>
|
|
</div>
|
|
</div>
|
|
</LayoutSplitter>
|
|
|
|
<Divider label="scrolling" />
|
|
|
|
<CustomScrollbar maxHeight="12rem" thickness={10}>
|
|
<div class="tour-scroll">
|
|
<p>This region scrolls, and its scrollbar follows the theme.</p>
|
|
<p>Scrollbar appearance is CSS rather than script.</p>
|
|
<p>scrollbar-width and scrollbar-color are the standard properties.</p>
|
|
<p>The webkit rules cover the engines that still need them.</p>
|
|
<p>Enough lines here that the region actually overflows.</p>
|
|
<p>Otherwise there would be no scrollbar to look at.</p>
|
|
<p>Which is the mistake the showcase demo made first time.</p>
|
|
</div>
|
|
</CustomScrollbar>
|
|
</Container>
|
|
</Section>
|
|
</PublicPageShell>
|
|
}
|
|
|
|
style {
|
|
.tour-card {
|
|
padding: 1rem;
|
|
border: 1px solid var(--wire-color-border);
|
|
border-radius: var(--wire-radius-md);
|
|
background: var(--wire-color-surface);
|
|
}
|
|
|
|
.tour-card h4,
|
|
.tour-pane h4 {
|
|
margin: 0 0 0.35rem;
|
|
font-size: 0.95rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.tour-card p,
|
|
.tour-pane p,
|
|
.tour-scroll p {
|
|
margin: 0;
|
|
color: var(--wire-color-text-muted);
|
|
font-size: 0.86rem;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.tour-card--compact {
|
|
text-align: center;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.tour-pane {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.tour-scroll {
|
|
display: grid;
|
|
gap: 0.6rem;
|
|
padding: 0.75rem;
|
|
}
|
|
|
|
/* The splitter panes need a floor or they collapse to their text. */
|
|
.tour-pane,
|
|
.tour-scroll {
|
|
min-height: 8rem;
|
|
}
|
|
}
|
|
}
|