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>
40 lines
1.4 KiB
TypeScript
40 lines
1.4 KiB
TypeScript
import { Play } from 'lucide-react'
|
|
import { formatTime } from '@/lib/format'
|
|
import { cn } from '@/lib/cn'
|
|
import type { Chapter } from '@/types/abs'
|
|
|
|
interface Props {
|
|
chapters: Chapter[]
|
|
activeStart?: number
|
|
onJump: (start: number) => void
|
|
}
|
|
|
|
export function ChapterList({ chapters, activeStart, onJump }: Props) {
|
|
if (!chapters.length) return null
|
|
return (
|
|
<div className="overflow-hidden rounded-lg border border-border">
|
|
{chapters.map((c) => {
|
|
const active = activeStart != null && activeStart === c.start
|
|
return (
|
|
<button
|
|
key={c.id}
|
|
onClick={() => onJump(c.start)}
|
|
className={cn(
|
|
'group flex w-full items-center gap-3 border-b border-border px-3 py-2.5 text-left text-sm last:border-b-0 transition-colors hover:bg-surface-2',
|
|
active && 'bg-accent-soft',
|
|
)}
|
|
>
|
|
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-surface-2 text-text-muted group-hover:bg-accent group-hover:text-on-accent">
|
|
<Play size={13} />
|
|
</span>
|
|
<span className={cn('flex-1 truncate', active ? 'text-text' : 'text-text')}>
|
|
{c.title}
|
|
</span>
|
|
<span className="tnum shrink-0 text-xs text-text-muted">{formatTime(c.start)}</span>
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|