Assessor_2.0/src/features/dashboard/ClientInfoTabs.tsx

38 lines
1.8 KiB
TypeScript

import { useId, useState, type ReactNode, type KeyboardEvent } from 'react';
export interface ClientInfoTab {
id: string;
label: string;
content?: ReactNode;
}
/** Shared attached-panel presentation for every Client Info tab. */
export function ClientInfoTabs({ tabs }: { tabs: readonly ClientInfoTab[] }) {
const prefix = useId();
const [selected, setSelected] = useState(tabs[0]?.id);
const active = tabs.some(tab => tab.id === selected) ? selected : tabs[0]?.id;
const navigate = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {
let next: number;
if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
else if (event.key === 'ArrowLeft') next = (index + tabs.length - 1) % tabs.length;
else if (event.key === 'Home') next = 0;
else if (event.key === 'End') next = tabs.length - 1;
else return;
event.preventDefault();
setSelected(tabs[next].id);
document.getElementById(`${prefix}-tab-${tabs[next].id}`)?.focus();
};
return <div className="client-info-tabs">
<div className="client-info-tablist" role="tablist" aria-label="Client Info">
{tabs.map((tab, index) => <button key={tab.id} type="button" role="tab"
className="client-info-tab" id={`${prefix}-tab-${tab.id}`}
aria-controls={`${prefix}-panel-${tab.id}`} aria-selected={tab.id === active}
tabIndex={tab.id === active ? 0 : -1} onClick={() => setSelected(tab.id)}
onKeyDown={event => navigate(event, index)}>{tab.label}</button>)}
</div>
{tabs.map(tab => <section key={tab.id} className="client-info-tabpanel" role="tabpanel"
id={`${prefix}-panel-${tab.id}`} aria-labelledby={`${prefix}-tab-${tab.id}`}
hidden={tab.id !== active} data-group-scroll={`client-info-${tab.id}`} tabIndex={0}>{tab.content}</section>)}
</div>;
}