/ src / components / chatrooms / reaction-picker.tsx
reaction-picker.tsx
  1  'use client'
  2  
  3  import { useState, useEffect, useRef, useMemo } from 'react'
  4  
  5  const CATEGORIES: Array<{ id: string; label: string; icon: string; emojis: string[] }> = [
  6    {
  7      id: 'frequent',
  8      label: 'Frequently Used',
  9      icon: '๐Ÿ•',
 10      emojis: ['๐Ÿ‘', 'โค๏ธ', '๐Ÿ˜‚', '๐Ÿ”ฅ', '๐ŸŽ‰', '๐Ÿ‘€', '๐Ÿš€', 'โœ…', '๐Ÿ’ฏ', '๐Ÿค”'],
 11    },
 12    {
 13      id: 'smileys',
 14      label: 'Smileys & People',
 15      icon: '๐Ÿ˜€',
 16      emojis: [
 17        '๐Ÿ˜€', '๐Ÿ˜ƒ', '๐Ÿ˜„', '๐Ÿ˜', '๐Ÿ˜†', '๐Ÿ˜…', '๐Ÿคฃ', '๐Ÿ˜‚', '๐Ÿ™‚', '๐Ÿ˜Š',
 18        '๐Ÿ˜‡', '๐Ÿฅฐ', '๐Ÿ˜', '๐Ÿคฉ', '๐Ÿ˜˜', '๐Ÿ˜—', '๐Ÿ˜š', '๐Ÿ˜™', '๐Ÿฅฒ', '๐Ÿ˜‹',
 19        '๐Ÿ˜›', '๐Ÿ˜œ', '๐Ÿคช', '๐Ÿ˜', '๐Ÿค‘', '๐Ÿค—', '๐Ÿคญ', '๐Ÿซข', '๐Ÿคซ', '๐Ÿค”',
 20        '๐Ÿซก', '๐Ÿค', '๐Ÿคจ', '๐Ÿ˜', '๐Ÿ˜‘', '๐Ÿ˜ถ', '๐Ÿซฅ', '๐Ÿ˜', '๐Ÿ˜’', '๐Ÿ™„',
 21        '๐Ÿ˜ฌ', '๐Ÿคฅ', '๐Ÿซจ', '๐Ÿ˜Œ', '๐Ÿ˜”', '๐Ÿ˜ช', '๐Ÿคค', '๐Ÿ˜ด', '๐Ÿ˜ท', '๐Ÿค’',
 22        '๐Ÿค•', '๐Ÿคข', '๐Ÿคฎ', '๐Ÿฅด', '๐Ÿ˜ต', '๐Ÿคฏ', '๐Ÿฅณ', '๐Ÿฅธ', '๐Ÿ˜Ž', '๐Ÿค“',
 23        '๐Ÿง', '๐Ÿ˜•', '๐Ÿซค', '๐Ÿ˜Ÿ', '๐Ÿ™', '๐Ÿ˜ฎ', '๐Ÿ˜ฏ', '๐Ÿ˜ฒ', '๐Ÿ˜ณ', '๐Ÿฅบ',
 24        '๐Ÿฅน', '๐Ÿ˜ฆ', '๐Ÿ˜ง', '๐Ÿ˜จ', '๐Ÿ˜ฐ', '๐Ÿ˜ฅ', '๐Ÿ˜ข', '๐Ÿ˜ญ', '๐Ÿ˜ฑ', '๐Ÿ˜–',
 25        '๐Ÿ˜ฃ', '๐Ÿ˜ž', '๐Ÿ˜“', '๐Ÿ˜ฉ', '๐Ÿ˜ซ', '๐Ÿฅฑ', '๐Ÿ˜ค', '๐Ÿ˜ก', '๐Ÿ˜ ', '๐Ÿคฌ',
 26        '๐Ÿ˜ˆ', '๐Ÿ‘ฟ', '๐Ÿ’€', 'โ˜ ๏ธ', '๐Ÿ’ฉ', '๐Ÿคก', '๐Ÿ‘น', '๐Ÿ‘บ', '๐Ÿ‘ป', '๐Ÿ‘ฝ',
 27        '๐Ÿค–', '๐Ÿ˜บ', '๐Ÿ˜ธ', '๐Ÿ˜น', '๐Ÿ˜ป', '๐Ÿ˜ผ', '๐Ÿ˜ฝ', '๐Ÿ™€', '๐Ÿ˜ฟ', '๐Ÿ˜พ',
 28        '๐Ÿ™ˆ', '๐Ÿ™‰', '๐Ÿ™Š', '๐Ÿ‘‹', '๐Ÿคš', '๐Ÿ–๏ธ', 'โœ‹', '๐Ÿ––', '๐Ÿซฑ', '๐Ÿซฒ',
 29        '๐Ÿซณ', '๐Ÿซด', '๐Ÿ‘Œ', '๐ŸคŒ', '๐Ÿค', 'โœŒ๏ธ', '๐Ÿคž', '๐Ÿซฐ', '๐ŸคŸ', '๐Ÿค˜',
 30        '๐Ÿค™', '๐Ÿ‘ˆ', '๐Ÿ‘‰', '๐Ÿ‘†', '๐Ÿ–•', '๐Ÿ‘‡', 'โ˜๏ธ', '๐Ÿซต', '๐Ÿ‘', '๐Ÿ‘Ž',
 31        'โœŠ', '๐Ÿ‘Š', '๐Ÿค›', '๐Ÿคœ', '๐Ÿ‘', '๐Ÿ™Œ', '๐Ÿซถ', '๐Ÿ‘', '๐Ÿคฒ', '๐Ÿค',
 32        '๐Ÿ™', 'โœ๏ธ', '๐Ÿ’ช', '๐Ÿฆพ', '๐Ÿง ', '๐Ÿ‘€', '๐Ÿ‘๏ธ', '๐Ÿ‘…', '๐Ÿ‘„', '๐Ÿซฆ',
 33      ],
 34    },
 35    {
 36      id: 'nature',
 37      label: 'Animals & Nature',
 38      icon: '๐Ÿถ',
 39      emojis: [
 40        '๐Ÿถ', '๐Ÿฑ', '๐Ÿญ', '๐Ÿน', '๐Ÿฐ', '๐ŸฆŠ', '๐Ÿป', '๐Ÿผ', '๐Ÿปโ€โ„๏ธ', '๐Ÿจ',
 41        '๐Ÿฏ', '๐Ÿฆ', '๐Ÿฎ', '๐Ÿท', '๐Ÿธ', '๐Ÿต', '๐Ÿ”', '๐Ÿง', '๐Ÿฆ', '๐Ÿค',
 42        '๐Ÿฆ†', '๐Ÿฆ…', '๐Ÿฆ‰', '๐Ÿฆ‡', '๐Ÿบ', '๐Ÿ—', '๐Ÿด', '๐Ÿฆ„', '๐Ÿ', '๐Ÿชฑ',
 43        '๐Ÿ›', '๐Ÿฆ‹', '๐ŸŒ', '๐Ÿž', '๐Ÿœ', '๐Ÿชฒ', '๐Ÿชณ', '๐Ÿ•ท๏ธ', '๐Ÿฆ‚', '๐Ÿข',
 44        '๐Ÿ', '๐ŸฆŽ', '๐Ÿ™', '๐Ÿฆ‘', '๐Ÿฆ', '๐Ÿฆž', '๐Ÿฆ€', '๐Ÿก', '๐Ÿ ', '๐ŸŸ',
 45        '๐Ÿฌ', '๐Ÿณ', '๐Ÿ‹', '๐Ÿฆˆ', '๐Ÿชธ', '๐ŸŠ', '๐Ÿ…', '๐Ÿ†', '๐Ÿฆ“', '๐Ÿฆ',
 46        '๐Ÿ˜', '๐Ÿฆ›', '๐Ÿฆ', '๐Ÿช', '๐Ÿซ', '๐Ÿฆ’', '๐Ÿฆ˜', '๐Ÿฆฌ', '๐Ÿƒ', '๐Ÿ‚',
 47        '๐Ÿ„', '๐ŸŽ', '๐Ÿ–', '๐Ÿ', '๐Ÿ‘', '๐Ÿฆ™', '๐Ÿ', '๐ŸฆŒ', '๐Ÿ•', '๐Ÿฉ',
 48        '๐ŸŒต', '๐ŸŽ„', '๐ŸŒฒ', '๐ŸŒณ', '๐ŸŒด', '๐Ÿชต', '๐ŸŒฑ', '๐ŸŒฟ', 'โ˜˜๏ธ', '๐Ÿ€',
 49        '๐Ÿ', '๐Ÿ‚', '๐Ÿƒ', '๐Ÿชน', '๐Ÿชบ', '๐ŸŒบ', '๐ŸŒป', '๐ŸŒน', '๐Ÿฅ€', '๐ŸŒท',
 50        '๐ŸŒผ', '๐ŸŒธ', '๐Ÿ’', '๐Ÿ„', '๐ŸŒฐ', '๐ŸŽƒ', '๐ŸŒ', '๐ŸŒ™', 'โญ', '๐ŸŒŸ',
 51        '๐Ÿ’ซ', 'โœจ', 'โšก', 'โ˜€๏ธ', '๐ŸŒค๏ธ', '๐ŸŒˆ', 'โ˜๏ธ', '๐ŸŒง๏ธ', 'โ„๏ธ', '๐Ÿ”ฅ',
 52      ],
 53    },
 54    {
 55      id: 'food',
 56      label: 'Food & Drink',
 57      icon: '๐Ÿ•',
 58      emojis: [
 59        '๐ŸŽ', '๐Ÿ', '๐ŸŠ', '๐Ÿ‹', '๐ŸŒ', '๐Ÿ‰', '๐Ÿ‡', '๐Ÿ“', '๐Ÿซ', '๐Ÿˆ',
 60        '๐Ÿ’', '๐Ÿ‘', '๐Ÿฅญ', '๐Ÿ', '๐Ÿฅฅ', '๐Ÿฅ', '๐Ÿ…', '๐Ÿฅ‘', '๐Ÿ†', '๐Ÿฅฆ',
 61        '๐Ÿฅฌ', '๐Ÿฅ’', '๐ŸŒถ๏ธ', '๐Ÿซ‘', '๐ŸŒฝ', '๐Ÿฅ•', '๐Ÿง„', '๐Ÿง…', '๐Ÿฅ”', '๐Ÿ ',
 62        '๐Ÿฅ', '๐Ÿž', '๐Ÿฅ–', '๐Ÿฅจ', '๐Ÿง€', '๐Ÿฅš', '๐Ÿณ', '๐Ÿฅž', '๐Ÿง‡', '๐Ÿฅ“',
 63        '๐Ÿฅฉ', '๐Ÿ—', '๐Ÿ–', '๐ŸŒญ', '๐Ÿ”', '๐ŸŸ', '๐Ÿ•', '๐Ÿซ“', '๐Ÿฅช', '๐Ÿฅ™',
 64        '๐Ÿง†', '๐ŸŒฎ', '๐ŸŒฏ', '๐Ÿซ”', '๐Ÿฅ—', '๐Ÿ', '๐Ÿœ', '๐Ÿฒ', '๐Ÿ›', '๐Ÿฃ',
 65        '๐Ÿฑ', '๐ŸฅŸ', '๐Ÿค', '๐Ÿ™', '๐Ÿš', '๐Ÿ˜', '๐Ÿฅ', '๐Ÿฅ ', '๐Ÿฅฎ', '๐Ÿก',
 66        '๐Ÿง', '๐Ÿจ', '๐Ÿฆ', '๐Ÿฅง', '๐Ÿง', '๐Ÿฐ', '๐ŸŽ‚', '๐Ÿฎ', '๐Ÿญ', '๐Ÿฌ',
 67        '๐Ÿซ', '๐Ÿฟ', '๐Ÿงˆ', '๐Ÿฅค', 'โ˜•', '๐Ÿต', '๐Ÿงƒ', '๐Ÿง‰', '๐Ÿถ', '๐Ÿบ',
 68        '๐Ÿป', '๐Ÿฅ‚', '๐Ÿท', '๐Ÿธ', '๐Ÿน', '๐Ÿพ', '๐ŸงŠ', '๐Ÿฅ„', '๐Ÿด', '๐Ÿฅข',
 69      ],
 70    },
 71    {
 72      id: 'activity',
 73      label: 'Activities',
 74      icon: 'โšฝ',
 75      emojis: [
 76        'โšฝ', '๐Ÿ€', '๐Ÿˆ', 'โšพ', '๐ŸฅŽ', '๐ŸŽพ', '๐Ÿ', '๐Ÿ‰', '๐Ÿฅ', '๐ŸŽฑ',
 77        '๐Ÿ“', '๐Ÿธ', '๐Ÿ’', '๐ŸฅŠ', '๐Ÿฅ‹', '๐Ÿฅ…', 'โ›ณ', 'โ›ธ๏ธ', '๐ŸŽฃ', '๐Ÿคฟ',
 78        '๐ŸŽฟ', '๐Ÿ›ท', '๐ŸฅŒ', '๐ŸŽฏ', '๐Ÿช€', '๐Ÿช', '๐ŸŽฎ', '๐Ÿ•น๏ธ', '๐ŸŽฐ', '๐Ÿงฉ',
 79        'โ™Ÿ๏ธ', '๐ŸŽฒ', '๐ŸŽญ', '๐ŸŽจ', '๐ŸŽฌ', '๐ŸŽค', '๐ŸŽง', '๐ŸŽผ', '๐ŸŽน', '๐Ÿฅ',
 80        '๐ŸŽท', '๐ŸŽบ', '๐Ÿช—', '๐ŸŽธ', '๐ŸŽป', '๐ŸŽช', '๐ŸŽซ', '๐ŸŽŸ๏ธ', '๐Ÿ†', '๐Ÿฅ‡',
 81        '๐Ÿฅˆ', '๐Ÿฅ‰', '๐Ÿ…', '๐ŸŽ–๏ธ', '๐Ÿต๏ธ', '๐ŸŽ—๏ธ', '๐ŸŽ', '๐ŸŽ€', '๐ŸŽˆ', '๐ŸŽŠ',
 82      ],
 83    },
 84    {
 85      id: 'travel',
 86      label: 'Travel & Places',
 87      icon: 'โœˆ๏ธ',
 88      emojis: [
 89        '๐Ÿš—', '๐Ÿš•', '๐Ÿš™', '๐ŸšŒ', '๐ŸšŽ', '๐ŸŽ๏ธ', '๐Ÿš“', '๐Ÿš‘', '๐Ÿš’', '๐Ÿš',
 90        '๐Ÿ›ป', '๐Ÿšš', '๐Ÿš›', '๐Ÿšœ', '๐Ÿ๏ธ', '๐Ÿ›ต', '๐Ÿšฒ', '๐Ÿ›ด', '๐Ÿ›บ', '๐Ÿš”',
 91        '๐Ÿš', '๐Ÿš˜', '๐Ÿš–', 'โœˆ๏ธ', '๐Ÿš€', '๐Ÿ›ธ', '๐Ÿš', '๐Ÿ›ถ', 'โ›ต', '๐Ÿšข',
 92        '๐Ÿ ', '๐Ÿก', '๐Ÿ˜๏ธ', '๐Ÿข', '๐Ÿฃ', '๐Ÿฅ', '๐Ÿฆ', '๐Ÿช', '๐Ÿซ', '๐Ÿฉ',
 93        '๐Ÿ’’', '๐Ÿ›๏ธ', 'โ›ช', '๐Ÿ•Œ', '๐Ÿ›•', '๐Ÿ•', 'โ›ฉ๏ธ', '๐Ÿฐ', '๐Ÿฏ', '๐Ÿ—ผ',
 94        '๐Ÿ—ฝ', '๐Ÿ—ฟ', '๐ŸŸ๏ธ', '๐ŸŽก', '๐ŸŽข', '๐ŸŽ ', 'โ›ฒ', 'โ›ฑ๏ธ', '๐Ÿ–๏ธ', '๐Ÿ๏ธ',
 95        '๐Ÿ”๏ธ', '๐Ÿ—ป', '๐ŸŒ‹', '๐Ÿ•๏ธ', '๐Ÿ›ค๏ธ', '๐Ÿ›ฃ๏ธ', '๐ŸŒ…', '๐ŸŒ„', '๐ŸŒƒ', '๐ŸŒ‰',
 96      ],
 97    },
 98    {
 99      id: 'objects',
100      label: 'Objects',
101      icon: '๐Ÿ’ก',
102      emojis: [
103        'โŒš', '๐Ÿ“ฑ', '๐Ÿ’ป', 'โŒจ๏ธ', '๐Ÿ–ฅ๏ธ', '๐Ÿ–จ๏ธ', '๐Ÿ–ฑ๏ธ', '๐Ÿ–ฒ๏ธ', '๐Ÿ’ฝ', '๐Ÿ’พ',
104        '๐Ÿ’ฟ', '๐Ÿ“€', '๐ŸŽฅ', '๐Ÿ“ท', '๐Ÿ“ธ', '๐Ÿ“น', '๐Ÿ“ผ', '๐Ÿ”', '๐Ÿ”Ž', '๐Ÿ•ฏ๏ธ',
105        '๐Ÿ’ก', '๐Ÿ”ฆ', '๐Ÿฎ', '๐Ÿช”', '๐Ÿ“”', '๐Ÿ“•', '๐Ÿ“–', '๐Ÿ“—', '๐Ÿ“˜', '๐Ÿ“™',
106        '๐Ÿ“š', '๐Ÿ““', '๐Ÿ“’', '๐Ÿ“ƒ', '๐Ÿ“œ', '๐Ÿ“„', '๐Ÿ“ฐ', '๐Ÿ“‘', '๐Ÿ”–', '๐Ÿ’ฐ',
107        '๐Ÿช™', '๐Ÿ’ด', '๐Ÿ’ต', '๐Ÿ’ถ', '๐Ÿ’ท', '๐Ÿ’ธ', '๐Ÿ’ณ', 'โœ‰๏ธ', '๐Ÿ“ง', '๐Ÿ“จ',
108        '๐Ÿ“ฉ', '๐Ÿ“ค', '๐Ÿ“ฅ', '๐Ÿ“ฆ', '๐Ÿ“ซ', '๐Ÿ“ช', '๐Ÿ“ฌ', '๐Ÿ“ญ', '๐Ÿ“ฎ', '๐Ÿ—ณ๏ธ',
109        'โœ๏ธ', 'โœ’๏ธ', '๐Ÿ–‹๏ธ', '๐Ÿ–Š๏ธ', '๐Ÿ–Œ๏ธ', '๐Ÿ–๏ธ', '๐Ÿ“', '๐Ÿ“', '๐Ÿ“‚', '๐Ÿ—‚๏ธ',
110        '๐Ÿ“…', '๐Ÿ“†', '๐Ÿ“‡', '๐Ÿ“ˆ', '๐Ÿ“‰', '๐Ÿ“Š', '๐Ÿ“‹', '๐Ÿ“Œ', '๐Ÿ“', '๐Ÿ“Ž',
111        '๐Ÿ”', '๐Ÿ”‘', '๐Ÿ—๏ธ', '๐Ÿ”จ', '๐Ÿช“', 'โ›๏ธ', 'โš’๏ธ', '๐Ÿ› ๏ธ', '๐Ÿ—ก๏ธ', 'โš”๏ธ',
112        '๐Ÿ”ง', '๐Ÿช›', '๐Ÿ”ฉ', 'โš™๏ธ', '๐Ÿ—œ๏ธ', 'โš–๏ธ', '๐Ÿฆฏ', '๐Ÿ”—', 'โ›“๏ธ', '๐Ÿช',
113      ],
114    },
115    {
116      id: 'symbols',
117      label: 'Symbols',
118      icon: 'โค๏ธ',
119      emojis: [
120        'โค๏ธ', '๐Ÿงก', '๐Ÿ’›', '๐Ÿ’š', '๐Ÿ’™', '๐Ÿ’œ', '๐Ÿ–ค', '๐Ÿค', '๐ŸคŽ', '๐Ÿ’”',
121        'โค๏ธโ€๐Ÿ”ฅ', 'โค๏ธโ€๐Ÿฉน', 'โฃ๏ธ', '๐Ÿ’•', '๐Ÿ’ž', '๐Ÿ’“', '๐Ÿ’—', '๐Ÿ’–', '๐Ÿ’˜', '๐Ÿ’',
122        '๐Ÿ’Ÿ', 'โ˜ฎ๏ธ', 'โœ๏ธ', 'โ˜ช๏ธ', '๐Ÿ•‰๏ธ', 'โ˜ธ๏ธ', 'โœก๏ธ', '๐Ÿ”ฏ', '๐Ÿ•Ž', 'โ˜ฏ๏ธ',
123        'โ™ˆ', 'โ™‰', 'โ™Š', 'โ™‹', 'โ™Œ', 'โ™', 'โ™Ž', 'โ™', 'โ™', 'โ™‘',
124        'โ™’', 'โ™“', 'โ›Ž', '๐Ÿ”€', '๐Ÿ”', '๐Ÿ”‚', 'โ–ถ๏ธ', 'โฉ', 'โญ๏ธ', 'โฏ๏ธ',
125        'โ—€๏ธ', 'โช', 'โฎ๏ธ', '๐Ÿ”ผ', 'โซ', '๐Ÿ”ฝ', 'โฌ', 'โธ๏ธ', 'โน๏ธ', 'โบ๏ธ',
126        'โ๏ธ', '๐ŸŽฆ', '๐Ÿ”…', '๐Ÿ”†', '๐Ÿ“ถ', '๐Ÿ›œ', '๐Ÿ“ณ', '๐Ÿ“ด', 'โ™€๏ธ', 'โ™‚๏ธ',
127        'โšง๏ธ', 'โœ–๏ธ', 'โž•', 'โž–', 'โž—', '๐ŸŸฐ', 'โ™พ๏ธ', 'โ€ผ๏ธ', 'โ‰๏ธ', 'โ“',
128        'โ”', 'โ•', 'โ—', 'ใ€ฐ๏ธ', '๐Ÿ’ฑ', '๐Ÿ’ฒ', 'โš•๏ธ', 'โ™ป๏ธ', 'โšœ๏ธ', '๐Ÿ”ฑ',
129        'โœ”๏ธ', 'โ˜‘๏ธ', 'โœ…', 'โŒ', 'โŽ', 'โžฐ', 'โžฟ', 'ใ€ฝ๏ธ', 'โœณ๏ธ', 'โœด๏ธ',
130        'โ‡๏ธ', 'ยฉ๏ธ', 'ยฎ๏ธ', 'โ„ข๏ธ', '#๏ธโƒฃ', '*๏ธโƒฃ', '0๏ธโƒฃ', '1๏ธโƒฃ', '2๏ธโƒฃ', '3๏ธโƒฃ',
131        '๐Ÿ”ด', '๐ŸŸ ', '๐ŸŸก', '๐ŸŸข', '๐Ÿ”ต', '๐ŸŸฃ', 'โšซ', 'โšช', '๐ŸŸค', '๐Ÿ”ถ',
132        '๐Ÿ”ท', '๐Ÿ”ธ', '๐Ÿ”น', '๐Ÿ”บ', '๐Ÿ”ป', '๐Ÿ’ ', '๐Ÿ”˜', '๐Ÿ”ณ', '๐Ÿ”ฒ', '๐Ÿ',
133        '๐Ÿšฉ', '๐ŸŽŒ', '๐Ÿด', '๐Ÿณ๏ธ', '๐Ÿณ๏ธโ€๐ŸŒˆ', '๐Ÿณ๏ธโ€โšง๏ธ', '๐Ÿดโ€โ˜ ๏ธ', '๐Ÿ‡บ๐Ÿ‡ธ', '๐Ÿ‡ฌ๐Ÿ‡ง', '๐Ÿ‡ฏ๐Ÿ‡ต',
134      ],
135    },
136  ]
137  
138  interface Props {
139    onSelect: (emoji: string) => void
140    onClose: () => void
141  }
142  
143  export function ReactionPicker({ onSelect, onClose }: Props) {
144    const ref = useRef<HTMLDivElement>(null)
145    const searchRef = useRef<HTMLInputElement>(null)
146    const [search, setSearch] = useState('')
147    const [activeCategory, setActiveCategory] = useState('frequent')
148  
149    useEffect(() => {
150      const handler = (e: MouseEvent) => {
151        if (ref.current && !ref.current.contains(e.target as Node)) {
152          onClose()
153        }
154      }
155      document.addEventListener('mousedown', handler)
156      return () => document.removeEventListener('mousedown', handler)
157    }, [onClose])
158  
159    // Auto-focus search on open
160    useEffect(() => {
161      setTimeout(() => searchRef.current?.focus(), 50)
162    }, [])
163  
164    const filteredEmojis = useMemo(() => {
165      if (!search.trim()) return null
166      const q = search.toLowerCase()
167      const directEmojiMatches: string[] = []
168      const seen = new Set<string>()
169      for (const cat of CATEGORIES) {
170        if (cat.id === 'frequent') continue
171        for (const emoji of cat.emojis) {
172          if (seen.has(emoji)) continue
173          seen.add(emoji)
174          if (emoji.includes(search.trim())) directEmojiMatches.push(emoji)
175        }
176      }
177      if (directEmojiMatches.length > 0) return directEmojiMatches
178  
179      // This lightweight picker only understands category labels, not emoji names.
180      const matchingCats = CATEGORIES.filter(
181        (c) => c.id !== 'frequent' && c.label.toLowerCase().includes(q)
182      )
183      const catResults: string[] = []
184      const catSeen = new Set<string>()
185      for (const cat of matchingCats) {
186        for (const emoji of cat.emojis) {
187          if (!catSeen.has(emoji)) {
188            catSeen.add(emoji)
189            catResults.push(emoji)
190          }
191        }
192      }
193      return catResults
194    }, [search])
195  
196    return (
197      <div
198        ref={ref}
199        className="absolute right-0 bottom-8 z-50 bg-[#13131e] border border-white/[0.1] rounded-[12px] shadow-[0_8px_40px_rgba(0,0,0,0.6)] w-[320px] flex flex-col overflow-hidden"
200        style={{ animation: 'msg-in 0.15s ease-out both' }}
201      >
202        {/* Search */}
203        <div className="px-3 pt-3 pb-2">
204          <input
205            ref={searchRef}
206            type="text"
207            value={search}
208            onChange={(e) => setSearch(e.target.value)}
209            placeholder="Filter by category or paste emoji..."
210            className="w-full px-2.5 py-1.5 rounded-[8px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
211          />
212          {search.trim() && (
213            <p className="mt-1 px-0.5 text-[10px] text-text-3/55">
214              This picker filters category labels rather than emoji names.
215            </p>
216          )}
217        </div>
218  
219        {/* Category tabs */}
220        {!search.trim() && (
221          <div className="flex px-2 gap-0.5 pb-1">
222            {CATEGORIES.map((cat) => (
223              <button
224                key={cat.id}
225                onClick={() => setActiveCategory(cat.id)}
226                title={cat.label}
227                className={`flex-1 py-1 flex items-center justify-center rounded-[6px] text-[14px] cursor-pointer transition-all ${
228                  activeCategory === cat.id ? 'bg-white/[0.08]' : 'hover:bg-white/[0.04]'
229                }`}
230              >
231                {cat.icon}
232              </button>
233            ))}
234          </div>
235        )}
236  
237        {/* Emoji grid */}
238        <div className="px-2 pb-2 max-h-[220px] overflow-y-auto">
239          {search.trim() ? (
240            filteredEmojis && filteredEmojis.length > 0 ? (
241              <div className="grid grid-cols-8 gap-0.5">
242                {filteredEmojis.map((emoji, i) => (
243                  <button
244                    key={`${emoji}-${i}`}
245                    onClick={() => onSelect(emoji)}
246                    className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] hover:bg-white/[0.08] transition-all cursor-pointer text-[18px]"
247                  >
248                    {emoji}
249                  </button>
250                ))}
251              </div>
252            ) : (
253              <div className="px-2 py-6 text-center text-[11px] text-text-3/60">
254                No category matches. Try terms like <span className="text-text-3">food</span>, <span className="text-text-3">travel</span>, or paste an emoji.
255              </div>
256            )
257          ) : (
258            CATEGORIES.filter((c) => c.id === activeCategory).map((cat) => (
259              <div key={cat.id}>
260                <div className="text-[10px] font-600 text-text-3 uppercase tracking-wider px-1 py-1.5">{cat.label}</div>
261                <div className="grid grid-cols-8 gap-0.5">
262                  {cat.emojis.map((emoji, i) => (
263                    <button
264                      key={`${emoji}-${i}`}
265                      onClick={() => onSelect(emoji)}
266                      className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] hover:bg-white/[0.08] transition-all cursor-pointer text-[18px]"
267                    >
268                      {emoji}
269                    </button>
270                  ))}
271                </div>
272              </div>
273            ))
274          )}
275        </div>
276      </div>
277    )
278  }