Inner.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. 'use client'
  2. import type { ChangeEvent, FC } from 'react'
  3. import React, { useState } from 'react'
  4. import data from '@emoji-mart/data'
  5. import type { EmojiMartData } from '@emoji-mart/data'
  6. import { init } from 'emoji-mart'
  7. import {
  8. MagnifyingGlassIcon,
  9. } from '@heroicons/react/24/outline'
  10. import Input from '@/app/components/base/input'
  11. import Divider from '@/app/components/base/divider'
  12. import { searchEmoji } from '@/utils/emoji'
  13. import cn from '@/utils/classnames'
  14. declare global {
  15. // eslint-disable-next-line ts/no-namespace
  16. namespace JSX {
  17. // eslint-disable-next-line ts/consistent-type-definitions
  18. interface IntrinsicElements {
  19. 'em-emoji': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
  20. }
  21. }
  22. }
  23. init({ data })
  24. const backgroundColors = [
  25. '#FFEAD5',
  26. '#E4FBCC',
  27. '#D3F8DF',
  28. '#E0F2FE',
  29. '#E0EAFF',
  30. '#EFF1F5',
  31. '#FBE8FF',
  32. '#FCE7F6',
  33. '#FEF7C3',
  34. '#E6F4D7',
  35. '#D5F5F6',
  36. '#D1E9FF',
  37. '#D1E0FF',
  38. '#D5D9EB',
  39. '#ECE9FE',
  40. '#FFE4E8',
  41. ]
  42. type IEmojiPickerInnerProps = {
  43. emoji?: string
  44. background?: string
  45. onSelect?: (emoji: string, background: string) => void
  46. className?: string
  47. }
  48. const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
  49. onSelect,
  50. className,
  51. }) => {
  52. const { categories } = data as EmojiMartData
  53. const [selectedEmoji, setSelectedEmoji] = useState('')
  54. const [selectedBackground, setSelectedBackground] = useState(backgroundColors[0])
  55. const [searchedEmojis, setSearchedEmojis] = useState<string[]>([])
  56. const [isSearching, setIsSearching] = useState(false)
  57. React.useEffect(() => {
  58. if (selectedEmoji && selectedBackground)
  59. onSelect?.(selectedEmoji, selectedBackground)
  60. }, [onSelect, selectedEmoji, selectedBackground])
  61. return <div className={cn(className)}>
  62. <div className='flex w-full flex-col items-center px-3 pb-2'>
  63. <div className="relative w-full">
  64. <div className="pointer-events-none absolute inset-y-0 left-0 z-10 flex items-center pl-3">
  65. <MagnifyingGlassIcon className="h-5 w-5 text-text-quaternary" aria-hidden="true" />
  66. </div>
  67. <Input
  68. className="pl-10"
  69. type="search"
  70. id="search"
  71. placeholder="Search emojis..."
  72. onChange={async (e: ChangeEvent<HTMLInputElement>) => {
  73. if (e.target.value === '') {
  74. setIsSearching(false)
  75. }
  76. else {
  77. setIsSearching(true)
  78. const emojis = await searchEmoji(e.target.value)
  79. setSearchedEmojis(emojis)
  80. }
  81. }}
  82. />
  83. </div>
  84. </div>
  85. <Divider className='my-3' />
  86. <div className="max-h-[200px] w-full overflow-y-auto overflow-x-hidden px-3">
  87. {isSearching && <>
  88. <div key={'category-search'} className='flex flex-col'>
  89. <p className='system-xs-medium-uppercase mb-1 text-text-primary'>Search</p>
  90. <div className='grid h-full w-full grid-cols-8 gap-1'>
  91. {searchedEmojis.map((emoji: string, index: number) => {
  92. return <div
  93. key={`emoji-search-${index}`}
  94. className='inline-flex h-10 w-10 items-center justify-center rounded-lg'
  95. onClick={() => {
  96. setSelectedEmoji(emoji)
  97. }}
  98. >
  99. <div className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg p-1 ring-components-input-border-hover ring-offset-1 hover:ring-1'>
  100. <em-emoji id={emoji} />
  101. </div>
  102. </div>
  103. })}
  104. </div>
  105. </div>
  106. </>}
  107. {categories.map((category, index: number) => {
  108. return <div key={`category-${index}`} className='flex flex-col'>
  109. <p className='system-xs-medium-uppercase mb-1 text-text-primary'>{category.id}</p>
  110. <div className='grid h-full w-full grid-cols-8 gap-1'>
  111. {category.emojis.map((emoji, index: number) => {
  112. return <div
  113. key={`emoji-${index}`}
  114. className='inline-flex h-10 w-10 items-center justify-center rounded-lg'
  115. onClick={() => {
  116. setSelectedEmoji(emoji)
  117. }}
  118. >
  119. <div className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg p-1 ring-components-input-border-hover ring-offset-1 hover:ring-1'>
  120. <em-emoji id={emoji} />
  121. </div>
  122. </div>
  123. })}
  124. </div>
  125. </div>
  126. })}
  127. </div>
  128. {/* Color Select */}
  129. <div className={cn('p-3 pb-0', selectedEmoji === '' ? 'opacity-25' : '')}>
  130. <p className='system-xs-medium-uppercase mb-2 text-text-primary'>Choose Style</p>
  131. <div className='grid h-full w-full grid-cols-8 gap-1'>
  132. {backgroundColors.map((color) => {
  133. return <div
  134. key={color}
  135. className={
  136. cn(
  137. 'cursor-pointer',
  138. 'ring-offset-1 hover:ring-1',
  139. 'inline-flex h-10 w-10 items-center justify-center rounded-lg',
  140. color === selectedBackground ? 'ring-1 ring-components-input-border-hover' : '',
  141. )}
  142. onClick={() => {
  143. setSelectedBackground(color)
  144. }}
  145. >
  146. <div className={cn(
  147. 'flex h-8 w-8 items-center justify-center rounded-lg p-1',
  148. )
  149. } style={{ background: color }}>
  150. {selectedEmoji !== '' && <em-emoji id={selectedEmoji} />}
  151. </div>
  152. </div>
  153. })}
  154. </div>
  155. </div>
  156. </div>
  157. }
  158. export default EmojiPickerInner