@clauzier/astro-kit (0.14.0)

Published 2026-08-28 10:17:30 +00:00 by 0x346e3730

Installation

@clauzier:registry=
npm install @clauzier/astro-kit@0.14.0
"@clauzier/astro-kit": "0.14.0"

About this package

@clauzier/astro-kit

Couche front partagée de la plateforme multi-clients : client typé vers cms-core, couche contenu, réglages et libellés, gabarit de site, composants Astro, styles socle et modules verticaux opt-in (esport, twitch).

Modules

Entrypoint Contenu
client createCmsClient : client typé minimal vers l'API cms-core (dont getSiteSettings)
entities createContentSource, createSiteSettings, normalisation, libellés de schémas
labels createLabels, interpolate : textes d'interface surchargeables depuis les réglages
layout buildSiteHead, pageTitle, footerLine : tête de page depuis les réglages
pages cmsPagePaths, cmsPageSeo, renderCmsPage, notFoundSeo : route fourre-tout et 404
blocks Registre de renderers + rendu HTML sûr (escapeHtml, sanitizeHtml, safeUrl)
seo Balises meta/OG, URLs absolues, robotsTxt
esport Opt-in. withEsport, createEsportBlockRegistry, helpers de fiche équipe
twitch Opt-in. renderWebTv, createWebTvRenderer, statut Twitch ; twitch/island (îlot)
components/* Composants Astro : SiteLayout, Header, Footer, NotFound, TeamHero, …
styles/* base.css (socle), esport.css, twitch.css : tout en tokens surchargeables

Le barrel racine et ./entities/./blocks ne contiennent rien d'esport ni de twitch : un site qui n'active pas ces presets n'en embarque rien.

Couche contenu

Une coquille cliente ne fournit que sa configuration et ses fixtures ; lecture API, normalisation snake_case → camelCase, tris et garde anti-repli viennent du paquet.

import { createContentSource, createSiteSettings } from '@clauzier/astro-kit/entities';
import { withEsport } from '@clauzier/astro-kit/esport';

const source = withEsport(
  createContentSource({
    baseUrl: import.meta.env.CMS_BASE_URL,
    fixtures: { site: createSiteSettings({ name: 'Mon club' }), pages, staff, socials, menus },
    avatarColors: ['#1226aa', '#e63946'],
  }),
  { teams, players, matches, achievements },
);

export const { getSiteSettings, getLabels, listPageEntries, listTeams, listPalmares } = source;

Sans baseUrl, les fixtures sont servies (CI, previews) et fixtures.site est obligatoire. Avec une API configurée, toute erreur de lecture fait échouer le build — y compris une fiche Site absente (« créez et publiez la fiche Site en back-office ») : un repli silencieux publierait un site plausible mais faux.

Les libellés de schéma (rôles, plateformes…) viennent de /api/content-schemas via schemaLabels() — ils ne sont jamais recopiés côté front.

Réglages et libellés

GET /api/site sert la fiche singleton site : identité, contact, SEO et surcharges de textes. getSiteSettings() la normalise (SiteSettings, champs absents à null, champs hors contrat dans extra, ex. twitch_channel) et la lit une fois par build.

Chaque texte humain a une clé de libellé, avec un défaut dans le module qui le rend et une surcharge possible depuis les réglages :

const labels = await getLabels(); // surcharges API > défauts TS > clé
labels.t('footer.copyright', { year: 2030, name: 'Mon club' }); // « © 2030 Mon club »
labels.t('header.menu'); // « Menu »

t() rend du texte brut, toujours échappé à l'affichage : une surcharge <b>…</b> s'affiche telle quelle. Les défauts génériques vivent dans labels/defaults.ts, ceux des modules dans esport/labels.ts et twitch/labels.ts ; withEsport étend getLabels() avec les siens, et chaque renderer garantit les siens quoi qu'on lui passe. La liste des clés est figée par tests/labelsCatalog.test.ts, miroir du LabelCatalog de cms-core : ajouter une clé se fait des deux côtés, jamais dans une coquille.

Les libellés s'injectent par closure : createCoreBlockRegistry(source, { labels }), createEsportBlockRegistry(source, { labels }), createWebTvRenderer({ labels }), createAwesomeFormsRenderer({ labels }), prop labels des composants, PartContext.labels des renderers de fragments.

Registres de briques : défauts + core + modules

Une coquille compose son registre par familles : les briques par défaut (heading, paragraph, hero, section, image, form), celles du preset core (staff_grid, socials_grid, depuis listStaff/listClubSocials), puis les modules qu'elle active.

staff_grid groupe les fiches par section quand le schéma en déclare, dans l'ordre du contrat et sous les libellés qu'il porte ; sans section déclarée la grille reste plate. teams_grid fait de même avec le champ « Jeu » d'une équipe, et affiche le libellé du contrat plutôt que la valeur stockée.

Aucun chemin du site n'est supposé par le socle : la coquille fournit sa base d'URL d'équipe et ses liens de repli, et sans lien il n'y a pas de bouton.

import { createCoreBlockRegistry, createDefaultBlockRegistry } from '@clauzier/astro-kit/blocks';
import { createEsportBlockRegistry } from '@clauzier/astro-kit/esport';
import { createWebTvRenderer } from '@clauzier/astro-kit/twitch';

const registry = createDefaultBlockRegistry({ layout: 'section', form });

createCoreBlockRegistry(source, { registry, labels });

createEsportBlockRegistry(source, {
  registry,
  labels,
  clubName: settings.name,
  teamBase: '/sections/',
  links: { calendar: '/calendrier/', tv: '/direct/' },
});

registry.register(
  createWebTvRenderer({
    apiBaseUrl: source.apiBaseUrl,
    channel: String(settings.extra.twitch_channel ?? ''),
    labels,
  }),
);

Briques esport : teams_grid, matches (mode: results, display: rows pour la liste détaillée plutôt que la bande de pastilles, filter_by_game), palmares (mode: tournaments, group_by_year, filter_by_game), next_match. Le filtre par jeu se passe de JavaScript et reste scopé à l'instance du bloc ; sur les matchs, la puce emprunte son logo à l'équipe qui joue le jeu, un match n'en portant pas. Fiche équipe : splitRoster(roster, staffRoles) et teamNote(team, staff, labels). L'encadrement se reconnaît à la case « Encadrement » de la fiche joueur ; staffRoles (['coach'] par défaut, comparé à la casse et aux espaces près) ne sert que de repli pour les fiches antérieures à cette case.

Les trois visuels d'une équipe ont chacun leur place : banner en fond de carte et de bandeau, logo en pastille, game_logo en filigrane de carte — et en pastille de repli tant qu'aucun logo d'équipe n'est renseigné.

Bloc webtv : coquille statique (jamais de faux LIVE), hydratée côté client par import '@clauzier/astro-kit/twitch/island' dans le gabarit — l'îlot lit ses textes en data-label-* et hydrate aussi les comptes à rebours. Sans canal ni origine d'API, seule la coquille est rendue. La pastille flottante est twitch/components/LivePill.astro. Les feuilles esport.css et twitch.css sont importées explicitement par la coquille ; twitch.css attend le token --live.

Gabarit de site et pages CMS

SiteLayout.astro rend la tête SEO (titre suffixé, favicon, OG de repli), l'en-tête (logo et CTA des réglages), le lien d'évitement, <main id="contenu"> et le pied de page (« © année nom · mention légale », ou footer_text). Il n'importe aucune feuille de style : la coquille charge sa palette puis base.css dans son propre gabarit.

---
import '@/styles/theme.css';
import '@clauzier/astro-kit/styles/base.css';
import SiteLayout from '@clauzier/astro-kit/components/SiteLayout.astro';
import { getLabels, getSiteSettings, listClubSocials, listMenu } from '@/lib/content.js';

const [settings, labels, header, footer, socials] = await Promise.all([
  getSiteSettings(),
  getLabels(),
  listMenu('header'),
  listMenu('footer'),
  listClubSocials(),
]);
---

<SiteLayout
  {settings}
  {labels}
  {socials}
  menus={{ header, footer }}
  brandSplit={3}
  {...Astro.props}
>
  <slot />
</SiteLayout>

Route fourre-tout des pages composées en back-office, en une quinzaine de lignes :

---
import { cmsPagePaths, cmsPageSeo, renderCmsPage } from '@clauzier/astro-kit/pages';

export async function getStaticPaths() {
  return cmsPagePaths({ source, homeSlug: 'accueil', reserved: ['404', 'sections'] });
}

const { entry } = Astro.props;
const seo = cmsPageSeo(entry);
const html = await renderCmsPage(entry, { registry, path: Astro.url.pathname });
---

<Base title={seo.title} description={seo.description} ogImage={seo.ogImage}>
  <Fragment set:html={html} />
</Base>

reserved accepte des préfixes ou un prédicat asynchrone ; un slug réservé ou un accueil absent (avec API) cassent le build avec un message explicite. NotFound.astro (settings, labels?, links?) et notFoundSeo() couvrent la 404 ; robotsTxt({ siteUrl }) sert robots.txt.

Composants et styles

Le Header ne charge aucun script : le burger mobile et les sous-menus sont des <details>/<summary>, dont l'état d'ouverture est exposé nativement aux technologies d'assistance. Au-dessus de 900 px, [open] est neutralisé par la feuille socle et les sous-menus reviennent au survol. L'entrée parente d'un sous-menu n'est plus un lien — ajouter un « Tout voir » en tête de ses enfants si elle porte une page propre.

base.css porte le reset global du document et n'écrit aucune couleur en dur : tout passe par des tokens (--bg, --panel, --accent, --muted, --font-display…) que la coquille définit dans sa propre feuille de palette (tokens.css en donne le contrat et un point de départ neutre).

Développement

bun install
bun run lint && bun run format:check && bun run typecheck && bun run test

Dependencies

Dependencies

ID Version
sanitize-html ^2.17.7

Development dependencies

ID Version
@eslint/js ^10.0.0
@types/node ^26.0.0
@types/sanitize-html ^2.16.1
astro ^7.2.4
eslint ^10.0.0
eslint-plugin-astro ^1.3.0
prettier ^3.9.0
prettier-plugin-astro ^0.14.0
typescript ~5.9.0
typescript-eslint ^8.40.0
vitest ^4.0.0

Peer dependencies

ID Version
astro ^7.0.0
Details
npm
2026-08-28 10:17:30 +00:00
28
UNLICENSED
61 KiB
Assets (1)
Versions (30) View all
0.26.0 2026-09-09
0.25.0 2026-09-07
0.24.0 2026-09-07
0.23.0 2026-09-05
0.22.0 2026-09-04