63 lines
1.5 KiB
TypeScript
63 lines
1.5 KiB
TypeScript
// /components/ui/Tabs.tsx
|
|
|
|
'use client'
|
|
|
|
import * as React from 'react'
|
|
|
|
export type TabItem = {
|
|
id: string
|
|
label: string
|
|
}
|
|
|
|
export type TabsProps = {
|
|
tabs: TabItem[]
|
|
defaultTabId?: string
|
|
onChange?(id: string): void
|
|
className?: string
|
|
}
|
|
|
|
export function Tabs({ tabs, defaultTabId, onChange, className }: TabsProps) {
|
|
const [activeId, setActiveId] = React.useState(
|
|
defaultTabId ?? (tabs[0]?.id ?? ''),
|
|
)
|
|
|
|
const handleClick = (id: string) => {
|
|
setActiveId(id)
|
|
onChange?.(id)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={[
|
|
'border-b border-gray-200 dark:border-white/10',
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')}
|
|
>
|
|
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
|
|
{tabs.map((tab) => {
|
|
const isActive = tab.id === activeId
|
|
return (
|
|
<button
|
|
key={tab.id}
|
|
type="button"
|
|
onClick={() => handleClick(tab.id)}
|
|
className={[
|
|
'whitespace-nowrap border-b-2 px-1 pb-3 pt-2 text-sm/6 font-semibold transition-colors',
|
|
isActive
|
|
? 'border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-300'
|
|
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200',
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
)
|
|
})}
|
|
</nav>
|
|
</div>
|
|
)
|
|
}
|