doclific 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/.gitattributes +2 -0
  2. package/.prettierignore +5 -0
  3. package/.prettierrc +9 -0
  4. package/.vscode/settings.json +13 -0
  5. package/dist/bin/doclific.d.ts +3 -0
  6. package/dist/bin/doclific.d.ts.map +1 -0
  7. package/dist/bin/doclific.js +11 -0
  8. package/dist/core/codebase.js +31 -0
  9. package/dist/core/docs.js +75 -0
  10. package/dist/core/git.js +47 -0
  11. package/dist/server/index.d.ts +2 -0
  12. package/dist/server/index.d.ts.map +1 -0
  13. package/dist/server/index.js +46 -0
  14. package/dist/server/router.d.ts +9 -0
  15. package/dist/server/router.d.ts.map +1 -0
  16. package/dist/server/router.js +55 -0
  17. package/frontend/README.md +73 -0
  18. package/frontend/components.json +24 -0
  19. package/frontend/eslint.config.js +23 -0
  20. package/frontend/index.html +25 -0
  21. package/frontend/package-lock.json +15754 -0
  22. package/frontend/package.json +122 -0
  23. package/frontend/public/logo.svg +1 -0
  24. package/frontend/src/App.tsx +21 -0
  25. package/frontend/src/components/app-sidebar.tsx +393 -0
  26. package/frontend/src/components/editor/editor-base-kit.tsx +43 -0
  27. package/frontend/src/components/editor/editor-kit.tsx +93 -0
  28. package/frontend/src/components/editor/plugins/align-base-kit.tsx +16 -0
  29. package/frontend/src/components/editor/plugins/align-kit.tsx +18 -0
  30. package/frontend/src/components/editor/plugins/autoformat-kit.tsx +236 -0
  31. package/frontend/src/components/editor/plugins/basic-blocks-base-kit.tsx +35 -0
  32. package/frontend/src/components/editor/plugins/basic-blocks-kit.tsx +88 -0
  33. package/frontend/src/components/editor/plugins/basic-marks-base-kit.tsx +27 -0
  34. package/frontend/src/components/editor/plugins/basic-marks-kit.tsx +41 -0
  35. package/frontend/src/components/editor/plugins/basic-nodes-kit.tsx +6 -0
  36. package/frontend/src/components/editor/plugins/block-menu-kit.tsx +14 -0
  37. package/frontend/src/components/editor/plugins/block-placeholder-kit.tsx +17 -0
  38. package/frontend/src/components/editor/plugins/block-selection-kit.tsx +32 -0
  39. package/frontend/src/components/editor/plugins/callout-base-kit.tsx +7 -0
  40. package/frontend/src/components/editor/plugins/callout-kit.tsx +7 -0
  41. package/frontend/src/components/editor/plugins/code-block-base-kit.tsx +23 -0
  42. package/frontend/src/components/editor/plugins/code-block-kit.tsx +26 -0
  43. package/frontend/src/components/editor/plugins/codebase-kit.tsx +23 -0
  44. package/frontend/src/components/editor/plugins/column-base-kit.tsx +11 -0
  45. package/frontend/src/components/editor/plugins/column-kit.tsx +10 -0
  46. package/frontend/src/components/editor/plugins/comment-base-kit.tsx +7 -0
  47. package/frontend/src/components/editor/plugins/comment-kit.tsx +97 -0
  48. package/frontend/src/components/editor/plugins/cursor-overlay-kit.tsx +13 -0
  49. package/frontend/src/components/editor/plugins/date-base-kit.tsx +5 -0
  50. package/frontend/src/components/editor/plugins/date-kit.tsx +7 -0
  51. package/frontend/src/components/editor/plugins/discussion-kit.tsx +148 -0
  52. package/frontend/src/components/editor/plugins/dnd-kit.tsx +28 -0
  53. package/frontend/src/components/editor/plugins/docx-kit.tsx +6 -0
  54. package/frontend/src/components/editor/plugins/emoji-kit.tsx +13 -0
  55. package/frontend/src/components/editor/plugins/excalidraw-kit.tsx +9 -0
  56. package/frontend/src/components/editor/plugins/exit-break-kit.tsx +12 -0
  57. package/frontend/src/components/editor/plugins/floating-toolbar-kit.tsx +19 -0
  58. package/frontend/src/components/editor/plugins/font-base-kit.tsx +20 -0
  59. package/frontend/src/components/editor/plugins/font-kit.tsx +29 -0
  60. package/frontend/src/components/editor/plugins/indent-base-kit.tsx +19 -0
  61. package/frontend/src/components/editor/plugins/indent-kit.tsx +22 -0
  62. package/frontend/src/components/editor/plugins/line-height-base-kit.tsx +14 -0
  63. package/frontend/src/components/editor/plugins/line-height-kit.tsx +16 -0
  64. package/frontend/src/components/editor/plugins/link-base-kit.tsx +5 -0
  65. package/frontend/src/components/editor/plugins/link-kit.tsx +15 -0
  66. package/frontend/src/components/editor/plugins/list-base-kit.tsx +23 -0
  67. package/frontend/src/components/editor/plugins/list-kit.tsx +26 -0
  68. package/frontend/src/components/editor/plugins/markdown-kit.tsx +46 -0
  69. package/frontend/src/components/editor/plugins/math-base-kit.tsx +11 -0
  70. package/frontend/src/components/editor/plugins/math-kit.tsx +13 -0
  71. package/frontend/src/components/editor/plugins/media-base-kit.tsx +31 -0
  72. package/frontend/src/components/editor/plugins/media-kit.tsx +43 -0
  73. package/frontend/src/components/editor/plugins/mention-base-kit.tsx +7 -0
  74. package/frontend/src/components/editor/plugins/mention-kit.tsx +15 -0
  75. package/frontend/src/components/editor/plugins/slash-kit.tsx +18 -0
  76. package/frontend/src/components/editor/plugins/suggestion-base-kit.tsx +7 -0
  77. package/frontend/src/components/editor/plugins/suggestion-kit.tsx +90 -0
  78. package/frontend/src/components/editor/plugins/table-base-kit.tsx +20 -0
  79. package/frontend/src/components/editor/plugins/table-kit.tsx +22 -0
  80. package/frontend/src/components/editor/plugins/toc-base-kit.tsx +5 -0
  81. package/frontend/src/components/editor/plugins/toc-kit.tsx +14 -0
  82. package/frontend/src/components/editor/plugins/toggle-base-kit.tsx +7 -0
  83. package/frontend/src/components/editor/plugins/toggle-kit.tsx +11 -0
  84. package/frontend/src/components/editor/transforms.ts +194 -0
  85. package/frontend/src/components/markdown-to-slate-demo.tsx +50 -0
  86. package/frontend/src/components/mode-toggle.tsx +15 -0
  87. package/frontend/src/components/theme-provider.tsx +73 -0
  88. package/frontend/src/components/ui/alert-dialog.tsx +155 -0
  89. package/frontend/src/components/ui/align-toolbar-button.tsx +84 -0
  90. package/frontend/src/components/ui/avatar.tsx +51 -0
  91. package/frontend/src/components/ui/block-context-menu.tsx +199 -0
  92. package/frontend/src/components/ui/block-discussion.tsx +365 -0
  93. package/frontend/src/components/ui/block-draggable.tsx +512 -0
  94. package/frontend/src/components/ui/block-list-static.tsx +80 -0
  95. package/frontend/src/components/ui/block-list.tsx +87 -0
  96. package/frontend/src/components/ui/block-selection.tsx +42 -0
  97. package/frontend/src/components/ui/block-suggestion.tsx +473 -0
  98. package/frontend/src/components/ui/blockquote-node-static.tsx +11 -0
  99. package/frontend/src/components/ui/blockquote-node.tsx +13 -0
  100. package/frontend/src/components/ui/button.tsx +62 -0
  101. package/frontend/src/components/ui/calendar.tsx +218 -0
  102. package/frontend/src/components/ui/callout-node-static.tsx +36 -0
  103. package/frontend/src/components/ui/callout-node.tsx +63 -0
  104. package/frontend/src/components/ui/caption.tsx +63 -0
  105. package/frontend/src/components/ui/checkbox.tsx +30 -0
  106. package/frontend/src/components/ui/code-block-node-static.tsx +35 -0
  107. package/frontend/src/components/ui/code-block-node.tsx +287 -0
  108. package/frontend/src/components/ui/code-node-static.tsx +15 -0
  109. package/frontend/src/components/ui/code-node.tsx +17 -0
  110. package/frontend/src/components/ui/codebase-snippet-node.tsx +237 -0
  111. package/frontend/src/components/ui/column-node-static.tsx +29 -0
  112. package/frontend/src/components/ui/column-node.tsx +317 -0
  113. package/frontend/src/components/ui/command.tsx +182 -0
  114. package/frontend/src/components/ui/comment-node-static.tsx +15 -0
  115. package/frontend/src/components/ui/comment-node.tsx +45 -0
  116. package/frontend/src/components/ui/comment-toolbar-button.tsx +24 -0
  117. package/frontend/src/components/ui/comment.tsx +618 -0
  118. package/frontend/src/components/ui/context-menu.tsx +250 -0
  119. package/frontend/src/components/ui/cursor-overlay.tsx +66 -0
  120. package/frontend/src/components/ui/date-node-static.tsx +45 -0
  121. package/frontend/src/components/ui/date-node.tsx +93 -0
  122. package/frontend/src/components/ui/dialog.tsx +143 -0
  123. package/frontend/src/components/ui/dropdown-menu.tsx +255 -0
  124. package/frontend/src/components/ui/dynamic-icon.tsx +12 -0
  125. package/frontend/src/components/ui/editor-static.tsx +53 -0
  126. package/frontend/src/components/ui/editor.tsx +130 -0
  127. package/frontend/src/components/ui/emoji-node.tsx +69 -0
  128. package/frontend/src/components/ui/emoji-toolbar-button.tsx +628 -0
  129. package/frontend/src/components/ui/equation-node-static.tsx +98 -0
  130. package/frontend/src/components/ui/equation-node.tsx +235 -0
  131. package/frontend/src/components/ui/equation-toolbar-button.tsx +25 -0
  132. package/frontend/src/components/ui/excalidraw-node.tsx +36 -0
  133. package/frontend/src/components/ui/export-toolbar-button.tsx +174 -0
  134. package/frontend/src/components/ui/file-selector.tsx +339 -0
  135. package/frontend/src/components/ui/floating-toolbar-buttons.tsx +73 -0
  136. package/frontend/src/components/ui/floating-toolbar.tsx +85 -0
  137. package/frontend/src/components/ui/font-color-toolbar-button.tsx +831 -0
  138. package/frontend/src/components/ui/font-size-toolbar-button.tsx +152 -0
  139. package/frontend/src/components/ui/heading-node-static.tsx +68 -0
  140. package/frontend/src/components/ui/heading-node.tsx +58 -0
  141. package/frontend/src/components/ui/highlight-node-static.tsx +11 -0
  142. package/frontend/src/components/ui/highlight-node.tsx +13 -0
  143. package/frontend/src/components/ui/history-toolbar-button.tsx +50 -0
  144. package/frontend/src/components/ui/hr-node-static.tsx +20 -0
  145. package/frontend/src/components/ui/hr-node.tsx +33 -0
  146. package/frontend/src/components/ui/import-toolbar-button.tsx +97 -0
  147. package/frontend/src/components/ui/indent-toolbar-button.tsx +30 -0
  148. package/frontend/src/components/ui/inline-combobox.tsx +414 -0
  149. package/frontend/src/components/ui/input.tsx +21 -0
  150. package/frontend/src/components/ui/insert-toolbar-button.tsx +254 -0
  151. package/frontend/src/components/ui/kbd-node-static.tsx +15 -0
  152. package/frontend/src/components/ui/kbd-node.tsx +17 -0
  153. package/frontend/src/components/ui/layout-header.tsx +35 -0
  154. package/frontend/src/components/ui/line-height-toolbar-button.tsx +68 -0
  155. package/frontend/src/components/ui/link-node-static.tsx +21 -0
  156. package/frontend/src/components/ui/link-node.tsx +39 -0
  157. package/frontend/src/components/ui/link-toolbar-button.tsx +22 -0
  158. package/frontend/src/components/ui/link-toolbar.tsx +206 -0
  159. package/frontend/src/components/ui/list-toolbar-button.tsx +204 -0
  160. package/frontend/src/components/ui/mark-toolbar-button.tsx +19 -0
  161. package/frontend/src/components/ui/media-audio-node-static.tsx +17 -0
  162. package/frontend/src/components/ui/media-audio-node.tsx +39 -0
  163. package/frontend/src/components/ui/media-embed-node.tsx +136 -0
  164. package/frontend/src/components/ui/media-file-node-static.tsx +29 -0
  165. package/frontend/src/components/ui/media-file-node.tsx +47 -0
  166. package/frontend/src/components/ui/media-image-node-static.tsx +39 -0
  167. package/frontend/src/components/ui/media-image-node.tsx +80 -0
  168. package/frontend/src/components/ui/media-placeholder-node.tsx +249 -0
  169. package/frontend/src/components/ui/media-preview-dialog.tsx +152 -0
  170. package/frontend/src/components/ui/media-toolbar-button.tsx +225 -0
  171. package/frontend/src/components/ui/media-toolbar.tsx +115 -0
  172. package/frontend/src/components/ui/media-upload-toast.tsx +66 -0
  173. package/frontend/src/components/ui/media-video-node-static.tsx +30 -0
  174. package/frontend/src/components/ui/media-video-node.tsx +121 -0
  175. package/frontend/src/components/ui/mention-node-static.tsx +36 -0
  176. package/frontend/src/components/ui/mention-node.tsx +194 -0
  177. package/frontend/src/components/ui/mode-toolbar-button.tsx +123 -0
  178. package/frontend/src/components/ui/more-toolbar-button.tsx +80 -0
  179. package/frontend/src/components/ui/paragraph-node-static.tsx +13 -0
  180. package/frontend/src/components/ui/paragraph-node.tsx +15 -0
  181. package/frontend/src/components/ui/popover.tsx +46 -0
  182. package/frontend/src/components/ui/resize-handle.tsx +87 -0
  183. package/frontend/src/components/ui/separator.tsx +28 -0
  184. package/frontend/src/components/ui/sheet.tsx +139 -0
  185. package/frontend/src/components/ui/sidebar.tsx +726 -0
  186. package/frontend/src/components/ui/skeleton.tsx +13 -0
  187. package/frontend/src/components/ui/slash-node.tsx +233 -0
  188. package/frontend/src/components/ui/sonner.tsx +38 -0
  189. package/frontend/src/components/ui/suggestion-node-static.tsx +35 -0
  190. package/frontend/src/components/ui/suggestion-node.tsx +162 -0
  191. package/frontend/src/components/ui/suggestion-toolbar-button.tsx +25 -0
  192. package/frontend/src/components/ui/table-icons.tsx +862 -0
  193. package/frontend/src/components/ui/table-node-static.tsx +98 -0
  194. package/frontend/src/components/ui/table-node.tsx +656 -0
  195. package/frontend/src/components/ui/table-toolbar-button.tsx +264 -0
  196. package/frontend/src/components/ui/toc-node-static.tsx +92 -0
  197. package/frontend/src/components/ui/toc-node.tsx +55 -0
  198. package/frontend/src/components/ui/toggle-node-static.tsx +18 -0
  199. package/frontend/src/components/ui/toggle-node.tsx +36 -0
  200. package/frontend/src/components/ui/toggle-toolbar-button.tsx +22 -0
  201. package/frontend/src/components/ui/toolbar.tsx +387 -0
  202. package/frontend/src/components/ui/tooltip.tsx +59 -0
  203. package/frontend/src/components/ui/turn-into-toolbar-button.tsx +188 -0
  204. package/frontend/src/hooks/use-debounce.ts +18 -0
  205. package/frontend/src/hooks/use-is-touch-device.ts +24 -0
  206. package/frontend/src/hooks/use-mobile.ts +19 -0
  207. package/frontend/src/hooks/use-mounted.ts +11 -0
  208. package/frontend/src/hooks/use-upload-file.ts +128 -0
  209. package/frontend/src/index.css +128 -0
  210. package/frontend/src/layout.tsx +42 -0
  211. package/frontend/src/lib/markdown-joiner-transform.ts +239 -0
  212. package/frontend/src/lib/orpc.ts +13 -0
  213. package/frontend/src/lib/uploadthing.ts +19 -0
  214. package/frontend/src/lib/utils.ts +6 -0
  215. package/frontend/src/main.tsx +13 -0
  216. package/frontend/src/pages/editor.tsx +44 -0
  217. package/frontend/src/types/docs.d.ts +6 -0
  218. package/frontend/src/types/global.d.ts +9 -0
  219. package/frontend/src/types/router.d.ts +4 -0
  220. package/frontend/tsconfig.app.json +33 -0
  221. package/frontend/tsconfig.json +10 -0
  222. package/frontend/tsconfig.node.json +26 -0
  223. package/frontend/vite.config.ts +14 -0
  224. package/package.json +30 -0
  225. package/src/bin/doclific.ts +17 -0
  226. package/src/core/codebase.ts +39 -0
  227. package/src/core/docs.ts +90 -0
  228. package/src/core/git.ts +48 -0
  229. package/src/server/index.ts +55 -0
  230. package/src/server/router.ts +65 -0
  231. package/tsconfig.json +15 -0
