---
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<string | null>(null);

  return (
    <StickerPicker
      opened={pickerOpen}
      onClose={() => setPickerOpen(false)}
      onSelect={(emoji) => setSticker(emoji)}
      target={
        <ActionIcon onClick={() => setPickerOpen((o) => !o)}>
          {sticker ?? "😀"}
        </ActionIcon>
      }
      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 `<div ref>`. 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.
