Responsive mobile / tablette / desktop

En-tête (onglets sur une seconde ligne en tablette, menu déroulant en
mobile), panneau d'affectation et fiche bénévole en tiroir par-dessus sous
lg: (plein écran en mobile), colonne des postes de la grille rétrécie,
liste des bénévoles réduite en mobile, écran « Postes & créneaux » empilé,
aperçu de l'éditeur de badges mis à l'échelle (glisser-déposer tactile),
ligne des types de paiement repliée, marges réduites — desktop inchangé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AdamThomas
2026-10-27 18:05:16 +01:00
co-authored by Claude Opus 5.5
parent 0f904f5e3d
commit 85e841ae4a
13 changed files with 269 additions and 129 deletions
+27 -7
View File
@@ -88,7 +88,9 @@ export function GrillePage() {
: matrix.data.positions.filter((p) => p.category === categoryFilter);
const gridTemplateColumns = [
'230px',
// Colonne des postes rétrécie sur petit écran, pour laisser voir au
// moins quelques tranches horaires à côté.
'clamp(132px, 36vw, 230px)',
...shownDays.flatMap((day) => {
const width = columnWidthPx(day.slotLabels.length);
return day.slotLabels.map(() => `${width}px`);
@@ -134,8 +136,8 @@ export function GrillePage() {
return (
<div className="flex min-h-0 flex-1 flex-col">
<div className="flex flex-wrap items-center gap-[14px] border-b-2 border-ink bg-sand px-4 py-[9px]">
<div className="flex items-center gap-[6px]">
<div className="flex flex-wrap items-center gap-[10px] border-b-2 border-ink bg-sand px-3 py-[9px] sm:gap-[14px] sm:px-4">
<div className="flex flex-wrap items-center gap-[6px]">
<span className="font-mono text-[9px] opacity-60">{t('grille.filter.day')}</span>
<Chip active={dayFilter === 'all'} onClick={() => setDayFilter('all')}>
{t('filter.all')}
@@ -163,7 +165,7 @@ export function GrillePage() {
))}
</div>
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={() => runSolver.mutate()}
@@ -199,7 +201,7 @@ export function GrillePage() {
) : null}
</div>
<div className="ml-auto flex items-center gap-3 border-2 border-ink bg-cream px-[9px] py-[5px]">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 border-2 border-ink bg-cream px-[9px] py-[5px] lg:ml-auto">
<span className="font-mono text-[8.5px] opacity-60">{t('legend.title')}</span>
<LegendItem swatch="bg-hatch-orange" glyph="▲">
{t('grille.legend.critical')}
@@ -220,7 +222,7 @@ export function GrillePage() {
</div>
<div className="flex min-h-0 flex-1 items-stretch">
<div className="max-h-[calc(100vh-118px)] min-w-0 flex-1 overflow-auto">
<div className="max-h-[calc(100dvh-150px)] min-w-0 flex-1 overflow-auto lg:max-h-[calc(100vh-118px)]">
<div className="grid w-max border-b-[3px] border-ink" style={{ gridTemplateColumns }}>
<div
style={{ gridColumn: '1 / span 1', gridRow: 1 }}
@@ -278,7 +280,25 @@ export function GrillePage() {
</div>
</div>
<aside className="max-h-[calc(100vh-118px)] w-[392px] flex-none overflow-auto border-l-[3px] border-ink bg-paper">
{/* Sous `lg:`, pas la place d'un panneau à côté de la grille : il
s'ouvre par-dessus (plein écran sur mobile, tiroir à droite sur
tablette) seulement quand un créneau est choisi. */}
{selected ? (
<div
aria-hidden
onClick={() => setSelectedShiftId(null)}
className="fixed inset-0 z-40 bg-ink/40 lg:hidden"
/>
) : null}
<aside
className={cx(
'overflow-auto border-ink bg-paper',
'lg:static lg:z-auto lg:block lg:max-h-[calc(100vh-118px)] lg:w-[392px] lg:flex-none lg:border-l-[3px]',
selected
? 'fixed inset-y-0 right-0 z-50 block w-full sm:w-[420px] sm:border-l-[3px]'
: 'hidden',
)}
>
{selected ? (
<AssignmentPanel
key={selected.shift.shift_id}