SPB Git forge

spb/resto-ka

Public

Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)

52commits 1branches 0releases
11.6 MBsize
maindefault branch
19 days agolast push
Python 69.3% TypeScript 16.7% CSS 7.9% JavaScript 4.7% HTML 1.4%

Mobile : menu compte via createPortal au-dessus de la tabbar, verrou scroll de la feuille filtres, logo tactile

- frontend/src/App.tsx : backdrop + menu compte rendus à la racine de <body>
  via createPortal (le backdrop-filter du header re-parentait le
  position:fixed → voile de 66 px, et le stacking context z-50 du header
  plaquait le menu SOUS la tabbar z-85 et le fab z-80) ; fermeture à Escape.
- frontend/src/styles.css : .account-backdrop → var(--z-overlay) (800),
  .account-menu → position:fixed ancré sous le header (--header-h 66/58 px) à
  var(--z-modal) (900) ; zone tactile ≥44 px du logo .brand (::after inset
  -8px, rendu inchangé) ; les 7 selects de filtres étaient déjà couverts par
  la règle 16 px ≤900 px + filet anti-zoom global de tokens.css.
- frontend/src/pages/Home.tsx : feuille de filtres mobile (sheetOpen) — gel du
  scroll d arrière-plan (pattern App.tsx) + fermeture à Escape.
- frontend/src/ka/tokens.css (commité avec Stats v2) : socle mobile commun.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 19, 2026) parent 749c169

3 changed files +50 −7

