lazy-load emoji-mart picker on web
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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,16 +150,21 @@ 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
|
||||||
autoFocus
|
fallback={
|
||||||
onEmojiSelect={(emoji: Emoji) => {
|
<View style={{width: PICKER_WIDTH, height: PICKER_HEIGHT}} />
|
||||||
onEmojiSelect(emoji)
|
}>
|
||||||
|
<EmojiPickerLazy
|
||||||
|
autoFocus
|
||||||
|
onEmojiSelect={(emoji: Emoji) => {
|
||||||
|
onEmojiSelect(emoji)
|
||||||
|
|
||||||
if (!keepOpenWhenShiftHeld || !isShiftDown.current) {
|
if (!keepOpenWhenShiftHeld || !isShiftDown.current) {
|
||||||
control.close()
|
control.close()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
|
|||||||
@@ -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) {}
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
Reference in New Issue
Block a user