import { useState, useEffect } from "react"; import { oklch, formatHex, inGamut, parse, type Oklch, rgb } from "culori"; import { Slider } from "./Slider"; import { displaySupportsP3, roundOklch } from "./utils"; export type LchChannel = "l" | "c" | "h"; export const toOklchString = (color: Oklch) => { return `oklch(${color.l} ${color.c} ${color.h})`; }; export type i18nKeys = LchChannel | "fallback" | "unsupported"; export type I18nProvider = (key: i18nKeys) => string; interface PickerProps extends React.HTMLAttributes { selectedColor: Oklch; onColorChange: (value: Oklch) => void; useP3: boolean; i18n: I18nProvider; } const Preview = ({ color, i18n }: { color: Oklch; i18n: I18nProvider }) => { const supportsP3 = displaySupportsP3(); const outOfRgb = !inGamut("rgb")(color); const outOfP3 = !inGamut("p3")(color); if (outOfP3 || (outOfRgb && !supportsP3)) { const rgbColor = rgb(color); const hex = formatHex(rgbColor); const fallbackColor = supportsP3 ? hex : toOklchString(color); return (
{i18n("unsupported")}
{i18n("fallback")}
); } else if (outOfRgb && supportsP3) { const rgbColor = rgb(color); const hex = formatHex(rgbColor); return (
P3
{i18n("fallback")}
); } return (
); }; export const Picker = ({ useP3, i18n, selectedColor, onColorChange, ...rest }: PickerProps) => { const [displayColor, setDisplayColor] = useState(selectedColor); const [hexText, setHexText] = useState(formatHex(selectedColor)); const [oklchText, setOklchText] = useState(toOklchString(selectedColor)); useEffect(() => { try { setHexText(formatHex(selectedColor)); setOklchText(toOklchString(selectedColor)); setDisplayColor(selectedColor); } catch (error) { console.warn("Invalid color combination"); } }, [selectedColor]); const handleChange = (channel: LchChannel, value: number) => { setDisplayColor((prev) => ({ ...prev, [channel]: value })); onColorChange({ ...selectedColor, [channel]: value }); }; const generateChangeHandler = (channel: LchChannel) => { return (value: number) => { handleChange(channel, value); }; }; const handleHexChange = (e: React.ChangeEvent) => { const hex = e.target.value; const color = parse(hex); const oklchColor = oklch(rgb(color)); if (oklchColor) { setDisplayColor(roundOklch(oklchColor)); } setHexText(hex); }; const handleHexInput = (e: React.ChangeEvent) => { const hex = e.target.value; const color = parse(hex); const oklchColor = oklch(rgb(color)); if (oklchColor) { onColorChange(roundOklch(oklchColor)); setHexText(hex); } else { setHexText(formatHex(selectedColor)); } }; const handleOklchChange = (e: React.ChangeEvent) => { const oklchString = e.target.value; const color = parse(oklchString); const oklchColor = oklch(color); if (oklchColor) { setDisplayColor(roundOklch(oklchColor)); } setOklchText(oklchString); }; const handleOklchInput = (e: React.ChangeEvent) => { const oklchString = e.target.value; const oklchColor = oklch(parse(oklchString)); if (oklchColor) { onColorChange(roundOklch(oklchColor)); setOklchText(oklchString); } else { setOklchText(toOklchString(selectedColor)); } }; return (
); };