Files
minabot/src/components/design-system/DesignSystemTabs.tsx
syntaxbullet dde5e77317 feat: enhance typography and accessibility in design system
- Added new UI heading utility with specific styles in typography.css.
- Updated typography tests to include new UI heading role and ensure proper size and styling.
- Documented design system audit findings, focusing on accessibility improvements and component consistency.
- Introduced CalendarExamples component to demonstrate calendar functionality with habit tracking.
- Created a shared Field component for consistent labeling and hinting of form controls.
- Implemented comprehensive accessibility tests for calendar and editor components, ensuring proper focus management and keyboard navigation.
- Added structure tests for Field and Card components to verify correct ID generation and heading levels.
2026-09-04 13:44:31 +02:00

70 lines
2.5 KiB
TypeScript

import { useEffect, useRef, type ReactNode } from "react";
import { useLocation, useNavigate } from "react-router";
type SystemTab = { id: string; label: string; content: ReactNode };
export function DesignSystemTabs({ tabs }: { tabs: SystemTab[] }) {
const { hash } = useLocation();
const navigate = useNavigate();
const buttons = useRef<Array<HTMLButtonElement | null>>([]);
// Keep bookmarks for retired preview sections useful without mounting them.
const replacementHash = hash === "#playground" ? "#calendar-states"
: hash === "#editing" || hash === "#views" ? "#foundations" : hash;
useEffect(() => {
if (replacementHash !== hash) navigate({ hash: replacementHash }, { replace: true });
}, [hash, replacementHash, navigate]);
const selected = tabs.findIndex((tab) => replacementHash === `#${tab.id}`);
const active = selected < 0 ? 0 : selected;
function select(index: number) {
navigate({ hash: `#${tabs[index]!.id}` });
}
return (
<div className="ds-explorer">
<div className="ds-tab-bar" role="tablist" aria-label="Design system sections">
{tabs.map((tab, index) => (
<button
key={tab.id}
ref={(element) => { buttons.current[index] = element; }}
type="button"
role="tab"
id={`ds-tab-${tab.id}`}
aria-controls={`ds-panel-${tab.id}`}
aria-selected={active === index}
tabIndex={active === index ? 0 : -1}
onClick={() => select(index)}
onKeyDown={(event) => {
let next = index;
if (event.key === "ArrowRight") next = (index + 1) % tabs.length;
else if (event.key === "ArrowLeft") next = (index - 1 + tabs.length) % tabs.length;
else if (event.key === "Home") next = 0;
else if (event.key === "End") next = tabs.length - 1;
else return;
event.preventDefault();
select(next);
buttons.current[next]?.focus();
}}
>
{tab.label}
</button>
))}
</div>
{/* Keep panels mounted so examples retain their local state. */}
{tabs.map((tab, index) => (
<div
key={tab.id}
id={`ds-panel-${tab.id}`}
role="tabpanel"
aria-labelledby={`ds-tab-${tab.id}`}
tabIndex={0}
hidden={active !== index}
className="ds-tab-panel"
>
{tab.content}
</div>
))}
</div>
);
}