"use client"; import { useState, useCallback, useRef } from "react"; import { Color, parseColor } from "react-aria-components"; import { Popover } from "components/Popover"; import { GoToArrow } from "components/Icons/GoToArrow"; import { useSmoker } from "components/Toast"; import { CopyTiny } from "components/Icons/CopyTiny"; import { PasteTiny } from "components/Icons/PasteTiny"; const HEX_ROWS = [ ["1", "2", "3", "A", "B", "C"], ["4", "5", "6", "D", "E", "F"], ] as const; const BOTTOM_ROW = ["7", "8", "9", "0"] as const; const HexKey = (props: { onClick: () => void; children: React.ReactNode; className?: string; disabled?: boolean; ariaLabel?: string; }) => { return ( ); }; export const HexKeyboard = (props: { value: Color | undefined; setValue: (c: Color) => void; }) => { let hexString = props.value ? props.value.toString("hex").toUpperCase() : "#000000"; let [draft, setDraft] = useState(hexString); let committed = useRef(false); let copyButtonRef = useRef(null); let smoker = useSmoker(); let syncDraftToValue = useCallback(() => { let hex = props.value ? props.value.toString("hex").toUpperCase() : "#000000"; setDraft(hex); committed.current = true; }, [props.value]); let handleKey = (key: string) => { if (committed.current) { setDraft("#" + key); committed.current = false; return; } // Max hex length is 7 chars including # if (draft.length >= 7) return; setDraft((prev) => prev + key); }; let handleDelete = () => { committed.current = false; setDraft((prev) => { // Don't delete the # prefix if (prev.length <= 1) return prev; return prev.slice(0, -1); }); }; let handleSubmit = () => { try { // Pad short values: if user typed #ABC, expand to #AABBCC let raw = draft.replace("#", ""); let padded = raw; if (raw.length === 3) { padded = raw[0] + raw[0] + raw[1] + raw[1] + raw[2] + raw[2]; } else if (raw.length < 6) { padded = raw.padEnd(6, "0"); } let color = parseColor("#" + padded); props.setValue(color); setDraft("#" + padded.toUpperCase()); committed.current = true; } catch { // Invalid color, reset draft syncDraftToValue(); } }; let isValidPartialHex = /^#[0-9A-Fa-f]{0,6}$/.test(draft); let isSubmittable = /^#[0-9A-Fa-f]{3}$/.test(draft) || /^#[0-9A-Fa-f]{6}$/.test(draft); let handleCopy = () => { navigator.clipboard.writeText(draft); let rect = copyButtonRef.current?.getBoundingClientRect(); smoker({ position: { x: rect ? rect.left + rect.width / 2 : 0, y: rect ? rect.top - 8 : 0, }, text: "Copied!", }); }; let handlePaste = async () => { try { let text = await navigator.clipboard.readText(); let cleaned = text.trim(); if (!cleaned.startsWith("#")) cleaned = "#" + cleaned; cleaned = cleaned.toUpperCase(); if (/^#[0-9A-Fa-f]{0,6}$/.test(cleaned)) { setDraft(cleaned); committed.current = false; } } catch { // Clipboard read failed, do nothing } }; return ( { if (open) { syncDraftToValue(); } }} trigger={
{hexString}
} >
{/* Display current draft value */}
{draft}
{/* Copy / Paste buttons */}

{/* Hex keyboard grid — 6 columns */}
{/* Row 1: 1 2 3 A B C */} {HEX_ROWS[0].map((key) => ( handleKey(key)}> {key} ))} {/* Row 2: 4 5 6 D E F */} {HEX_ROWS[1].map((key) => ( handleKey(key)}> {key} ))} {/* Row 3: 7 8 9 0 [delete] [submit] */} {BOTTOM_ROW.map((key) => ( handleKey(key)}> {key} ))} ⌫
); };