Learn: per-lesson URLs, a :::data shared-dataset construct, and a linked-views deep dive

This commit is contained in:
2026-06-28 12:34:30 +03:00
parent fd5c51a761
commit 20e70bee0e
16 changed files with 1818 additions and 178 deletions
+60
View File
@@ -0,0 +1,60 @@
import { useMemo, useState, type ReactNode } from 'react';
import { chartConfigForSelection } from '@core/vega-themes';
import type { UiTheme } from '@core/theme';
import styles from './Learn.module.css';
type ChartConfig = ReturnType<typeof chartConfigForSelection>;
/**
* Shared chrome for the learning section (header, footer, light/dark theme) wrapping
* either the lesson index or one lesson. The theme state lives here because both the
* header toggle and the chart config depend on it; it's handed to the page via a
* render prop so a lesson's charts re-theme with the toggle.
*
* Theme handling mirrors the landing (light/dark via `data-theme` on the root),
* duplicated rather than shared because the two entries are independent pages.
*/
export function LearnLayout({
children,
}: {
children: (config: ChartConfig) => ReactNode;
}): ReactNode {
const [theme, setTheme] = useState<UiTheme>('light');
const config = useMemo(() => chartConfigForSelection('astrolabe', theme), [theme]);
// TODO: this light/dark toggle is duplicated in landing/Landing.tsx — extract a
// shared `useUiTheme()` hook into a home both marketing entries can import.
function toggleTheme(): void {
const next: UiTheme = theme === 'dark' ? 'light' : 'dark';
setTheme(next);
document.documentElement.dataset.theme = next;
}
return (
<div className={styles.page}>
<header className={styles.header}>
<a className={styles.brand} href="/">
Astrolabe
</a>
<nav className={styles.nav}>
<a className={styles.kicker} href="/learn/">
Vega-Lite, deeper
</a>
<button className={styles.ghost} type="button" onClick={toggleTheme}>
{theme === 'dark' ? 'Light' : 'Dark'}
</button>
<a className={styles.appLink} href="/app/">
Open the app
</a>
</nav>
</header>
<main className={styles.main}>{children(config)}</main>
<footer className={styles.footer}>
<span>An interactive deep dive into the parts of Vega-Lite the docs gloss over.</span>
<a href="/"> Back to Astrolabe</a>
</footer>
</div>
);
}