Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions apps/website/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,40 @@
margin-inline-start: calc(var(--sidebar-width) * -1);
}
}

/* Same story for `?q=` and `?library=`, one step earlier: a static export
ships the list rendered whole, so all ~160 cards paint while the JS that
will narrow them down is still loading, and the filter lands as a visible
jump. `bootNav` marks a filtered arrival, and these hold the list back
behind a handful of skeletons until React has applied it -- only for those
arrivals; everyone else gets the prerendered list painted as it always was
and never renders the skeletons at all.

`visibility`, not `display`: the cards have to keep their boxes, both
because the list windowing measures them on that very commit -- collapsed
to nothing, every card reads as on-screen and all ~160 thumbnails mount at
once -- and because it is what lets the skeletons sit over the list rather
than push it down. */
#example-skeletons,
#nav-filters-skeleton {
display: none;
}

html[data-nav-filtering] #example-skeletons {
display: flex;
}

html[data-nav-filtering] #nav-filters-skeleton {
display: block;
}

html[data-nav-filtering] #example-list {
visibility: hidden;
}

/* The filter row is the same story in miniature: whichever of its two forms
the HTML was built with is the wrong one here -- the dropdown where the
search field belongs, or the dropdown with nothing selected. */
html[data-nav-filtering] #nav-filters {
display: none;
}
61 changes: 47 additions & 14 deletions apps/website/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,50 @@ import { builder } from "material-theme-builder";
const inter = Inter({ subsets: ["latin"] });
const examples = getExamples();

/**
* Two things only the client knows, both needed before the first paint, so the
* blocking script below runs this and leaves the verdict on <html>: whether the
* rail starts collapsed, and whether this is a filtered arrival — `?q=`
* /`?library=`, whose list must not paint whole while the JS that will narrow
* it is still loading. `globals.css` acts on both marks and `Nav` takes them
* over, then drops them. A filter beats a stored collapse: a shared link has to
* be able to show what it filtered down to.
*
* A function rather than a template string, so it is typed, formatted and
* linted like everything else — `String(bootNav)` is what ends up in the page.
* The catch that comes with that: it has to stay hermetic. No imports, no
* module-level constants, nothing but its arguments, or the bundler leaves a
* dangling reference in the string. Hence the storage key coming in as one —
* `Nav` owns the other half of that contract.
*/
function bootNav(storageKey: string) {
const params = new URLSearchParams(window.location.search);
const filtering = !!(params.get("q") || params.get("library"));

const isCollapsed = () => {
const parts = window.location.pathname.split("/").filter(Boolean);
const examplesIndex = parts.indexOf("examples");
const onAnExample = examplesIndex !== -1 && !!parts[examplesIndex + 1];

/* Two cases where the rail stays out whatever this visitor last left it
at: the index, where there is nothing to look at beside it, and a
shared filter link, which has to show what it filtered down to. */
if (!onAnExample || filtering) return false;

/* Then `?nav=`, a link saying how it wants to arrive… */
const nav = params.get("nav");
if (nav === "closed") return true;
if (nav === "open") return false;

/* …and failing that, wherever this visitor left it. */
return localStorage.getItem(storageKey) === "1";
};

const root = document.documentElement;
root.toggleAttribute("data-nav-collapsed", isCollapsed());
root.toggleAttribute("data-nav-filtering", filtering);
}

/**
* The one hex the whole palette hangs off -- poimandres' signature mint.
* Material Color Utilities derives every `--md-sys-color-*` role from it, and
Expand Down Expand Up @@ -81,21 +125,10 @@ export default function RootLayout({
precedence="high"
dangerouslySetInnerHTML={{ __html: mcuCss }}
/>
{/* Blocking on purpose: `bootNav` settles what the rail looks like
before anything paints. */}
<script
dangerouslySetInnerHTML={{
__html: `
(() => {
const storageKey = "nav-collapsed";
const parts = window.location.pathname.split("/").filter(Boolean);
const examplesIndex = parts.indexOf("examples");
const hasExampleSelected = examplesIndex !== -1 && !!parts[examplesIndex + 1];
const nav = new URLSearchParams(window.location.search).get("nav");
const collapsed =
!hasExampleSelected ? false : nav === "closed" ? true : nav === "open" ? false : localStorage.getItem(storageKey) === "1";
document.documentElement.toggleAttribute("data-nav-collapsed", collapsed);
})();
`,
}}
dangerouslySetInnerHTML={{ __html: `(${bootNav})("nav-collapsed")` }}
/>
<ThemeProvider
attribute="class"
Expand Down
Loading
Loading