@@ -0,0 +1,628 @@
1
+ 'use client';
2
+ /* eslint-disable react-hooks/refs */
3
+
4
+ import * as React from 'react';
5
+
6
+ import type { Emoji } from '@emoji-mart/data';
7
+
8
+ import {
9
+ type EmojiCategoryList,
10
+ type EmojiIconList,
11
+ type GridRow,
12
+ EmojiSettings,
13
+ } from '@platejs/emoji';
14
+ import {
15
+ type EmojiDropdownMenuOptions,
16
+ type UseEmojiPickerType,
17
+ useEmojiDropdownMenuState,
18
+ } from '@platejs/emoji/react';
19
+ import * as Popover from '@radix-ui/react-popover';
20
+ import {
21
+ AppleIcon,
22
+ ClockIcon,
23
+ CompassIcon,
24
+ FlagIcon,
25
+ LeafIcon,
26
+ LightbulbIcon,
27
+ MusicIcon,
28
+ SearchIcon,
29
+ SmileIcon,
30
+ StarIcon,
31
+ XIcon,
32
+ } from 'lucide-react';
33
+
34
+ import { Button } from '@/components/ui/button';
35
+ import {
36
+ Tooltip,
37
+ TooltipContent,
38
+ TooltipProvider,
39
+ TooltipTrigger,
40
+ } from '@/components/ui/tooltip';
41
+ import { cn } from '@/lib/utils';
42
+ import { ToolbarButton } from '@/components/ui/toolbar';
43
+
44
+ export function EmojiToolbarButton({
45
+ options,
46
+ ...props
47
+ }: {
48
+ options?: EmojiDropdownMenuOptions;
49
+ } & React.ComponentPropsWithoutRef<typeof ToolbarButton>) {
50
+ const { emojiPickerState, isOpen, setIsOpen } =
51
+ useEmojiDropdownMenuState(options);
52
+
53
+ return (
54
+ <EmojiPopover
55
+ control={
56
+ <ToolbarButton pressed={isOpen} tooltip="Emoji" isDropdown {...props}>
57
+ <SmileIcon />
58
+ </ToolbarButton>
59
+ }
60
+ isOpen={isOpen}
61
+ setIsOpen={setIsOpen}
62
+ >
63
+ <EmojiPicker
64
+ {...emojiPickerState}
65
+ isOpen={isOpen}
66
+ setIsOpen={setIsOpen}
67
+ settings={options?.settings}
68
+ />
69
+ </EmojiPopover>
70
+ );
71
+ }
72
+
73
+ export function EmojiPopover({
74
+ children,
75
+ control,
76
+ isOpen,
77
+ setIsOpen,
78
+ }: {
79
+ children: React.ReactNode;
80
+ control: React.ReactNode;
81
+ isOpen: boolean;
82
+ setIsOpen: (open: boolean) => void;
83
+ }) {
84
+ return (
85
+ <Popover.Root open={isOpen} onOpenChange={setIsOpen}>
86
+ <Popover.Trigger asChild>{control}</Popover.Trigger>
87
+
88
+ <Popover.Portal>
89
+ <Popover.Content className="z-100">{children}</Popover.Content>
90
+ </Popover.Portal>
91
+ </Popover.Root>
92
+ );
93
+ }
94
+
95
+ export function EmojiPicker({
96
+ clearSearch,
97
+ emoji,
98
+ emojiLibrary,
99
+ focusedCategory,
100
+ hasFound,
101
+ i18n,
102
+ icons = {
103
+ categories: emojiCategoryIcons,
104
+ search: emojiSearchIcons,
105
+ },
106
+ isSearching,
107
+ refs,
108
+ searchResult,
109
+ searchValue,
110
+ setSearch,
111
+ settings = EmojiSettings,
112
+ visibleCategories,
113
+ handleCategoryClick,
114
+ onMouseOver,
115
+ onSelectEmoji,
116
+ }: Omit<UseEmojiPickerType, 'icons'> & {
117
+ icons?: EmojiIconList<React.ReactElement>;
118
+ }) {
119
+ return (
120
+ <div
121
+ className={cn(
122
+ 'flex flex-col rounded-xl bg-popover text-popover-foreground',
123
+ 'h-[23rem] w-80 border shadow-md'
124
+ )}
125
+ >
126
+ <EmojiPickerNavigation
127
+ onClick={handleCategoryClick}
128
+ emojiLibrary={emojiLibrary}
129
+ focusedCategory={focusedCategory}
130
+ i18n={i18n}
131
+ icons={icons}
132
+ />
133
+ <EmojiPickerSearchBar
134
+ i18n={i18n}
135
+ searchValue={searchValue}
136
+ setSearch={setSearch}
137
+ >
138
+ <EmojiPickerSearchAndClear
139
+ clearSearch={clearSearch}
140
+ i18n={i18n}
141
+ searchValue={searchValue}
142
+ />
143
+ </EmojiPickerSearchBar>
144
+ <EmojiPickerContent
145
+ onMouseOver={onMouseOver}
146
+ onSelectEmoji={onSelectEmoji}
147
+ emojiLibrary={emojiLibrary}
148
+ i18n={i18n}
149
+ isSearching={isSearching}
150
+ refs={refs}
151
+ searchResult={searchResult}
152
+ settings={settings}
153
+ visibleCategories={visibleCategories}
154
+ />
155
+ <EmojiPickerPreview
156
+ emoji={emoji}
157
+ hasFound={hasFound}
158
+ i18n={i18n}
159
+ isSearching={isSearching}
160
+ />
161
+ </div>
162
+ );
163
+ }
164
+
165
+ const EmojiButton = React.memo(function EmojiButton({
166
+ emoji,
167
+ index,
168
+ onMouseOver,
169
+ onSelect,
170
+ }: {
171
+ emoji: Emoji;
172
+ index: number;
173
+ onMouseOver: (emoji?: Emoji) => void;
174
+ onSelect: (emoji: Emoji) => void;
175
+ }) {
176
+ return (
177
+ <button
178
+ className="group relative flex size-9 cursor-pointer items-center justify-center border-none bg-transparent text-2xl leading-none"
179
+ onClick={() => onSelect(emoji)}
180
+ onMouseEnter={() => onMouseOver(emoji)}
181
+ onMouseLeave={() => onMouseOver()}
182
+ aria-label={emoji.skins[0].native}
183
+ data-index={index}
184
+ tabIndex={-1}
185
+ type="button"
186
+ >
187
+ <div
188
+ className="absolute inset-0 rounded-full opacity-0 group-hover:opacity-100"
189
+ aria-hidden="true"
190
+ />
191
+ <span
192
+ className="relative"
193
+ style={{
194
+ fontFamily:
195
+ '"Apple Color Emoji", "Segoe UI Emoji", NotoColorEmoji, "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols',
196
+ }}
197
+ data-emoji-set="native"
198
+ >
199
+ {emoji.skins[0].native}
200
+ </span>
201
+ </button>
202
+ );
203
+ });
204
+
205
+ const RowOfButtons = React.memo(function RowOfButtons({
206
+ emojiLibrary,
207
+ row,
208
+ onMouseOver,
209
+ onSelectEmoji,
210
+ }: {
211
+ row: GridRow;
212
+ } & Pick<
213
+ UseEmojiPickerType,
214
+ 'emojiLibrary' | 'onMouseOver' | 'onSelectEmoji'
215
+ >) {
216
+ return (
217
+ <div key={row.id} className="flex" data-index={row.id}>
218
+ {row.elements.map((emojiId, index) => (
219
+ <EmojiButton
220
+ key={emojiId}
221
+ onMouseOver={onMouseOver}
222
+ onSelect={onSelectEmoji}
223
+ emoji={emojiLibrary.getEmoji(emojiId)}
224
+ index={index}
225
+ />
226
+ ))}
227
+ </div>
228
+ );
229
+ });
230
+
231
+ function EmojiPickerContent({
232
+ emojiLibrary,
233
+ i18n,
234
+ isSearching = false,
235
+ refs,
236
+ searchResult,
237
+ settings = EmojiSettings,
238
+ visibleCategories,
239
+ onMouseOver,
240
+ onSelectEmoji,
241
+ }: Pick<
242
+ UseEmojiPickerType,
243
+ | 'emojiLibrary'
244
+ | 'i18n'
245
+ | 'isSearching'
246
+ | 'onMouseOver'
247
+ | 'onSelectEmoji'
248
+ | 'refs'
249
+ | 'searchResult'
250
+ | 'settings'
251
+ | 'visibleCategories'
252
+ >) {
253
+ const getRowWidth = settings.perLine.value * settings.buttonSize.value;
254
+
255
+ const isCategoryVisible = React.useCallback(
256
+ (categoryId: any) =>
257
+ visibleCategories.has(categoryId)
258
+ ? visibleCategories.get(categoryId)
259
+ : false,
260
+ [visibleCategories]
261
+ );
262
+
263
+ const EmojiList = React.useCallback(
264
+ () =>
265
+ emojiLibrary
266
+ .getGrid()
267
+ .sections()
268
+ .map(({ id: categoryId }) => {
269
+ const section = emojiLibrary.getGrid().section(categoryId);
270
+ const { buttonSize } = settings;
271
+
272
+ return (
273
+ <div
274
+ key={categoryId}
275
+ ref={section.root}
276
+ style={{ width: getRowWidth }}
277
+ data-id={categoryId}
278
+ >
279
+ <div className="-top-px sticky z-1 bg-popover/90 p-1 py-2 font-semibold text-sm backdrop-blur-xs">
280
+ {i18n.categories[categoryId]}
281
+ </div>
282
+ <div
283
+ className="relative flex flex-wrap"
284
+ style={{ height: section.getRows().length * buttonSize.value }}
285
+ >
286
+ {isCategoryVisible(categoryId) &&
287
+ section
288
+ .getRows()
289
+ .map((row: GridRow) => (
290
+ <RowOfButtons
291
+ key={row.id}
292
+ onMouseOver={onMouseOver}
293
+ onSelectEmoji={onSelectEmoji}
294
+ emojiLibrary={emojiLibrary}
295
+ row={row}
296
+ />
297
+ ))}
298
+ </div>
299
+ </div>
300
+ );
301
+ }),
302
+ [
303
+ emojiLibrary,
304
+ getRowWidth,
305
+ i18n.categories,
306
+ isCategoryVisible,
307
+ onSelectEmoji,
308
+ onMouseOver,
309
+ settings,
310
+ ]
311
+ );
312
+
313
+ const SearchList = React.useCallback(
314
+ () => (
315
+ <div style={{ width: getRowWidth }} data-id="search">
316
+ <div className="-top-px sticky z-1 bg-popover/90 p-1 py-2 font-semibold text-card-foreground text-sm backdrop-blur-xs">
317
+ {i18n.searchResult}
318
+ </div>
319
+ <div className="relative flex flex-wrap">
320
+ {searchResult.map((emoji: Emoji, index: number) => (
321
+ <EmojiButton
322
+ key={emoji.id}
323
+ onMouseOver={onMouseOver}
324
+ onSelect={onSelectEmoji}
325
+ emoji={emojiLibrary.getEmoji(emoji.id)}
326
+ index={index}
327
+ />
328
+ ))}
329
+ </div>
330
+ </div>
331
+ ),
332
+ [
333
+ emojiLibrary,
334
+ getRowWidth,
335
+ i18n.searchResult,
336
+ searchResult,
337
+ onSelectEmoji,
338
+ onMouseOver,
339
+ ]
340
+ );
341
+
342
+ return (
343
+ <div
344
+ ref={refs.current.contentRoot}
345
+ className={cn(
346
+ 'h-full min-h-[50%] overflow-y-auto overflow-x-hidden px-2',
347
+ '[&::-webkit-scrollbar]:w-4',
348
+ '[&::-webkit-scrollbar-button]:hidden [&::-webkit-scrollbar-button]:size-0',
349
+ '[&::-webkit-scrollbar-thumb]:min-h-11 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-muted [&::-webkit-scrollbar-thumb]:hover:bg-muted-foreground/25',
350
+ '[&::-webkit-scrollbar-thumb]:border-4 [&::-webkit-scrollbar-thumb]:border-popover [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:bg-clip-padding'
351
+ )}
352
+ data-id="scroll"
353
+ >
354
+ <div ref={refs.current.content} className="h-full">
355
+ {isSearching ? SearchList() : EmojiList()}
356
+ </div>
357
+ </div>
358
+ );
359
+ }
360
+
361
+ function EmojiPickerSearchBar({
362
+ children,
363
+ i18n,
364
+ searchValue,
365
+ setSearch,
366
+ }: {
367
+ children: React.ReactNode;
368
+ } & Pick<UseEmojiPickerType, 'i18n' | 'searchValue' | 'setSearch'>) {
369
+ return (
370
+ <div className="flex items-center px-2">
371
+ <div className="relative flex grow items-center">
372
+ <input
373
+ 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"
374
+ value={searchValue}
375
+ onChange={(event) => setSearch(event.target.value)}
376
+ placeholder={i18n.search}
377
+ aria-label="Search"
378
+ autoComplete="off"
379
+ type="text"
380
+ autoFocus
381
+ />
382
+ {children}
383
+ </div>
384
+ </div>
385
+ );
386
+ }
387
+
388
+ function EmojiPickerSearchAndClear({
389
+ clearSearch,
390
+ i18n,
391
+ searchValue,
392
+ }: Pick<UseEmojiPickerType, 'clearSearch' | 'i18n' | 'searchValue'>) {
393
+ return (
394
+ <div className="flex items-center text-foreground">
395
+ <div
396
+ className={cn(
397
+ '-translate-y-1/2 absolute top-1/2 left-2.5 z-10 flex size-5 items-center justify-center text-foreground'
398
+ )}
399
+ >
400
+ {emojiSearchIcons.loupe}
401
+ </div>
402
+ {searchValue && (
403
+ <Button
404
+ size="icon"
405
+ variant="ghost"
406
+ className={cn(
407
+ '-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'
408
+ )}
409
+ onClick={clearSearch}
410
+ title={i18n.clear}
411
+ aria-label="Clear"
412
+ type="button"
413
+ >
414
+ {emojiSearchIcons.delete}
415
+ </Button>
416
+ )}
417
+ </div>
418
+ );
419
+ }
420
+
421
+ function EmojiPreview({ emoji }: Pick<UseEmojiPickerType, 'emoji'>) {
422
+ return (
423
+ <div className="flex h-14 max-h-14 min-h-14 items-center border-muted border-t p-2">
424
+ <div className="flex items-center justify-center text-2xl">
425
+ {emoji?.skins[0].native}
426
+ </div>
427
+ <div className="overflow-hidden pl-2">
428
+ <div className="truncate font-semibold text-sm">{emoji?.name}</div>
429
+ <div className="truncate text-sm">{`:${emoji?.id}:`}</div>
430
+ </div>
431
+ </div>
432
+ );
433
+ }
434
+
435
+ function NoEmoji({ i18n }: Pick<UseEmojiPickerType, 'i18n'>) {
436
+ return (
437
+ <div className="flex h-14 max-h-14 min-h-14 items-center border-muted border-t p-2">
438
+ <div className="flex items-center justify-center text-2xl">😢</div>
439
+ <div className="overflow-hidden pl-2">
440
+ <div className="truncate font-bold text-sm">
441
+ {i18n.searchNoResultsTitle}
442
+ </div>
443
+ <div className="truncate text-sm">{i18n.searchNoResultsSubtitle}</div>
444
+ </div>
445
+ </div>
446
+ );
447
+ }
448
+
449
+ function PickAnEmoji({ i18n }: Pick<UseEmojiPickerType, 'i18n'>) {
450
+ return (
451
+ <div className="flex h-14 max-h-14 min-h-14 items-center border-muted border-t p-2">
452
+ <div className="flex items-center justify-center text-2xl">☝️</div>
453
+ <div className="overflow-hidden pl-2">
454
+ <div className="truncate font-semibold text-sm">{i18n.pick}</div>
455
+ </div>
456
+ </div>
457
+ );
458
+ }
459
+
460
+ function EmojiPickerPreview({
461
+ emoji,
462
+ hasFound = true,
463
+ i18n,
464
+ isSearching = false,
465
+ ...props
466
+ }: Pick<UseEmojiPickerType, 'emoji' | 'hasFound' | 'i18n' | 'isSearching'>) {
467
+ const showPickEmoji = !emoji && (!isSearching || hasFound);
468
+ const showNoEmoji = isSearching && !hasFound;
469
+ const showPreview = emoji && !showNoEmoji && !showNoEmoji;
470
+
471
+ return (
472
+ <>
473
+ {showPreview && <EmojiPreview emoji={emoji} {...props} />}
474
+ {showPickEmoji && <PickAnEmoji i18n={i18n} {...props} />}
475
+ {showNoEmoji && <NoEmoji i18n={i18n} {...props} />}
476
+ </>
477
+ );
478
+ }
479
+
480
+ function EmojiPickerNavigation({
481
+ emojiLibrary,
482
+ focusedCategory,
483
+ i18n,
484
+ icons,
485
+ onClick,
486
+ }: {
487
+ onClick: (id: EmojiCategoryList) => void;
488
+ } & Pick<
489
+ UseEmojiPickerType,
490
+ 'emojiLibrary' | 'focusedCategory' | 'i18n' | 'icons'
491
+ >) {
492
+ return (
493
+ <TooltipProvider delayDuration={500}>
494
+ <nav
495
+ id="emoji-nav"
496
+ className="mb-2.5 border-0 border-b border-b-border border-solid p-1.5"
497
+ >
498
+ <div className="relative flex items-center justify-evenly">
499
+ {emojiLibrary
500
+ .getGrid()
501
+ .sections()
502
+ .map(({ id }) => (
503
+ <Tooltip key={id}>
504
+ <TooltipTrigger asChild>
505
+ <Button
506
+ size="sm"
507
+ variant="ghost"
508
+ className={cn(
509
+ 'h-fit rounded-full fill-current p-1.5 text-muted-foreground hover:bg-muted hover:text-muted-foreground',
510
+ id === focusedCategory &&
511
+ 'pointer-events-none bg-accent fill-current text-accent-foreground'
512
+ )}
513
+ onClick={() => {
514
+ onClick(id);
515
+ }}
516
+ aria-label={i18n.categories[id]}
517
+ type="button"
518
+ >
519
+ <span className="inline-flex size-5 items-center justify-center">
520
+ {icons.categories[id].outline}
521
+ </span>
522
+ </Button>
523
+ </TooltipTrigger>
524
+ <TooltipContent side="bottom">
525
+ {i18n.categories[id]}
526
+ </TooltipContent>
527
+ </Tooltip>
528
+ ))}
529
+ </div>
530
+ </nav>
531
+ </TooltipProvider>
532
+ );
533
+ }
534
+
535
+ const emojiCategoryIcons: Record<
536
+ EmojiCategoryList,
537
+ {
538
+ outline: React.ReactElement;
539
+ solid: React.ReactElement; // Needed to add another solid variant - outline will be used for now
540
+ }
541
+ > = {
542
+ activity: {
543
+ outline: (
544
+ <svg
545
+ className="size-full"
546
+ fill="none"
547
+ stroke="currentColor"
548
+ strokeLinecap="round"
549
+ strokeLinejoin="round"
550
+ strokeWidth="2"
551
+ viewBox="0 0 24 24"
552
+ xmlns="http://www.w3.org/2000/svg"
553
+ >
554
+ <circle cx="12" cy="12" r="10" />
555
+ <path d="M2.1 13.4A10.1 10.1 0 0 0 13.4 2.1" />
556
+ <path d="m5 4.9 14 14.2" />
557
+ <path d="M21.9 10.6a10.1 10.1 0 0 0-11.3 11.3" />
558
+ </svg>
559
+ ),
560
+ solid: (
561
+ <svg
562
+ className="size-full"
563
+ fill="none"
564
+ stroke="currentColor"
565
+ strokeLinecap="round"
566
+ strokeLinejoin="round"
567
+ strokeWidth="2"
568
+ viewBox="0 0 24 24"
569
+ xmlns="http://www.w3.org/2000/svg"
570
+ >
571
+ <circle cx="12" cy="12" r="10" />
572
+ <path d="M2.1 13.4A10.1 10.1 0 0 0 13.4 2.1" />
573
+ <path d="m5 4.9 14 14.2" />
574
+ <path d="M21.9 10.6a10.1 10.1 0 0 0-11.3 11.3" />
575
+ </svg>
576
+ ),
577
+ },
578
+
579
+ custom: {
580
+ outline: <StarIcon className="size-full" />,
581
+ solid: <StarIcon className="size-full" />,
582
+ },
583
+
584
+ flags: {
585
+ outline: <FlagIcon className="size-full" />,
586
+ solid: <FlagIcon className="size-full" />,
587
+ },
588
+
589
+ foods: {
590
+ outline: <AppleIcon className="size-full" />,
591
+ solid: <AppleIcon className="size-full" />,
592
+ },
593
+
594
+ frequent: {
595
+ outline: <ClockIcon className="size-full" />,
596
+ solid: <ClockIcon className="size-full" />,
597
+ },
598
+
599
+ nature: {
600
+ outline: <LeafIcon className="size-full" />,
601
+ solid: <LeafIcon className="size-full" />,
602
+ },
603
+
604
+ objects: {
605
+ outline: <LightbulbIcon className="size-full" />,
606
+ solid: <LightbulbIcon className="size-full" />,
607
+ },
608
+
609
+ people: {
610
+ outline: <SmileIcon className="size-full" />,
611
+ solid: <SmileIcon className="size-full" />,
612
+ },
613
+
614
+ places: {
615
+ outline: <CompassIcon className="size-full" />,
616
+ solid: <CompassIcon className="size-full" />,
617
+ },
618
+
619
+ symbols: {
620
+ outline: <MusicIcon className="size-full" />,
621
+ solid: <MusicIcon className="size-full" />,
622
+ },
623
+ };
624
+
625
+ const emojiSearchIcons = {
626
+ delete: <XIcon className="size-4 text-current" />,
627
+ loupe: <SearchIcon className="size-4 text-current" />,
628
+ };