【React】基于 React+Tailwind 的 EmojiPicker 选择器组件

1.背景

React 写一个 EmojiPicker 组件,基于 emoji-mart 组件二次封装。支持添加自定义背景 、Emoji 图标选择!并在页面上展示! 

2.技术栈

@emoji-mart/data 、emoji-mart : emoji 图标库、元数据

tailwindcss: 原子化 CSS 样式库

antd : 组件库

"@emoji-mart/data": "^1.2.1",
"@remixicon/react": "^4.6.0",
"antd": "^5.24.3",
"emoji-mart": "^5.6.0",
"react": "^19.0.0",
"tailwindcss": "^3.4.17",

PS:

  • emoji-mart/data@remixicon/reactantd 这些库直接用 pnpm 、npm 、yarm 直接安装即可

  • tailwindcss 安装配置参考

  • cn函数参考

3.emoji选择器组件

src/components/emojiPicker/index.tsx

import type { FC } from 'react';
import { useCallback, useState } from 'react';
import EmojiPickerInner from './emojiCom';
import { Button, Modal } from 'antd';type IEmojiPickerProps = {onSelect: (emoji: string, background: string) => void
};
const EmojiPicker: FC<IEmojiPickerProps> = ({onSelect
}) => {const [isEmojiModalOpen, setIsEmojiModalOpen] = useState<boolean>(false);const [selectedBackground, setSelectedBackground] = useState<string>();const [selectedEmoji, setSelectedEmoji] = useState<string>();const selectEmoji = useCallback((emoji: string, background: string) => {setSelectedEmoji(emoji)setSelectedBackground(background)}, [setSelectedEmoji, setSelectedBackground]);const onModalSelectEmojOk = () => {if (!(selectedEmoji && selectedBackground))returnonSelect(selectedEmoji!, selectedBackground!);clear();setIsEmojiModalOpen(false);};const clear = () => {setSelectedEmoji('')setSelectedBackground('')};return (<><Button onClick={() => setIsEmojiModalOpen(true)}> Emoj 表情</Button><Modaltitle="Emoj 表情选择"open={isEmojiModalOpen}onOk={onModalSelectEmojOk}okText="确定"cancelText="取消"okButtonProps={{ disabled: !(Boolean(selectedEmoji) && Boolean(selectedBackground)) }}onCancel={() => { setIsEmojiModalOpen(false) }}>{isEmojiModalOpen && (<EmojiPickerInner onSelect={selectEmoji} />)}</Modal ></>)
}
export default EmojiPicker

src/components/emojiPicker/emojiCom.tsx

