diff --git a/packages/react/src/hooks/useSearchEmoji.js b/packages/react/src/hooks/useSearchEmoji.js new file mode 100644 index 0000000000..7112fc3d81 --- /dev/null +++ b/packages/react/src/hooks/useSearchEmoji.js @@ -0,0 +1,67 @@ +import { useCallback } from 'react'; +import emojiList from '../lib/emojiList'; + +const useSearchEmoji = ( + startReadEmoji, + setStartReadEmoji, + setFilteredEmojis, + setEmojiIndex, + setShowEmojiList +) => + useCallback( + (message) => { + const lastChar = message ? message[message.length - 1] : ''; + + if (message.length === 0) { + setShowEmojiList(false); + setStartReadEmoji(false); + setFilteredEmojis([]); + setEmojiIndex(-1); + return; + } + + // Check if user is typing emoji syntax (:emoji:) + const emojiMatch = message.match(/:([a-zA-Z0-9_+-]*?)$/); + + if (emojiMatch) { + const query = emojiMatch[1].toLowerCase(); + + // Only show suggestions if query is at least 2 characters + if (query.length >= 2) { + setStartReadEmoji(true); + + const filteredEmojis = emojiList + .filter( + (emoji) => + emoji.shortname.toLowerCase().includes(query) || + emoji.aliases.some((alias) => + alias.toLowerCase().includes(query) + ) + ) + .slice(0, 10); + + setFilteredEmojis(filteredEmojis); + setEmojiIndex(filteredEmojis.length > 0 ? 0 : -1); + setShowEmojiList(filteredEmojis.length > 0); + } else { + setShowEmojiList(false); + setFilteredEmojis([]); + setEmojiIndex(-1); + } + } else if (startReadEmoji) { + setStartReadEmoji(false); + setFilteredEmojis([]); + setEmojiIndex(-1); + setShowEmojiList(false); + } + }, + [ + startReadEmoji, + setStartReadEmoji, + setFilteredEmojis, + setEmojiIndex, + setShowEmojiList, + ] + ); + +export default useSearchEmoji; diff --git a/packages/react/src/lib/emojiList.js b/packages/react/src/lib/emojiList.js new file mode 100644 index 0000000000..4aa794a7a0 --- /dev/null +++ b/packages/react/src/lib/emojiList.js @@ -0,0 +1,464 @@ +// Comprehensive list of popular emojis with emoji-toolkit compatible shortnames +// Based on the most commonly used emojis and matching Rocket.Chat's emoji autocomplete + +const emojiList = [ + // Popular smileys and emotions + { shortname: 'smile', emoji: '๐Ÿ˜Š', aliases: ['happy', 'grinning'] }, + { shortname: 'laughing', emoji: '๐Ÿ˜‚', aliases: ['joy', 'tears'] }, + { shortname: 'heart_eyes', emoji: '๐Ÿ˜', aliases: ['love', 'heart'] }, + { shortname: 'kissing_heart', emoji: '๐Ÿ˜˜', aliases: ['kiss', 'love'] }, + { shortname: 'wink', emoji: '๐Ÿ˜‰', aliases: ['winking'] }, + { shortname: 'blush', emoji: '๐Ÿ˜Š', aliases: ['smiling', 'happy'] }, + { shortname: 'grinning', emoji: '๐Ÿ˜€', aliases: ['grin', 'happy'] }, + { shortname: 'grin', emoji: '๐Ÿ˜', aliases: ['grinning', 'happy'] }, + { shortname: 'joy', emoji: '๐Ÿ˜‚', aliases: ['laughing', 'tears'] }, + { shortname: 'smiley', emoji: '๐Ÿ˜ƒ', aliases: ['happy', 'grinning'] }, + { shortname: 'smirk', emoji: '๐Ÿ˜', aliases: ['smirking'] }, + { shortname: 'relaxed', emoji: 'โ˜บ๏ธ', aliases: ['smile', 'happy'] }, + { + shortname: 'stuck_out_tongue', + emoji: '๐Ÿ˜›', + aliases: ['tongue', 'playful'], + }, + { + shortname: 'stuck_out_tongue_winking_eye', + emoji: '๐Ÿ˜œ', + aliases: ['wink', 'tongue'], + }, + { + shortname: 'stuck_out_tongue_closed_eyes', + emoji: '๐Ÿ˜', + aliases: ['tongue', 'playful'], + }, + { shortname: 'kissing', emoji: '๐Ÿ˜—', aliases: ['kiss'] }, + { + shortname: 'kissing_smiling_eyes', + emoji: '๐Ÿ˜™', + aliases: ['kiss', 'happy'], + }, + { shortname: 'kissing_closed_eyes', emoji: '๐Ÿ˜š', aliases: ['kiss', 'love'] }, + { shortname: 'yum', emoji: '๐Ÿ˜‹', aliases: ['delicious', 'tasty'] }, + { shortname: 'relieved', emoji: '๐Ÿ˜Œ', aliases: ['relief', 'sigh'] }, + { shortname: 'hugs', emoji: '๐Ÿค—', aliases: ['hugging'] }, + { shortname: 'thinking', emoji: '๐Ÿค”', aliases: ['thought', 'considering'] }, + { shortname: 'neutral_face', emoji: '๐Ÿ˜', aliases: ['neutral', 'meh'] }, + { shortname: 'expressionless', emoji: '๐Ÿ˜‘', aliases: ['blank', 'neutral'] }, + { shortname: 'no_mouth', emoji: '๐Ÿ˜ถ', aliases: ['mute', 'silent'] }, + { shortname: 'rolling_eyes', emoji: '๐Ÿ™„', aliases: ['eyeroll', 'sarcasm'] }, + { shortname: 'smiling_imp', emoji: '๐Ÿ˜ˆ', aliases: ['devil', 'evil'] }, + { shortname: 'imp', emoji: '๐Ÿ‘ฟ', aliases: ['devil', 'evil'] }, + { shortname: 'japanese_ogre', emoji: '๐Ÿ‘น', aliases: ['monster', 'ogre'] }, + { shortname: 'japanese_goblin', emoji: '๐Ÿ‘บ', aliases: ['goblin', 'monster'] }, + { shortname: 'skull', emoji: '๐Ÿ’€', aliases: ['death', 'dead'] }, + { shortname: 'ghost', emoji: '๐Ÿ‘ป', aliases: ['spooky', 'halloween'] }, + { shortname: 'alien', emoji: '๐Ÿ‘ฝ', aliases: ['ufo', 'extraterrestrial'] }, + { shortname: 'robot', emoji: '๐Ÿค–', aliases: ['bot', 'automation'] }, + { shortname: 'smiley_cat', emoji: '๐Ÿ˜ธ', aliases: ['cat', 'happy'] }, + { shortname: 'smile_cat', emoji: '๐Ÿ˜น', aliases: ['cat', 'laughing'] }, + { shortname: 'joy_cat', emoji: '๐Ÿ˜ป', aliases: ['cat', 'love'] }, + { shortname: 'heart_eyes_cat', emoji: '๐Ÿ˜ป', aliases: ['cat', 'love'] }, + { shortname: 'smirk_cat', emoji: '๐Ÿ˜ผ', aliases: ['cat', 'smirk'] }, + { shortname: 'kissing_cat', emoji: '๐Ÿ˜ฝ', aliases: ['cat', 'kiss'] }, + { shortname: 'scream_cat', emoji: '๐Ÿ™€', aliases: ['cat', 'scared'] }, + { shortname: 'crying_cat_face', emoji: '๐Ÿ˜ฟ', aliases: ['cat', 'cry', 'sad'] }, + { shortname: 'pouting_cat', emoji: '๐Ÿ˜พ', aliases: ['cat', 'angry'] }, + + // Hand gestures + { shortname: 'thumbsup', emoji: '๐Ÿ‘', aliases: ['yes', 'good', 'approve'] }, + { + shortname: 'thumbsdown', + emoji: '๐Ÿ‘Ž', + aliases: ['no', 'bad', 'disapprove'], + }, + { shortname: 'clap', emoji: '๐Ÿ‘', aliases: ['applause', 'bravo'] }, + { shortname: 'open_hands', emoji: '๐Ÿ‘', aliases: ['open', 'hands'] }, + { shortname: 'palms_up_together', emoji: '๐Ÿคฒ', aliases: ['prayer', 'hands'] }, + { shortname: 'handshake', emoji: '๐Ÿค', aliases: ['deal', 'agreement'] }, + { shortname: 'pray', emoji: '๐Ÿ™', aliases: ['please', 'thanks', 'hope'] }, + { shortname: 'writing_hand', emoji: 'โœ๏ธ', aliases: ['write', 'signature'] }, + { shortname: 'nail_care', emoji: '๐Ÿ’…', aliases: ['nail', 'polish'] }, + { shortname: 'selfie', emoji: '๐Ÿคณ', aliases: ['camera', 'photo'] }, + { shortname: 'muscle', emoji: '๐Ÿ’ช', aliases: ['strong', 'flex', 'biceps'] }, + { shortname: 'mechanical_arm', emoji: '๐Ÿฆพ', aliases: ['arm', 'robot'] }, + { shortname: 'mechanical_leg', emoji: '๐Ÿฆฟ', aliases: ['leg', 'robot'] }, + { shortname: 'leg', emoji: '๐Ÿฆต', aliases: ['kick', 'limb'] }, + { shortname: 'foot', emoji: '๐Ÿฆถ', aliases: ['kick', 'limb'] }, + { shortname: 'ear', emoji: '๐Ÿ‘‚', aliases: ['listen', 'hear'] }, + { + shortname: 'ear_with_hearing_aid', + emoji: '๐Ÿฆป', + aliases: ['hearing', 'aid'], + }, + { shortname: 'nose', emoji: '๐Ÿ‘ƒ', aliases: ['smell', 'sniff'] }, + { shortname: 'brain', emoji: '๐Ÿง ', aliases: ['think', 'smart'] }, + { shortname: 'anatomical_heart', emoji: '๐Ÿซ€', aliases: ['heart', 'health'] }, + { shortname: 'lungs', emoji: '๐Ÿซ', aliases: ['breath', 'health'] }, + { shortname: 'tooth', emoji: '๐Ÿฆท', aliases: ['dental', 'health'] }, + { shortname: 'bone', emoji: '๐Ÿฆด', aliases: ['skeleton', 'health'] }, + { shortname: 'eyes', emoji: '๐Ÿ‘€', aliases: ['see', 'look', 'watch'] }, + { shortname: 'eye', emoji: '๐Ÿ‘๏ธ', aliases: ['see', 'look'] }, + { shortname: 'tongue', emoji: '๐Ÿ‘…', aliases: ['taste', 'lick'] }, + { shortname: 'lips', emoji: '๐Ÿ‘„', aliases: ['kiss', 'mouth'] }, + + // Hearts and symbols + { shortname: 'heart', emoji: 'โค๏ธ', aliases: ['love', 'red'] }, + { shortname: 'orange_heart', emoji: '๐Ÿงก', aliases: ['love', 'orange'] }, + { shortname: 'yellow_heart', emoji: '๐Ÿ’›', aliases: ['love', 'yellow'] }, + { shortname: 'green_heart', emoji: '๐Ÿ’š', aliases: ['love', 'green'] }, + { shortname: 'blue_heart', emoji: '๐Ÿ’™', aliases: ['love', 'blue'] }, + { shortname: 'purple_heart', emoji: '๐Ÿ’œ', aliases: ['love', 'purple'] }, + { shortname: 'black_heart', emoji: '๐Ÿ–ค', aliases: ['love', 'black'] }, + { shortname: 'white_heart', emoji: '๐Ÿค', aliases: ['love', 'white'] }, + { shortname: 'brown_heart', emoji: '๐ŸคŽ', aliases: ['love', 'brown'] }, + { shortname: 'broken_heart', emoji: '๐Ÿ’”', aliases: ['sad', 'broken'] }, + { + shortname: 'heart_exclamation', + emoji: 'โฃ๏ธ', + aliases: ['love', 'exclamation'], + }, + { shortname: 'two_hearts', emoji: '๐Ÿ’•', aliases: ['love', 'two'] }, + { + shortname: 'revolving_hearts', + emoji: '๐Ÿ’ž', + aliases: ['love', 'revolving'], + }, + { shortname: 'heartbeat', emoji: '๐Ÿ’“', aliases: ['love', 'beat'] }, + { shortname: 'heartpulse', emoji: '๐Ÿ’—', aliases: ['love', 'pulse'] }, + { shortname: 'heart_eyes', emoji: '๐Ÿ˜', aliases: ['love', 'heart'] }, + { shortname: 'sparkling_heart', emoji: '๐Ÿ’–', aliases: ['love', 'sparkle'] }, + { shortname: 'cupid', emoji: '๐Ÿ’˜', aliases: ['love', 'arrow'] }, + { shortname: 'gift_heart', emoji: '๐Ÿ’', aliases: ['love', 'gift'] }, + { + shortname: 'heart_decoration', + emoji: '๐Ÿ’Ÿ', + aliases: ['love', 'decoration'], + }, + { shortname: 'peace_symbol', emoji: 'โ˜ฎ๏ธ', aliases: ['peace', 'symbol'] }, + { shortname: 'latin_cross', emoji: 'โœ๏ธ', aliases: ['cross', 'christian'] }, + { shortname: 'star_and_crescent', emoji: 'โ˜ช๏ธ', aliases: ['islam', 'muslim'] }, + { shortname: 'om', emoji: '๐Ÿ•‰๏ธ', aliases: ['hindu', 'om'] }, + { + shortname: 'wheel_of_dharma', + emoji: 'โ˜ธ๏ธ', + aliases: ['buddhism', 'dharma'], + }, + { shortname: 'star_of_david', emoji: 'โœก๏ธ', aliases: ['judaism', 'star'] }, + { shortname: 'six_pointed_star', emoji: '๐Ÿ”ฏ', aliases: ['star', 'jewish'] }, + { shortname: 'menorah', emoji: '๐Ÿ•Ž', aliases: ['hanukkah', 'candles'] }, + { shortname: 'yin_yang', emoji: 'โ˜ฏ๏ธ', aliases: ['balance', 'tao'] }, + { shortname: 'orthodox_cross', emoji: 'โ˜ฆ๏ธ', aliases: ['christian', 'cross'] }, + { + shortname: 'place_of_worship', + emoji: '๐Ÿ›', + aliases: ['worship', 'religion'], + }, + { shortname: 'ophiuchus', emoji: 'โ›Ž', aliases: ['snake', 'zodiac'] }, + { shortname: 'aries', emoji: 'โ™ˆ', aliases: ['ram', 'zodiac'] }, + { shortname: 'taurus', emoji: 'โ™‰', aliases: ['bull', 'zodiac'] }, + { shortname: 'gemini', emoji: 'โ™Š', aliases: ['twins', 'zodiac'] }, + { shortname: 'cancer', emoji: 'โ™‹', aliases: ['crab', 'zodiac'] }, + { shortname: 'leo', emoji: 'โ™Œ', aliases: ['lion', 'zodiac'] }, + { shortname: 'virgo', emoji: 'โ™', aliases: ['maiden', 'zodiac'] }, + { shortname: 'libra', emoji: 'โ™Ž', aliases: ['scales', 'zodiac'] }, + { shortname: 'scorpius', emoji: 'โ™', aliases: ['scorpion', 'zodiac'] }, + { shortname: 'sagittarius', emoji: 'โ™', aliases: ['archer', 'zodiac'] }, + { shortname: 'capricorn', emoji: 'โ™‘', aliases: ['goat', 'zodiac'] }, + { shortname: 'aquarius', emoji: 'โ™’', aliases: ['water', 'zodiac'] }, + { shortname: 'pisces', emoji: 'โ™“', aliases: ['fish', 'zodiac'] }, + { shortname: 'id', emoji: '๐Ÿ†”', aliases: ['identification', 'id'] }, + { shortname: 'atom_symbol', emoji: 'โš›๏ธ', aliases: ['atom', 'science'] }, + { shortname: 'u7a7a', emoji: '๐Ÿˆณ', aliases: ['japanese', 'empty'] }, + { shortname: 'u5272', emoji: '๐Ÿˆน', aliases: ['japanese', 'discount'] }, + { + shortname: 'radioactive', + emoji: 'โ˜ข๏ธ', + aliases: ['nuclear', 'radioactive'], + }, + { shortname: 'biohazard', emoji: 'โ˜ฃ๏ธ', aliases: ['danger', 'biohazard'] }, + { shortname: 'mobile_phone_off', emoji: '๐Ÿ“ด', aliases: ['off', 'phone'] }, + { shortname: 'vibration_mode', emoji: '๐Ÿ“ณ', aliases: ['vibrate', 'phone'] }, + { shortname: 'u6709', emoji: '๐Ÿˆถ', aliases: ['japanese', 'available'] }, + { shortname: 'u7121', emoji: '๐Ÿˆš', aliases: ['japanese', 'not'] }, + { shortname: 'u7533', emoji: '๐Ÿˆธ', aliases: ['japanese', 'application'] }, + { shortname: 'u55b6', emoji: '๐Ÿˆบ', aliases: ['japanese', 'open'] }, + { shortname: 'u6708', emoji: '๐Ÿˆท๏ธ', aliases: ['japanese', 'monthly'] }, + { + shortname: 'eight_pointed_black_star', + emoji: 'โœด๏ธ', + aliases: ['star', 'eight'], + }, + { shortname: 'vs', emoji: '๐Ÿ†š', aliases: ['versus', 'vs'] }, + { shortname: 'accept', emoji: '๐Ÿ‰‘', aliases: ['japanese', 'acceptable'] }, + { shortname: 'white_flower', emoji: '๐Ÿ’ฎ', aliases: ['flower', 'white'] }, + { + shortname: 'ideograph_advantage', + emoji: '๐Ÿ‰', + aliases: ['japanese', 'advantage'], + }, + { shortname: 'secret', emoji: 'ใŠ™๏ธ', aliases: ['japanese', 'secret'] }, + { + shortname: 'congratulations', + emoji: 'ใŠ—๏ธ', + aliases: ['japanese', 'congratulations'], + }, + { shortname: 'u5408', emoji: '๐Ÿˆด', aliases: ['japanese', 'congratulation'] }, + { shortname: 'u6e80', emoji: '๐Ÿˆต', aliases: ['japanese', 'full'] }, + { shortname: 'u7981', emoji: '๐Ÿˆฒ', aliases: ['japanese', 'prohibited'] }, + { shortname: 'a', emoji: '๐Ÿ…ฐ๏ธ', aliases: ['blood', 'type'] }, + { shortname: 'b', emoji: '๐Ÿ…ฑ๏ธ', aliases: ['blood', 'type'] }, + { shortname: 'ab', emoji: '๐Ÿ†Ž', aliases: ['blood', 'type'] }, + { shortname: 'cl', emoji: '๐Ÿ†‘', aliases: ['clear', 'clear'] }, + { shortname: 'o2', emoji: '๐Ÿ…พ๏ธ', aliases: ['blood', 'type'] }, + { shortname: 'sos', emoji: '๐Ÿ†˜', aliases: ['help', 'emergency'] }, + { shortname: 'no_entry', emoji: 'โ›”', aliases: ['no', 'entry'] }, + { shortname: 'name_badge', emoji: '๐Ÿ“›', aliases: ['name', 'badge'] }, + { shortname: 'no_entry_sign', emoji: '๐Ÿšซ', aliases: ['no', 'entry'] }, + { shortname: 'x', emoji: 'โŒ', aliases: ['no', 'cross'] }, + { shortname: 'o', emoji: 'โญ•', aliases: ['yes', 'circle'] }, + { shortname: 'stop_sign', emoji: '๐Ÿ›‘', aliases: ['stop', 'sign'] }, + { shortname: 'anger', emoji: '๐Ÿ’ข', aliases: ['angry', 'mad'] }, + { shortname: 'hotsprings', emoji: 'โ™จ๏ธ', aliases: ['hot', 'springs'] }, + { shortname: 'no_pedestrians', emoji: '๐Ÿšท', aliases: ['no', 'pedestrians'] }, + { shortname: 'do_not_litter', emoji: '๐Ÿšฏ', aliases: ['no', 'litter'] }, + { shortname: 'no_bicycles', emoji: '๐Ÿšณ', aliases: ['no', 'bicycles'] }, + { shortname: 'non-potable_water', emoji: '๐Ÿšฑ', aliases: ['no', 'water'] }, + { shortname: 'underage', emoji: '๐Ÿ”ž', aliases: ['underage', '18'] }, + { shortname: 'no_mobile_phones', emoji: '๐Ÿ“ต', aliases: ['no', 'phone'] }, + { + shortname: 'exclamation', + emoji: 'โ—', + aliases: ['exclamation', 'warning'], + }, + { + shortname: 'grey_exclamation', + emoji: 'โ•', + aliases: ['exclamation', 'grey'], + }, + { shortname: 'question', emoji: 'โ“', aliases: ['question', 'help'] }, + { shortname: 'grey_question', emoji: 'โ”', aliases: ['question', 'grey'] }, + { shortname: 'bangbang', emoji: 'โ€ผ๏ธ', aliases: ['exclamation', 'double'] }, + { + shortname: 'interrobang', + emoji: 'โ‰๏ธ', + aliases: ['exclamation', 'question'], + }, + { shortname: '100', emoji: '๐Ÿ’ฏ', aliases: ['hundred', 'perfect'] }, + { shortname: 'low_brightness', emoji: '๐Ÿ”…', aliases: ['dim', 'brightness'] }, + { + shortname: 'high_brightness', + emoji: '๐Ÿ”†', + aliases: ['bright', 'brightness'], + }, + { shortname: 'trident', emoji: '๐Ÿ”ฑ', aliases: ['trident', 'fork'] }, + { shortname: 'fleur_de_lis', emoji: 'โšœ๏ธ', aliases: ['fleur', 'lily'] }, + { + shortname: 'part_alternation_mark', + emoji: 'ใ€ฝ๏ธ', + aliases: ['japanese', 'part'], + }, + { shortname: 'warning', emoji: 'โš ๏ธ', aliases: ['warning', 'caution'] }, + { + shortname: 'children_crossing', + emoji: '๐Ÿšธ', + aliases: ['children', 'crossing'], + }, + { shortname: 'beginner', emoji: '๐Ÿ”ฐ', aliases: ['beginner', 'leaf'] }, + { shortname: 'recycle', emoji: 'โ™ป๏ธ', aliases: ['recycle', 'green'] }, + { shortname: 'u6307', emoji: '๐Ÿˆฏ', aliases: ['japanese', 'point'] }, + { shortname: 'chart', emoji: '๐Ÿ’น', aliases: ['chart', 'yen'] }, + { shortname: 'sparkle', emoji: 'โœจ', aliases: ['sparkle', 'shine'] }, + { + shortname: 'eight_spoked_asterisk', + emoji: 'โœณ๏ธ', + aliases: ['asterisk', 'eight'], + }, + { + shortname: 'negative_squared_cross_mark', + emoji: 'โŽ', + aliases: ['no', 'cross'], + }, + { shortname: 'white_check_mark', emoji: 'โœ…', aliases: ['yes', 'check'] }, + { + shortname: 'diamond_shape_with_a_dot_inside', + emoji: '๐Ÿ’ ', + aliases: ['diamond', 'dot'], + }, + { shortname: 'cyclone', emoji: '๐ŸŒ€', aliases: ['cyclone', 'spiral'] }, + { shortname: 'loop', emoji: 'โžฟ', aliases: ['loop', 'curly'] }, + { + shortname: 'globe_with_meridians', + emoji: '๐ŸŒ', + aliases: ['globe', 'world'], + }, + { shortname: 'm', emoji: 'โ“‚๏ธ', aliases: ['metro', 'm'] }, + { shortname: 'atm', emoji: '๐Ÿง', aliases: ['atm', 'cash'] }, + { shortname: 'sa', emoji: '๐Ÿˆ‚๏ธ', aliases: ['japanese', 'service'] }, + { + shortname: 'passport_control', + emoji: '๐Ÿ›‚', + aliases: ['passport', 'control'], + }, + { shortname: 'customs', emoji: '๐Ÿ›ƒ', aliases: ['customs', 'border'] }, + { shortname: 'baggage_claim', emoji: '๐Ÿ›„', aliases: ['baggage', 'claim'] }, + { shortname: 'left_luggage', emoji: '๐Ÿ›…', aliases: ['left', 'luggage'] }, + { + shortname: 'wheelchair', + emoji: 'โ™ฟ', + aliases: ['wheelchair', 'accessibility'], + }, + { shortname: 'no_smoking', emoji: '๐Ÿšญ', aliases: ['no', 'smoking'] }, + { shortname: 'wc', emoji: '๐Ÿšพ', aliases: ['toilet', 'wc'] }, + { shortname: 'parking', emoji: '๐Ÿ…ฟ๏ธ', aliases: ['parking', 'p'] }, + { shortname: 'potable_water', emoji: '๐Ÿšฐ', aliases: ['water', 'drinking'] }, + { shortname: 'mens', emoji: '๐Ÿšน', aliases: ['men', 'male'] }, + { shortname: 'womens', emoji: '๐Ÿšบ', aliases: ['women', 'female'] }, + { shortname: 'baby_symbol', emoji: '๐Ÿšผ', aliases: ['baby', 'symbol'] }, + { shortname: 'restroom', emoji: '๐Ÿšป', aliases: ['restroom', 'toilet'] }, + { + shortname: 'put_litter_in_its_place', + emoji: '๐Ÿšฎ', + aliases: ['litter', 'bin'], + }, + { shortname: 'cinema', emoji: '๐ŸŽฆ', aliases: ['cinema', 'movie'] }, + { + shortname: 'signal_strength', + emoji: '๐Ÿ“ถ', + aliases: ['signal', 'strength'], + }, + { shortname: 'koko', emoji: '๐Ÿˆ', aliases: ['japanese', 'here'] }, + { shortname: 'ng', emoji: '๐Ÿ†–', aliases: ['ng', 'no'] }, + { shortname: 'ok', emoji: '๐Ÿ†—', aliases: ['ok', 'okay'] }, + { shortname: 'up', emoji: '๐Ÿ†™', aliases: ['up', 'arrow'] }, + { shortname: 'cool', emoji: '๐Ÿ†’', aliases: ['cool', 'fresh'] }, + { shortname: 'new', emoji: '๐Ÿ†•', aliases: ['new', 'fresh'] }, + { shortname: 'free', emoji: '๐Ÿ†“', aliases: ['free', 'complimentary'] }, + { shortname: 'zero', emoji: '0๏ธโƒฃ', aliases: ['zero', '0'] }, + { shortname: 'one', emoji: '1๏ธโƒฃ', aliases: ['one', '1'] }, + { shortname: 'two', emoji: '2๏ธโƒฃ', aliases: ['two', '2'] }, + { shortname: 'three', emoji: '3๏ธโƒฃ', aliases: ['three', '3'] }, + { shortname: 'four', emoji: '4๏ธโƒฃ', aliases: ['four', '4'] }, + { shortname: 'five', emoji: '5๏ธโƒฃ', aliases: ['five', '5'] }, + { shortname: 'six', emoji: '6๏ธโƒฃ', aliases: ['six', '6'] }, + { shortname: 'seven', emoji: '7๏ธโƒฃ', aliases: ['seven', '7'] }, + { shortname: 'eight', emoji: '8๏ธโƒฃ', aliases: ['eight', '8'] }, + { shortname: 'nine', emoji: '9๏ธโƒฃ', aliases: ['nine', '9'] }, + { shortname: 'keycap_ten', emoji: '๐Ÿ”Ÿ', aliases: ['ten', '10'] }, + + // Additional popular emojis + { shortname: 'fire', emoji: '๐Ÿ”ฅ', aliases: ['hot', 'flame'] }, + { shortname: 'boom', emoji: '๐Ÿ’ฅ', aliases: ['explosion', 'bang'] }, + { shortname: 'collision', emoji: '๐Ÿ’ฅ', aliases: ['explosion', 'bang'] }, + { shortname: 'sweat_drops', emoji: '๐Ÿ’ฆ', aliases: ['water', 'drops'] }, + { shortname: 'droplet', emoji: '๐Ÿ’ง', aliases: ['water', 'drop'] }, + { shortname: 'dash', emoji: '๐Ÿ’จ', aliases: ['wind', 'fast'] }, + { shortname: 'hole', emoji: '๐Ÿ•ณ๏ธ', aliases: ['hole', 'dark'] }, + { shortname: 'bomb', emoji: '๐Ÿ’ฃ', aliases: ['bomb', 'explosive'] }, + { shortname: 'speech_balloon', emoji: '๐Ÿ’ฌ', aliases: ['speech', 'bubble'] }, + { shortname: 'eye_speech_bubble', emoji: '๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ', aliases: ['eye', 'speech'] }, + { shortname: 'left_speech_bubble', emoji: '๐Ÿ—จ๏ธ', aliases: ['left', 'speech'] }, + { shortname: 'right_anger_bubble', emoji: '๐Ÿ—ฏ๏ธ', aliases: ['right', 'anger'] }, + { shortname: 'thought_balloon', emoji: '๐Ÿ’ญ', aliases: ['thought', 'bubble'] }, + { shortname: 'zzz', emoji: '๐Ÿ’ค', aliases: ['sleep', 'tired'] }, + { shortname: 'wave', emoji: '๐Ÿ‘‹', aliases: ['hello', 'goodbye'] }, + { shortname: 'raised_back_of_hand', emoji: '๐Ÿคš', aliases: ['stop', 'hand'] }, + { + shortname: 'raised_hand_with_fingers_splayed', + emoji: '๐Ÿ–๏ธ', + aliases: ['stop', 'hand'], + }, + { shortname: 'hand', emoji: 'โœ‹', aliases: ['stop', 'hand'] }, + { shortname: 'spock-hand', emoji: '๐Ÿ––', aliases: ['spock', 'vulcan'] }, + { shortname: 'the_horns', emoji: '๐Ÿค˜', aliases: ['rock', 'metal'] }, + { shortname: 'call_me_hand', emoji: '๐Ÿค™', aliases: ['call', 'phone'] }, + { shortname: 'point_left', emoji: '๐Ÿ‘ˆ', aliases: ['left', 'point'] }, + { shortname: 'point_right', emoji: '๐Ÿ‘‰', aliases: ['right', 'point'] }, + { shortname: 'point_up_2', emoji: '๐Ÿ‘†', aliases: ['up', 'point'] }, + { shortname: 'middle_finger', emoji: '๐Ÿ–•', aliases: ['middle', 'finger'] }, + { shortname: 'point_down', emoji: '๐Ÿ‘‡', aliases: ['down', 'point'] }, + { shortname: 'point_up', emoji: 'โ˜๏ธ', aliases: ['up', 'point'] }, + { shortname: 'point_up_2', emoji: '๐Ÿ‘†', aliases: ['up', 'point'] }, + { shortname: 'crossed_fingers', emoji: '๐Ÿคž', aliases: ['luck', 'cross'] }, + { shortname: 'love_you_gesture', emoji: '๐ŸคŸ', aliases: ['love', 'you'] }, + { shortname: 'metal', emoji: '๐Ÿค˜', aliases: ['rock', 'metal'] }, + { shortname: 'vulcan_salute', emoji: '๐Ÿ––', aliases: ['spock', 'vulcan'] }, + { + shortname: 'index_pointing_at_the_viewer', + emoji: '๐Ÿซต', + aliases: ['point', 'you'], + }, + { shortname: 'palms_up_together', emoji: '๐Ÿคฒ', aliases: ['prayer', 'hands'] }, + { shortname: 'back_of_hand', emoji: '๐Ÿคš', aliases: ['back', 'hand'] }, + { + shortname: 'hand_with_index_finger_and_thumb_crossed', + emoji: '๐ŸคŒ', + aliases: ['pinch', 'money'], + }, + { shortname: 'index_pointing_up', emoji: 'โ˜๏ธ', aliases: ['up', 'point'] }, + { + shortname: 'index_pointing_at_the_viewer_dark_skin_tone', + emoji: '๐Ÿซต๐Ÿฟ', + aliases: ['point', 'you', 'dark'], + }, + { + shortname: 'index_pointing_at_the_viewer_light_skin_tone', + emoji: '๐Ÿซต๐Ÿป', + aliases: ['point', 'you', 'light'], + }, + { + shortname: 'index_pointing_at_the_viewer_medium_dark_skin_tone', + emoji: '๐Ÿซต๐Ÿพ', + aliases: ['point', 'you', 'medium_dark'], + }, + { + shortname: 'index_pointing_at_the_viewer_medium_light_skin_tone', + emoji: '๐Ÿซต๐Ÿผ', + aliases: ['point', 'you', 'medium_light'], + }, + { + shortname: 'index_pointing_at_the_viewer_medium_skin_tone', + emoji: '๐Ÿซต๐Ÿฝ', + aliases: ['point', 'you', 'medium'], + }, + { shortname: 'thumbs_up', emoji: '๐Ÿ‘', aliases: ['yes', 'good', 'approve'] }, + { + shortname: 'thumbs_down', + emoji: '๐Ÿ‘Ž', + aliases: ['no', 'bad', 'disapprove'], + }, + { shortname: 'raised_fist', emoji: 'โœŠ', aliases: ['fist', 'punch'] }, + { shortname: 'oncoming_fist', emoji: '๐Ÿ‘Š', aliases: ['fist', 'punch'] }, + { shortname: 'left_facing_fist', emoji: '๐Ÿค›', aliases: ['fist', 'left'] }, + { shortname: 'right_facing_fist', emoji: '๐Ÿคœ', aliases: ['fist', 'right'] }, + { shortname: 'clap', emoji: '๐Ÿ‘', aliases: ['applause', 'bravo'] }, + { shortname: 'raised_hands', emoji: '๐Ÿ™Œ', aliases: ['celebration', 'hands'] }, + { shortname: 'open_hands', emoji: '๐Ÿ‘', aliases: ['open', 'hands'] }, + { shortname: 'palms_up_together', emoji: '๐Ÿคฒ', aliases: ['prayer', 'hands'] }, + { shortname: 'handshake', emoji: '๐Ÿค', aliases: ['deal', 'agreement'] }, + { shortname: 'pray', emoji: '๐Ÿ™', aliases: ['please', 'thanks', 'hope'] }, + { shortname: 'writing_hand', emoji: 'โœ๏ธ', aliases: ['write', 'signature'] }, + { shortname: 'nail_care', emoji: '๐Ÿ’…', aliases: ['nail', 'polish'] }, + { shortname: 'selfie', emoji: '๐Ÿคณ', aliases: ['camera', 'photo'] }, + { shortname: 'muscle', emoji: '๐Ÿ’ช', aliases: ['strong', 'flex', 'biceps'] }, + { shortname: 'mechanical_arm', emoji: '๐Ÿฆพ', aliases: ['arm', 'robot'] }, + { shortname: 'mechanical_leg', emoji: '๐Ÿฆฟ', aliases: ['leg', 'robot'] }, + { shortname: 'leg', emoji: '๐Ÿฆต', aliases: ['kick', 'limb'] }, + { shortname: 'foot', emoji: '๐Ÿฆถ', aliases: ['kick', 'limb'] }, + { shortname: 'ear', emoji: '๐Ÿ‘‚', aliases: ['listen', 'hear'] }, + { + shortname: 'ear_with_hearing_aid', + emoji: '๐Ÿฆป', + aliases: ['hearing', 'aid'], + }, + { shortname: 'nose', emoji: '๐Ÿ‘ƒ', aliases: ['smell', 'sniff'] }, + { shortname: 'brain', emoji: '๐Ÿง ', aliases: ['think', 'smart'] }, + { shortname: 'anatomical_heart', emoji: '๐Ÿซ€', aliases: ['heart', 'health'] }, + { shortname: 'lungs', emoji: '๐Ÿซ', aliases: ['breath', 'health'] }, + { shortname: 'tooth', emoji: '๐Ÿฆท', aliases: ['dental', 'health'] }, + { shortname: 'bone', emoji: '๐Ÿฆด', aliases: ['skeleton', 'health'] }, + { shortname: 'eyes', emoji: '๐Ÿ‘€', aliases: ['see', 'look', 'watch'] }, + { shortname: 'eye', emoji: '๐Ÿ‘๏ธ', aliases: ['see', 'look'] }, + { shortname: 'tongue', emoji: '๐Ÿ‘…', aliases: ['taste', 'lick'] }, + { shortname: 'lips', emoji: '๐Ÿ‘„', aliases: ['kiss', 'mouth'] }, +]; + +export default emojiList; diff --git a/packages/react/src/views/ChatInput/ChatInput.js b/packages/react/src/views/ChatInput/ChatInput.js index ca748520a6..cc0fc49d9f 100644 --- a/packages/react/src/views/ChatInput/ChatInput.js +++ b/packages/react/src/views/ChatInput/ChatInput.js @@ -26,12 +26,14 @@ import MembersList from '../Mentions/MembersList'; import { TypingUsers } from '../TypingUsers'; import createPendingMessage from '../../lib/createPendingMessage'; import { CommandsList } from '../CommandList'; +import { EmojiList } from '../EmojiList'; import useSettingsStore from '../../store/settingsStore'; import ChannelState from '../ChannelState/ChannelState'; import QuoteMessage from '../QuoteMessage/QuoteMessage'; import { getChatInputStyles } from './ChatInput.styles'; import useShowCommands from '../../hooks/useShowCommands'; import useSearchMentionUser from '../../hooks/useSearchMentionUser'; +import useSearchEmoji from '../../hooks/useSearchEmoji'; import formatSelection from '../../lib/formatSelection'; import { parseEmoji } from '../../lib/emoji'; @@ -56,6 +58,10 @@ const ChatInput = ({ scrollToBottom }) => { const [showMembersList, setShowMembersList] = useState(false); const [showCommandList, setShowCommandList] = useState(false); const [filteredCommands, setFilteredCommands] = useState([]); + const [showEmojiList, setShowEmojiList] = useState(false); + const [filteredEmojis, setFilteredEmojis] = useState([]); + const [emojiIndex, setEmojiIndex] = useState(-1); + const [startReadEmoji, setStartReadEmoji] = useState(false); const [isMsgLong, setIsMsgLong] = useState(false); const { @@ -141,6 +147,14 @@ const ChatInput = ({ scrollToBottom }) => { setShowMembersList ); + const searchEmoji = useSearchEmoji( + startReadEmoji, + setStartReadEmoji, + setFilteredEmojis, + setEmojiIndex, + setShowEmojiList + ); + useEffect(() => { RCInstance.auth.onAuthChange((user) => { if (user) { @@ -396,12 +410,17 @@ const ChatInput = ({ scrollToBottom }) => { const onTextChange = (e, val) => { sendTypingStart(); const message = val || e.target.value; - messageRef.current.value = parseEmoji(message); + + // Don't parse emojis if user is currently typing emoji autocomplete + const shouldParseEmoji = !message.match(/:([a-zA-Z0-9_+-]*?)$/); + messageRef.current.value = shouldParseEmoji ? parseEmoji(message) : message; + setDisableButton(!messageRef.current.value.length); if (e !== null) { handleNewLine(e, false); searchMentionUser(message); showCommands(e); + searchEmoji(message); } }; @@ -444,7 +463,7 @@ const ChatInput = ({ scrollToBottom }) => { case e.code === 'Enter': e.preventDefault(); - if (!showCommandList && !showMembersList) { + if (!showCommandList && !showMembersList && !showEmojiList) { sendTypingStop(); sendMessage(); } @@ -567,6 +586,18 @@ const ChatInput = ({ scrollToBottom }) => { /> )} + {showEmojiList && ( + + )} + { + const currentMessage = messageRef.current.value; + const emojiMatch = currentMessage.match(/:(.*?)$/); + + if (emojiMatch) { + // Replace the :query with the selected emoji + const beforeQuery = currentMessage.substring( + 0, + currentMessage.lastIndexOf(':') + ); + const insertionText = `${beforeQuery}${selectedEmoji.emoji} `; + + messageRef.current.value = insertionText; + + // Set cursor position after the emoji and space + const cursorPosition = insertionText.length; + messageRef.current.setSelectionRange(cursorPosition, cursorPosition); + messageRef.current.focus(); + + // Clear emoji autocomplete state + setFilteredEmojis([]); + setEmojiIndex(-1); + setStartReadEmoji(false); + setShowEmojiList(false); + } + }, + [ + messageRef, + setFilteredEmojis, + setEmojiIndex, + setShowEmojiList, + setStartReadEmoji, + ] + ); + + const setItemRef = (el, index) => { + itemRefs.current[index] = el; + }; + + useEffect(() => { + const handleKeyPress = (event) => { + switch (event.key) { + case 'Enter': { + const selectedEmoji = filteredEmojis[emojiIndex]; + if (selectedEmoji) { + handleEmojiClick(selectedEmoji); + } + break; + } + case 'Escape': { + // Cancel emoji selection + setFilteredEmojis([]); + setEmojiIndex(-1); + setStartReadEmoji(false); + setShowEmojiList(false); + messageRef.current.focus(); + break; + } + case 'ArrowUp': + event.preventDefault(); + setEmojiIndex( + emojiIndex - 1 < 0 ? filteredEmojis.length - 1 : emojiIndex - 1 + ); + break; + case 'ArrowDown': + event.preventDefault(); + setEmojiIndex( + emojiIndex + 1 >= filteredEmojis.length ? 0 : emojiIndex + 1 + ); + break; + default: + break; + } + }; + + document.addEventListener('keydown', handleKeyPress); + + return () => { + document.removeEventListener('keydown', handleKeyPress); + }; + }, [ + emojiIndex, + filteredEmojis, + handleEmojiClick, + setEmojiIndex, + setFilteredEmojis, + setStartReadEmoji, + setShowEmojiList, + messageRef, + ]); + + useEffect(() => { + if (itemRefs.current[emojiIndex]) { + itemRefs.current[emojiIndex].scrollIntoView({ + block: 'nearest', + }); + } + }, [emojiIndex]); + + if (!filteredEmojis || filteredEmojis.length === 0) { + return null; + } + + return ( + +
    + {filteredEmojis.map((emoji, index) => ( +
  • handleEmojiClick(emoji)} + ref={(el) => setItemRef(el, index)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleEmojiClick(emoji); + } + }} + style={{ + backgroundColor: index === emojiIndex && 'rgba(0, 0, 0, 0.1)', + color: index === emojiIndex && theme.colors.foreground, + }} + > + {emoji.emoji} + :{emoji.shortname}: +
  • + ))} +
