chore init
This commit is contained in:
1 parent
1b7406b1d3
commit
a0d312a884
60 files changed
+5046
-125
No files matched your search
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { type KeyboardEvent } from "react";
|
||||
|
||||
export interface TabItem<T extends string> {
|
||||
id: T;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface TabsProps<T extends string> {
|
||||
items: TabItem<T>[];
|
||||
activeId: T;
|
||||
onChange: (id: T) => void;
|
||||
}
|
||||
|
||||
export function Tabs<T extends string>({ items, activeId, onChange }: TabsProps<T>) {
|
||||
function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {
|
||||
const currentIndex = items.findIndex((item) => item.id === activeId);
|
||||
if (event.key === "ArrowRight") {
|
||||
event.preventDefault();
|
||||
const next = items.at((currentIndex + 1) % items.length);
|
||||
if (next) onChange(next.id);
|
||||
} else if (event.key === "ArrowLeft") {
|
||||
event.preventDefault();
|
||||
const prev = items.at((currentIndex - 1 + items.length) % items.length);
|
||||
if (prev) onChange(prev.id);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div role="tablist" aria-label="Sections du CV" className="flex flex-wrap gap-1">
|
||||
{items.map((item) => {
|
||||
const selected = item.id === activeId;
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
role="tab"
|
||||
id={`tab-${item.id}`}
|
||||
aria-selected={selected}
|
||||
aria-controls={`panel-${item.id}`}
|
||||
tabIndex={selected ? 0 : -1}
|
||||
onClick={() => onChange(item.id)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={`rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${
|
||||
selected
|
||||
? "bg-accent text-white"
|
||||
: "border border-border bg-white text-muted hover:text-ink"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user