--- name: sticker_picker kind: component lang: ts domain: ui version: "1.0.0" framework: react purity: impure signature: "StickerPicker(props: StickerPickerProps): JSX.Element" description: "Selector de emoji/sticker encapsulado en un Popover de Mantine. Monta emoji-mart Picker una sola vez para evitar re-creaciones en cada render." tags: [emoji, sticker, picker, popover, mantine, emoji-mart, react] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: - "@mantine/core" - "@emoji-mart/data" - "emoji-mart" tested: false tests: [] test_file_path: "" file_path: "frontend/functions/ui/sticker_picker.tsx" props: - name: opened type: "boolean" required: true description: "Controla si el Popover está abierto." - name: onClose type: "() => void" required: true description: "Callback invocado al cerrar (click fuera, Escape, o tras selección)." - name: onSelect type: "(emoji: string) => void" required: true description: "Callback invocado con el emoji seleccionado. Prefiere unicode nativo; si no está disponible usa shortcode." - name: target type: "React.ReactNode" required: true description: "Elemento ancla que dispara el Popover." - name: theme type: '"dark" | "light" | "auto"' required: false description: 'Tema visual del Picker de emoji-mart. Por defecto "dark".' - name: position type: 'PopoverProps["position"]' required: false description: 'Posición del Popover respecto al ancla. Por defecto "bottom-start".' emits: [onClose, onSelect] has_state: false output: "Popover de Mantine con emoji-mart Picker embebido. Tras selección emite onSelect(emoji) y onClose()." params: - name: opened desc: "Estado de visibilidad del picker. Gestionado por el componente padre." - name: onClose desc: "Se llama cuando el picker debe cerrarse." - name: onSelect desc: "Se llama con el string unicode o shortcode del emoji elegido." - name: target desc: "Nodo React que actúa como ancla del Popover (generalmente un botón o icono)." - name: theme desc: 'Tema de color del picker. Valores: "dark" | "light" | "auto". Por defecto "dark".' - name: position desc: 'Posición del Popover. Cualquier valor de PopoverProps["position"] de Mantine. Por defecto "bottom-start".' --- ## Ejemplo ```tsx import { useState } from "react"; import { ActionIcon } from "@mantine/core"; import { StickerPicker } from "@fn_library/sticker_picker"; function MyCard() { const [pickerOpen, setPickerOpen] = useState(false); const [sticker, setSticker] = useState(null); return ( setPickerOpen(false)} onSelect={(emoji) => setSticker(emoji)} target={ setPickerOpen((o) => !o)}> {sticker ?? "😀"} } theme="dark" position="bottom-start" /> ); } ``` ## Notas **Dependencias externas requeridas** (no incluidas en el registry): ```bash pnpm add @emoji-mart/data emoji-mart ``` El componente interno `PickerInner` instancia `emoji-mart` Picker con `useEffect` y lo monta en un `
`. El cleanup borra el `innerHTML` del div al desmontar. `onSelectRef` mantiene el callback actualizado sin recrear la instancia en cada render del padre. El prop `theme` se pasa al montar — cambios posteriores de `theme` no provocan remontaje (la instancia mantiene el tema inicial), comportamiento aceptable para la mayoría de casos de uso. Si se necesita cambio dinámico de tema, destructurar el key del componente padre fuerza remontaje. El Popover usa `withinPortal` para evitar clipping por overflow de contenedores padre, `closeOnClickOutside` y `closeOnEscape` para comportamiento estándar, y `trapFocus={false}` para que emoji-mart gestione el foco internamente.