mirror of
https://github.com/Since-3/since3-website.git
synced 2026-10-12 19:51:16 +00:00
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
"use client"
|
|
|
|
import { useId, useRef, useState } from "react"
|
|
import { motion } from "motion/react"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
import { ContrastTool } from "./contrast-tool"
|
|
import { SnippetTool } from "./snippet-tool"
|
|
|
|
const TOOLS = [
|
|
{ key: "kontrast", label: "Kontrast-Check", Component: ContrastTool },
|
|
{ key: "snippet", label: "Snippet-Check", Component: SnippetTool },
|
|
] as const
|
|
|
|
type ToolKey = (typeof TOOLS)[number]["key"]
|
|
|
|
/** Glass panel hosting the live tools, with accessible tabs. */
|
|
export function ToolPanel() {
|
|
const id = useId()
|
|
const [active, setActive] = useState<ToolKey>("kontrast")
|
|
const tabRefs = useRef<Record<string, HTMLButtonElement | null>>({})
|
|
|
|
const select = (key: ToolKey) => {
|
|
if (key === active) return
|
|
setActive(key)
|
|
}
|
|
|
|
// Arrow keys move between tabs (WAI-ARIA tabs pattern)
|
|
const onKeyDown = (event: React.KeyboardEvent) => {
|
|
const index = TOOLS.findIndex((tool) => tool.key === active)
|
|
const offset = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0
|
|
if (!offset) return
|
|
event.preventDefault()
|
|
const next = TOOLS[(index + offset + TOOLS.length) % TOOLS.length]
|
|
select(next.key)
|
|
tabRefs.current[next.key]?.focus()
|
|
}
|
|
|
|
const ActiveTool = TOOLS.find((tool) => tool.key === active)!.Component
|
|
|
|
return (
|
|
<div className="glass p-4 sm:p-8">
|
|
<div
|
|
role="tablist"
|
|
aria-label="Werkzeuge"
|
|
onKeyDown={onKeyDown}
|
|
className="flex w-fit gap-1 rounded-full border border-white/10 p-1"
|
|
>
|
|
{TOOLS.map((tool) => {
|
|
const selected = tool.key === active
|
|
return (
|
|
<button
|
|
key={tool.key}
|
|
ref={(node) => {
|
|
tabRefs.current[tool.key] = node
|
|
}}
|
|
role="tab"
|
|
type="button"
|
|
id={`${id}-tab-${tool.key}`}
|
|
aria-selected={selected}
|
|
aria-controls={`${id}-panel`}
|
|
tabIndex={selected ? 0 : -1}
|
|
onClick={() => select(tool.key)}
|
|
className={cn(
|
|
"relative rounded-full px-4 py-2 font-mono text-label transition-colors duration-200",
|
|
selected ? "text-black" : "text-ink-300 hover:text-white"
|
|
)}
|
|
>
|
|
{selected ? (
|
|
<motion.span
|
|
layoutId={`${id}-indicator`}
|
|
className="absolute inset-0 rounded-full bg-white"
|
|
transition={{ type: "spring", duration: 0.35, bounce: 0.15 }}
|
|
/>
|
|
) : null}
|
|
<span className="relative">{tool.label}</span>
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<div
|
|
role="tabpanel"
|
|
id={`${id}-panel`}
|
|
aria-labelledby={`${id}-tab-${active}`}
|
|
className="mt-8"
|
|
>
|
|
{/* key resets the tool state when switching tabs */}
|
|
<ActiveTool key={active} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|