modified frontend/src/App.tsx +17 −2
@@ -5,6 +5,7 @@
5 5 // même patron que le App.tsx de Lou·Ka.
6 6 // ==============================================================================
7 7 import { useEffect, useState } from "react";
8 +import { createPortal } from "react-dom";
8 9 import { NavLink, Route, Routes, useLocation } from "react-router-dom";
9 10 import { AccountProvider, useAccount } from "./account";
10 11 import {
@@ -34,6 +35,14 @@ function AccountMenu() {
34 35 fetchAuthConfig().then((c) => setEnabled(c.ka)).catch(() => {});
35 36 }, []);
36 37
38 + // menu ouvert : fermeture à Escape (le tap extérieur passe par le backdrop)
39 + useEffect(() => {
40 + if (!menuOpen) return;
41 + const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setMenuOpen(false); };
42 + window.addEventListener("keydown", onKey);
43 + return () => window.removeEventListener("keydown", onKey);
44 + }, [menuOpen]);
45 +
37 46 if (!enabled) return null;
38 47 if (!me) {
39 48 return (
@@ -55,7 +64,12 @@ function AccountMenu() {
55 64 ? <img src={me.picture} alt="" referrerPolicy="no-referrer" />
56 65 : <span className="account-initial">{(me.name || me.email).charAt(0).toUpperCase()}</span>}
57 66 </button>
58 − {menuOpen && (
67 + {/* Backdrop + menu rendus à la racine via createPortal : le
68 + backdrop-filter du header re-parentait le position:fixed (voile de
69 + 66 px) et le stacking context du header (z-50) plaquait le menu SOUS
70 + la tabbar (85) et le fab (80). Le menu devient fixed, ancré sous le
71 + header (voir .account-menu dans styles.css). */}
72 + {menuOpen && createPortal(
59 73 <>
60 74 <div className="account-backdrop" onClick={() => setMenuOpen(false)} aria-hidden="true" />
61 75 <div className="account-menu" role="menu">
@@ -79,7 +93,8 @@ function AccountMenu() {
79 93 Se déconnecter
80 94 </button>
81 95 </div>
82 − </>
96 + </>,
97 + document.body,
83 98 )}
84 99 </div>
85 100 );
modified frontend/src/pages/Home.tsx +13 −0
@@ -68,6 +68,19 @@ export default function Home() {
68 68 const [statChips, setStatChips] = useState<{ label: string; value: number }[]>([]);
69 69 const [sheetOpen, setSheetOpen] = useState(false);
70 70
71 + // feuille de filtres ouverte : gel du scroll d'arrière-plan + fermeture à
72 + // Escape (même pattern que le menu hamburger d'App.tsx)
73 + useEffect(() => {
74 + document.body.style.overflow = sheetOpen ? "hidden" : "";
75 + if (!sheetOpen) return () => { document.body.style.overflow = ""; };
76 + const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setSheetOpen(false); };
77 + window.addEventListener("keydown", onKey);
78 + return () => {
79 + document.body.style.overflow = "";
80 + window.removeEventListener("keydown", onKey);
81 + };
82 + }, [sheetOpen]);
83 +
71 84 const filters = useMemo(() => {
72 85 const f: Partial<Record<FilterKey, string>> = {};
73 86 for (const k of FILTER_KEYS) {
modified frontend/src/styles.css +20 −5
@@ -36,7 +36,8 @@ img { display: block; }
36 36 backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
37 37 border-bottom: 1.5px solid var(--ink);
38 38 }
39 −.header-inner { display: flex; align-items: center; gap: 20px; height: 66px; }
39 +:root { --header-h: 66px; } /* hauteur du header (ancre du menu compte fixed) */
40 +.header-inner { display: flex; align-items: center; gap: 20px; height: var(--header-h); }
40 41 .brand {
41 42 font-family: var(--font-display); font-weight: 700; font-size: 24px;
42 43 letter-spacing: -0.04em; display: flex; align-items: center; line-height: 1;
@@ -583,7 +584,7 @@ img { display: block; }
583 584 .fab { display: none; }
584 585
585 586 @media (max-width: 640px) {
586 − .header-inner { height: 58px; }
587 + :root { --header-h: 58px; }
587 588 .brand { font-size: 20px; }
588 589 .nav a { padding: 8px 13px; font-size: 13.5px; }
589 590 .ticker { font-size: 10px; padding: 6px 0; }
@@ -671,11 +672,18 @@ img { display: block; }
671 672 .ka-footer { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
672 673 }
673 674
674 −/* Anti-zoom iOS : les champs doivent faire >= 16px */
675 +/* Anti-zoom iOS : les champs doivent faire >= 16px
676 + (filet global : tokens.css force aussi 16 px au doigt) */
675 677 @media (max-width: 900px) {
676 678 .f-search input, .f-ctl select { font-size: 16px; }
677 679 }
678 680
681 +/* Cible tactile ≥44 px du logo (138×30 rendus) : zone étendue invisible */
682 +@media (pointer: coarse) {
683 + .brand { position: relative; }
684 + .brand::after { content: ""; position: absolute; inset: -8px; }
685 +}
686 +
679 687 /* ================= Footer (KaFooter — ka-ui) ================= */
680 688 /* Le footer commun vient de tokens.css (.ka-footer) + KaFooter.tsx. On garde
681 689 ici seulement la rangée de nav locale posée au-dessus. */
@@ -728,9 +736,16 @@ img { display: block; }
728 736 width: 100%; height: 100%;
729 737 font-family: var(--font-display); font-weight: 700; font-size: 17px; color: var(--ink);
730 738 }
731 −.account-backdrop { position: fixed; inset: 0; z-index: 55; }
739 +/* Backdrop + menu rendus à la racine de <body> via createPortal (un fixed
740 + descendant du header blurré se re-parentait, et le stacking context z-50 du
741 + header plaquait le menu sous la tabbar z-85 / le fab z-80). Échelle commune
742 + tokens.css : voile = --z-overlay (800), menu = --z-modal (900). */
743 +.account-backdrop { position: fixed; inset: 0; z-index: var(--z-overlay); }
732 744 .account-menu {
733 − position: absolute; right: 0; top: calc(100% + 10px); z-index: 60;
745 + position: fixed;
746 + right: max(12px, env(safe-area-inset-right));
747 + top: calc(var(--header-h, 66px) + 10px);
748 + z-index: var(--z-modal);
734 749 min-width: 230px; background: var(--surface);
735 750 border: 1.5px solid var(--ink); border-radius: var(--r-card);
736 751 box-shadow: var(--shadow-off); padding: 8px; display: flex; flex-direction: column;
737 752