HomeCountries

←→

Section de l’accueil sur les pays couverts, groupés par statut du plus sûr au moins sûr, puis le reste du monde ; un pays ouvre la fenêtre « Où Pathou fonctionne » sur lui.

stableÀ valider
// Titre : Carte des pays couverts
import HomeCountries from '@pathou/components/HomeCountries';

export default function Exemple() {
  return (
    <div style={{ width: 900 }}>
      <HomeCountries onOpen={() => {}} />
    </div>
  );
}

Installation

Copie le fichier dans ton projet, ainsi que les tokens qu’il utilise.

src/components/HomeCountries.tsx
import { REAL_PROFILE } from '../lib/sources/profile';
import type { ReactNode } from 'react';
import type { CountryCode, Support } from '../lib/sources/types';
import { COUNTRY_GROUPS, FLAG, STATUS } from './CountriesInfo';
import { opensModal } from './opensModal';

/** Finesse de l’altitude d’un pays : « à 1 m », ou « de 1 à 5 m » quand ses régions diffèrent (l’Allemagne). Insécable : la ligne se coupe avant, pas au milieu. */
function resolution(regions: string[]): string | null {
  const res = regions.flatMap((r) => REAL_PROFILE.regions[r as keyof typeof REAL_PROFILE.regions]?.elevation?.resolutionM ?? []);
  if (!res.length) return null;
  const [lo, hi] = [Math.min(...res), Math.max(...res)].map((m) => m.toLocaleString('fr-FR'));
  return (lo === hi ? `à ${lo} m` : `de ${lo} à ${hi} m`).replace(/ /g, '\u00a0');
}

/**
 * Section de l’accueil : les pays couverts, groupés par statut, du plus sûr au moins sûr, puis le reste du monde.
 * Même source que la modale « Où Pathou fonctionne », qui s’ouvre sur le pays choisi.
 */
export default function HomeCountries({ onOpen }: { onOpen: (code?: CountryCode) => void }) {
  return (
    <section className="hp-countries" aria-labelledby="hp-countries">
      <div className="hp-inner">
        <div className="hpc-head">
          <h2 id="hp-countries">Où Pathou fonctionne</h2>
          <p>Dans ces pays, Pathou lit l’altitude des instituts géographiques nationaux, bien plus fine que le relief mondial.</p>
        </div>
        {COUNTRY_GROUPS.map((g) => (
          <Band key={g.status} status={g.status} label={STATUS[g.status].label} text={STATUS[g.status].text}>
            <ul className="hpc-list">
              {g.cards.map((c) => {
                const res = resolution(c.regions);
                return (
                  <li key={c.code}>
                    <button type="button" className="hpc-country" onClick={() => onOpen(c.code)} aria-label={`${c.name}, ${STATUS[g.status].label.toLowerCase()} : voir le détail`}>
                      {FLAG[c.code] ? <img className="hpc-flag" src={FLAG[c.code]} alt="" width={32} height={32} /> : <span className="hpc-flag" aria-hidden="true" />}
                      <span>
                        <b>{c.name}</b>
                        {res && <small>Altitude {res}</small>}
                      </span>
                    </button>
                  </li>
                );
              })}
            </ul>
          </Band>
        ))}
        <Band status="degrade" label="Partout ailleurs" text="Pathou fonctionne avec l’altitude du GPX ou le relief mondial : le verdict est moins sûr.">
          <a className="hp-link hpc-more" href="/pays" onClick={opensModal(() => onOpen())}>
            Voir le détail par pays
          </a>
        </Band>
      </div>
    </section>
  );
}

function Band({ status, label, text, children }: { status: Support; label: string; text: string; children: ReactNode }) {
  return (
    <div className={`hpc-band hpc-${status}`}>
      <div className="hpc-status">
        <h3>
          <Mark status={status} />
          {label}
        </h3>
        <p>{text}</p>
      </div>
      {children}
    </div>
  );
}

/** Pastille du statut, au motif de la carte des pays (à-plat, hachures, points) : le statut se lit sans la couleur. */
function Mark({ status }: { status: Support }) {
  return (
    <svg className="hpc-mark" viewBox="0 0 16 16" aria-hidden="true">
      <clipPath id={`hpc-clip-${status}`}>
        <rect x="0.5" y="0.5" width="15" height="15" rx="4" />
      </clipPath>
      <g clipPath={`url(#hpc-clip-${status})`}>
        <rect width="16" height="16" className="hpc-mark-bg" />
        {status === 'beta' && [-8, -4, 0, 4, 8, 12, 16].map((d) => <line key={d} x1={d} y1="16" x2={d + 16} y2="0" className="hpc-mark-ink" />)}
        {status === 'degrade' && [3, 8, 13].flatMap((cx) => [3, 8, 13].map((cy) => <circle key={`${cx}-${cy}`} cx={cx} cy={cy} r="1.3" className="hpc-mark-dot" />))}
      </g>
    </svg>
  );
}

Tokens : --bg --panel --text --muted --line --accent --chip --ease-out --font --display --status-complet (non déterminé) --status-beta --status-degrade

Utilisation

import HomeCountries from './components/HomeCountries';

<HomeCountries onOpen={(code) => openCountries(code)} />

Guidelines

  • MUST
    Tirer les pays et leur statut de la même source que la fenêtre des pays (CountriesInfo) ; les deux ne doivent jamais diverger.
  • SHOULD
    Dire le statut en mots (complet, bêta, dégradé), la couleur des tokens --status-* ne faisant que l’appuyer.

Référence de l’API

PropTypeDéfaut
onOpen(code?: CountryCode) => voidrequis