fix: support keyboard navigation in tender details
This commit is contained in:
@@ -45,9 +45,9 @@ export const TenderCard = ({ tender }) => {
|
||||
{/* Top Header Row */}
|
||||
<div>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<h3
|
||||
<h3><button type="button" aria-haspopup="dialog"
|
||||
onClick={() => setActiveTenderDetail(tender)}
|
||||
className="font-bold text-sm sm:text-base text-slate-900 hover:text-blue-600 cursor-pointer leading-snug tracking-tight"
|
||||
className="text-left focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 rounded font-bold text-sm sm:text-base text-slate-900 hover:text-blue-600 cursor-pointer leading-snug tracking-tight"
|
||||
>
|
||||
{tender.baslik}
|
||||
{tender.isYeni && (
|
||||
@@ -55,7 +55,7 @@ export const TenderCard = ({ tender }) => {
|
||||
YENİ
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
</button></h3>
|
||||
|
||||
{/* Bookmark Star Button */}
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef, useId } from 'react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { BidPackageBuilder } from './BidPackageBuilder';
|
||||
import { TenderDocuments } from './TenderDocuments';
|
||||
@@ -14,6 +14,32 @@ import { createTenderICS } from '../services/tenderCalendar';
|
||||
|
||||
export const TenderDetailModal = () => {
|
||||
const { activeTenderDetail, setActiveTenderDetail, toggleTrackTender, userProfile, setIsAiSpecAnalyzerOpen, gorevAc, gorevKontrol, aiAnalysisResults, tenders, mutateTender, savingCount, saveError, authUser } = useApp();
|
||||
const dialogRef = useRef(null);
|
||||
const dialogTitleId = useId();
|
||||
const isOpen = Boolean(activeTenderDetail);
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const opener = document.activeElement;
|
||||
const root = dialogRef.current;
|
||||
root?.querySelector('button')?.focus();
|
||||
return () => { if (opener?.isConnected) opener.focus(); };
|
||||
}, [isOpen]);
|
||||
const handleDialogKey = e => {
|
||||
if (e.defaultPrevented || e.target.closest('[role="dialog"]') !== dialogRef.current) return;
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault(); e.stopPropagation(); setActiveTenderDetail(null); return;
|
||||
}
|
||||
if (e.key !== 'Tab') return;
|
||||
const focusable = [...dialogRef.current.querySelectorAll('button, a[href], input, select, textarea, [tabindex]')]
|
||||
.filter(el => el.tabIndex >= 0 && !el.disabled && el.getClientRects().length && !el.closest('[inert]'));
|
||||
const first = focusable[0], last = focusable.at(-1);
|
||||
if (!first) { e.preventDefault(); dialogRef.current.focus(); }
|
||||
else if (e.shiftKey && (document.activeElement === first || document.activeElement === dialogRef.current)) {
|
||||
e.preventDefault(); last.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault(); first.focus();
|
||||
}
|
||||
};
|
||||
const [activeTab, setActiveTab] = useState('details'); // 'details', 'pdf_stamp', 'ai_assistant', 'checklist', 'kurum_tenders'
|
||||
const noteOwner = authUser?.email || '';
|
||||
const [noteDrafts, setNoteDrafts] = useState(() => ({ owner: noteOwner, drafts: {} }));
|
||||
@@ -76,7 +102,7 @@ export const TenderDetailModal = () => {
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto bg-slate-900/70 backdrop-blur-xs flex justify-center p-2 sm:p-4 animate-fade-in">
|
||||
<div className="bg-white w-full max-w-6xl rounded-2xl shadow-2xl border border-slate-200 overflow-hidden my-auto flex flex-col max-h-[92vh]">
|
||||
<div ref={dialogRef} role="dialog" aria-modal="true" aria-labelledby={dialogTitleId} tabIndex={-1} onKeyDown={handleDialogKey} className="bg-white w-full max-w-6xl rounded-2xl shadow-2xl border border-slate-200 overflow-hidden my-auto flex flex-col max-h-[92vh]">
|
||||
|
||||
{(actionError || saveError) && <p role="alert" className="p-3 bg-red-50 text-red-900">{actionError || saveError}</p>}
|
||||
{savingCount > 0 && <p role="status" className="p-2 bg-blue-50">Kaydediliyor…</p>}
|
||||
@@ -123,6 +149,7 @@ export const TenderDetailModal = () => {
|
||||
)}
|
||||
<button
|
||||
onClick={() => setActiveTenderDetail(null)}
|
||||
aria-label="İhale detayını kapat"
|
||||
className="text-slate-400 hover:text-white p-1"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
@@ -150,7 +177,7 @@ export const TenderDetailModal = () => {
|
||||
<span className="text-xs text-slate-500 ml-auto">Ekleme: {tender.eklenmeZamani}</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-xl sm:text-2xl font-black text-slate-900 leading-snug">
|
||||
<h1 id={dialogTitleId} className="text-xl sm:text-2xl font-black text-slate-900 leading-snug">
|
||||
{tender.baslik}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user