Aller au contenu

Proto HTML/CSS → Uniwind

Procédure issue de la modale d'intention Levée 2026 (modules/fundraising-v2). Le proto est la source de vérité visuelle ; le code du monorepo (API, providers, Modal uimmo) reste intouché.

1. Extraire les règles, ne pas les deviner

Le CSS du proto est minifié sur quelques lignes de 20 000+ caractères : un Read ne suffit pas. Extraire par sélecteur avec un script, en gardant toutes les règles d'un sélecteur (la dernière déclarée gagne, et les overrides .v2-responsive … / .device .sheet … sont fréquents) :

python3 - <<'PY'
import re; from pathlib import Path
text = Path('<proto>/prototype/index.html').read_text(errors='ignore')
for sel in ['.amount-wrap', '.quick-amounts', '.field-help']:
    for m in re.finditer(r'[^{}]*' + re.escape(sel) + r'[^{}]*\{[^{}]*\}', text):
        print(re.sub(r'\s+', ' ', m.group(0)).strip())
PY
  • Le markup vit dans les fonctions JS (amountHTML, renderV2IntentConfirmation…) : chercher function <nom> et lire le template literal. Les overrides V2 (design/participation-v2.js) patchent ce HTML par .replace(...) — lire les deux.
  • Une couleur absente sur l'élément est héritée : remonter jusqu'à .sheet{color:var(--sheet-text)} ou p{color:var(--muted)}, puis résoudre les variables (--sheet-text:#f3f7fc, --sheet-muted:#b8cbdc).
  • Les fichiers design/*.css / design/*.js sont la version la plus récente ; prototype/index.html les inline. En cas de doute, le design/ gagne.

2. Convertir les unités — le piège n° 1

Uniwind ≠ CSS. Une valeur arbitraire sans unité n'est pas un pixel partout :

CSS proto Uniwind Pourquoi
font-size:13px text-[13px] (ou variant-text-sm) text-[13] est écrasé par cn() — tailwind-merge le confond avec text-[#hex]
line-height:1.6 leading-[21px] (13 × 1.6, arrondi) leading-[21] = multiplicateur sur web → 21 × 14 px = 294 px
letter-spacing:-1px tracking-[-1px] idem
margin:12px 0 20px mt-lg mb-2xl mb-[18] sort sur l'échelle Tailwind (énorme). Tokens : 3xs2 2xs4 xs6 sm8 md10 lg12 xl16 2xl20 3xl24 4xl32
width:65px / border-radius:12px size-[65px] / rounded-[12px] sans unité marche, mais garder px pour la cohérence
#93a6be5c (hex alpha) border-[#93a6be5c] supporté tel quel, ne pas convertir en rgba

Variants typographiques (variant-text-sm = 12/18.5, variant-text-md = 14/20.9, variant-heading-2xs = 14 Bold, variant-heading-xs = 16 Bold) : projects/common/front/theme/src/fonts.ts. Les préférer quand la valeur proto est à ±1 px.

3. Éléments qui n'ont pas d'équivalent direct

  • <input> numérique : ne pas recréer un champ custom (focus ring RN-web, etc.). Réutiliser NumberInputWithControllers uimmo (@bricks-common/uimmo/components/inputs), comme dans FundraisingV2IntentionModal/steps/IntentionAmountStep.tsx.
  • Bouton proto (.primary, .secondary, .link-btn) : Button uimmo (@bricks-common/uimmo/components/buttons), pas un Pressable dédié — cf. les steps d'intention et le dock.
  • Animation ambient-glow (bande lumineuse 9 s) : Reanimated, pas le ShineEffect uimmo du solde boosté. Easing.bezierFn (pas Easing.bezier, qui n'est pas appelable dans un worklet). Modèle : fundraising-v2/components/atoms/AmbientGlow.tsx.
  • Rangée de chips flex:1 0 auto + overflow-x:auto : ScrollView horizontal avec contentContainerClassName="flex-grow gap-xs" et chaque item en shrink-0 grow + whitespace-nowrap. flex-1 seul rétrécit et tronque.
  • :focus-within sur le wrapper : state isFocused local, classe conditionnelle sur la bordure.

4. Vérifier dans Storybook, pas à l'œil

  1. Story sans play pour l'état manuel, flows avec play pour chaque branche métier.
  2. Ouvrir l'iframe (/iframe.html?id=<story>&viewMode=story) en viewport desktop (Emulation.setDeviceMetricsOverride 1280 px) — le canvas par défaut est mobile.
  3. Comparer les valeurs calculées, pas le screenshot : getComputedStyle(el).fontSize / lineHeight / marginTop. Un lineHeight à 3 chiffres = unité oubliée.
  4. « No Preview » après HMR = Fast Refresh cassé (export non-composant dans un fichier de composant, ex. INTENTION_AMOUNT_SUGGESTIONS). Recharger la page, pas chercher un bug.

5. Ce qui ne se copie pas

  • Les textes proto passent par fr.json (modules.fundraisingV2.*), jamais en dur.
  • Les CTA de sortie (data-action="hub", v2-topup-start) deviennent des callbacks props ; la modale se ferme avant d'ouvrir la suivante ou de naviguer.
  • Les flags de scénario du proto (kycReady(), state.wallet) deviennent des props booléennes / Cents calculées dans le container (needsKyc, currentBalance).