diff --git a/projects/demo/public/favicon.svg b/projects/demo/public/favicon.svg new file mode 100644 index 0000000..b78f6c2 --- /dev/null +++ b/projects/demo/public/favicon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/projects/demo/src/app/app.css b/projects/demo/src/app/app.css index f4ecc21..273a9c2 100644 --- a/projects/demo/src/app/app.css +++ b/projects/demo/src/app/app.css @@ -1,79 +1,191 @@ +:host { + display: block; + min-height: 100dvh; +} + +/* + * One measure for the header, the content and the footer, so the brand, the + * first card and the footer text all start on the same vertical line. + */ +.shell { + width: 100%; + max-width: 1560px; + margin: 0 auto; + padding-inline: var(--s5); +} + +/* ---- Masthead ------------------------------------------------------------- */ + +/* + * Sticky, and deliberately short. The examples are long, and on a page whose + * whole subject is scrolling through a grid, losing the nav to the top of the + * document is a small but constant annoyance. + */ .masthead { + position: sticky; + top: 0; + z-index: 20; + border-bottom: 1px solid var(--line); + background: color-mix(in srgb, var(--bg) 88%, transparent); + backdrop-filter: blur(10px); +} + +.masthead .shell { display: flex; flex-wrap: wrap; - gap: 20px; + gap: var(--s3) var(--s5); align-items: center; - justify-content: space-between; - margin-bottom: 26px; - border-bottom: 1px solid var(--line); - padding: 10px 10px; + min-height: 58px; + padding-block: var(--s2); } -.masthead h1 { - margin: 0 0 6px; - font-size: 1.55rem; +.brand { + display: inline-flex; + gap: var(--s2); + align-items: center; + color: var(--accent); + text-decoration: none; } -.masthead p { - margin: 0; - color: var(--muted); +.mark { + width: 24px; + height: 24px; + flex: none; + border-radius: 6px; +} + +.wordmark { + color: var(--text); + font-size: 0.98rem; + font-weight: 650; + letter-spacing: -0.02em; + white-space: nowrap; } nav { display: flex; flex-wrap: wrap; - gap: 6px; + gap: 2px; + margin-inline: auto; } nav a { - padding: 6px 12px; - border: 1px solid transparent; - border-radius: 8px; + padding: 5px var(--s3); + border-radius: var(--r1); color: var(--muted); - font-size: 0.85rem; + font-size: 0.86rem; text-decoration: none; + white-space: nowrap; + transition: + background 0.12s ease, + color 0.12s ease; } nav a:hover { + background: var(--sunken); color: var(--text); - background: var(--btn); } nav a.active { - border-color: var(--line); - background: var(--panel); + background: var(--accent-soft); color: var(--accent); - font-weight: 600; + font-weight: 620; } -main { - padding: 20px 10px; -} - -.masthead .links { +.links { display: flex; - flex-wrap: wrap; - gap: 14px; + gap: var(--s4); align-items: center; - margin-top: 10px; - font-size: 0.85rem; + font-size: 0.84rem; } -.masthead .links a { - color: var(--accent); +.links a { + color: var(--muted); text-decoration: none; border-bottom: 1px solid transparent; } -.masthead .links a:hover { - border-bottom-color: currentcolor; +.links a:hover { + color: var(--text); + border-bottom-color: var(--line-strong); } -.masthead .links code { - padding: 2px 8px; +.links code { + padding: 3px var(--s2); border: 1px solid var(--line); - border-radius: 6px; + border-radius: 7px; background: var(--panel); color: var(--muted); - font-size: 0.8rem; + font-size: 0.76rem; + white-space: nowrap; +} + +/* ---- Content and footer --------------------------------------------------- */ + +main { + padding-block: var(--s6) var(--s7); +} + +footer { + padding-block: var(--s6) var(--s7); + margin-top: var(--s7); + border-top: 1px solid var(--line); + color: var(--muted); + font-size: 0.84rem; +} + +footer p { + margin: 0 0 var(--s2); + max-width: 70ch; +} + +footer a { + color: var(--muted); +} + +footer a:hover { + color: var(--accent); +} + +/* ---- Narrow --------------------------------------------------------------- */ + +@media (max-width: 900px) { + /* The install command is the first thing worth dropping: it is a convenience, + and at this width the nav needs the room more. */ + .links code { + display: none; + } +} + +@media (max-width: 680px) { + .shell { + padding-inline: var(--s4); + } + + /* Stop pinning the header once it needs two rows — half a phone screen of + chrome is worse than scrolling back up for the nav. */ + .masthead { + position: static; + } + + .masthead .shell { + align-items: flex-start; + } + + nav { + order: 3; + width: 100%; + margin-inline: 0; + overflow-x: auto; + flex-wrap: nowrap; + scrollbar-width: none; + } + + nav::-webkit-scrollbar { + display: none; + } + + .links { + margin-left: auto; + } } diff --git a/projects/demo/src/app/app.html b/projects/demo/src/app/app.html index cf0f48a..7f54861 100644 --- a/projects/demo/src/app/app.html +++ b/projects/demo/src/app/app.html @@ -1,35 +1,60 @@
-
-

masonry-angular

-

Zero-dependency masonry for Angular — signal-based, zoneless and SSR-safe.

+
+ + + masonry-angular + - - + + +
- -
-
+
+ + diff --git a/projects/demo/src/app/example-frame.ts b/projects/demo/src/app/example-frame.ts index 662b83a..e46629d 100644 --- a/projects/demo/src/app/example-frame.ts +++ b/projects/demo/src/app/example-frame.ts @@ -1,59 +1,82 @@ import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core'; -type Panel = 'none' | 'config' | 'code'; +type Panel = 'none' | 'notes' | 'config' | 'code'; /** - * The shell every example sits in: a description, a toggle for the controls, a - * toggle for the source, and a copy button. + * The shell every example sits in. + * + * Two decisions worth knowing about. + * + * Every panel is closed by default. This is a demo for a layout library, so the + * layout is what a visitor came to see — the explanation used to sit above it as + * two paragraphs of prose, which pushed the grid most of a screen down. The + * title and a one-line summary stay visible; everything longer is a click away. * * The preview is never hidden. Tabs that swap the grid out would take it to * `display: none`, where it measures zero and has to lay out again on the way * back — a flicker caused entirely by the demo, in the one place a visitor is - * judging whether the layout is steady. So the panels open *above* a preview - * that stays mounted, which also lets you read the code and watch the result at - * the same time. + * judging whether the layout is steady. So panels open above a preview that + * stays mounted, which also lets you read the code and watch the result at once. */ @Component({ selector: 'demo-example', changeDetection: ChangeDetectionStrategy.OnPush, template: ` -
- -
- -
- - - - @if (panel() === 'code') { - - } -
+ + + + - -
- -
+ @if (panel() === 'notes') { +
+ +
+ } + + + @if (panel() === 'config') { +
+ +
+ } @if (panel() === 'code') { -
{{ code() }}
+
+ +
{{ code() }}
+
} @@ -63,57 +86,122 @@ type Panel = 'none' | 'config' | 'code'; display: block; } - .toolbar { + .example-head { display: flex; - gap: 6px; - align-items: center; - margin: 0 0 14px; + flex-wrap: wrap; + gap: var(--s4); + align-items: flex-start; + justify-content: space-between; + margin-bottom: var(--s5); + } + + .titles h2 { + margin: 0; + font-size: 1.15rem; + font-weight: 620; + letter-spacing: -0.02em; } - .toolbar button { - padding: 5px 12px; + .titles p { + margin: 4px 0 0; + max-width: 62ch; + color: var(--muted); + font-size: 0.9rem; + } + + /* A segmented control: one object, three states, rather than three buttons. */ + .switch { + display: flex; + flex-shrink: 0; + padding: 3px; border: 1px solid var(--line); - border-radius: 8px; - background: var(--panel); + border-radius: 10px; + background: var(--sunken); + } + + .switch button { + padding: 5px 13px; + border: 0; + border-radius: 7px; + background: transparent; color: var(--muted); font: inherit; font-size: 0.82rem; + line-height: 1.4; cursor: pointer; + transition: + background 0.12s ease, + color 0.12s ease; } - .toolbar button:hover { + .switch button:hover { color: var(--text); } - .toolbar button.on { - border-color: var(--accent); - color: var(--accent); + .switch button.on { + background: var(--panel); + box-shadow: 0 1px 2px rgb(0 0 0 / 0.08); + color: var(--text); font-weight: 600; } - .toolbar .copy { - margin-left: auto; + .panel { + margin-bottom: var(--s5); + } + + .notes { + padding: var(--s4) var(--s5); + border: 1px solid var(--line); + border-left: 2px solid var(--accent); + border-radius: var(--r2); + background: var(--panel); + } + + .code-panel { + position: relative; + } + + .copy { + position: absolute; + top: var(--s3); + right: var(--s3); + z-index: 1; + padding: 4px 11px; + font-size: 0.76rem; } .code { - margin: 0 0 18px; - padding: 14px 16px; - max-height: 420px; + margin: 0; + padding: var(--s4) var(--s5); + max-height: 440px; overflow: auto; border: 1px solid var(--line); - border-radius: 10px; + border-radius: var(--r2); background: var(--panel); + font-family: var(--mono); font-size: 0.8rem; - line-height: 1.55; + line-height: 1.6; tab-size: 2; } - .controls { - margin-bottom: 18px; + @media (max-width: 560px) { + .switch { + width: 100%; + } + + .switch button { + flex: 1; + } } `, }) export class DemoExample { + /** The example's name, shown as the heading. */ + readonly heading = input.required(); + + /** One line, always visible. Everything longer belongs in the notes panel. */ + readonly summary = input.required(); + /** The snippet shown under "Code", and what the copy button puts on the clipboard. */ readonly code = input.required(); @@ -128,9 +216,9 @@ export class DemoExample { try { await navigator.clipboard.writeText(this.code()); } catch { - // Clipboard access is refused in some embedded contexts (an iframe - // without permission, or plain http). Fall back to a selection the - // visitor can copy with the keyboard rather than failing silently. + // Clipboard access is refused in some embedded contexts (an iframe without + // permission, or plain http). Fall back to a selection the visitor can + // copy with the keyboard rather than failing silently. const area = document.createElement('textarea'); area.value = this.code(); area.style.position = 'fixed'; diff --git a/projects/demo/src/app/examples/dashboard.ts b/projects/demo/src/app/examples/dashboard.ts index 9036efe..4ce2a89 100644 --- a/projects/demo/src/app/examples/dashboard.ts +++ b/projects/demo/src/app/examples/dashboard.ts @@ -23,7 +23,11 @@ import { DashboardWidget } from './dashboard-widget'; imports: [NG_MASONRY_GRID, DashboardWidget, DemoExample], changeDetection: ChangeDetectionStrategy.OnPush, template: ` - +

Twenty tiles, each a static size: height in pixels, width in whole columns via masonryColSpan. No body is rendered up front — every one sits behind diff --git a/projects/demo/src/app/examples/dynamic.ts b/projects/demo/src/app/examples/dynamic.ts index 069731b..31b9be2 100644 --- a/projects/demo/src/app/examples/dynamic.ts +++ b/projects/demo/src/app/examples/dynamic.ts @@ -19,7 +19,11 @@ const OPTIONS: MasonryGridOptions = { imports: [NG_MASONRY_GRID, DemoExample], changeDetection: ChangeDetectionStrategy.OnPush, template: ` - +

Every button below mutates the source array and nothing else. Item order is derived from the DOM at layout time, so a prepend really lands first — the ordering bug that makes other diff --git a/projects/demo/src/app/examples/gallery.ts b/projects/demo/src/app/examples/gallery.ts index f2a1c24..52ee4c0 100644 --- a/projects/demo/src/app/examples/gallery.ts +++ b/projects/demo/src/app/examples/gallery.ts @@ -16,7 +16,11 @@ type SizingMode = 'breakpoints' | 'fixed' | 'columnWidth'; imports: [NG_MASONRY_GRID, DecimalPipe, DemoExample], changeDetection: ChangeDetectionStrategy.OnPush, template: ` - +

Column count follows the container width through a breakpoint map — { xs: 1, sm: 2, md: 3, xl: 4, '2xl': 5 }, named against the default diff --git a/projects/demo/src/app/examples/performance.ts b/projects/demo/src/app/examples/performance.ts index 4765011..e384d5f 100644 --- a/projects/demo/src/app/examples/performance.ts +++ b/projects/demo/src/app/examples/performance.ts @@ -26,7 +26,11 @@ function makeTiles(count: number): Tile[] { imports: [NG_MASONRY_GRID, DecimalPipe, DemoExample], changeDetection: ChangeDetectionStrategy.OnPush, template: ` - +

Solve time is measured across the whole pass — reading measurements, running the solver and writing every transform. Sizes arrive pre-computed from one shared diff --git a/projects/demo/src/app/examples/spans.ts b/projects/demo/src/app/examples/spans.ts index c4d5386..7ae49ff 100644 --- a/projects/demo/src/app/examples/spans.ts +++ b/projects/demo/src/app/examples/spans.ts @@ -13,7 +13,11 @@ import { makeCards, type DemoCard } from './cards'; imports: [NG_MASONRY_GRID, DemoExample], changeDetection: ChangeDetectionStrategy.OnPush, template: ` - +

masonryColSpan widens an item across whole columns; the solver drops it into the group of columns with the lowest shared top edge. A diff --git a/projects/demo/src/index.html b/projects/demo/src/index.html index 92182bf..cd58c16 100644 --- a/projects/demo/src/index.html +++ b/projects/demo/src/index.html @@ -5,7 +5,8 @@ masonry-angular — cascading grid layout for Angular - + +