{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "emoji-toolbar-button",
  "title": "Emoji Toolbar Button",
  "description": "An emoji picker toolbar button.",
  "dependencies": [
    "@platejs/emoji",
    "@emoji-mart/data@1.2.1",
    "@radix-ui/react-popover"
  ],
  "registryDependencies": [
    "https://platejs.org/r/toolbar.json"
  ],
  "files": [
    {
      "path": "src/registry/ui/emoji-toolbar-button.tsx",
      "content": "'use client';\n/* eslint-disable react-hooks/refs */\n\nimport * as React from 'react';\n\nimport type { Emoji } from '@emoji-mart/data';\n\nimport {\n  type EmojiCategoryList,\n  type EmojiIconList,\n  type GridRow,\n  EmojiSettings,\n} from '@platejs/emoji';\nimport {\n  type EmojiDropdownMenuOptions,\n  type UseEmojiPickerType,\n  useEmojiDropdownMenuState,\n} from '@platejs/emoji/react';\nimport * as Popover from '@radix-ui/react-popover';\nimport {\n  AppleIcon,\n  ClockIcon,\n  CompassIcon,\n  FlagIcon,\n  LeafIcon,\n  LightbulbIcon,\n  MusicIcon,\n  SearchIcon,\n  SmileIcon,\n  StarIcon,\n  XIcon,\n} from 'lucide-react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from '@/components/ui/tooltip';\nimport { cn } from '@/lib/utils';\nimport { ToolbarButton } from '@/registry/ui/toolbar';\n\nexport function EmojiToolbarButton({\n  options,\n  ...props\n}: {\n  options?: EmojiDropdownMenuOptions;\n} & React.ComponentPropsWithoutRef<typeof ToolbarButton>) {\n  const { emojiPickerState, isOpen, setIsOpen } =\n    useEmojiDropdownMenuState(options);\n\n  return (\n    <EmojiPopover\n      control={\n        <ToolbarButton pressed={isOpen} tooltip=\"Emoji\" isDropdown {...props}>\n          <SmileIcon />\n        </ToolbarButton>\n      }\n      isOpen={isOpen}\n      setIsOpen={setIsOpen}\n    >\n      <EmojiPicker\n        {...emojiPickerState}\n        isOpen={isOpen}\n        setIsOpen={setIsOpen}\n        settings={options?.settings}\n      />\n    </EmojiPopover>\n  );\n}\n\nexport function EmojiPopover({\n  children,\n  control,\n  isOpen,\n  setIsOpen,\n}: {\n  children: React.ReactNode;\n  control: React.ReactNode;\n  isOpen: boolean;\n  setIsOpen: (open: boolean) => void;\n}) {\n  return (\n    <Popover.Root open={isOpen} onOpenChange={setIsOpen}>\n      <Popover.Trigger asChild>{control}</Popover.Trigger>\n\n      <Popover.Portal>\n        <Popover.Content className=\"z-100\">{children}</Popover.Content>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nexport function EmojiPicker({\n  clearSearch,\n  emoji,\n  emojiLibrary,\n  focusedCategory,\n  hasFound,\n  i18n,\n  icons = {\n    categories: emojiCategoryIcons,\n    search: emojiSearchIcons,\n  },\n  isSearching,\n  refs,\n  searchResult,\n  searchValue,\n  setSearch,\n  settings = EmojiSettings,\n  visibleCategories,\n  handleCategoryClick,\n  onMouseOver,\n  onSelectEmoji,\n}: Omit<UseEmojiPickerType, 'icons'> & {\n  icons?: EmojiIconList<React.ReactElement>;\n}) {\n  return (\n    <div\n      className={cn(\n        'flex flex-col rounded-xl bg-popover text-popover-foreground',\n        'h-[23rem] w-80 border shadow-md'\n      )}\n    >\n      <EmojiPickerNavigation\n        onClick={handleCategoryClick}\n        emojiLibrary={emojiLibrary}\n        focusedCategory={focusedCategory}\n        i18n={i18n}\n        icons={icons}\n      />\n      <EmojiPickerSearchBar\n        i18n={i18n}\n        searchValue={searchValue}\n        setSearch={setSearch}\n      >\n        <EmojiPickerSearchAndClear\n          clearSearch={clearSearch}\n          i18n={i18n}\n          searchValue={searchValue}\n        />\n      </EmojiPickerSearchBar>\n      <EmojiPickerContent\n        onMouseOver={onMouseOver}\n        onSelectEmoji={onSelectEmoji}\n        emojiLibrary={emojiLibrary}\n        i18n={i18n}\n        isSearching={isSearching}\n        refs={refs}\n        searchResult={searchResult}\n        settings={settings}\n        visibleCategories={visibleCategories}\n      />\n      <EmojiPickerPreview\n        emoji={emoji}\n        hasFound={hasFound}\n        i18n={i18n}\n        isSearching={isSearching}\n      />\n    </div>\n  );\n}\n\nconst EmojiButton = React.memo(function EmojiButton({\n  emoji,\n  index,\n  onMouseOver,\n  onSelect,\n}: {\n  emoji: Emoji;\n  index: number;\n  onMouseOver: (emoji?: Emoji) => void;\n  onSelect: (emoji: Emoji) => void;\n}) {\n  return (\n    <button\n      className=\"group relative flex size-9 cursor-pointer items-center justify-center border-none bg-transparent text-2xl leading-none\"\n      onClick={() => onSelect(emoji)}\n      onMouseEnter={() => onMouseOver(emoji)}\n      onMouseLeave={() => onMouseOver()}\n      aria-label={emoji.skins[0].native}\n      data-index={index}\n      tabIndex={-1}\n      type=\"button\"\n    >\n      <div\n        className=\"absolute inset-0 rounded-full opacity-0 group-hover:opacity-100\"\n        aria-hidden=\"true\"\n      />\n      <span\n        className=\"relative\"\n        style={{\n          fontFamily:\n            '\"Apple Color Emoji\", \"Segoe UI Emoji\", NotoColorEmoji, \"Noto Color Emoji\", \"Segoe UI Symbol\", \"Android Emoji\", EmojiSymbols',\n        }}\n        data-emoji-set=\"native\"\n      >\n        {emoji.skins[0].native}\n      </span>\n    </button>\n  );\n});\n\nconst RowOfButtons = React.memo(function RowOfButtons({\n  emojiLibrary,\n  row,\n  onMouseOver,\n  onSelectEmoji,\n}: {\n  row: GridRow;\n} & Pick<\n  UseEmojiPickerType,\n  'emojiLibrary' | 'onMouseOver' | 'onSelectEmoji'\n>) {\n  return (\n    <div key={row.id} className=\"flex\" data-index={row.id}>\n      {row.elements.map((emojiId, index) => (\n        <EmojiButton\n          key={emojiId}\n          onMouseOver={onMouseOver}\n          onSelect={onSelectEmoji}\n          emoji={emojiLibrary.getEmoji(emojiId)}\n          index={index}\n        />\n      ))}\n    </div>\n  );\n});\n\nfunction EmojiPickerContent({\n  emojiLibrary,\n  i18n,\n  isSearching = false,\n  refs,\n  searchResult,\n  settings = EmojiSettings,\n  visibleCategories,\n  onMouseOver,\n  onSelectEmoji,\n}: Pick<\n  UseEmojiPickerType,\n  | 'emojiLibrary'\n  | 'i18n'\n  | 'isSearching'\n  | 'onMouseOver'\n  | 'onSelectEmoji'\n  | 'refs'\n  | 'searchResult'\n  | 'settings'\n  | 'visibleCategories'\n>) {\n  const getRowWidth = settings.perLine.value * settings.buttonSize.value;\n\n  const isCategoryVisible = React.useCallback(\n    (categoryId: any) =>\n      visibleCategories.has(categoryId)\n        ? visibleCategories.get(categoryId)\n        : false,\n    [visibleCategories]\n  );\n\n  const EmojiList = React.useCallback(\n    () =>\n      emojiLibrary\n        .getGrid()\n        .sections()\n        .map(({ id: categoryId }) => {\n          const section = emojiLibrary.getGrid().section(categoryId);\n          const { buttonSize } = settings;\n\n          return (\n            <div\n              key={categoryId}\n              ref={section.root}\n              style={{ width: getRowWidth }}\n              data-id={categoryId}\n            >\n              <div className=\"-top-px sticky z-1 bg-popover/90 p-1 py-2 font-semibold text-sm backdrop-blur-xs\">\n                {i18n.categories[categoryId]}\n              </div>\n              <div\n                className=\"relative flex flex-wrap\"\n                style={{ height: section.getRows().length * buttonSize.value }}\n              >\n                {isCategoryVisible(categoryId) &&\n                  section\n                    .getRows()\n                    .map((row: GridRow) => (\n                      <RowOfButtons\n                        key={row.id}\n                        onMouseOver={onMouseOver}\n                        onSelectEmoji={onSelectEmoji}\n                        emojiLibrary={emojiLibrary}\n                        row={row}\n                      />\n                    ))}\n              </div>\n            </div>\n          );\n        }),\n    [\n      emojiLibrary,\n      getRowWidth,\n      i18n.categories,\n      isCategoryVisible,\n      onSelectEmoji,\n      onMouseOver,\n      settings,\n    ]\n  );\n\n  const SearchList = React.useCallback(\n    () => (\n      <div style={{ width: getRowWidth }} data-id=\"search\">\n        <div className=\"-top-px sticky z-1 bg-popover/90 p-1 py-2 font-semibold text-card-foreground text-sm backdrop-blur-xs\">\n          {i18n.searchResult}\n        </div>\n        <div className=\"relative flex flex-wrap\">\n          {searchResult.map((emoji: Emoji, index: number) => (\n            <EmojiButton\n              key={emoji.id}\n              onMouseOver={onMouseOver}\n              onSelect={onSelectEmoji}\n              emoji={emojiLibrary.getEmoji(emoji.id)}\n              index={index}\n            />\n          ))}\n        </div>\n      </div>\n    ),\n    [\n      emojiLibrary,\n      getRowWidth,\n      i18n.searchResult,\n      searchResult,\n      onSelectEmoji,\n      onMouseOver,\n    ]\n  );\n\n  return (\n    <div\n      ref={refs.current.contentRoot}\n      className={cn(\n        'h-full min-h-[50%] overflow-y-auto overflow-x-hidden px-2',\n        '[&::-webkit-scrollbar]:w-4',\n        '[&::-webkit-scrollbar-button]:hidden [&::-webkit-scrollbar-button]:size-0',\n        '[&::-webkit-scrollbar-thumb]:min-h-11 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-muted [&::-webkit-scrollbar-thumb]:hover:bg-muted-foreground/25',\n        '[&::-webkit-scrollbar-thumb]:border-4 [&::-webkit-scrollbar-thumb]:border-popover [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:bg-clip-padding'\n      )}\n      data-id=\"scroll\"\n    >\n      <div ref={refs.current.content} className=\"h-full\">\n        {isSearching ? SearchList() : EmojiList()}\n      </div>\n    </div>\n  );\n}\n\nfunction EmojiPickerSearchBar({\n  children,\n  i18n,\n  searchValue,\n  setSearch,\n}: {\n  children: React.ReactNode;\n} & Pick<UseEmojiPickerType, 'i18n' | 'searchValue' | 'setSearch'>) {\n  return (\n    <div className=\"flex items-center px-2\">\n      <div className=\"relative flex grow items-center\">\n        <input\n          className=\"block w-full appearance-none rounded-full border-0 bg-muted px-10 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:outline-none\"\n          value={searchValue}\n          onChange={(event) => setSearch(event.target.value)}\n          placeholder={i18n.search}\n          aria-label=\"Search\"\n          autoComplete=\"off\"\n          type=\"text\"\n          autoFocus\n        />\n        {children}\n      </div>\n    </div>\n  );\n}\n\nfunction EmojiPickerSearchAndClear({\n  clearSearch,\n  i18n,\n  searchValue,\n}: Pick<UseEmojiPickerType, 'clearSearch' | 'i18n' | 'searchValue'>) {\n  return (\n    <div className=\"flex items-center text-foreground\">\n      <div\n        className={cn(\n          '-translate-y-1/2 absolute top-1/2 left-2.5 z-10 flex size-5 items-center justify-center text-foreground'\n        )}\n      >\n        {emojiSearchIcons.loupe}\n      </div>\n      {searchValue && (\n        <Button\n          size=\"icon\"\n          variant=\"ghost\"\n          className={cn(\n            '-translate-y-1/2 absolute top-1/2 right-0.5 flex size-8 cursor-pointer items-center justify-center rounded-full border-none bg-transparent text-popover-foreground hover:bg-transparent'\n          )}\n          onClick={clearSearch}\n          title={i18n.clear}\n          aria-label=\"Clear\"\n          type=\"button\"\n        >\n          {emojiSearchIcons.delete}\n        </Button>\n      )}\n    </div>\n  );\n}\n\nfunction EmojiPreview({ emoji }: Pick<UseEmojiPickerType, 'emoji'>) {\n  return (\n    <div className=\"flex h-14 max-h-14 min-h-14 items-center border-muted border-t p-2\">\n      <div className=\"flex items-center justify-center text-2xl\">\n        {emoji?.skins[0].native}\n      </div>\n      <div className=\"overflow-hidden pl-2\">\n        <div className=\"truncate font-semibold text-sm\">{emoji?.name}</div>\n        <div className=\"truncate text-sm\">{`:${emoji?.id}:`}</div>\n      </div>\n    </div>\n  );\n}\n\nfunction NoEmoji({ i18n }: Pick<UseEmojiPickerType, 'i18n'>) {\n  return (\n    <div className=\"flex h-14 max-h-14 min-h-14 items-center border-muted border-t p-2\">\n      <div className=\"flex items-center justify-center text-2xl\">😢</div>\n      <div className=\"overflow-hidden pl-2\">\n        <div className=\"truncate font-bold text-sm\">\n          {i18n.searchNoResultsTitle}\n        </div>\n        <div className=\"truncate text-sm\">{i18n.searchNoResultsSubtitle}</div>\n      </div>\n    </div>\n  );\n}\n\nfunction PickAnEmoji({ i18n }: Pick<UseEmojiPickerType, 'i18n'>) {\n  return (\n    <div className=\"flex h-14 max-h-14 min-h-14 items-center border-muted border-t p-2\">\n      <div className=\"flex items-center justify-center text-2xl\">☝️</div>\n      <div className=\"overflow-hidden pl-2\">\n        <div className=\"truncate font-semibold text-sm\">{i18n.pick}</div>\n      </div>\n    </div>\n  );\n}\n\nfunction EmojiPickerPreview({\n  emoji,\n  hasFound = true,\n  i18n,\n  isSearching = false,\n  ...props\n}: Pick<UseEmojiPickerType, 'emoji' | 'hasFound' | 'i18n' | 'isSearching'>) {\n  const showPickEmoji = !emoji && (!isSearching || hasFound);\n  const showNoEmoji = isSearching && !hasFound;\n  const showPreview = emoji && !showNoEmoji && !showNoEmoji;\n\n  return (\n    <>\n      {showPreview && <EmojiPreview emoji={emoji} {...props} />}\n      {showPickEmoji && <PickAnEmoji i18n={i18n} {...props} />}\n      {showNoEmoji && <NoEmoji i18n={i18n} {...props} />}\n    </>\n  );\n}\n\nfunction EmojiPickerNavigation({\n  emojiLibrary,\n  focusedCategory,\n  i18n,\n  icons,\n  onClick,\n}: {\n  onClick: (id: EmojiCategoryList) => void;\n} & Pick<\n  UseEmojiPickerType,\n  'emojiLibrary' | 'focusedCategory' | 'i18n' | 'icons'\n>) {\n  return (\n    <TooltipProvider delayDuration={500}>\n      <nav\n        id=\"emoji-nav\"\n        className=\"mb-2.5 border-0 border-b border-b-border border-solid p-1.5\"\n      >\n        <div className=\"relative flex items-center justify-evenly\">\n          {emojiLibrary\n            .getGrid()\n            .sections()\n            .map(({ id }) => (\n              <Tooltip key={id}>\n                <TooltipTrigger asChild>\n                  <Button\n                    size=\"sm\"\n                    variant=\"ghost\"\n                    className={cn(\n                      'h-fit rounded-full fill-current p-1.5 text-muted-foreground hover:bg-muted hover:text-muted-foreground',\n                      id === focusedCategory &&\n                        'pointer-events-none bg-accent fill-current text-accent-foreground'\n                    )}\n                    onClick={() => {\n                      onClick(id);\n                    }}\n                    aria-label={i18n.categories[id]}\n                    type=\"button\"\n                  >\n                    <span className=\"inline-flex size-5 items-center justify-center\">\n                      {icons.categories[id].outline}\n                    </span>\n                  </Button>\n                </TooltipTrigger>\n                <TooltipContent side=\"bottom\">\n                  {i18n.categories[id]}\n                </TooltipContent>\n              </Tooltip>\n            ))}\n        </div>\n      </nav>\n    </TooltipProvider>\n  );\n}\n\nconst emojiCategoryIcons: Record<\n  EmojiCategoryList,\n  {\n    outline: React.ReactElement;\n    solid: React.ReactElement; // Needed to add another solid variant - outline will be used for now\n  }\n> = {\n  activity: {\n    outline: (\n      <svg\n        className=\"size-full\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"2\"\n        viewBox=\"0 0 24 24\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" />\n        <path d=\"M2.1 13.4A10.1 10.1 0 0 0 13.4 2.1\" />\n        <path d=\"m5 4.9 14 14.2\" />\n        <path d=\"M21.9 10.6a10.1 10.1 0 0 0-11.3 11.3\" />\n      </svg>\n    ),\n    solid: (\n      <svg\n        className=\"size-full\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"2\"\n        viewBox=\"0 0 24 24\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" />\n        <path d=\"M2.1 13.4A10.1 10.1 0 0 0 13.4 2.1\" />\n        <path d=\"m5 4.9 14 14.2\" />\n        <path d=\"M21.9 10.6a10.1 10.1 0 0 0-11.3 11.3\" />\n      </svg>\n    ),\n  },\n\n  custom: {\n    outline: <StarIcon className=\"size-full\" />,\n    solid: <StarIcon className=\"size-full\" />,\n  },\n\n  flags: {\n    outline: <FlagIcon className=\"size-full\" />,\n    solid: <FlagIcon className=\"size-full\" />,\n  },\n\n  foods: {\n    outline: <AppleIcon className=\"size-full\" />,\n    solid: <AppleIcon className=\"size-full\" />,\n  },\n\n  frequent: {\n    outline: <ClockIcon className=\"size-full\" />,\n    solid: <ClockIcon className=\"size-full\" />,\n  },\n\n  nature: {\n    outline: <LeafIcon className=\"size-full\" />,\n    solid: <LeafIcon className=\"size-full\" />,\n  },\n\n  objects: {\n    outline: <LightbulbIcon className=\"size-full\" />,\n    solid: <LightbulbIcon className=\"size-full\" />,\n  },\n\n  people: {\n    outline: <SmileIcon className=\"size-full\" />,\n    solid: <SmileIcon className=\"size-full\" />,\n  },\n\n  places: {\n    outline: <CompassIcon className=\"size-full\" />,\n    solid: <CompassIcon className=\"size-full\" />,\n  },\n\n  symbols: {\n    outline: <MusicIcon className=\"size-full\" />,\n    solid: <MusicIcon className=\"size-full\" />,\n  },\n};\n\nconst emojiSearchIcons = {\n  delete: <XIcon className=\"size-4 text-current\" />,\n  loupe: <SearchIcon className=\"size-4 text-current\" />,\n};\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "docs": [
      {
        "route": "/docs/emoji"
      },
      {
        "route": "https://pro.platejs.org/docs/components/emoji-picker"
      }
    ],
    "examples": [
      "emoji-demo",
      "emoji-pro"
    ]
  },
  "type": "registry:ui"
}