Files
astrolabe/src/learn/LearnLayout.tsx
T

61 lines
2.2 KiB
TypeScript

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>
);
}