Files
project.linkedcorp.net/frontend/src/components/Tabs.jsx
T
Milton Gonzalez e65111a7f4 feat: version 3.0.0 -- Correcciones de auditoria de seguridad, accesibilidad y redundancia
Permisos (superadmin, viewer, busqueda, tareas recurrentes), bootstrap atomico,
revocacion de JWT por version de sesion (028), control optimista de edicion (029),
logo sin SVG + CSP, ingesta endurecida, correos escapados, borradores, paginacion
de proyectos, accesibilidad de dialogos/controles e i18n.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:36:02 -05:00

50 lines
1.9 KiB
React

import { DraftMark } from './NavigationGuard.jsx';
import { useEffect, useId, useRef } from 'react';
export function Tabs({ tabs, active, onChange }) {
const id = useId();
const listRef = useRef(null);
const tabRefs = useRef({});
useEffect(() => {
const panel = listRef.current?.nextElementSibling;
if (!panel) return;
panel.id = `${id}-panel`;
panel.setAttribute('role', 'tabpanel');
panel.setAttribute('aria-labelledby', `${id}-${active}`);
}, [active, id]);
function onKeyDown(event, index) {
const delta = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;
const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : delta ? (index + delta + tabs.length) % tabs.length : null;
if (next === null) return;
event.preventDefault();
onChange(tabs[next].key);
tabRefs.current[tabs[next].key]?.focus();
}
return (
<div className="tabs" role="tablist" ref={listRef}>
{tabs.map((tab, index) => (
<button
key={tab.key}
type="button"
id={`${id}-${tab.key}`}
ref={(node) => { tabRefs.current[tab.key] = node; }}
role="tab"
aria-selected={active === tab.key}
aria-controls={`${id}-panel`}
tabIndex={active === tab.key ? 0 : -1}
onKeyDown={(event) => onKeyDown(event, index)}
className={`tab-item ${active === tab.key ? 'active' : ''} ${tab.dirty ? 'tab-dirty' : ''}`}
onClick={() => onChange(tab.key)}
>
{tab.label}
{/* count=0 SI se muestra (badge "0") -- solo se omite si la pestaña ni siquiera manda
la prop (undefined), para pestañas que no tienen numero que mostrar (Info/Gantt). */}
{tab.count !== undefined && <span className="tab-badge">{tab.count}</span>}
{/* Pestaña con cambios sin guardar (borrador pendiente). */}
{tab.dirty && <DraftMark />}
</button>
))}
</div>
);
}