lazy-load emoji-mart picker on web

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-07-08 13:39:07 +03:00
parent ea3d6827c7
commit b49f2c589d
2 changed files with 46 additions and 13 deletions
+34 -3
View File
@@ -1,5 +1,13 @@
import {createContext, useContext, useEffect, useMemo, useRef} from 'react' import {
import EmojiPicker from '@emoji-mart/react' createContext,
lazy,
Suspense,
useContext,
useEffect,
useMemo,
useRef,
} from 'react'
import {View} from 'react-native'
import {DropdownMenu} from 'radix-ui' import {DropdownMenu} from 'radix-ui'
import {useA11y} from '#/state/a11y' import {useA11y} from '#/state/a11y'
@@ -16,6 +24,24 @@ import {
export * from './types' export * from './types'
/**
* The emoji-mart picker is code-split into its own chunk (`emoji-mart`) so its
* weight stays out of the main bundle and is only fetched when the picker
* actually mounts (i.e. when the dropdown opens). {@link useWebPreloadEmoji}
* warms the same chunk ahead of time.
*/
const EmojiPickerLazy = lazy(
() => import(/* webpackChunkName: "emoji-mart" */ '@emoji-mart/react'),
)
/**
* Fixed-size placeholder shown while the emoji-mart chunk loads, sized to match
* emoji-mart's default picker so the dropdown doesn't visually jump when the
* real picker mounts.
*/
const PICKER_WIDTH = 352
const PICKER_HEIGHT = 435
const EmojiPickerContext = createContext<{ const EmojiPickerContext = createContext<{
onEmojiSelect: (emoji: Emoji) => void onEmojiSelect: (emoji: Emoji) => void
nextFocusRef: RootProps['nextFocusRef'] nextFocusRef: RootProps['nextFocusRef']
@@ -124,7 +150,11 @@ export function Picker({keepOpenWhenShiftHeld = true}: PickerProps) {
<div <div
onWheel={evt => evt.stopPropagation()} onWheel={evt => evt.stopPropagation()}
style={flatten([!reduceMotionEnabled && a.zoom_fade_in])}> style={flatten([!reduceMotionEnabled && a.zoom_fade_in])}>
<EmojiPicker <Suspense
fallback={
<View style={{width: PICKER_WIDTH, height: PICKER_HEIGHT}} />
}>
<EmojiPickerLazy
autoFocus autoFocus
onEmojiSelect={(emoji: Emoji) => { onEmojiSelect={(emoji: Emoji) => {
onEmojiSelect(emoji) onEmojiSelect(emoji)
@@ -134,6 +164,7 @@ export function Picker({keepOpenWhenShiftHeld = true}: PickerProps) {
} }
}} }}
/> />
</Suspense>
</div> </div>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Portal> </DropdownMenu.Portal>
+4 -2
View File
@@ -1,5 +1,4 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {init} from 'emoji-mart'
/** /**
* Only load the emoji picker data once per page load. * Only load the emoji picker data once per page load.
@@ -21,7 +20,10 @@ export function useWebPreloadEmoji({immediate}: {immediate?: boolean} = {}) {
if (loadRequested) return if (loadRequested) return
loadRequested = true loadRequested = true
try { try {
const data = (await import('@emoji-mart/data')).default const [{init}, {default: data}] = await Promise.all([
import(/* webpackChunkName: "emoji-mart" */ 'emoji-mart'),
import(/* webpackChunkName: "emoji-mart-data" */ '@emoji-mart/data'),
])
init({data}) init({data})
} catch (e) {} } catch (e) {}
}, []) }, [])