"use client"; /** * EmojiPicker โ a tiny, dependency-free emoji popover for text inputs. * * Renders just a trigger button; the panel is portaled to
with fixed * positioning and a transparent full-screen backdrop that swallows the * dismiss click. That keeps it OUTSIDE any surrounding drawer/sheet DOM, so * opening it can't resize or reposition the host (e.g. the vaul comments * drawer), and the dismiss click can't leak through and close that drawer. * * It stays open after a pick so several emojis can be added in a row; the * caller decides what to do with each via `onSelect`. */ import { useRef, useState } from "react"; import { createPortal } from "react-dom"; import { Smile } from "lucide-react"; import { cn } from "@/lib/utils"; // A curated, broadly-useful set โ enough to feel like a picker without // shipping a full emoji database. Grouped for easy scanning. const EMOJI_GROUPS: { label: string; emojis: string[] }[] = [ { label: "Smileys", emojis: [ "๐", "๐", "๐", "๐", "๐ ", "๐", "๐", "๐", "๐", "๐", "๐ฅฐ", "๐", "๐", "๐", "๐", "๐ค", "๐ค", "๐ด", "๐ข", "๐ญ", "๐ฎ", "๐ฒ", "๐ฅฒ", "๐ฌ", ], }, { label: "Gestures & people", emojis: [ "๐", "๐", "๐", "๐", "๐", "๐ค", "โ๏ธ", "๐ค", "๐ช", "๐ซถ", "๐", "๐", "๐ง", "๐๏ธ", "โฎ๏ธ", "๐คฒ", ], }, { label: "Hearts", emojis: [ "โค๏ธ", "๐งก", "๐", "๐", "๐", "๐", "๐ค", "๐ค", "๐ค", "๐", "๐", "๐", "๐", "๐", "๐", "โฃ๏ธ", ], }, { label: "Nature & spirit", emojis: [ "๐ธ", "๐บ", "๐น", "๐ชท", "๐ป", "๐ผ", "๐", "๐ฟ", "๐ช", "๐ฏ๏ธ", "๐ฅ", "โจ", "๐", "โญ", "โ๏ธ", "๐", ], }, { label: "Symbols", emojis: [ "๐", "๐", "๐ฅณ", "๐ฏ", "โ ", "โ", "โ", "๐ฌ", "๐ฟ", "๐", "๐ฎ", "๐ฆ", "๐ช", "๐ถ", "๐", "๐๐ฝ", ], }, ]; const PANEL_WIDTH = 300; export function EmojiPicker({ onSelect, className, }: { onSelect: (emoji: string) => void; className?: string; }) { const [open, setOpen] = useState(false); const [pos, setPos] = useState<{ bottom: number; left: number } | null>(null); const btnRef = useRef{g.label}