Utilitaires
Fichiers rangés avec les composants mais qui n’en sont pas : ils servent aux composants.
opensModal.ts
Exporte : opensModal
import type { MouseEvent } from 'react';
/**
* Lien vers une page d'information (/methode, /sources, /pays) qui ouvre sa modale au clic simple : l'adresse reste
* lisible des moteurs et s'ouvre dans un onglet au clic du milieu ou avec ⌘/Ctrl.
*/
export const opensModal = (open: () => void) => (e: MouseEvent) => {
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
e.preventDefault();
open();
};
preloadable.tsx
Exporte : preloadable
import { lazy, useState, type ComponentType } from 'react';
/**
* Composant chargé à part, comme avec `lazy`, mais affiché tout de suite quand son fichier est déjà téléchargé :
* `lazy` passe toujours par l'écran de chargement au premier affichage (au moins 300 ms avec React 19), même préchargé.
* `preload()` télécharge le fichier à l'avance.
*/
export function preloadable<P extends object>(load: () => Promise<{ default: ComponentType<P> }>) {
let loaded: ComponentType<P> | null = null;
const preload = () =>
load().then((m) => {
loaded = m.default;
return m;
});
const Lazy = lazy(preload) as unknown as ComponentType<P>;
function Preloadable(props: P) {
// choisi une fois pour toutes : passer de l'un à l'autre en cours de route recréerait le composant
const [C] = useState(() => loaded ?? Lazy);
return <C {...props} />;
}
return Object.assign(Preloadable, { preload });
}
techStyle.ts
Exporte : colStyle
import type { CSSProperties } from 'react';
import { mtbColor } from '../lib/colors';
/** Couleur d'une cotation S0–S5 pour les pastilles et colonnes ; S3 (noir) suit le thème pour rester visible sur fond sombre. */
export const colStyle = (g: number): CSSProperties =>
g === 3 ? ({ '--c': 'var(--tech-black)', '--ink': 'var(--tech-black-ink)' } as CSSProperties) : ({ '--c': mtbColor(g) } as CSSProperties);