P1 fondation : tokens layerés, reset, thèmes (base/renovation/aep/tmip), 9 primitives Astro, pont Tailwind 4, démo kitchen sink
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
94
components/BaseLayout.astro
Normal file
94
components/BaseLayout.astro
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
/**
|
||||
* BaseLayout — coquille HTML commune à tous les sites.
|
||||
*
|
||||
* Props :
|
||||
* - title (string, requis) : <title> + og:title
|
||||
* - description (string) : meta description + og:description
|
||||
* - lang (string, 'fr') : attribut lang du <html>
|
||||
* - ogImage (string) : URL absolue de l'image OG
|
||||
* - canonical (string) : URL canonique
|
||||
*
|
||||
* Slots : default (contenu du <main>), header, footer, head (metas en plus).
|
||||
*
|
||||
* Le THÈME n'est pas importé ici : le site importe son theme/*.css dans son
|
||||
* propre layout, à côté de <BaseLayout> (cf. README).
|
||||
* Gestion clair/sombre : data-theme posé avant le premier paint depuis
|
||||
* localStorage('tf-theme') ; helper global window.tfSetTheme('dark'|'light'|'auto').
|
||||
*/
|
||||
import '../tokens.css';
|
||||
import '../reset.css';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
lang?: string;
|
||||
ogImage?: string;
|
||||
canonical?: string;
|
||||
}
|
||||
|
||||
const { title, description, lang = 'fr', ogImage, canonical } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang={lang}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{title}</title>
|
||||
{description && <meta name="description" content={description} />}
|
||||
{canonical && <link rel="canonical" href={canonical} />}
|
||||
<meta property="og:title" content={title} />
|
||||
{description && <meta property="og:description" content={description} />}
|
||||
<meta property="og:type" content="website" />
|
||||
{ogImage && <meta property="og:image" content={ogImage} />}
|
||||
<script is:inline>
|
||||
(() => {
|
||||
try {
|
||||
const t = localStorage.getItem("tf-theme");
|
||||
if (t === "dark" || t === "light") document.documentElement.dataset.theme = t;
|
||||
} catch {}
|
||||
document.documentElement.dataset.js = "";
|
||||
})();
|
||||
</script>
|
||||
<slot name="head" />
|
||||
</head>
|
||||
<body>
|
||||
<a class="tf-skip-link" href="#main">Aller au contenu</a>
|
||||
<slot name="header" />
|
||||
<main id="main"><slot /></main>
|
||||
<slot name="footer" />
|
||||
<script is:inline>
|
||||
window.tfSetTheme = (mode) => {
|
||||
const root = document.documentElement;
|
||||
try {
|
||||
if (mode === "auto") {
|
||||
delete root.dataset.theme;
|
||||
localStorage.removeItem("tf-theme");
|
||||
} else {
|
||||
root.dataset.theme = mode;
|
||||
localStorage.setItem("tf-theme", mode);
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
.tf-skip-link {
|
||||
position: absolute;
|
||||
inset-inline-start: var(--space-md);
|
||||
inset-block-start: var(--space-md);
|
||||
z-index: var(--z-toast);
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
background: var(--primary);
|
||||
color: var(--on-primary);
|
||||
border-radius: var(--radius-md);
|
||||
text-decoration: none;
|
||||
transform: translateY(-200%);
|
||||
}
|
||||
|
||||
.tf-skip-link:focus-visible {
|
||||
transform: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user