geraete/components/ui/Tabs.tsx
2025-11-18 14:44:36 +01:00

84 lines
2.7 KiB
TypeScript

// components/ui/Tabs.tsx
'use client';
import { ChevronDownIcon } from '@heroicons/react/16/solid';
import clsx from 'clsx';
import * as React from 'react';
export type TabItem = {
id: string;
label: string;
};
type TabsProps = {
tabs: TabItem[];
/** aktuell ausgewählter Tab (id) */
value: string;
/** Callback bei Wechsel */
onChange: (id: string) => void;
className?: string;
/** Optional eigenes aria-label */
ariaLabel?: string;
};
export default function Tabs({
tabs,
value,
onChange,
className,
ariaLabel = 'Ansicht auswählen',
}: TabsProps) {
const current = tabs.find((t) => t.id === value) ?? tabs[0];
return (
<div className={className}>
{/* Mobile: Select + Chevron */}
<div className="grid grid-cols-1 sm:hidden">
<select
value={current.id}
onChange={(e) => onChange(e.target.value)}
aria-label={ariaLabel}
className="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-2 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 dark:bg-white/5 dark:text-gray-100 dark:outline-white/10 dark:*:bg-gray-800 dark:focus:outline-indigo-500"
>
{tabs.map((tab) => (
<option key={tab.id} value={tab.id}>
{tab.label}
</option>
))}
</select>
<ChevronDownIcon
aria-hidden="true"
className="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end fill-gray-500 dark:fill-gray-400"
/>
</div>
{/* Desktop: Underline-Tabs */}
<div className="hidden sm:block">
<div className="border-b border-gray-200 dark:border-white/10">
<nav aria-label={ariaLabel} className="-mb-px flex space-x-8">
{tabs.map((tab) => {
const isCurrent = tab.id === current.id;
return (
<button
key={tab.id}
type="button"
onClick={() => onChange(tab.id)}
className={clsx(
isCurrent
? 'border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-400'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-white/20 dark:hover:text-gray-200',
'border-b-2 px-1 py-3 text-sm font-medium whitespace-nowrap',
)}
>
{tab.label}
</button>
);
})}
</nav>
</div>
</div>
</div>
);
}