mirror of
https://github.com/hex248/tsos.git
synced 2026-02-07 18:23:05 +00:00
keyboard component
This commit is contained in:
101
src/components/controls/ColorKeyboard.tsx
Normal file
101
src/components/controls/ColorKeyboard.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
|
import { colorScale } from "@/constants/colorScale";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const WHITE_KEYS = ["C", "D", "E", "F", "G", "A", "B"];
|
||||||
|
const BLACK_KEYS = [
|
||||||
|
{ note: "C#", position: 0 },
|
||||||
|
{ note: "D#", position: 1 },
|
||||||
|
{ note: "F#", position: 3 },
|
||||||
|
{ note: "G#", position: 4 },
|
||||||
|
{ note: "A#", position: 5 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function ColorKeyboard({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
onChange: (color: string) => void;
|
||||||
|
}) {
|
||||||
|
const colorByNote = Object.fromEntries(colorScale.map((entry) => [entry.note, entry.color]));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex w-full select-none">
|
||||||
|
{WHITE_KEYS.map((note) => {
|
||||||
|
const color = colorByNote[note];
|
||||||
|
const isActive = color.toLowerCase() === value.toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Toggle
|
||||||
|
key={note}
|
||||||
|
pressed={isActive}
|
||||||
|
onPressedChange={() => onChange(color)}
|
||||||
|
className={cn(
|
||||||
|
"relative flex flex-1 items-end justify-center rounded-none border border-foreground dark:border-background cursor-pointer",
|
||||||
|
"h-36 min-w-0 px-0",
|
||||||
|
note === "C" ? "rounded-l-md" : "",
|
||||||
|
note === "B" ? "rounded-r-md" : "",
|
||||||
|
isActive ? "z-10" : "",
|
||||||
|
)}
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
aria-label={`Select ${note}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"mb-2 inline-flex size-6 items-center justify-center rounded-full text-xs font-medium leading-none",
|
||||||
|
"text-black",
|
||||||
|
)}
|
||||||
|
style={
|
||||||
|
isActive
|
||||||
|
? {
|
||||||
|
backgroundColor: note === "E" ? "#000000" : "#ffffff",
|
||||||
|
color: note === "E" ? "#ffffff" : "#000000",
|
||||||
|
}
|
||||||
|
: { backgroundColor: color }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{note}
|
||||||
|
</span>
|
||||||
|
</Toggle>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{BLACK_KEYS.map((key) => {
|
||||||
|
const color = colorByNote[key.note];
|
||||||
|
const isActive = color.toLowerCase() === value.toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Toggle
|
||||||
|
key={key.note}
|
||||||
|
size="sm"
|
||||||
|
pressed={isActive}
|
||||||
|
onPressedChange={() => onChange(color)}
|
||||||
|
className={cn(
|
||||||
|
"absolute top-0 z-20 h-24 rounded-none border border-black min-w-0 px-0 cursor-pointer",
|
||||||
|
isActive ? "" : "",
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
width: "8%",
|
||||||
|
left: `${(key.position + 1) * (100 / WHITE_KEYS.length) - 4}%`,
|
||||||
|
backgroundColor: color,
|
||||||
|
}}
|
||||||
|
aria-label={`Select ${key.note}`}
|
||||||
|
>
|
||||||
|
<span className={cn("flex h-full w-full items-end justify-center pb-2")}>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"inline-flex size-5 items-center justify-center rounded-full text-[10px] font-medium leading-none",
|
||||||
|
isActive ? "bg-white text-black" : "text-white",
|
||||||
|
)}
|
||||||
|
style={!isActive ? { backgroundColor: color } : undefined}
|
||||||
|
>
|
||||||
|
{key.note}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Toggle>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user