Make mobile filters a keyboard-accessible modal drawer

This commit is contained in:
2026-09-06 09:56:34 +03:00
parent 3550f1a9bc
commit 5895f60b28
4 changed files with 55 additions and 24 deletions
+2 -1
View File
@@ -6,7 +6,7 @@ import {
Bot, PlusCircle, Trash2, ChevronDown, ChevronUp, Filter, MapPin, Building2, Tag, Star, Hash
} from 'lucide-react';
export const LeftSidebar = () => {
export const LeftSidebar = ({onOpenProductSearch} = {}) => {
const {
favoriteKurumlar,
quickFilter,
@@ -49,6 +49,7 @@ export const LeftSidebar = () => {
return () => { iptal = true; clearTimeout(z); };
}, [okasQ]);
const okasIleAra = (k) => {
onOpenProductSearch?.();
setUrunAramaBaslangic({ okasKod: k.kod, okasAd: k.ad });
setIsUrunAramaOpen(true);
};
+36 -23
View File
@@ -1,35 +1,48 @@
import React, { useState } from 'react';
import React, { useEffect, useId, useRef, useState } from 'react';
import { SlidersHorizontal, X } from 'lucide-react';
import { LeftSidebar } from './LeftSidebar';
/**
* Mobil filtre çekmecesi: küçük ekranda sol menü listeyi aşağı itiyordu
* (72 satırlık panel). Artık lg altında gizli; "Filtreler" düğmesiyle
* soldan açılan çekmecede aynı bileşen çalışır.
*/
export const MobilFiltreCekmecesi = () => {
const [acik, setAcik] = useState(false);
const dialog = useRef(null);
const trigger = useRef(null);
const titleId = useId();
useEffect(() => {
if (!acik) return;
const element = dialog.current;
element.showModal();
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
const desktop = window.matchMedia('(min-width: 1024px)');
const closeOnDesktop = () => { if (desktop.matches) setAcik(false); };
desktop.addEventListener('change', closeOnDesktop);
closeOnDesktop();
return () => {
desktop.removeEventListener('change', closeOnDesktop);
element.close();
document.body.style.overflow = previousOverflow;
if (trigger.current?.getClientRects().length) trigger.current.focus();
};
}, [acik]);
return (
<div className="lg:hidden">
<button
type="button"
onClick={() => setAcik(true)}
className="w-full mb-3 bg-white border border-slate-200 rounded-xl px-4 py-2.5 text-xs font-bold text-slate-800 flex items-center justify-center gap-2 shadow-xs"
>
<SlidersHorizontal className="w-4 h-4 text-blue-600" /> Filtreler & Hızlı Erişim
<button ref={trigger} type="button" onClick={() => setAcik(true)} aria-haspopup="dialog" aria-expanded={acik}
className="w-full mb-3 bg-white border border-slate-200 rounded-xl px-4 py-2.5 text-xs font-bold text-slate-800 flex items-center justify-center gap-2 shadow-xs">
<SlidersHorizontal className="w-4 h-4 text-blue-600" /> Filtreler &amp; Hızlı Erişim
</button>
{acik && (
<div className="fixed inset-0 z-50 flex" onClick={() => setAcik(false)}>
<div className="absolute inset-0 bg-slate-900/50" />
<div onClick={(e) => e.stopPropagation()} className="relative h-full w-[88%] max-w-sm bg-slate-100 shadow-2xl overflow-y-auto p-3 animate-fade-in">
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-black text-slate-800 uppercase tracking-wider">Filtreler</span>
<button onClick={() => setAcik(false)} className="p-1.5 rounded-lg bg-white border border-slate-200" aria-label="Kapat"><X className="w-4 h-4" /></button>
</div>
<LeftSidebar />
</div>
{acik && <dialog ref={dialog} aria-labelledby={titleId} onCancel={() => setAcik(false)} onClose={() => setAcik(false)}
onClick={event => {
if (event.target !== event.currentTarget) return;
const box = event.currentTarget.getBoundingClientRect();
if (event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom) setAcik(false);
}}
className="fixed inset-y-0 left-0 right-auto m-0 h-dvh max-h-none w-[88%] max-w-sm border-0 bg-slate-100 shadow-2xl overflow-y-auto p-3 backdrop:bg-slate-900/50">
<div className="flex items-center justify-between mb-3">
<h2 id={titleId} className="text-xs font-black text-slate-800 uppercase tracking-wider">Filtreler</h2>
<button autoFocus onClick={() => setAcik(false)} className="p-2.5 rounded-lg bg-white border border-slate-200" aria-label="Filtreleri kapat"><X className="w-4 h-4" /></button>
</div>
)}
<LeftSidebar onOpenProductSearch={() => setAcik(false)} />
</dialog>}
</div>
);
};