Initial commit: Shelfless – alternative Audiobookshelf frontend
React + Vite + TypeScript SPA covering the full ABS feature set (library browsing, item detail, metadata/cover editing, podcasts, player with session sync, admin: users/libraries/scanner/server settings). Dev uses a dynamic CORS proxy; production is served by server/index.mjs (static + reverse proxy to ABS_URL). Includes systemd unit and installer under deploy/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
38
src/components/ui/Tabs.tsx
Normal file
38
src/components/ui/Tabs.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { cn } from '@/lib/cn'
|
||||
|
||||
export interface TabDef {
|
||||
key: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
tabs: TabDef[]
|
||||
active: string
|
||||
onChange: (key: string) => void
|
||||
}
|
||||
|
||||
export function Tabs({ tabs, active, onChange }: Props) {
|
||||
return (
|
||||
<div role="tablist" className="flex gap-1 border-b border-border">
|
||||
{tabs.map((t) => (
|
||||
<button
|
||||
key={t.key}
|
||||
role="tab"
|
||||
aria-selected={active === t.key}
|
||||
onClick={() => onChange(t.key)}
|
||||
className={cn(
|
||||
'relative -mb-px px-4 py-2.5 text-sm font-medium transition-colors',
|
||||
active === t.key
|
||||
? 'text-text'
|
||||
: 'text-text-muted hover:text-text',
|
||||
)}
|
||||
>
|
||||
{t.label}
|
||||
{active === t.key && (
|
||||
<span className="absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-accent" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user