@refraction-ui/astro 0.4.2 → 0.4.4

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 (334) hide show
  1. package/dist/accordion/index.ts +1 -0
  2. package/dist/ai/ai-manager.ts +121 -0
  3. package/dist/ai/index.ts +23 -0
  4. package/dist/ai/mock-providers.ts +68 -0
  5. package/dist/ai/sanitizer.ts +174 -0
  6. package/dist/ai/tts-manager.ts +80 -0
  7. package/dist/ai/types.ts +72 -0
  8. package/dist/animated-text/animated-text.styles.ts +28 -0
  9. package/dist/animated-text/animated-text.ts +83 -0
  10. package/dist/animated-text/index.ts +15 -0
  11. package/dist/app-shell/app-shell.ts +252 -0
  12. package/dist/app-shell/auth-shell.ts +84 -0
  13. package/dist/app-shell/index.ts +22 -0
  14. package/dist/app-shell/page-shell.ts +125 -0
  15. package/dist/astro-accordion/Button.astro +1 -1
  16. package/dist/astro-ai/index.ts +1 -1
  17. package/dist/astro-animated-text/AnimatedText.astro +1 -1
  18. package/dist/astro-animated-text/TypewriterText.astro +1 -1
  19. package/dist/astro-app-shell/AppShell.astro +1 -1
  20. package/dist/astro-app-shell/AuthShell.astro +1 -1
  21. package/dist/astro-app-shell/AuthShellCard.astro +1 -1
  22. package/dist/astro-app-shell/PageShell.astro +1 -1
  23. package/dist/astro-app-shell/PageShellSection.astro +1 -1
  24. package/dist/astro-app-shell/index.ts +1 -1
  25. package/dist/astro-auth/index.ts +2 -2
  26. package/dist/astro-avatar/Avatar.astro +1 -1
  27. package/dist/astro-avatar/AvatarFallback.astro +1 -1
  28. package/dist/astro-avatar/AvatarImage.astro +1 -1
  29. package/dist/astro-avatar-group/AvatarGroup.astro +1 -1
  30. package/dist/astro-badge/Badge.astro +1 -1
  31. package/dist/astro-bottom-nav/BottomNav.astro +1 -1
  32. package/dist/astro-breadcrumbs/Breadcrumbs.astro +1 -1
  33. package/dist/astro-button/Button.astro +1 -1
  34. package/dist/astro-calendar/Calendar.astro +1 -1
  35. package/dist/astro-card/Card.astro +1 -1
  36. package/dist/astro-card/CardContent.astro +1 -1
  37. package/dist/astro-card/CardDescription.astro +1 -1
  38. package/dist/astro-card/CardFooter.astro +1 -1
  39. package/dist/astro-card/CardHeader.astro +1 -1
  40. package/dist/astro-card/CardTitle.astro +1 -1
  41. package/dist/astro-card-grid/CardGrid.astro +1 -1
  42. package/dist/astro-carousel/Button.astro +1 -1
  43. package/dist/astro-charts/Bars.astro +1 -1
  44. package/dist/astro-charts/Chart.astro +1 -1
  45. package/dist/astro-charts/Circles.astro +1 -1
  46. package/dist/astro-charts/Histogram.astro +1 -1
  47. package/dist/astro-charts/Line.astro +1 -1
  48. package/dist/astro-charts/PieChart.astro +1 -1
  49. package/dist/astro-charts/ScatterPlot.astro +1 -1
  50. package/dist/astro-charts/index.ts +1 -1
  51. package/dist/astro-checkbox/Checkbox.astro +1 -1
  52. package/dist/astro-code-editor/CodeEditor.astro +1 -1
  53. package/dist/astro-collapsible/Collapsible.astro +1 -1
  54. package/dist/astro-collapsible/CollapsibleContent.astro +1 -1
  55. package/dist/astro-command/Command.astro +1 -1
  56. package/dist/astro-command/CommandGroup.astro +1 -1
  57. package/dist/astro-command/CommandInput.astro +1 -1
  58. package/dist/astro-command/CommandItem.astro +1 -1
  59. package/dist/astro-command-input/CommandInput.astro +1 -1
  60. package/dist/astro-content-protection/ContentProtection.astro +1 -1
  61. package/dist/astro-data-table/DataTable.astro +1 -1
  62. package/dist/astro-date-picker/DatePicker.astro +1 -1
  63. package/dist/astro-device-frame/DeviceFrame.astro +1 -1
  64. package/dist/astro-dialog/DialogContent.astro +1 -1
  65. package/dist/astro-dialog/DialogOverlay.astro +1 -1
  66. package/dist/astro-diff-viewer/DiffViewer.astro +1 -1
  67. package/dist/astro-dropdown-menu/DropdownMenuContent.astro +1 -1
  68. package/dist/astro-dropdown-menu/DropdownMenuItem.astro +1 -1
  69. package/dist/astro-emoji-picker/EmojiPicker.astro +1 -1
  70. package/dist/astro-feedback-dialog/FeedbackDialog.astro +1 -1
  71. package/dist/astro-file-upload/FileUpload.astro +1 -1
  72. package/dist/astro-footer/Footer.astro +1 -1
  73. package/dist/astro-inline-editor/InlineEditor.astro +1 -1
  74. package/dist/astro-input/Input.astro +1 -1
  75. package/dist/astro-input-group/InputGroup.astro +1 -1
  76. package/dist/astro-input-group/InputGroupAddon.astro +1 -1
  77. package/dist/astro-input-group/InputGroupButton.astro +1 -1
  78. package/dist/astro-install-prompt/InstallPrompt.astro +1 -1
  79. package/dist/astro-keyboard-shortcut/ShortcutBadge.astro +1 -1
  80. package/dist/astro-language-selector/LanguageSelector.astro +1 -1
  81. package/dist/astro-link-card/LinkCard.astro +1 -1
  82. package/dist/astro-markdown-renderer/MarkdownRenderer.astro +1 -1
  83. package/dist/astro-mobile-nav/MobileNav.astro +1 -1
  84. package/dist/astro-mobile-nav/MobileNavLink.astro +1 -1
  85. package/dist/astro-mobile-nav/MobileNavTrigger.astro +1 -1
  86. package/dist/astro-navbar/Navbar.astro +1 -1
  87. package/dist/astro-otp-input/OtpInput.astro +1 -1
  88. package/dist/astro-payment/Payment.astro +1 -1
  89. package/dist/astro-popover/PopoverContent.astro +1 -1
  90. package/dist/astro-presence-indicator/PresenceIndicator.astro +1 -1
  91. package/dist/astro-progress-display/BadgeDisplay.astro +1 -1
  92. package/dist/astro-progress-display/ProgressBar.astro +1 -1
  93. package/dist/astro-progress-display/StatsGrid.astro +1 -1
  94. package/dist/astro-radio/RadioGroup.astro +1 -1
  95. package/dist/astro-radio/RadioItem.astro +1 -1
  96. package/dist/astro-reaction-bar/ReactionBar.astro +1 -1
  97. package/dist/astro-resizable-layout/ResizableDivider.astro +1 -1
  98. package/dist/astro-resizable-layout/ResizableLayout.astro +1 -1
  99. package/dist/astro-resizable-layout/ResizablePane.astro +1 -1
  100. package/dist/astro-resizable-layout/index.ts +1 -1
  101. package/dist/astro-rich-editor/index.ts +2 -2
  102. package/dist/astro-search-bar/SearchBar.astro +1 -1
  103. package/dist/astro-search-bar/SearchResults.astro +1 -1
  104. package/dist/astro-select/Select.astro +1 -1
  105. package/dist/astro-select/SelectContent.astro +1 -1
  106. package/dist/astro-select/SelectItem.astro +1 -1
  107. package/dist/astro-select/SelectTrigger.astro +1 -1
  108. package/dist/astro-sidebar/Sidebar.astro +1 -1
  109. package/dist/astro-skeleton/Skeleton.astro +1 -1
  110. package/dist/astro-slide-viewer/SlideViewer.astro +1 -1
  111. package/dist/astro-status-indicator/StatusIndicator.astro +1 -1
  112. package/dist/astro-switch/Switch.astro +1 -1
  113. package/dist/astro-table-of-contents/TableOfContents.astro +1 -1
  114. package/dist/astro-tabs/Tabs.astro +1 -1
  115. package/dist/astro-tabs/TabsList.astro +1 -1
  116. package/dist/astro-tabs/TabsTrigger.astro +1 -1
  117. package/dist/astro-textarea/Textarea.astro +1 -1
  118. package/dist/astro-theme/ThemeScript.astro +1 -1
  119. package/dist/astro-thread-view/ThreadView.astro +1 -1
  120. package/dist/astro-toast/Toast.astro +1 -1
  121. package/dist/astro-tooltip/TooltipContent.astro +1 -1
  122. package/dist/astro-version-selector/VersionSelector.astro +1 -1
  123. package/dist/astro-video-player/VideoPlayer.astro +1 -1
  124. package/dist/auth/adapters/mock.ts +74 -0
  125. package/dist/auth/auth-machine.ts +120 -0
  126. package/dist/auth/index.ts +22 -0
  127. package/dist/auth/rbac.ts +44 -0
  128. package/dist/auth/types.ts +44 -0
  129. package/dist/avatar/avatar.styles.ts +47 -0
  130. package/dist/avatar/avatar.ts +73 -0
  131. package/dist/avatar/index.ts +15 -0
  132. package/dist/avatar-group/avatar-group.styles.ts +63 -0
  133. package/dist/avatar-group/avatar-group.ts +106 -0
  134. package/dist/avatar-group/index.ts +18 -0
  135. package/dist/badge/badge.styles.ts +24 -0
  136. package/dist/badge/badge.ts +48 -0
  137. package/dist/badge/index.ts +10 -0
  138. package/dist/bottom-nav/bottom-nav.styles.ts +18 -0
  139. package/dist/bottom-nav/bottom-nav.ts +33 -0
  140. package/dist/bottom-nav/index.ts +2 -0
  141. package/dist/breadcrumbs/breadcrumbs.styles.ts +20 -0
  142. package/dist/breadcrumbs/breadcrumbs.ts +72 -0
  143. package/dist/breadcrumbs/index.ts +8 -0
  144. package/dist/button/button.styles.ts +38 -0
  145. package/dist/button/button.ts +64 -0
  146. package/dist/button/index.ts +10 -0
  147. package/dist/calendar/calendar.styles.ts +21 -0
  148. package/dist/calendar/calendar.ts +202 -0
  149. package/dist/calendar/index.ts +9 -0
  150. package/dist/callout/callout.ts +1 -0
  151. package/dist/callout/index.ts +1 -0
  152. package/dist/card/card.styles.ts +55 -0
  153. package/dist/card/card.ts +45 -0
  154. package/dist/card/index.ts +20 -0
  155. package/dist/card-grid/card-grid.ts +9 -0
  156. package/dist/card-grid/index.ts +1 -0
  157. package/dist/carousel/index.ts +1 -0
  158. package/dist/charts/axis.ts +50 -0
  159. package/dist/charts/chart.ts +73 -0
  160. package/dist/charts/colorblind.ts +100 -0
  161. package/dist/charts/data.ts +72 -0
  162. package/dist/charts/geometry.ts +54 -0
  163. package/dist/charts/index.ts +61 -0
  164. package/dist/charts/scales.ts +151 -0
  165. package/dist/checkbox/checkbox.styles.ts +33 -0
  166. package/dist/checkbox/checkbox.ts +73 -0
  167. package/dist/checkbox/index.ts +10 -0
  168. package/dist/code-block/code-block.ts +1 -0
  169. package/dist/code-block/index.ts +1 -0
  170. package/dist/code-editor/code-editor.styles.ts +32 -0
  171. package/dist/code-editor/code-editor.ts +140 -0
  172. package/dist/code-editor/index.ts +9 -0
  173. package/dist/collapsible/collapsible.styles.ts +14 -0
  174. package/dist/collapsible/collapsible.ts +93 -0
  175. package/dist/collapsible/index.ts +7 -0
  176. package/dist/command/command.styles.ts +27 -0
  177. package/dist/command/command.ts +179 -0
  178. package/dist/command/index.ts +14 -0
  179. package/dist/command-input/command-input.ts +176 -0
  180. package/dist/command-input/index.ts +1 -0
  181. package/dist/content-protection/content-protection.styles.ts +9 -0
  182. package/dist/content-protection/content-protection.ts +68 -0
  183. package/dist/content-protection/index.ts +8 -0
  184. package/dist/data-table/data-table.styles.ts +55 -0
  185. package/dist/data-table/data-table.ts +183 -0
  186. package/dist/data-table/index.ts +15 -0
  187. package/dist/date-picker/date-picker.styles.ts +31 -0
  188. package/dist/date-picker/date-picker.ts +333 -0
  189. package/dist/date-picker/index.ts +18 -0
  190. package/dist/device-frame/device-frame.styles.ts +21 -0
  191. package/dist/device-frame/device-frame.ts +112 -0
  192. package/dist/device-frame/index.ts +10 -0
  193. package/dist/dialog/dialog.styles.ts +8 -0
  194. package/dist/dialog/dialog.ts +133 -0
  195. package/dist/dialog/index.ts +8 -0
  196. package/dist/diff-viewer/diff-viewer.styles.ts +124 -0
  197. package/dist/diff-viewer/diff-viewer.ts +255 -0
  198. package/dist/diff-viewer/index.ts +20 -0
  199. package/dist/dropdown-menu/dropdown-menu.styles.ts +9 -0
  200. package/dist/dropdown-menu/dropdown-menu.ts +139 -0
  201. package/dist/dropdown-menu/index.ts +9 -0
  202. package/dist/emoji-picker/emoji-data.ts +282 -0
  203. package/dist/emoji-picker/emoji-picker.styles.ts +32 -0
  204. package/dist/emoji-picker/emoji-picker.ts +165 -0
  205. package/dist/emoji-picker/index.ts +25 -0
  206. package/dist/feedback-dialog/feedback-dialog.styles.ts +15 -0
  207. package/dist/feedback-dialog/feedback-dialog.ts +147 -0
  208. package/dist/feedback-dialog/index.ts +11 -0
  209. package/dist/file-upload/file-upload.styles.ts +30 -0
  210. package/dist/file-upload/file-upload.ts +260 -0
  211. package/dist/file-upload/index.ts +19 -0
  212. package/dist/footer/footer.styles.ts +5 -0
  213. package/dist/footer/footer.ts +32 -0
  214. package/dist/footer/index.ts +9 -0
  215. package/dist/index.ts +1 -1
  216. package/dist/inline-editor/index.ts +13 -0
  217. package/dist/inline-editor/inline-editor.styles.ts +42 -0
  218. package/dist/inline-editor/inline-editor.ts +96 -0
  219. package/dist/input/index.ts +9 -0
  220. package/dist/input/input.styles.ts +15 -0
  221. package/dist/input/input.ts +84 -0
  222. package/dist/input-group/index.ts +13 -0
  223. package/dist/input-group/input-group.styles.ts +50 -0
  224. package/dist/input-group/input-group.ts +44 -0
  225. package/dist/install-prompt/index.ts +9 -0
  226. package/dist/install-prompt/install-prompt.styles.ts +5 -0
  227. package/dist/install-prompt/install-prompt.ts +68 -0
  228. package/dist/keyboard-shortcut/index.ts +12 -0
  229. package/dist/keyboard-shortcut/keyboard-shortcut.styles.ts +8 -0
  230. package/dist/keyboard-shortcut/keyboard-shortcut.ts +153 -0
  231. package/dist/language-selector/index.ts +8 -0
  232. package/dist/language-selector/language-selector.styles.ts +28 -0
  233. package/dist/language-selector/language-selector.ts +149 -0
  234. package/dist/link-card/index.ts +1 -0
  235. package/dist/link-card/link-card.ts +9 -0
  236. package/dist/markdown-renderer/index.ts +8 -0
  237. package/dist/markdown-renderer/markdown-renderer.styles.ts +31 -0
  238. package/dist/markdown-renderer/markdown-renderer.ts +251 -0
  239. package/dist/mobile-nav/index.ts +14 -0
  240. package/dist/mobile-nav/mobile-nav.styles.ts +39 -0
  241. package/dist/mobile-nav/mobile-nav.ts +74 -0
  242. package/dist/navbar/index.ts +9 -0
  243. package/dist/navbar/navbar.styles.ts +29 -0
  244. package/dist/navbar/navbar.ts +39 -0
  245. package/dist/otp-input/index.ts +13 -0
  246. package/dist/otp-input/otp-input.styles.ts +51 -0
  247. package/dist/otp-input/otp-input.ts +198 -0
  248. package/dist/pagination/index.ts +1 -0
  249. package/dist/payment/index.ts +1 -0
  250. package/dist/payment/payment.ts +16 -0
  251. package/dist/popover/index.ts +7 -0
  252. package/dist/popover/popover.styles.ts +16 -0
  253. package/dist/popover/popover.ts +89 -0
  254. package/dist/presence-indicator/index.ts +14 -0
  255. package/dist/presence-indicator/presence-indicator.styles.ts +29 -0
  256. package/dist/presence-indicator/presence-indicator.ts +63 -0
  257. package/dist/progress-display/index.ts +15 -0
  258. package/dist/progress-display/progress-display.styles.ts +72 -0
  259. package/dist/progress-display/progress-display.ts +60 -0
  260. package/dist/radio/index.ts +8 -0
  261. package/dist/radio/radio.styles.ts +34 -0
  262. package/dist/radio/radio.ts +70 -0
  263. package/dist/reaction-bar/index.ts +14 -0
  264. package/dist/reaction-bar/reaction-bar.styles.ts +23 -0
  265. package/dist/reaction-bar/reaction-bar.ts +87 -0
  266. package/dist/resizable-layout/index.ts +12 -0
  267. package/dist/resizable-layout/resizable-layout.styles.ts +50 -0
  268. package/dist/resizable-layout/resizable-layout.ts +139 -0
  269. package/dist/rich-editor/editor.ts +342 -0
  270. package/dist/rich-editor/emoji.ts +175 -0
  271. package/dist/rich-editor/history.ts +79 -0
  272. package/dist/rich-editor/index.ts +106 -0
  273. package/dist/rich-editor/markdown-shortcuts.ts +248 -0
  274. package/dist/rich-editor/mentions.ts +162 -0
  275. package/dist/rich-editor/model.ts +206 -0
  276. package/dist/rich-editor/operations.ts +772 -0
  277. package/dist/rich-editor/selection.ts +157 -0
  278. package/dist/rich-editor/serialization.ts +711 -0
  279. package/dist/rich-editor/slash-commands.ts +212 -0
  280. package/dist/search-bar/index.ts +7 -0
  281. package/dist/search-bar/search-bar.styles.ts +29 -0
  282. package/dist/search-bar/search-bar.ts +130 -0
  283. package/dist/select/index.ts +13 -0
  284. package/dist/select/select.styles.ts +52 -0
  285. package/dist/select/select.ts +152 -0
  286. package/dist/sidebar/index.ts +9 -0
  287. package/dist/sidebar/sidebar.styles.ts +27 -0
  288. package/dist/sidebar/sidebar.ts +59 -0
  289. package/dist/skeleton/index.ts +8 -0
  290. package/dist/skeleton/skeleton.styles.ts +16 -0
  291. package/dist/skeleton/skeleton.ts +39 -0
  292. package/dist/skip-to-content/index.ts +1 -0
  293. package/dist/skip-to-content/skip-to-content.ts +1 -0
  294. package/dist/slide-viewer/index.ts +16 -0
  295. package/dist/slide-viewer/slide-viewer.styles.ts +57 -0
  296. package/dist/slide-viewer/slide-viewer.ts +152 -0
  297. package/dist/slider/index.ts +1 -0
  298. package/dist/slider/slider.ts +2 -0
  299. package/dist/status-indicator/index.ts +15 -0
  300. package/dist/status-indicator/status-indicator.styles.ts +41 -0
  301. package/dist/status-indicator/status-indicator.ts +66 -0
  302. package/dist/steps/index.ts +1 -0
  303. package/dist/steps/steps.ts +1 -0
  304. package/dist/switch/index.ts +7 -0
  305. package/dist/switch/switch.styles.ts +55 -0
  306. package/dist/switch/switch.ts +63 -0
  307. package/dist/table-of-contents/index.ts +1 -0
  308. package/dist/table-of-contents/table-of-contents.ts +37 -0
  309. package/dist/tabs/index.ts +8 -0
  310. package/dist/tabs/tabs.styles.ts +9 -0
  311. package/dist/tabs/tabs.ts +133 -0
  312. package/dist/textarea/index.ts +7 -0
  313. package/dist/textarea/textarea.styles.ts +15 -0
  314. package/dist/textarea/textarea.ts +59 -0
  315. package/dist/theme/dom-adapters.ts +60 -0
  316. package/dist/theme/index.ts +18 -0
  317. package/dist/theme/theme-machine.ts +130 -0
  318. package/dist/theme/theme-script.ts +18 -0
  319. package/dist/thread-view/index.ts +26 -0
  320. package/dist/thread-view/thread-view.styles.ts +35 -0
  321. package/dist/thread-view/thread-view.ts +176 -0
  322. package/dist/toast/index.ts +13 -0
  323. package/dist/toast/toast.styles.ts +19 -0
  324. package/dist/toast/toast.ts +203 -0
  325. package/dist/tooltip/index.ts +7 -0
  326. package/dist/tooltip/tooltip.styles.ts +16 -0
  327. package/dist/tooltip/tooltip.ts +109 -0
  328. package/dist/version-selector/index.ts +12 -0
  329. package/dist/version-selector/version-selector.styles.ts +41 -0
  330. package/dist/version-selector/version-selector.ts +136 -0
  331. package/dist/video-player/index.ts +12 -0
  332. package/dist/video-player/video-player.styles.ts +42 -0
  333. package/dist/video-player/video-player.ts +100 -0
  334. package/package.json +1 -1