import type { ChangeEvent, FC } from 'react';
import { useState, useEffect } from 'react';// components
import data from '@emoji-mart/data';
import type { EmojiMartData } from '@emoji-mart/data';
import { init, SearchIndex } from 'emoji-mart';// icons
import { RiSearch2Line } from '@remixicon/react';// utils
import { cn } from '@/lib/utils';export interface Skins {native: string
};export interface Emoji {id: stringname: stringkeywords: string[]skins: Skins[]version: numberemoticons?: string[]
};type IEmojiPickerInnerProps = {emoji?: stringbackground?: stringonSelect?: (emoji: string, background: string) => voidclassName?: string
};init({ data });const backgroundColors = ['#FFEAD5','#E4FBCC','#D3F8DF','#E0F2FE','#E0EAFF','#EFF1F5','#FBE8FF','#FCE7F6','#FEF7C3','#E6F4D7','#D5F5F6','#D1E9FF','#D1E0FF','#D5D9EB','#ECE9FE','#FFE4E8',
];const classNameComm = 'cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-gray-300'const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({onSelect, className,
}) => {const { categories } = data as EmojiMartData;const [selectedEmoji, setSelectedEmoji] = useState('');const [selectedBackground, setSelectedBackground] = useState(backgroundColors[0]);const [searchedEmojis, setSearchedEmojis] = useState<string[]>([]);const [isSearching, setIsSearching] = useState<boolean>(false);//  search iconsconst searchEmoji = async function searchEmoji(value: string) {const emojis: Emoji[] = await SearchIndex.search(value) || []const results = emojis.map((emoji) => {return emoji.skins[0]?.native})return results};// useEffectuseEffect(() => {if (selectedEmoji && selectedBackground) onSelect?.(selectedEmoji, selectedBackground)}, [onSelect, selectedEmoji, selectedBackground]);return <div className={cn(className)}><div className='flex flex-col items-center w-full px-3'><div className="relative w-full"><div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"><RiSearch2Line className="w-5 h-5 text-gray-400" aria-hidden="true" /></div><inputtype="search"id="search"className='block w-full h-10 px-3 pl-10 text-sm font-normal bg-gray-100 rounded-lg'placeholder="Search emojis..."onChange={async (e: ChangeEvent<HTMLInputElement>) => {if (e.target.value === '') {setIsSearching(false)} else {setIsSearching(true)const emojis = await searchEmoji(e.target.value)setSearchedEmojis(emojis)}}}/></div></div><div className="w-full max-h-[200px] overflow-x-hidden overflow-y-auto px-3">{isSearching && <><div key={'category-search'} className='flex flex-col'><p className='font-medium uppercase text-xs text-[#101828] mb-1'>Search</p><div className='w-full h-full grid grid-cols-8 gap-1'>{searchedEmojis.map((emoji: string, index: number) => {return <divkey={`emoji-search-${index}`}className='inline-flex w-10 h-10 rounded-lg items-center justify-center'onClick={() => {setSelectedEmoji(emoji)}}><div className={classNameComm}><em-emoji id={emoji} /></div></div>})}</div></div></>}{categories.map((category, index: number) => {return <div key={`category-${index}`} className='flex flex-col'><p className='font-medium uppercase text-xs text-[#101828] mb-1'>{category.id}</p><div className='w-full h-full grid grid-cols-8 gap-1'>{category.emojis.map((emoji, index: number) => {return <divkey={`emoji-${index}`}className='inline-flex w-10 h-10 rounded-lg items-center justify-center'onClick={() => {setSelectedEmoji(emoji)}}><div className={classNameComm}><em-emoji id={emoji} /></div></div>})}</div></div>})}</div>{/* Color Select */}<div className={cn('p-3 pb-0', selectedEmoji === '' ? 'opacity-25' : '')}><p className='font-medium uppercase text-xs text-[#101828] mb-2'>Choose Style</p><div className='w-full h-full grid grid-cols-8 gap-1'>{backgroundColors.map((color) => {return <divkey={color}className={cn('cursor-pointer','hover:ring-1 ring-offset-1','inline-flex w-10 h-10 rounded-lg items-center justify-center',color === selectedBackground ? 'ring-1 ring-gray-300' : '',)}onClick={() => {setSelectedBackground(color)}}><div className={cn('w-8 h-8 p-1 flex items-center justify-center rounded-lg',)} style={{ background: color }}>{selectedEmoji !== '' && <em-emoji id={selectedEmoji} />}</div></div>})}</div></div></div>
};export default EmojiPickerInner;

4. emoji 图标展示组件

src/components/appIcon/index.tsx

import type { FC } from 'react';
import { init } from 'emoji-mart';;
import data from '@emoji-mart/data';
import { cn } from '@/lib/utils';type AppIconType = 'image' | 'emoji';init({ data });export type AppIconProps = {size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'rounded?: booleaniconType?: AppIconType | nullicon?: stringbackground?: string | nullimageUrl?: string | null | undefinedclassName?: stringinnerIcon?: React.ReactNodeonClick?: () => void
};// used for emojiPicker
const AppIcon: FC<AppIconProps> = ({size = 'medium',rounded = false,iconType,icon,background,imageUrl,className,innerIcon,onClick,
}) => {const wrapperClassName = cn('flex items-center justify-center relative w-9 h-9 text-lg rounded-lg grow-0 shrink-0',size !== 'medium' && { large: 'w-10 h-10', small: 'w-8 h-8', tiny: 'w-6 h-6 text-base', xs: 'w-3 h-3 text-base' }[size],rounded && 'rounded-full',className ?? '','overflow-hidden',);const isValidImageIcon = iconType === 'image' && imageUrl;return <spanclassName={wrapperClassName}style={{ background: isValidImageIcon ? undefined : (background || '#FFEAD5') }}onClick={onClick}>{isValidImageIcon? <img src={imageUrl} className="w-full h-full" alt="app icon" />: (innerIcon || ((icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />))}</span>
};export default AppIcon;

5.cn 函数

src/lib/utils.ts

import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));
}

6.测试使用

src\App.tsx

