All checks were successful
Fondation du chantier UI mobile & tablette (#91) : jusqu'ici, toute adaptation mobile de ce repo etait du CSS pur sur le meme DOM (aucun composant ne branchait son JSX selon la largeur/plateforme). Ce ticket introduit le premier vrai branchement, et les primitifs que les 8 tickets suivants du chantier reutiliseront. - useIsMobileViewport() (apps/web/src/lib/) : point de verite unique, base sur matchMedia, live (reagit a un resize/une rotation). - _breakpoints.scss : $phone/$tablet Sass + mixins phone/phone-up/ tablet-down, remplacant les nombres magiques 640/1024 repetes en dur dans 6 fichiers (AppLayout, landing, recipes, recipe-picker-dialog, planning-page, dialog, tours-page). Les deux occurrences de 860px (landing hero, tours-page) sont deliberement laissees telles quelles - un collapse de grille 2 colonnes qui n'a rien a voir avec le breakpoint tablette du catalogue recettes, les unifier aurait change un comportement visuel reel sans raison. - useNativeDialogBehavior : mecanique (showModal/close event/clic exterieur) extraite de Dialog.tsx, partagee avec le nouveau BottomSheet.tsx (feuille mobile plein-largeur, meme fondation <dialog> natif que Dialog, juste ancree en bas). - AppLayout : sur telephone, la sidebar n'est plus reflow-ee en CSS en barre horizontale (l'ancien traitement) - elle est remplacee par une vraie barre d'onglets basse (Planning/Recettes/Liste de courses + Plus), le "Plus" ouvrant une BottomSheet combinant Parametres et Compte (deux dropdowns flottants separes cote desktop -> un seul point d'entree mobile, IA plus simple, un seul emplacement d'onglet). - dialog.scss : vrai plein-ecran (bord a bord, sans arrondi) sous le breakpoint phone au lieu d'une marge simplement reduite. Verifie en local : pnpm --filter web exec tsc -b --force, biome check, pnpm --filter web build, suite Cypress e2e complete (140/140) et composants (63/63) - y compris layout.cy.ts, reecrit pour tester la nouvelle barre basse (l'ancien test de la barre horizontale n'a plus de sens, ce traitement n'existe plus). Closes #91 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import "./bottom-sheet.scss";
|
|
import { useNativeDialogBehavior } from "./useNativeDialogBehavior";
|
|
|
|
/**
|
|
* A native `<dialog>` anchored to the bottom edge instead of centered —
|
|
* the mobile-native replacement for a small anchored floating popover
|
|
* (a menu, a short list of actions) that a phone-width screen has no good
|
|
* place to float. Same open/close mechanics as `Dialog.tsx` (via the
|
|
* shared `useNativeDialogBehavior`: `showModal()` on mount, Escape/`close`
|
|
* event, tap-outside-to-dismiss) — only the panel's own position/shape
|
|
* differs, so both get the browser's focus trap and top-layer stacking for
|
|
* free.
|
|
*
|
|
* First consumer: `AppLayout`'s mobile nav, for the "Plus" tab (Paramètres
|
|
* + Compte, previously two separate floating dropdowns that had no
|
|
* viewport-edge handling at all — see the mobile/tablet UI chantier's own
|
|
* audit). Meant to be reused by the planning popovers (leftovers basket,
|
|
* week calendar) in a later ticket of that same chantier — kept generic
|
|
* (title + children, no planning-specific assumptions) for that reason.
|
|
*
|
|
* Not used on desktop: a sheet sliding up from the bottom of a full
|
|
* viewport only reads as "anchored to what you tapped" on a narrow screen
|
|
* where the tapped control is usually near the bottom already (a bottom
|
|
* tab bar, a floating action button). Desktop keeps the existing anchored
|
|
* dropdown/popover pattern for the same triggers — callers decide which to
|
|
* render (typically via `useIsMobileViewport()`), this component doesn't
|
|
* check the viewport itself.
|
|
*/
|
|
export function BottomSheet({
|
|
onClose,
|
|
title,
|
|
children,
|
|
}: {
|
|
onClose: () => void;
|
|
title?: string;
|
|
children: ReactNode;
|
|
}) {
|
|
const dialogRef = useNativeDialogBehavior(onClose);
|
|
|
|
return (
|
|
<dialog ref={dialogRef} className="bottom-sheet" aria-label={title}>
|
|
{/* Purely decorative drag-affordance — this sheet closes via
|
|
tap-outside/Escape/the close button, never an actual drag
|
|
gesture, but the grabber bar is still the expected visual
|
|
convention for "this slid up from the bottom" on both Android and
|
|
iOS, so it's worth keeping even without the gesture behind it. */}
|
|
<div className="bottom-sheet__grabber" aria-hidden="true" />
|
|
{title && (
|
|
<div className="bottom-sheet__header">
|
|
<h2>{title}</h2>
|
|
<button
|
|
type="button"
|
|
className="bottom-sheet__close"
|
|
onClick={() => dialogRef.current?.close()}
|
|
aria-label="Fermer"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
)}
|
|
<div className="bottom-sheet__body">{children}</div>
|
|
</dialog>
|
|
);
|
|
}
|