Components
Loading preview...
A 2D color picker for selecting colors from a gradient area.
@reapollo
npx shadcn@latest add https://21st.dev/r/larsen66/heroui-color-area"use client";
import { useState } from "react";
import { ColorArea, parseColor, type ColorValue } from "@/components/ui/heroui-color-area";
type ColorSpace = "rgb" | "hsl" | "hsb";
type ColorChannel = "hue" | "saturation" | "brightness" | "lightness" | "red" | "green" | "blue";
const colorSpaces: Array<{ id: ColorSpace; name: string }> = [
{ id: "rgb", name: "RGB" },
{ id: "hsl", name: "HSL" },
{ id: "hsb", name: "HSB" },
];
const channelsBySpace: Record<ColorSpace, Array<{ id: ColorChannel; name: string }>> = {
hsb: [
{ id: "hue", name: "Hue" },
{ id: "saturation", name: "Saturation" },
{ id: "brightness", name: "Brightness" },
],
hsl: [
{ id: "hue", name: "Hue" },
{ id: "saturation", name: "Saturation" },
{ id: "lightness", name: "Lightness" },
],
rgb: [
{ id: "red", name: "Red" },
{ id: "green", name: "Green" },
{ id: "blue", name: "Blue" },
],
};
export default function ColorSpaceChannelsDemo() {
const [colorSpace, setColorSpace] = useState<ColorSpace>("hsb");
const [color, setColor] = useState<ColorValue>(() => parseColor("hsb(219, 58%, 93%)"));
const [xChannel, setXChannel] = useState<ColorChannel>("saturation");
const [yChannel, setYChannel] = useState<ColorChannel>("brightness");
const channels = channelsBySpace[colorSpace];
const xChannelOptions = channels.filter((channel) => channel.id !== yChannel);
const yChannelOptions = channels.filter((channel) => channel.id !== xChannel);
function handleColorSpaceChange(value: ColorSpace) {
setColorSpace(value);
if (value === "rgb") {
setXChannel("blue");
setYChannel("green");
} else if (value === "hsl") {
setXChannel("saturation");
setYChannel("lightness");
} else {
setXChannel("saturation");
setYChannel("brightness");
}
}
return (
<div className="flex min-h-screen w-full items-center justify-center overflow-hidden bg-background p-8">
<div className="flex flex-col items-center gap-6">
<div className="flex gap-4">
<label className="select select--primary w-32">
<span className="label" data-slot="label">Color Space</span>
<select
aria-label="Color Space"
className="select__trigger"
value={colorSpace}
onChange={(event) => handleColorSpaceChange(event.target.value as ColorSpace)}
>
{colorSpaces.map((space) => (
<option key={space.id} value={space.id}>
{space.name}
</option>
))}
</select>
</label>
<label className="select select--primary w-36">
<span className="label" data-slot="label">X Axis</span>
<select
aria-label="X Axis"
className="select__trigger"
value={xChannel}
onChange={(event) => setXChannel(event.target.value as ColorChannel)}
>
{xChannelOptions.map((channel) => (
<option key={channel.id} value={channel.id}>
{channel.name}
</option>
))}
</select>
</label>
<label className="select select--primary w-36">
<span className="label" data-slot="label">Y Axis</span>
<select
aria-label="Y Axis"
className="select__trigger"
value={yChannel}
onChange={(event) => setYChannel(event.target.value as ColorChannel)}
>
{yChannelOptions.map((channel) => (
<option key={channel.id} value={channel.id}>
{channel.name}
</option>
))}
</select>
</label>
</div>
<ColorArea colorSpace={colorSpace} value={color} xChannel={xChannel} yChannel={yChannel} onChange={setColor}>
<ColorArea.Thumb />
</ColorArea>
<div className="flex items-center gap-3">
<div
className="size-8 rounded-md border border-default"
style={{ backgroundColor: color.toString("css") }}
/>
<code className="rounded bg-default/50 px-2 py-1 text-sm">{color.toString(colorSpace)}</code>
</div>
</div>
</div>
);
}
export { ColorSpaceChannelsDemo };