+
+ ); +} + +EmojiList.propTypes = { + emojiIndex: PropTypes.number, + messageRef: PropTypes.object.isRequired, + filteredEmojis: PropTypes.array, + setFilteredEmojis: PropTypes.func.isRequired, + setEmojiIndex: PropTypes.func.isRequired, + setStartReadEmoji: PropTypes.func.isRequired, + setShowEmojiList: PropTypes.func.isRequired, +}; + +export default EmojiList; diff --git a/packages/react/src/views/EmojiList/EmojiList.styles.js b/packages/react/src/views/EmojiList/EmojiList.styles.js new file mode 100644 index 0000000000..098e0a2bd4 --- /dev/null +++ b/packages/react/src/views/EmojiList/EmojiList.styles.js @@ -0,0 +1,53 @@ +import { css } from '@emotion/react'; + +const getEmojiListStyles = (theme) => { + const styles = { + main: css` + margin: 0.2rem 2rem; + display: block; + max-height: 10rem; + overflow-y: auto; + overflow-x: hidden; + border: 1px solid ${theme.colors.border}; + border-radius: 0.2rem; + background-color: ${theme.colors.background}; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + z-index: 1000; + position: relative; + `, + + listItem: css` + cursor: pointer; + display: flex; + align-items: center; + padding: 0.5rem 0.75rem; + font-size: 14px; + border-bottom: 1px solid ${theme.colors.border}; + transition: background-color 0.2s ease; + + &:hover { + background-color: ${theme.colors.secondary}; + } + + &:last-child { + border-bottom: none; + } + `, + + emoji: css` + font-size: 16px; + margin-right: 0.5rem; + flex-shrink: 0; + `, + + shortname: css` + font-family: monospace; + color: ${theme.colors.mutedForeground}; + font-size: 12px; + `, + }; + + return styles; +}; + +export default getEmojiListStyles; diff --git a/packages/react/src/views/EmojiList/index.js b/packages/react/src/views/EmojiList/index.js new file mode 100644 index 0000000000..794f574143 --- /dev/null +++ b/packages/react/src/views/EmojiList/index.js @@ -0,0 +1 @@ +export { default as EmojiList } from './EmojiList';