feat(app): add TransposeBar collapsible header
This commit is contained in:
85
app/app/components/transpose-bar.tsx
Normal file
85
app/app/components/transpose-bar.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { ChevronUp, ChevronDown, Minus, Plus } from "lucide-react";
|
||||||
|
import type { SongMeta } from "~/lib/types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
meta: SongMeta;
|
||||||
|
offset: number;
|
||||||
|
onOffsetChange: (offset: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TransposeBar({ meta, offset, onOffsetChange }: Props) {
|
||||||
|
const [expanded, setExpanded] = useState(true);
|
||||||
|
|
||||||
|
const label = offset === 0
|
||||||
|
? "±0"
|
||||||
|
: offset > 0
|
||||||
|
? `+${offset}`
|
||||||
|
: `${offset}`;
|
||||||
|
|
||||||
|
if (!expanded) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between px-4 py-2 border-b bg-background sticky top-0">
|
||||||
|
<span className="text-sm font-semibold truncate">{meta.title}</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7"
|
||||||
|
onClick={() => setExpanded(true)}
|
||||||
|
>
|
||||||
|
<ChevronDown className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-b bg-background sticky top-0 px-4 py-3 flex flex-col gap-2">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-bold text-base">{meta.title}</span>
|
||||||
|
<span className="text-sm text-muted-foreground">{meta.artist}</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 shrink-0"
|
||||||
|
onClick={() => setExpanded(false)}
|
||||||
|
>
|
||||||
|
<ChevronUp className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-3 text-xs text-muted-foreground">
|
||||||
|
{meta.original_key && <span>Key: {meta.original_key}</span>}
|
||||||
|
{meta.capo != null && <span>Capo: {meta.capo}</span>}
|
||||||
|
{meta.tuning && <span>{meta.tuning}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => onOffsetChange(Math.max(-11, offset - 1))}
|
||||||
|
>
|
||||||
|
<Minus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<span className="w-8 text-center text-sm font-mono font-semibold">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => onOffsetChange(Math.min(11, offset + 1))}
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user