import { useState } from 'react';
import EmojiPicker from '@/components/emojiPicker';
import AppIcon from '@/components/appIcon';function App() {const [emoji, setEmoji] = useState('😀');const [background, setBackground] = useState('');const onSelect = (emoji: string, background: string) => {setEmoji(emoji)setBackground(background)};return (<div className="flex justify-center items-center h-screen gap-2"><AppIcon icon={emoji} background={background} /><EmojiPicker onSelect={onSelect} /></div>);
}export default App;

效果展示
在这里插入图片描述

如果你有更多问题,欢迎随时问我!😊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/42417.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

skynet.socket.limit 使用详解

目录 核心作用方法定义使用场景场景 1&#xff1a;限制接收缓冲区&#xff08;防御大包攻击&#xff09;场景 2&#xff1a;动态调整限制&#xff08;应对不同负载&#xff09; 底层机制注意事项完整示例&#xff1a;带流量控制的 Echo 服务总结 在 Skynet 框架中&#xff0c;s…

electron打包vue2项目流程

1&#xff0c;安装一个node vue2 的项目 2&#xff0c;安装electron&#xff1a; npm install electron -g//如果安装还是 特比慢 或 不想安装cnpn 淘宝镜像查看是否安装成功&#xff1a;electron -v 3&#xff0c;进入到项目目录&#xff1a;cd electron-demo 进入项目目录…

【面试八股】:常见的锁策略

常见的锁策略 synchronized &#xff08;标准库的锁不够你用了&#xff09;锁策略和 Java 不强相关&#xff0c;其他语言涉及到锁&#xff0c;也有这样的锁策略。 1. 悲观锁&#xff0c;乐观锁&#xff08;描述的加锁时遇到的场景&#xff09; 悲观锁&#xff1a;预测接下来…

【数据分享】基于联合国城市化程度框架的全球城市边界数据集(免费获取/Shp格式)

在全球城市化进程不断加快的今天&#xff0c;如何精准定义和测量“城市”成为关键问题。不同国家和机构采用不同的标准&#xff0c;导致全球城市化水平的统计结果存在较大差异。同时&#xff0c;由于数据来源分散、标准不统一&#xff0c;获取一套完整、可比的全球城市边界数据…

acwing 每日一题4888. 领导者

目录 题目简述&#xff1a; 思路梳理&#xff1a; 总代码&#xff1a; https://www.acwing.com/problem/content/description/4891/ 题目简述&#xff1a; 有两个品种的奶牛&#xff0c;分别为G和H&#xff0c;我们要在每个品种中各找一头牛当领导者&#xff0c;最后输出全…

在Windows下VSCodeSSH远程登录到Ubuntu

Window用VSCode通过SSH远程登录Ubuntu SSH 服务开启Windows远程登录 SSH 服务开启 首先要确保 Ubuntu 的 SSH 服务开启了&#xff0c;开启 Ubuntu 的 SSH 服务以后我们就可以在 Windwos 下使用终端软件登陆到 Ubuntu 开启 SSH sudo apt-get install openssh-serverWindows远…

软件性能测试中的“假阳性”陷阱

软件性能测试中的“假阳性”陷阱主要表现为错误警报频繁、资源浪费严重、测试可信度降低。其中&#xff0c;错误警报频繁是最常见且最严重的问题之一&#xff0c;“假阳性”现象会导致开发团队在解决不存在的问题上花费大量时间。据行业调查显示&#xff0c;超过30%的性能优化成…

AwesomeQt分享3(含源码)

AwesomeQt 这个项目包含了多个Qt组件的使用示例&#xff0c;旨在展示Qt各种强大功能的实现方式。 源码分享 github: awesome_Qtgitee: 后续同步 项目进度 QCustomPlot曲线控件示例 支持排序和筛选的列表控件示例 支持排序和筛选的表格控件示例 属性表示例 Dock窗口示例 自绘…

如何验证极端工况下的系统可靠性?

验证极端工况下系统可靠性的方法主要包括设计极限测试、环境应力筛选&#xff08;ESS&#xff09;、可靠性预测与建模。其中&#xff0c;设计极限测试最为关键&#xff0c;通过在试验中施加超过预期使用条件的应力&#xff0c;可以有效评估系统的真实承受能力和潜在弱点。这类测…

[计算机网络]网络I/O模型