@@ -0,0 +1,139 @@
1
+ import type { AccessibilityProps, KeyboardHandlerMap } from '../shared/index.ts'
2
+ import { createMachine, Keys, generateId } from '../shared/index.ts'
3
+
4
+ export interface DropdownMenuProps {
5
+ /** Whether the menu is open (controlled) */
6
+ open?: boolean
7
+ /** Whether the menu starts open (uncontrolled) */
8
+ defaultOpen?: boolean
9
+ /** Callback when open state changes */
10
+ onOpenChange?: (open: boolean) => void
11
+ }
12
+
13
+ export interface MenuItemProps {
14
+ /** Whether the item is disabled */
15
+ disabled?: boolean
16
+ /** Callback when the item is selected */
17
+ onSelect?: () => void
18
+ }
19
+
20
+ export interface DropdownMenuState {
21
+ open: boolean
22
+ }
23
+
24
+ export interface DropdownMenuAPI {
25
+ /** Current menu state */
26
+ state: DropdownMenuState
27
+ /** ARIA attributes for the trigger button */
28
+ triggerProps: Partial<AccessibilityProps> & Record<string, unknown>
29
+ /** ARIA attributes for the menu content */
30
+ contentProps: Partial<AccessibilityProps> & Record<string, unknown>
31
+ /** Factory for menu item props */
32
+ getItemProps(item: MenuItemProps): Partial<AccessibilityProps> & Record<string, unknown>
33
+ /** Open the menu */
34
+ open(): void
35
+ /** Close the menu */
36
+ close(): void
37
+ /** Toggle the menu */
38
+ toggle(): void
39
+ /** Keyboard handlers for the menu */
40
+ keyboardHandlers: KeyboardHandlerMap
41
+ /** Generated IDs for aria linking */
42
+ ids: {
43
+ content: string
44
+ }
45
+ }
46
+
47
+ export function createDropdownMenu(props: DropdownMenuProps = {}): DropdownMenuAPI {
48
+ const { open: controlledOpen, defaultOpen = false, onOpenChange } = props
49
+
50
+ const machine = createMachine<'open' | 'closed', 'OPEN' | 'CLOSE'>({
51
+ initial: controlledOpen ?? defaultOpen ? 'open' : 'closed',
52
+ states: {
53
+ closed: { on: { OPEN: 'open' } },
54
+ open: { on: { CLOSE: 'closed' } },
55
+ },
56
+ })
57
+
58
+ const contentId = generateId('rfr-dropdown-menu')
59
+
60
+ function isOpen(): boolean {
61
+ if (controlledOpen !== undefined) return controlledOpen
62
+ return machine.matches('open')
63
+ }
64
+
65
+ function openMenu(): void {
66
+ machine.send('OPEN')
67
+ onOpenChange?.(true)
68
+ }
69
+
70
+ function closeMenu(): void {
71
+ machine.send('CLOSE')
72
+ onOpenChange?.(false)
73
+ }
74
+
75
+ function toggleMenu(): void {
76
+ if (isOpen()) {
77
+ closeMenu()
78
+ } else {
79
+ openMenu()
80
+ }
81
+ }
82
+
83
+ function getItemProps(item: MenuItemProps): Partial<AccessibilityProps> & Record<string, unknown> {
84
+ return {
85
+ role: 'menuitem',
86
+ tabIndex: item.disabled ? -1 : 0,
87
+ 'data-disabled': item.disabled ? '' : undefined,
88
+ 'aria-disabled': item.disabled || undefined,
89
+ }
90
+ }
91
+
92
+ const keyboardHandlers: KeyboardHandlerMap = {
93
+ [Keys.Escape]: (e) => {
94
+ e.preventDefault()
95
+ closeMenu()
96
+ },
97
+ [Keys.ArrowDown]: (e) => {
98
+ e.preventDefault()
99
+ },
100
+ [Keys.ArrowUp]: (e) => {
101
+ e.preventDefault()
102
+ },
103
+ [Keys.Enter]: (e) => {
104
+ e.preventDefault()
105
+ },
106
+ [Keys.Home]: (e) => {
107
+ e.preventDefault()
108
+ },
109
+ [Keys.End]: (e) => {
110
+ e.preventDefault()
111
+ },
112
+ }
113
+
114
+ const triggerProps: Partial<AccessibilityProps> & Record<string, unknown> = {
115
+ 'aria-expanded': isOpen(),
116
+ 'aria-controls': contentId,
117
+ 'aria-haspopup': 'menu',
118
+ }
119
+
120
+ const contentProps: Partial<AccessibilityProps> & Record<string, unknown> = {
121
+ role: 'menu',
122
+ id: contentId,
123
+ 'data-state': isOpen() ? 'open' : 'closed',
124
+ }
125
+
126
+ return {
127
+ state: { open: isOpen() },
128
+ triggerProps,
129
+ contentProps,
130
+ getItemProps,
131
+ open: openMenu,
132
+ close: closeMenu,
133
+ toggle: toggleMenu,
134
+ keyboardHandlers,
135
+ ids: {
136
+ content: contentId,
137
+ },
138
+ }
139
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ createDropdownMenu,
3
+ type DropdownMenuProps,
4
+ type MenuItemProps,
5
+ type DropdownMenuAPI,
6
+ type DropdownMenuState,
7
+ } from './dropdown-menu.js'
8
+
9
+ export { menuContentVariants, menuItemVariants } from './dropdown-menu.styles.js'
@@ -0,0 +1,282 @@
1
+ export type EmojiCategory =
2
+ | 'smileys'
3
+ | 'people'
4
+ | 'nature'
5
+ | 'food'
6
+ | 'travel'
7
+ | 'activities'
8
+ | 'objects'
9
+ | 'symbols'
10
+ | 'flags'
11
+
12
+ export interface EmojiEntry {
13
+ emoji: string
14
+ name: string
15
+ category: EmojiCategory
16
+ keywords: string[]
17
+ }
18
+
19
+ export const EMOJI_DATA: Record<EmojiCategory, EmojiEntry[]> = {
20
+ smileys: [
21
+ { emoji: '\u{1F600}', name: 'grinning face', category: 'smileys', keywords: ['happy', 'smile', 'grin'] },
22
+ { emoji: '\u{1F603}', name: 'grinning face with big eyes', category: 'smileys', keywords: ['happy', 'smile'] },
23
+ { emoji: '\u{1F604}', name: 'grinning face with smiling eyes', category: 'smileys', keywords: ['happy', 'joy'] },
24
+ { emoji: '\u{1F601}', name: 'beaming face', category: 'smileys', keywords: ['happy', 'grin', 'beam'] },
25
+ { emoji: '\u{1F606}', name: 'grinning squinting face', category: 'smileys', keywords: ['laugh', 'happy'] },
26
+ { emoji: '\u{1F605}', name: 'grinning face with sweat', category: 'smileys', keywords: ['nervous', 'laugh'] },
27
+ { emoji: '\u{1F923}', name: 'rolling on the floor laughing', category: 'smileys', keywords: ['lol', 'rofl', 'laugh'] },
28
+ { emoji: '\u{1F602}', name: 'face with tears of joy', category: 'smileys', keywords: ['laugh', 'cry', 'joy', 'lol'] },
29
+ { emoji: '\u{1F642}', name: 'slightly smiling face', category: 'smileys', keywords: ['smile', 'happy'] },
30
+ { emoji: '\u{1F643}', name: 'upside-down face', category: 'smileys', keywords: ['silly', 'sarcasm'] },
31
+ { emoji: '\u{1F609}', name: 'winking face', category: 'smileys', keywords: ['wink', 'flirt'] },
32
+ { emoji: '\u{1F60A}', name: 'smiling face with smiling eyes', category: 'smileys', keywords: ['blush', 'happy'] },
33
+ { emoji: '\u{1F607}', name: 'smiling face with halo', category: 'smileys', keywords: ['angel', 'innocent'] },
34
+ { emoji: '\u{1F970}', name: 'smiling face with hearts', category: 'smileys', keywords: ['love', 'crush', 'adore'] },
35
+ { emoji: '\u{1F60D}', name: 'smiling face with heart-eyes', category: 'smileys', keywords: ['love', 'heart'] },
36
+ { emoji: '\u{1F929}', name: 'star-struck', category: 'smileys', keywords: ['star', 'eyes', 'wow'] },
37
+ { emoji: '\u{1F618}', name: 'face blowing a kiss', category: 'smileys', keywords: ['kiss', 'love'] },
38
+ { emoji: '\u{1F617}', name: 'kissing face', category: 'smileys', keywords: ['kiss'] },
39
+ { emoji: '\u{1F61A}', name: 'kissing face with closed eyes', category: 'smileys', keywords: ['kiss', 'love'] },
40
+ { emoji: '\u{1F619}', name: 'kissing face with smiling eyes', category: 'smileys', keywords: ['kiss'] },
41
+ { emoji: '\u{1F60B}', name: 'face savoring food', category: 'smileys', keywords: ['yummy', 'delicious'] },
42
+ { emoji: '\u{1F61C}', name: 'winking face with tongue', category: 'smileys', keywords: ['tongue', 'wink', 'silly'] },
43
+ { emoji: '\u{1F61D}', name: 'squinting face with tongue', category: 'smileys', keywords: ['tongue', 'silly'] },
44
+ { emoji: '\u{1F911}', name: 'money-mouth face', category: 'smileys', keywords: ['money', 'rich'] },
45
+ { emoji: '\u{1F917}', name: 'hugging face', category: 'smileys', keywords: ['hug', 'warm'] },
46
+ { emoji: '\u{1F914}', name: 'thinking face', category: 'smileys', keywords: ['think', 'hmm'] },
47
+ { emoji: '\u{1F910}', name: 'zipper-mouth face', category: 'smileys', keywords: ['secret', 'quiet', 'zip'] },
48
+ { emoji: '\u{1F928}', name: 'face with raised eyebrow', category: 'smileys', keywords: ['skeptical', 'doubt'] },
49
+ { emoji: '\u{1F610}', name: 'neutral face', category: 'smileys', keywords: ['meh', 'neutral'] },
50
+ { emoji: '\u{1F611}', name: 'expressionless face', category: 'smileys', keywords: ['blank', 'expressionless'] },
51
+ ],
52
+ people: [
53
+ { emoji: '\u{1F44B}', name: 'waving hand', category: 'people', keywords: ['wave', 'hello', 'hi', 'bye'] },
54
+ { emoji: '\u{1F91A}', name: 'raised back of hand', category: 'people', keywords: ['hand', 'backhand'] },
55
+ { emoji: '\u{1F590}', name: 'hand with fingers splayed', category: 'people', keywords: ['hand', 'fingers'] },
56
+ { emoji: '\u270B', name: 'raised hand', category: 'people', keywords: ['hand', 'stop', 'high five'] },
57
+ { emoji: '\u{1F596}', name: 'vulcan salute', category: 'people', keywords: ['spock', 'star trek'] },
58
+ { emoji: '\u{1F44C}', name: 'OK hand', category: 'people', keywords: ['ok', 'perfect', 'fine'] },
59
+ { emoji: '\u270C', name: 'victory hand', category: 'people', keywords: ['peace', 'victory', 'v'] },
60
+ { emoji: '\u{1F91E}', name: 'crossed fingers', category: 'people', keywords: ['luck', 'hope'] },
61
+ { emoji: '\u{1F918}', name: 'love-you gesture', category: 'people', keywords: ['love', 'rock'] },
62
+ { emoji: '\u{1F919}', name: 'call me hand', category: 'people', keywords: ['call', 'phone'] },
63
+ { emoji: '\u{1F448}', name: 'backhand index pointing left', category: 'people', keywords: ['left', 'point'] },
64
+ { emoji: '\u{1F449}', name: 'backhand index pointing right', category: 'people', keywords: ['right', 'point'] },
65
+ { emoji: '\u{1F446}', name: 'backhand index pointing up', category: 'people', keywords: ['up', 'point'] },
66
+ { emoji: '\u{1F447}', name: 'backhand index pointing down', category: 'people', keywords: ['down', 'point'] },
67
+ { emoji: '\u261D', name: 'index pointing up', category: 'people', keywords: ['up', 'point'] },
68
+ { emoji: '\u{1F44D}', name: 'thumbs up', category: 'people', keywords: ['like', 'yes', 'approve', 'thumbsup'] },
69
+ { emoji: '\u{1F44E}', name: 'thumbs down', category: 'people', keywords: ['dislike', 'no', 'disapprove'] },
70
+ { emoji: '\u270A', name: 'raised fist', category: 'people', keywords: ['fist', 'power'] },
71
+ { emoji: '\u{1F44A}', name: 'oncoming fist', category: 'people', keywords: ['punch', 'fist bump'] },
72
+ { emoji: '\u{1F91B}', name: 'left-facing fist', category: 'people', keywords: ['fist'] },
73
+ { emoji: '\u{1F91C}', name: 'right-facing fist', category: 'people', keywords: ['fist'] },
74
+ { emoji: '\u{1F44F}', name: 'clapping hands', category: 'people', keywords: ['clap', 'applause', 'bravo'] },
75
+ { emoji: '\u{1F64C}', name: 'raising hands', category: 'people', keywords: ['celebrate', 'hooray'] },
76
+ { emoji: '\u{1F450}', name: 'open hands', category: 'people', keywords: ['hands', 'open'] },
77
+ { emoji: '\u{1F932}', name: 'palms up together', category: 'people', keywords: ['prayer', 'please'] },
78
+ { emoji: '\u{1F91D}', name: 'handshake', category: 'people', keywords: ['deal', 'agree', 'shake'] },
79
+ { emoji: '\u{1F64F}', name: 'folded hands', category: 'people', keywords: ['pray', 'please', 'thank you'] },
80
+ ],
81
+ nature: [
82
+ { emoji: '\u{1F436}', name: 'dog face', category: 'nature', keywords: ['dog', 'puppy', 'pet'] },
83
+ { emoji: '\u{1F431}', name: 'cat face', category: 'nature', keywords: ['cat', 'kitten', 'pet'] },
84
+ { emoji: '\u{1F42D}', name: 'mouse face', category: 'nature', keywords: ['mouse', 'rodent'] },
85
+ { emoji: '\u{1F439}', name: 'hamster', category: 'nature', keywords: ['hamster', 'pet'] },
86
+ { emoji: '\u{1F430}', name: 'rabbit face', category: 'nature', keywords: ['rabbit', 'bunny'] },
87
+ { emoji: '\u{1F98A}', name: 'fox', category: 'nature', keywords: ['fox', 'animal'] },
88
+ { emoji: '\u{1F43B}', name: 'bear', category: 'nature', keywords: ['bear', 'animal'] },
89
+ { emoji: '\u{1F43C}', name: 'panda', category: 'nature', keywords: ['panda', 'animal'] },
90
+ { emoji: '\u{1F428}', name: 'koala', category: 'nature', keywords: ['koala', 'animal'] },
91
+ { emoji: '\u{1F42F}', name: 'tiger face', category: 'nature', keywords: ['tiger', 'animal'] },
92
+ { emoji: '\u{1F981}', name: 'lion', category: 'nature', keywords: ['lion', 'animal'] },
93
+ { emoji: '\u{1F42E}', name: 'cow face', category: 'nature', keywords: ['cow', 'animal'] },
94
+ { emoji: '\u{1F437}', name: 'pig face', category: 'nature', keywords: ['pig', 'animal'] },
95
+ { emoji: '\u{1F438}', name: 'frog', category: 'nature', keywords: ['frog', 'animal'] },
96
+ { emoji: '\u{1F435}', name: 'monkey face', category: 'nature', keywords: ['monkey', 'animal'] },
97
+ { emoji: '\u{1F412}', name: 'monkey', category: 'nature', keywords: ['monkey', 'animal'] },
98
+ { emoji: '\u{1F414}', name: 'chicken', category: 'nature', keywords: ['chicken', 'animal'] },
99
+ { emoji: '\u{1F427}', name: 'penguin', category: 'nature', keywords: ['penguin', 'animal'] },
100
+ { emoji: '\u{1F426}', name: 'bird', category: 'nature', keywords: ['bird', 'animal'] },
101
+ { emoji: '\u{1F40A}', name: 'crocodile', category: 'nature', keywords: ['crocodile', 'animal'] },
102
+ { emoji: '\u{1F422}', name: 'turtle', category: 'nature', keywords: ['turtle', 'slow'] },
103
+ { emoji: '\u{1F40D}', name: 'snake', category: 'nature', keywords: ['snake', 'animal'] },
104
+ { emoji: '\u{1F332}', name: 'evergreen tree', category: 'nature', keywords: ['tree', 'pine'] },
105
+ { emoji: '\u{1F333}', name: 'deciduous tree', category: 'nature', keywords: ['tree'] },
106
+ { emoji: '\u{1F334}', name: 'palm tree', category: 'nature', keywords: ['palm', 'tree', 'tropical'] },
107
+ { emoji: '\u{1F335}', name: 'cactus', category: 'nature', keywords: ['cactus', 'desert'] },
108
+ { emoji: '\u{1F337}', name: 'tulip', category: 'nature', keywords: ['flower', 'tulip'] },
109
+ { emoji: '\u{1F339}', name: 'rose', category: 'nature', keywords: ['flower', 'rose', 'love'] },
110
+ ],
111
+ food: [
112
+ { emoji: '\u{1F34E}', name: 'red apple', category: 'food', keywords: ['apple', 'fruit'] },
113
+ { emoji: '\u{1F34A}', name: 'tangerine', category: 'food', keywords: ['orange', 'fruit'] },
114
+ { emoji: '\u{1F34B}', name: 'lemon', category: 'food', keywords: ['lemon', 'fruit'] },
115
+ { emoji: '\u{1F34C}', name: 'banana', category: 'food', keywords: ['banana', 'fruit'] },
116
+ { emoji: '\u{1F349}', name: 'watermelon', category: 'food', keywords: ['watermelon', 'fruit'] },
117
+ { emoji: '\u{1F347}', name: 'grapes', category: 'food', keywords: ['grapes', 'fruit'] },
118
+ { emoji: '\u{1F353}', name: 'strawberry', category: 'food', keywords: ['strawberry', 'fruit'] },
119
+ { emoji: '\u{1F352}', name: 'cherries', category: 'food', keywords: ['cherry', 'fruit'] },
120
+ { emoji: '\u{1F351}', name: 'peach', category: 'food', keywords: ['peach', 'fruit'] },
121
+ { emoji: '\u{1F34D}', name: 'pineapple', category: 'food', keywords: ['pineapple', 'fruit'] },
122
+ { emoji: '\u{1F354}', name: 'hamburger', category: 'food', keywords: ['burger', 'fast food'] },
123
+ { emoji: '\u{1F355}', name: 'pizza', category: 'food', keywords: ['pizza', 'food'] },
124
+ { emoji: '\u{1F32E}', name: 'taco', category: 'food', keywords: ['taco', 'mexican'] },
125
+ { emoji: '\u{1F32F}', name: 'burrito', category: 'food', keywords: ['burrito', 'mexican'] },
126
+ { emoji: '\u{1F35F}', name: 'french fries', category: 'food', keywords: ['fries', 'fast food'] },
127
+ { emoji: '\u{1F357}', name: 'poultry leg', category: 'food', keywords: ['chicken', 'meat'] },
128
+ { emoji: '\u{1F356}', name: 'meat on bone', category: 'food', keywords: ['meat', 'bone'] },
129
+ { emoji: '\u{1F363}', name: 'sushi', category: 'food', keywords: ['sushi', 'japanese'] },
130
+ { emoji: '\u{1F370}', name: 'shortcake', category: 'food', keywords: ['cake', 'dessert'] },
131
+ { emoji: '\u{1F36B}', name: 'chocolate bar', category: 'food', keywords: ['chocolate', 'candy'] },
132
+ { emoji: '\u{1F369}', name: 'doughnut', category: 'food', keywords: ['donut', 'dessert'] },
133
+ { emoji: '\u{1F36A}', name: 'cookie', category: 'food', keywords: ['cookie', 'dessert'] },
134
+ { emoji: '\u2615', name: 'hot beverage', category: 'food', keywords: ['coffee', 'tea', 'drink'] },
135
+ { emoji: '\u{1F37A}', name: 'beer mug', category: 'food', keywords: ['beer', 'drink'] },
136
+ { emoji: '\u{1F377}', name: 'wine glass', category: 'food', keywords: ['wine', 'drink'] },
137
+ ],
138
+ travel: [
139
+ { emoji: '\u{1F697}', name: 'automobile', category: 'travel', keywords: ['car', 'drive'] },
140
+ { emoji: '\u{1F695}', name: 'taxi', category: 'travel', keywords: ['taxi', 'cab'] },
141
+ { emoji: '\u{1F68C}', name: 'bus', category: 'travel', keywords: ['bus', 'transit'] },
142
+ { emoji: '\u{1F682}', name: 'locomotive', category: 'travel', keywords: ['train', 'rail'] },
143
+ { emoji: '\u2708', name: 'airplane', category: 'travel', keywords: ['plane', 'fly', 'travel'] },
144
+ { emoji: '\u{1F680}', name: 'rocket', category: 'travel', keywords: ['rocket', 'space', 'launch'] },
145
+ { emoji: '\u{1F6F8}', name: 'flying saucer', category: 'travel', keywords: ['ufo', 'alien'] },
146
+ { emoji: '\u{1F6A2}', name: 'ship', category: 'travel', keywords: ['ship', 'boat', 'cruise'] },
147
+ { emoji: '\u{1F3E0}', name: 'house', category: 'travel', keywords: ['house', 'home'] },
148
+ { emoji: '\u{1F3E2}', name: 'office building', category: 'travel', keywords: ['office', 'building', 'work'] },
149
+ { emoji: '\u{1F3EB}', name: 'school', category: 'travel', keywords: ['school', 'education'] },
150
+ { emoji: '\u{1F3E5}', name: 'hospital', category: 'travel', keywords: ['hospital', 'health'] },
151
+ { emoji: '\u{1F30D}', name: 'globe showing Europe-Africa', category: 'travel', keywords: ['earth', 'globe', 'world'] },
152
+ { emoji: '\u{1F30E}', name: 'globe showing Americas', category: 'travel', keywords: ['earth', 'globe', 'world'] },
153
+ { emoji: '\u{1F30F}', name: 'globe showing Asia-Australia', category: 'travel', keywords: ['earth', 'globe', 'world'] },
154
+ { emoji: '\u{1F5FC}', name: 'Tokyo tower', category: 'travel', keywords: ['tokyo', 'japan', 'tower'] },
155
+ { emoji: '\u{1F5FD}', name: 'Statue of Liberty', category: 'travel', keywords: ['statue', 'liberty', 'new york'] },
156
+ { emoji: '\u26F0', name: 'mountain', category: 'travel', keywords: ['mountain', 'nature'] },
157
+ { emoji: '\u{1F3D6}', name: 'beach with umbrella', category: 'travel', keywords: ['beach', 'vacation'] },
158
+ { emoji: '\u{1F3DD}', name: 'desert island', category: 'travel', keywords: ['island', 'tropical'] },
159
+ ],
160
+ activities: [
161
+ { emoji: '\u26BD', name: 'soccer ball', category: 'activities', keywords: ['soccer', 'football', 'sport'] },
162
+ { emoji: '\u{1F3C0}', name: 'basketball', category: 'activities', keywords: ['basketball', 'sport'] },
163
+ { emoji: '\u{1F3C8}', name: 'american football', category: 'activities', keywords: ['football', 'sport'] },
164
+ { emoji: '\u26BE', name: 'baseball', category: 'activities', keywords: ['baseball', 'sport'] },
165
+ { emoji: '\u{1F3BE}', name: 'tennis', category: 'activities', keywords: ['tennis', 'sport'] },
166
+ { emoji: '\u{1F3D0}', name: 'volleyball', category: 'activities', keywords: ['volleyball', 'sport'] },
167
+ { emoji: '\u{1F3B1}', name: 'pool 8 ball', category: 'activities', keywords: ['pool', 'billiards'] },
168
+ { emoji: '\u{1F3D3}', name: 'ping pong', category: 'activities', keywords: ['table tennis', 'sport'] },
169
+ { emoji: '\u{1F3C6}', name: 'trophy', category: 'activities', keywords: ['trophy', 'winner', 'award'] },
170
+ { emoji: '\u{1F3C5}', name: 'sports medal', category: 'activities', keywords: ['medal', 'winner'] },
171
+ { emoji: '\u{1F947}', name: 'first place medal', category: 'activities', keywords: ['gold', 'first', 'winner'] },
172
+ { emoji: '\u{1F948}', name: 'second place medal', category: 'activities', keywords: ['silver', 'second'] },
173
+ { emoji: '\u{1F949}', name: 'third place medal', category: 'activities', keywords: ['bronze', 'third'] },
174
+ { emoji: '\u{1F3AF}', name: 'bullseye', category: 'activities', keywords: ['target', 'dart'] },
175
+ { emoji: '\u{1F3AE}', name: 'video game', category: 'activities', keywords: ['game', 'controller'] },
176
+ { emoji: '\u{1F3B2}', name: 'game die', category: 'activities', keywords: ['dice', 'game'] },
177
+ { emoji: '\u{1F3B5}', name: 'musical note', category: 'activities', keywords: ['music', 'note'] },
178
+ { emoji: '\u{1F3B6}', name: 'musical notes', category: 'activities', keywords: ['music', 'notes'] },
179
+ { emoji: '\u{1F3A4}', name: 'microphone', category: 'activities', keywords: ['karaoke', 'sing', 'mic'] },
180
+ { emoji: '\u{1F3AC}', name: 'clapper board', category: 'activities', keywords: ['movie', 'film'] },
181
+ ],
182
+ objects: [
183
+ { emoji: '\u{1F4F1}', name: 'mobile phone', category: 'objects', keywords: ['phone', 'mobile', 'cell'] },
184
+ { emoji: '\u{1F4BB}', name: 'laptop', category: 'objects', keywords: ['laptop', 'computer'] },
185
+ { emoji: '\u{1F5A5}', name: 'desktop computer', category: 'objects', keywords: ['computer', 'desktop'] },
186
+ { emoji: '\u2328', name: 'keyboard', category: 'objects', keywords: ['keyboard', 'type'] },
187
+ { emoji: '\u{1F4F7}', name: 'camera', category: 'objects', keywords: ['camera', 'photo'] },
188
+ { emoji: '\u{1F4FA}', name: 'television', category: 'objects', keywords: ['tv', 'television'] },
189
+ { emoji: '\u{1F4E7}', name: 'e-mail', category: 'objects', keywords: ['email', 'mail'] },
190
+ { emoji: '\u{1F4DD}', name: 'memo', category: 'objects', keywords: ['memo', 'note', 'write'] },
191
+ { emoji: '\u{1F4D6}', name: 'open book', category: 'objects', keywords: ['book', 'read'] },
192
+ { emoji: '\u{1F4DA}', name: 'books', category: 'objects', keywords: ['books', 'library'] },
193
+ { emoji: '\u{1F4C5}', name: 'calendar', category: 'objects', keywords: ['calendar', 'date'] },
194
+ { emoji: '\u{1F4CB}', name: 'clipboard', category: 'objects', keywords: ['clipboard', 'paste'] },
195
+ { emoji: '\u{1F4CC}', name: 'pushpin', category: 'objects', keywords: ['pin', 'pushpin'] },
196
+ { emoji: '\u{1F4CE}', name: 'paperclip', category: 'objects', keywords: ['paperclip', 'attach'] },
197
+ { emoji: '\u{1F511}', name: 'key', category: 'objects', keywords: ['key', 'lock'] },
198
+ { emoji: '\u{1F512}', name: 'locked', category: 'objects', keywords: ['lock', 'secure'] },
199
+ { emoji: '\u{1F513}', name: 'unlocked', category: 'objects', keywords: ['unlock', 'open'] },
200
+ { emoji: '\u{1F50D}', name: 'magnifying glass tilted left', category: 'objects', keywords: ['search', 'find'] },
201
+ { emoji: '\u{1F4A1}', name: 'light bulb', category: 'objects', keywords: ['idea', 'light'] },
202
+ { emoji: '\u{1F527}', name: 'wrench', category: 'objects', keywords: ['tool', 'wrench', 'fix'] },
203
+ { emoji: '\u{1F528}', name: 'hammer', category: 'objects', keywords: ['tool', 'hammer', 'build'] },
204
+ { emoji: '\u2699', name: 'gear', category: 'objects', keywords: ['settings', 'gear', 'config'] },
205
+ ],
206
+ symbols: [
207
+ { emoji: '\u2764', name: 'red heart', category: 'symbols', keywords: ['heart', 'love'] },
208
+ { emoji: '\u{1F9E1}', name: 'orange heart', category: 'symbols', keywords: ['heart', 'orange'] },
209
+ { emoji: '\u{1F49B}', name: 'yellow heart', category: 'symbols', keywords: ['heart', 'yellow'] },
210
+ { emoji: '\u{1F49A}', name: 'green heart', category: 'symbols', keywords: ['heart', 'green'] },
211
+ { emoji: '\u{1F499}', name: 'blue heart', category: 'symbols', keywords: ['heart', 'blue'] },
212
+ { emoji: '\u{1F49C}', name: 'purple heart', category: 'symbols', keywords: ['heart', 'purple'] },
213
+ { emoji: '\u{1F494}', name: 'broken heart', category: 'symbols', keywords: ['heart', 'broken', 'sad'] },
214
+ { emoji: '\u{1F495}', name: 'two hearts', category: 'symbols', keywords: ['heart', 'love'] },
215
+ { emoji: '\u{1F4AF}', name: 'hundred points', category: 'symbols', keywords: ['100', 'perfect', 'score'] },
216
+ { emoji: '\u{1F4A5}', name: 'collision', category: 'symbols', keywords: ['boom', 'bang', 'explosion'] },
217
+ { emoji: '\u{1F4AB}', name: 'dizzy', category: 'symbols', keywords: ['star', 'dizzy'] },
218
+ { emoji: '\u2728', name: 'sparkles', category: 'symbols', keywords: ['sparkle', 'shine', 'star'] },
219
+ { emoji: '\u{1F525}', name: 'fire', category: 'symbols', keywords: ['fire', 'hot', 'flame'] },
220
+ { emoji: '\u2705', name: 'check mark button', category: 'symbols', keywords: ['check', 'done', 'yes'] },
221
+ { emoji: '\u274C', name: 'cross mark', category: 'symbols', keywords: ['no', 'wrong', 'x'] },
222
+ { emoji: '\u2757', name: 'exclamation mark', category: 'symbols', keywords: ['exclamation', 'warning', 'alert'] },
223
+ { emoji: '\u2753', name: 'question mark', category: 'symbols', keywords: ['question', 'what'] },
224
+ { emoji: '\u{1F6AB}', name: 'prohibited', category: 'symbols', keywords: ['no', 'forbidden', 'stop'] },
225
+ { emoji: '\u267B', name: 'recycling symbol', category: 'symbols', keywords: ['recycle', 'green'] },
226
+ { emoji: '\u{1F4AC}', name: 'speech balloon', category: 'symbols', keywords: ['speech', 'chat', 'message'] },
227
+ { emoji: '\u{1F4AD}', name: 'thought balloon', category: 'symbols', keywords: ['thought', 'think'] },
228
+ { emoji: '\u{1F514}', name: 'bell', category: 'symbols', keywords: ['bell', 'notification', 'alert'] },
229
+ ],
230
+ flags: [
231
+ { emoji: '\u{1F1FA}\u{1F1F8}', name: 'flag: United States', category: 'flags', keywords: ['us', 'usa', 'america'] },
232
+ { emoji: '\u{1F1EC}\u{1F1E7}', name: 'flag: United Kingdom', category: 'flags', keywords: ['uk', 'britain', 'england'] },
233
+ { emoji: '\u{1F1E8}\u{1F1E6}', name: 'flag: Canada', category: 'flags', keywords: ['canada'] },
234
+ { emoji: '\u{1F1E6}\u{1F1FA}', name: 'flag: Australia', category: 'flags', keywords: ['australia'] },
235
+ { emoji: '\u{1F1EF}\u{1F1F5}', name: 'flag: Japan', category: 'flags', keywords: ['japan'] },
236
+ { emoji: '\u{1F1EB}\u{1F1F7}', name: 'flag: France', category: 'flags', keywords: ['france'] },
237
+ { emoji: '\u{1F1E9}\u{1F1EA}', name: 'flag: Germany', category: 'flags', keywords: ['germany'] },
238
+ { emoji: '\u{1F1EE}\u{1F1F3}', name: 'flag: India', category: 'flags', keywords: ['india'] },
239
+ { emoji: '\u{1F1E7}\u{1F1F7}', name: 'flag: Brazil', category: 'flags', keywords: ['brazil'] },
240
+ { emoji: '\u{1F1F0}\u{1F1F7}', name: 'flag: South Korea', category: 'flags', keywords: ['korea', 'south korea'] },
241
+ { emoji: '\u{1F1EE}\u{1F1F9}', name: 'flag: Italy', category: 'flags', keywords: ['italy'] },
242
+ { emoji: '\u{1F1EA}\u{1F1F8}', name: 'flag: Spain', category: 'flags', keywords: ['spain'] },
243
+ { emoji: '\u{1F1F2}\u{1F1FD}', name: 'flag: Mexico', category: 'flags', keywords: ['mexico'] },
244
+ { emoji: '\u{1F1F7}\u{1F1FA}', name: 'flag: Russia', category: 'flags', keywords: ['russia'] },
245
+ { emoji: '\u{1F1E8}\u{1F1F3}', name: 'flag: China', category: 'flags', keywords: ['china'] },
246
+ { emoji: '\u{1F3C1}', name: 'chequered flag', category: 'flags', keywords: ['finish', 'race'] },
247
+ { emoji: '\u{1F3F3}', name: 'white flag', category: 'flags', keywords: ['surrender', 'peace'] },
248
+ { emoji: '\u{1F3F4}', name: 'black flag', category: 'flags', keywords: ['pirate'] },
249
+ { emoji: '\u{1F6A9}', name: 'triangular flag', category: 'flags', keywords: ['flag', 'marker'] },
250
+ ],
251
+ }
252
+
253
+ /** All categories in display order */
254
+ export const EMOJI_CATEGORIES: EmojiCategory[] = [
255
+ 'smileys',
256
+ 'people',
257
+ 'nature',
258
+ 'food',
259
+ 'travel',
260
+ 'activities',
261
+ 'objects',
262
+ 'symbols',
263
+ 'flags',
264
+ ]
265
+
266
+ /** Category display labels */
267
+ export const CATEGORY_LABELS: Record<EmojiCategory, string> = {
268
+ smileys: 'Smileys & Emotion',
269
+ people: 'People & Body',
270
+ nature: 'Animals & Nature',
271
+ food: 'Food & Drink',
272
+ travel: 'Travel & Places',
273
+ activities: 'Activities',
274
+ objects: 'Objects',
275
+ symbols: 'Symbols',
276
+ flags: 'Flags',
277
+ }
278
+
279
+ /** Get all emojis as a flat array */
280
+ export function getAllEmojis(): EmojiEntry[] {
281
+ return EMOJI_CATEGORIES.flatMap((cat) => EMOJI_DATA[cat])
282
+ }
@@ -0,0 +1,32 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ export const emojiPickerContainerStyles =
4
+ 'w-80 rounded-lg border bg-popover text-popover-foreground shadow-md'
5
+
6
+ export const emojiPickerSearchStyles =
7
+ 'w-full border-b px-3 py-2 text-sm bg-transparent outline-none placeholder:text-muted-foreground'
8
+
9
+ export const emojiPickerCategoryBarStyles =
10
+ 'flex border-b px-1'
11
+
12
+ export const emojiPickerCategoryTabVariants = cva({
13
+ base: 'flex-1 py-1.5 text-center text-lg cursor-pointer hover:bg-accent rounded-md transition-colors',
14
+ variants: {
15
+ state: {
16
+ active: 'bg-accent',
17
+ inactive: '',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ state: 'inactive',
22
+ },
23
+ })
24
+
25
+ export const emojiPickerGridStyles =
26
+ 'grid grid-cols-8 gap-0.5 p-2 max-h-64 overflow-y-auto'
27
+
28
+ export const emojiPickerEmojiButtonStyles =
29
+ 'flex items-center justify-center h-8 w-8 rounded cursor-pointer text-lg hover:bg-accent transition-colors'
30
+
31
+ export const emojiPickerSectionLabelStyles =
32
+ 'px-2 py-1 text-xs font-medium text-muted-foreground'
@@ -0,0 +1,165 @@
1
+ import type { AccessibilityProps } from '../shared/index.ts'
2
+ import { generateId } from '../shared/index.ts'
3
+ import {
4
+ EMOJI_DATA,
5
+ EMOJI_CATEGORIES,
6
+ CATEGORY_LABELS,
7
+ getAllEmojis,
8
+ type EmojiCategory,
9
+ type EmojiEntry,
10
+ } from './emoji-data.js'
11
+
12
+ export interface EmojiPickerProps {
13
+ /** Callback when an emoji is selected */
14
+ onSelect?: (emoji: EmojiEntry) => void
15
+ /** Initial search query */
16
+ search?: string
17
+ /** List of recently used emojis */
18
+ recentEmojis?: EmojiEntry[]
19
+ /** Maximum recent emojis to show */
20
+ maxRecent?: number
21
+ }
22
+
23
+ export interface EmojiPickerState {
24
+ search: string
25
+ activeCategory: EmojiCategory
26
+ filteredEmojis: EmojiEntry[]
27
+ recentEmojis: EmojiEntry[]
28
+ }
29
+
30
+ export interface EmojiPickerAPI {
31
+ /** Current state */
32
+ state: EmojiPickerState
33
+ /** Set search query */
34
+ setSearch(query: string): void
35
+ /** Select an emoji */
36
+ select(emoji: EmojiEntry): void
37
+ /** Set active category tab */
38
+ setCategory(category: EmojiCategory): void
39
+ /** Category tabs with labels */
40
+ categoryTabs: { category: EmojiCategory; label: string; emoji: string }[]
41
+ /** ARIA props for the picker container */
42
+ ariaProps: Partial<AccessibilityProps> & Record<string, unknown>
43
+ /** ARIA props for the search input */
44
+ searchInputProps: Record<string, unknown>
45
+ /** Get ARIA props for an emoji button */
46
+ getEmojiAriaProps(emoji: EmojiEntry): Record<string, unknown>
47
+ /** Generated IDs */
48
+ ids: {
49
+ container: string
50
+ search: string
51
+ grid: string
52
+ label: string
53
+ }
54
+ }
55
+
56
+ const CATEGORY_ICONS: Record<EmojiCategory, string> = {
57
+ smileys: '\u{1F600}',
58
+ people: '\u{1F44B}',
59
+ nature: '\u{1F436}',
60
+ food: '\u{1F34E}',
61
+ travel: '\u{1F697}',
62
+ activities: '\u26BD',
63
+ objects: '\u{1F4BB}',
64
+ symbols: '\u2764',
65
+ flags: '\u{1F3C1}',
66
+ }
67
+
68
+ export function createEmojiPicker(props: EmojiPickerProps = {}): EmojiPickerAPI {
69
+ const {
70
+ onSelect,
71
+ search: initialSearch = '',
72
+ recentEmojis: initialRecent = [],
73
+ maxRecent = 20,
74
+ } = props
75
+
76
+ let searchQuery = initialSearch
77
+ let activeCategory: EmojiCategory = 'smileys'
78
+ let recentEmojis = [...initialRecent].slice(0, maxRecent)
79
+
80
+ const containerId = generateId('rfr-emoji-picker')
81
+ const searchId = generateId('rfr-emoji-search')
82
+ const gridId = generateId('rfr-emoji-grid')
83
+ const labelId = generateId('rfr-emoji-label')
84
+
85
+ function getFilteredEmojis(): EmojiEntry[] {
86
+ if (searchQuery.trim()) {
87
+ const query = searchQuery.toLowerCase().trim()
88
+ return getAllEmojis().filter(
89
+ (entry) =>
90
+ entry.name.toLowerCase().includes(query) ||
91
+ entry.keywords.some((kw) => kw.toLowerCase().includes(query)),
92
+ )
93
+ }
94
+ return EMOJI_DATA[activeCategory]
95
+ }
96
+
97
+ function setSearch(query: string): void {
98
+ searchQuery = query
99
+ }
100
+
101
+ function select(emoji: EmojiEntry): void {
102
+ // Add to recent (deduplicate, prepend)
103
+ recentEmojis = [
104
+ emoji,
105
+ ...recentEmojis.filter((e) => e.emoji !== emoji.emoji),
106
+ ].slice(0, maxRecent)
107
+ onSelect?.(emoji)
108
+ }
109
+
110
+ function setCategory(category: EmojiCategory): void {
111
+ activeCategory = category
112
+ searchQuery = ''
113
+ }
114
+
115
+ const categoryTabs = EMOJI_CATEGORIES.map((cat) => ({
116
+ category: cat,
117
+ label: CATEGORY_LABELS[cat],
118
+ emoji: CATEGORY_ICONS[cat],
119
+ }))
120
+
121
+ const ariaProps: Partial<AccessibilityProps> & Record<string, unknown> = {
122
+ role: 'dialog',
123
+ 'aria-labelledby': labelId,
124
+ id: containerId,
125
+ }
126
+
127
+ const searchInputProps: Record<string, unknown> = {
128
+ type: 'search',
129
+ role: 'searchbox',
130
+ 'aria-label': 'Search emojis',
131
+ id: searchId,
132
+ placeholder: 'Search emojis...',
133
+ autoComplete: 'off',
134
+ }
135
+
136
+ function getEmojiAriaProps(emoji: EmojiEntry): Record<string, unknown> {
137
+ return {
138
+ role: 'button',
139
+ 'aria-label': `${emoji.name} ${emoji.emoji}`,
140
+ title: emoji.name,
141
+ }
142
+ }
143
+
144
+ return {
145
+ state: {
146
+ search: searchQuery,
147
+ activeCategory,
148
+ filteredEmojis: getFilteredEmojis(),
149
+ recentEmojis,
150
+ },
151
+ setSearch,
152
+ select,
153
+ setCategory,
154
+ categoryTabs,
155
+ ariaProps,
156
+ searchInputProps,
157
+ getEmojiAriaProps,
158
+ ids: {
159
+ container: containerId,
160
+ search: searchId,
161
+ grid: gridId,
162
+ label: labelId,
163
+ },
164
+ }
165
+ }