From 6faaefebee13fcfb84f4eabcb6cb58cd95973ce1 Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Mon, 19 Oct 2026 11:06:35 +0200 Subject: [PATCH] =?UTF-8?q?Fiche=20:=20fermer=20en=20glissant=20vers=20le?= =?UTF-8?q?=20bas=20sur=20t=C3=A9l=C3=A9phone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La feuille de la fiche (PersonCard) se ferme en glissant sa poignée ou son en-tête vers le bas, comme le menu de la barre. Le geste est factorisé : logique pure dans utils/sheetDrag.js, hook dans hooks/useSheetDrag.js, utilisé aussi par NavMenu (poignée, seuil de 70 px inchangé). - Le geste part seulement de la poignée et de l'en-tête. Jamais d'un champ de saisie, d'un libellé ni pendant une sélection de texte. Le contenu des onglets défile comme avant. - La feuille suit le doigt (transform), ne monte jamais au-delà de sa position de repos. - Fermeture au-delà d'un tiers de la hauteur, ou sur un lâcher rapide vers le bas. Sinon retour court. - La fermeture passe par le même chemin que la croix (enregistrement automatique compris). Si l'enregistrement est refusé, la fiche revient et affiche son erreur. - Mode feuille seulement (PHONE_QUERY et largeur de la feuille CSS). Sur ordinateur, rien ne change. - prefers-reduced-motion respecté. Tests : logique du geste (zones, direction, seuil, vitesse) en vitest. Recette Playwright iPhone 13 émulé, gestes tactiles CDP : fermeture par la poignée, retour après un petit glisser, lâcher rapide, défilement du contenu sans fermer, pas de geste depuis un champ, enregistrement puis fermeture, ordinateur inchangé, croix. Co-Authored-By: Claude Haiku 5.5 --- frontend/src/App.jsx | 2 +- frontend/src/components/PersonCard.css | 10 +- frontend/src/components/PersonCard.jsx | 13 +- .../components/navbar/ContextualNavbar.jsx | 4 +- frontend/src/components/navbar/NavMenu.css | 3 + frontend/src/components/navbar/NavMenu.jsx | 42 ++--- frontend/src/hooks/useSheetDrag.js | 147 ++++++++++++++++++ frontend/src/utils/sheetDrag.js | 51 ++++++ frontend/src/utils/sheetDrag.test.js | 97 ++++++++++++ 9 files changed, 336 insertions(+), 33 deletions(-) create mode 100644 frontend/src/hooks/useSheetDrag.js create mode 100644 frontend/src/utils/sheetDrag.js create mode 100644 frontend/src/utils/sheetDrag.test.js diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5043b51..2fcd386 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3142,7 +3142,7 @@ function App() { {tree.selectedPerson && ( { chooseSelectedPerson(null) }} + onClose={() => chooseSelectedPerson(null)} onMediaSelect={handleMediaSelectFresh} saveOnLeaveRef={personSaveOnLeaveRef} editMode={editMode.isActive} diff --git a/frontend/src/components/PersonCard.css b/frontend/src/components/PersonCard.css index 547b951..8930ab2 100644 --- a/frontend/src/components/PersonCard.css +++ b/frontend/src/components/PersonCard.css @@ -252,8 +252,15 @@ left: 0; right: 0; bottom: 0; width: auto; max-height: 78dvh; padding: 10px 18px calc(18px + env(safe-area-inset-bottom)); border-radius: 26px 26px 0 0; border-bottom: 0; - animation: pc-sheet 400ms var(--pz-ease-drawer) both; + /* Pas de fill-mode « both » : une fois l'entrée finie, le glisser (style inline) reprend la main sur transform */ + animation: pc-sheet 400ms var(--pz-ease-drawer) backwards; + transition: transform 240ms var(--pz-ease-drawer); } + /* Pendant le glisser, la feuille suit le doigt sans transition */ + .person-card[data-dragging] { transition: none; } + /* La poignée et l'en-tête portent le geste : le navigateur ne doit pas le prendre pour un défilement (les champs gardent le leur) */ + .person-card .pc-grab, .person-card .pc-close, .person-card .pc-head { touch-action: none; } + .person-card .pc-head :is(input, textarea, select, label, [contenteditable]) { touch-action: auto; } .pc-grab { display: block; width: 38px; height: 4px; margin: 0 auto 2px; border-radius: 4px; background: var(--pz-line); flex: none; } .pc-close { top: 10px; right: 10px; } .pc-photo { width: 68px; height: 68px; } @@ -263,6 +270,7 @@ @media (prefers-reduced-motion: reduce) { .person-card { animation: pz-fade 200ms ease both; } + .person-card { transition-duration: 1ms; } } /* Prénom inconnu (issue Anaïs, 18/10) : libellé en italique, case à cocher sous les champs de nom */ diff --git a/frontend/src/components/PersonCard.jsx b/frontend/src/components/PersonCard.jsx index 22c26f7..8b74ad3 100644 --- a/frontend/src/components/PersonCard.jsx +++ b/frontend/src/components/PersonCard.jsx @@ -10,6 +10,7 @@ import GeoMediaThumbnail from './GeoMediaThumbnail' import ImmichFacePanel from './ImmichFacePanel.jsx' import './PersonCard.css' import PzBusy from './PzBusy.jsx' +import { useSheetDrag, useSheetMode } from '../hooks/useSheetDrag' import PortraitCropModal from './PortraitCropModal.jsx' import { imagePlacement } from '../utils/portraitCrop.js' import { t, dateLocale } from '../i18n/index.js' @@ -143,6 +144,9 @@ const PersonCard = ({ const [openedFields, setOpenedFields] = useState([]) const [focusField, setFocusField] = useState(null) const bodyRef = useRef(null) + // Feuille du bas (téléphone) : on la ferme en la glissant par la poignée ou l'en-tête, comme la croix + const sheetMode = useSheetMode() + const drag = useSheetDrag({ enabled: sheetMode, zone: '.pc-grab, .pc-head, .pc-close', onClose }) // Reset edits quand la personne change useEffect(() => { @@ -634,7 +638,14 @@ const PersonCard = ({ ) return ( -
+