欢迎来到啾啾的博客&#x1f431;。 这是一个致力于构建完善的Java程序员知识体系的博客&#x1f4da;&#xff0c;记录学习的点滴&#xff0c;分享工作的思考、实用的技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 欢迎评论交流&#xff0c;感谢您的阅读&#x1f604;。…

MyBaitis-Plus 使用动态表名 selectPage 不生效

在使用 MyBatis-Plus 时&#xff0c;采用动态表名策略后&#xff0c;selectPage 方法无法正常生效。 MyBatis-Plus动态表名插件配置MyBatis-Plus动态表名失效原因MyBatis-Plus动态表名失效解决办法 MyBatis-Plus动态表名插件配置 以下是我项目中 MyBatis - Plus 的插件配置&am…

C语言基础—构造类型

数据类型 1.基本类型/基础类型 整型 短整型&#xff1a;short[int] --2字节 基本整型&#xff1a;int --4字节 长整型&#xff1a;long[int] --32位4字节/64位8字节 长长整型&#xff1a;long long [int] &#xff08;C99&#xff09; 注意&#xff1a;以上类型又都分为sig…

交流电机类型及其控制技术

交流电机可分为同步电机和异步电机两大种类&#xff0c;如果电机转子的转速与定子旋转磁场的转速相等&#xff0c;转子与定子旋转磁场在空间同步地旋转&#xff0c;这种电机就称为同步电机。如果电机转子的转速不等于定子旋转磁场的转速&#xff0c;转子与定子旋转磁场在空间旋…

「HTML5+Canvas实战」星际空战游戏开发 - 纯前端实现 源码即开即用【附演示视频】

纯前端实现星际空战游戏【简易版】 博主上次分享的简易版飞机大战收到了不少建议,今天再给大家来一波福利!带来全新升级的飞机大战进阶版!不仅拥有更丰富的游戏机制和更精美的游戏画面,还加入了超燃的BOSS战斗系统。源码完全免费开放,拿来即用无门槛,欢迎感兴趣的小伙伴…

7-项目负责人-添加产品

点击一个项目集&#xff0c;进入项目集的页面。可以进行产品、项目、人员和干系人的管理。 点击“添加产品”&#xff0c;为该项目集添加关联产品。一个项目集可以关联多个产品。还可以通过“产品线”管理一些列产品。 产品。

深度赋能!北京智和信通融合DeepSeek,解锁智能运维无限可能

在数字化飞速发展的今天&#xff0c;传统运维模式面临着设备规模激增、故障复杂度攀升、人工响应滞后等多重挑战。随着DeepSeek、腾讯元宝等AI大模型的兴起&#xff0c;为传统运维模式带来了新的变革。 北京智和信通基于DeepSeek大模型技术&#xff0c;将AI和运维场景深度融合&…

flex和bison笔记

文章目录 flex语法&#xff1a;定义部分:规则部分:flex全局变量&#xff1a;yyin: bison和flex联合编译: flex词法分析 bison语法分析 flex有两种使用方式&#xff0c;一种是flex单独做一个词法分析程序&#xff0c;另一种是flex和bison协同构建一个词法语法分析程序 我们在北…

rbpf虚拟机-call指令

文章目录 一、概述背景知识 二、call 指令的主要方法2.1 注册辅助函数2.2 执行辅助函数 三、完整代码示例与详解3.1 示例辅助函数3.2 测试虚拟机的 call 指令测试代码代码解析 四、总结 Welcome to Code Blocks blog 本篇文章主要介绍了 [rbpf虚拟机-call指令] ❤博主广交技术…

Java构造函数与普通函数

1.概解 tips&#xff1a; 1.声明函数主要用public/private&#xff0c;public可以在其他函数中访问。 2.public后面跟函数返回类型&#xff0c;void表示无返回值。 3.main函数是自动执行的构造函数&#xff0c;而其他函数除非被调用则不会被自动执行 运行结果&#xff1a…

MySQL: 创建两个关联的表,用联表sql创建一个新表

MySQL: 创建两个关联的表 建表思路 USERS 表&#xff1a;包含用户的基本信息&#xff0c;像 ID、NAME、EMAIL 等。v_card 表&#xff1a;存有虚拟卡的相关信息&#xff0c;如 type 和 amount。关联字段&#xff1a;USERS 表的 V_CARD 字段和 v_card 表的 v_card 字段用于建立…