Fix FunLightbox and FunTooltip

This commit is contained in:
Jamie Kyle
2025-06-03 06:29:51 -07:00
committed by GitHub
parent 0a91232634
commit 06ff9fa09e
13 changed files with 891 additions and 211 deletions

View File

@@ -1,12 +1,13 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ForwardedRef, ReactNode } from 'react';
import React, { forwardRef, useEffect, useRef } from 'react';
import { type PressEvent, useLongPress } from 'react-aria';
import type { LongPressEvent } from '@react-types/shared';
import { Button } from 'react-aria-components';
import { mergeRefs } from '@react-aria/utils';
import { PressResponder } from '@react-aria/interactions';
import type {
ForwardedRef,
ReactNode,
DOMAttributes,
PointerEvent,
} from 'react';
import React, { forwardRef, useCallback, useEffect, useMemo } from 'react';
import { mergeProps } from '@react-aria/utils';
import { strictAssert } from '../../../util/assert';
/**
@@ -28,49 +29,120 @@ export type FunItemButtonProps = Readonly<
{
'aria-label': string;
excludeFromTabOrder: boolean;
onPress: (event: PressEvent) => void;
onContextMenu?: (event: MouseEvent) => void;
onClick: (event: PointerEvent) => void;
onContextMenu?: (event: PointerEvent) => void;
children: ReactNode;
} & FunItemButtonLongPressProps
>;
export const FunItemButton = forwardRef(function FunItemButton(
props: FunItemButtonProps,
outerRef: ForwardedRef<HTMLButtonElement>
ref: ForwardedRef<HTMLButtonElement>
): JSX.Element {
const { onContextMenu } = props;
const innerRef = useRef<HTMLButtonElement>(null);
const {
'aria-label': ariaLabel,
excludeFromTabOrder,
onClick,
onContextMenu,
children,
longPressAccessibilityDescription,
onLongPress,
...rest
} = props;
const { longPressProps } = useLongPress({
isDisabled: props.onLongPress == null,
accessibilityDescription: props.longPressAccessibilityDescription,
onLongPress: props.onLongPress,
});
const longPressProps = useLongPress(onLongPress ?? null);
useEffect(() => {
strictAssert(innerRef.current, 'Missing ref element');
const element = innerRef.current;
if (onContextMenu == null) {
return () => null;
}
element.addEventListener('contextmenu', onContextMenu);
return () => {
element.removeEventListener('contextmenu', onContextMenu);
};
}, [onContextMenu]);
const handleClick = useCallback(
(event: PointerEvent) => {
if (!event.defaultPrevented) {
onClick(event);
}
},
[onClick]
);
return (
<PressResponder {...longPressProps}>
<Button
ref={mergeRefs(innerRef, outerRef)}
type="button"
className="FunItem__Button"
aria-label={props['aria-label']}
excludeFromTabOrder={props.excludeFromTabOrder}
onPress={props.onPress}
>
{props.children}
</Button>
</PressResponder>
// eslint-disable-next-line jsx-a11y/role-supports-aria-props
<button
ref={ref}
type="button"
className="FunItem__Button"
aria-label={ariaLabel}
aria-description={longPressAccessibilityDescription}
tabIndex={excludeFromTabOrder ? -1 : undefined}
{...mergeProps(
longPressProps,
{
onClick: handleClick,
onContextMenu,
},
rest
)}
>
{children}
</button>
);
});
type LongPressEvent = Readonly<{
pointerType: PointerEvent['pointerType'];
}>;
function useLongPress(
onLongPress: ((event: LongPressEvent) => void) | null
): DOMAttributes<Element> {
const { cleanup, props } = useMemo(() => {
if (onLongPress == null) {
return { props: {} };
}
let timer: ReturnType<typeof setTimeout>;
let isLongPressed = false;
let lastLongPress: number | null = null;
function reset() {
clearTimeout(timer);
isLongPressed = false;
}
function handleCancel(event: PointerEvent) {
if (isLongPressed) {
lastLongPress = event.timeStamp;
}
reset();
}
function handleStart(event: PointerEvent) {
const press: LongPressEvent = { pointerType: event.pointerType };
reset();
timer = setTimeout(() => {
isLongPressed = true;
strictAssert(onLongPress != null, 'Missing callback');
onLongPress(press);
}, 500);
}
function handleClick(event: PointerEvent) {
if (event.timeStamp === lastLongPress) {
event.preventDefault();
}
}
return {
cleanup: reset,
props: {
onPointerDown: handleStart,
onPointerUp: handleCancel,
onPointerCancel: handleCancel,
onPointerLeave: handleCancel,
onClick: handleClick,
} satisfies DOMAttributes<Element>,
};
}, [onLongPress]);
useEffect(() => {
return cleanup;
}, [cleanup]);
return props;
}

View File

@@ -5,6 +5,7 @@ import React, { useCallback } from 'react';
import type { Placement } from 'react-aria';
import { Dialog, Popover } from 'react-aria-components';
import classNames from 'classnames';
import * as Tooltip from '@radix-ui/react-tooltip';
import { ThemeType } from '../../../types/Util';
export type FunPopoverProps = Readonly<{
@@ -16,8 +17,14 @@ export type FunPopoverProps = Readonly<{
export function FunPopover(props: FunPopoverProps): JSX.Element {
const shouldCloseOnInteractOutside = useCallback(
(element: Element): boolean => {
// Don't close when quill steals focus
const match = element.closest('.module-composition-input__input');
const match = element.closest(
[
// Don't close when quill steals focus
'.module-composition-input__input',
// Don't close when clicking tooltip
'.FunTooltip',
].join(', ')
);
if (match != null) {
return false;
}
@@ -27,16 +34,18 @@ export function FunPopover(props: FunPopoverProps): JSX.Element {
);
return (
<Popover
data-fun-overlay
className={classNames('FunPopover', {
'light-theme': props.theme === ThemeType.light,
'dark-theme': props.theme === ThemeType.dark,
})}
placement={props.placement}
shouldCloseOnInteractOutside={shouldCloseOnInteractOutside}
>
<Dialog className="FunPopover__Dialog">{props.children}</Dialog>
</Popover>
<Tooltip.Provider>
<Popover
data-fun-overlay
className={classNames('FunPopover', {
'light-theme': props.theme === ThemeType.light,
'dark-theme': props.theme === ThemeType.dark,
})}
placement={props.placement}
shouldCloseOnInteractOutside={shouldCloseOnInteractOutside}
>
<Dialog className="FunPopover__Dialog">{props.children}</Dialog>
</Popover>
</Tooltip.Provider>
);
}

View File

@@ -3,7 +3,7 @@
import classNames from 'classnames';
import type { Transition } from 'framer-motion';
import { motion } from 'framer-motion';
import type { ReactNode } from 'react';
import type { ReactNode, Ref } from 'react';
import React, {
createContext,
useCallback,
@@ -13,6 +13,7 @@ import React, {
useRef,
useState,
useId,
forwardRef,
} from 'react';
import type { Selection } from 'react-aria-components';
import { ListBox, ListBoxItem } from 'react-aria-components';
@@ -25,6 +26,7 @@ import * as log from '../../../logging/log';
import * as Errors from '../../../types/errors';
import { strictAssert } from '../../../util/assert';
import { FunImage } from './FunImage';
import { FunTooltip } from './FunTooltip';
/**
* Sub Nav
@@ -246,11 +248,30 @@ function FunSubNavListBoxItemButton(props: {
);
}
const FunSubNavListBoxItemTooltipTarget = forwardRef(
function FunSubNavListBoxItemTooltipTarget(props, ref: Ref<HTMLSpanElement>) {
return (
<span
ref={ref}
{...props}
className="FunSubNav__ListBoxItem__TooltipTarget"
/>
);
}
);
export function FunSubNavListBoxItem(
props: FunSubNavListBoxItemProps
): JSX.Element {
const context = useContext(FunSubNavListBoxContext);
strictAssert(context, 'Must be wrapped with <FunSubNavListBox>');
const [tooltipOpen, setTooltipOpen] = useState(false);
const handleTooltipOpenChange = useCallback((open: boolean) => {
setTooltipOpen(open);
}, []);
return (
<ListBoxItem
id={props.id}
@@ -260,22 +281,35 @@ export function FunSubNavListBoxItem(
>
{({ isSelected, isFocusVisible }) => {
return (
<FunSubNavListBoxItemButton isSelected={isSelected}>
<span className="FunSubNav__ListBoxItem__ButtonIcon">
{props.children}
</span>
{isSelected && (
<motion.div
className="FunSubNav__ListBoxItem__ButtonIndicator"
layoutId={`FunSubNav__ListBoxItem__ButtonIndicator--${context.id}`}
layoutDependency={context.selected}
transition={FunSubNavListBoxItemTransition}
/>
)}
{!isSelected && isFocusVisible && (
<div className="FunSubNav__ListBoxItem__ButtonIndicator" />
)}
</FunSubNavListBoxItemButton>
<>
<FunTooltip
open={tooltipOpen || (isSelected && isFocusVisible)}
onOpenChange={handleTooltipOpenChange}
side="top"
content={props.label}
collisionBoundarySelector=".FunPanel"
collisionPadding={6}
disableHoverableContent
>
<FunSubNavListBoxItemTooltipTarget />
</FunTooltip>
<FunSubNavListBoxItemButton isSelected={isSelected}>
<span className="FunSubNav__ListBoxItem__ButtonIcon">
{props.children}
</span>
{isSelected && (
<motion.div
className="FunSubNav__ListBoxItem__ButtonIndicator"
layoutId={`FunSubNav__ListBoxItem__ButtonIndicator--${context.id}`}
layoutDependency={context.selected}
transition={FunSubNavListBoxItemTransition}
/>
)}
{!isSelected && isFocusVisible && (
<div className="FunSubNav__ListBoxItem__ButtonIndicator" />
)}
</FunSubNavListBoxItemButton>
</>
);
}}
</ListBoxItem>

View File

@@ -1,19 +1,57 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { type ReactNode } from 'react';
import type { Placement } from 'react-aria';
import { Tooltip } from 'react-aria-components';
import React, { useRef, useState, type ReactNode } from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';
import { useLayoutEffect } from '@react-aria/utils';
import { strictAssert } from '../../../util/assert';
export type FunTooltipProps = Readonly<{
placement?: Placement;
open?: boolean;
onOpenChange?: (open: boolean) => void;
disableHoverableContent?: boolean;
side?: Tooltip.TooltipContentProps['side'];
align?: Tooltip.TooltipContentProps['align'];
collisionBoundarySelector?: string;
collisionPadding?: number;
content: ReactNode;
children: ReactNode;
}>;
export function FunTooltip(props: FunTooltipProps): JSX.Element {
const ref = useRef<HTMLButtonElement>(null);
const [collisionBoundary, setCollisionBoundary] = useState<Element | null>(
null
);
useLayoutEffect(() => {
if (props.collisionBoundarySelector == null) {
return;
}
strictAssert(ref.current, 'missing ref');
const trigger = ref.current;
setCollisionBoundary(trigger.closest(props.collisionBoundarySelector));
}, [props.collisionBoundarySelector]);
return (
<Tooltip className="FunTooltip" placement={props.placement}>
{props.children}
</Tooltip>
<Tooltip.Root
open={props.open}
onOpenChange={props.onOpenChange}
disableHoverableContent={props.disableHoverableContent}
>
<Tooltip.Trigger ref={ref} asChild>
{props.children}
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side={props.side}
align={props.align}
className="FunTooltip"
collisionBoundary={collisionBoundary}
collisionPadding={props.collisionPadding}
>
<span className="FunTooltip__Text">{props.content}</span>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
}

View File

@@ -1,16 +1,16 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
import type { MouseEvent, PointerEvent } from 'react';
import {
Dialog,
DialogTrigger,
Heading,
OverlayArrow,
Popover,
TooltipTrigger,
} from 'react-aria-components';
import type { PressEvent } from 'react-aria';
import { VisuallyHidden } from 'react-aria';
import * as Tooltip from '@radix-ui/react-tooltip';
import type { LocalizerType } from '../../../types/I18N';
import { strictAssert } from '../../../util/assert';
import { missingCaseError } from '../../../util/missingCaseError';
@@ -388,95 +388,99 @@ export function FunPanelEmojis({
</FunPanelFooter>
)}
<FunPanelBody>
<FunScroller
ref={scrollerRef}
sectionGap={EMOJI_GRID_SECTION_GAP}
onScrollSectionChange={handleScrollSectionChange}
>
{layout.sections.length === 0 && (
<FunResults aria-busy={false}>
<FunResultsHeader>
{i18n('icu:FunPanelEmojis__SearchResults__EmptyHeading')}{' '}
<FunStaticEmoji
size={16}
role="presentation"
emoji={emojiVariantConstant('\u{1F641}')}
/>
</FunResultsHeader>
</FunResults>
)}
{layout.sections.length > 0 && (
<FunKeyboard
scrollerRef={scrollerRef}
keyboard={keyboard}
onStateChange={handleKeyboardStateChange}
>
<FunGridContainer
totalSize={layout.totalHeight}
columnCount={EMOJI_GRID_COLUMNS}
cellWidth={EMOJI_GRID_CELL_WIDTH}
cellHeight={EMOJI_GRID_CELL_HEIGHT}
<Tooltip.Provider skipDelayDuration={0}>
<FunScroller
ref={scrollerRef}
sectionGap={EMOJI_GRID_SECTION_GAP}
onScrollSectionChange={handleScrollSectionChange}
>
{layout.sections.length === 0 && (
<FunResults aria-busy={false}>
<FunResultsHeader>
{i18n('icu:FunPanelEmojis__SearchResults__EmptyHeading')}{' '}
<FunStaticEmoji
size={16}
role="presentation"
emoji={emojiVariantConstant('\u{1F641}')}
/>
</FunResultsHeader>
</FunResults>
)}
{layout.sections.length > 0 && (
<FunKeyboard
scrollerRef={scrollerRef}
keyboard={keyboard}
onStateChange={handleKeyboardStateChange}
>
{layout.sections.map(section => {
return (
<FunGridScrollerSection
key={section.key}
id={section.id}
sectionOffset={section.sectionOffset}
sectionSize={section.sectionSize}
>
<FunGridHeader
id={section.header.key}
headerOffset={section.header.headerOffset}
headerSize={section.header.headerSize}
<FunGridContainer
totalSize={layout.totalHeight}
columnCount={EMOJI_GRID_COLUMNS}
cellWidth={EMOJI_GRID_CELL_WIDTH}
cellHeight={EMOJI_GRID_CELL_HEIGHT}
>
{layout.sections.map(section => {
return (
<FunGridScrollerSection
key={section.key}
id={section.id}
sectionOffset={section.sectionOffset}
sectionSize={section.sectionSize}
>
<FunGridHeaderText>
{getTitleForSection(
i18n,
section.id as FunEmojisSection
)}
</FunGridHeaderText>
{section.id ===
EmojiPickerCategory.SmileysAndPeople && (
<SectionSkinToneHeaderPopover
i18n={i18n}
open={skinTonePopoverOpen}
onOpenChange={handleSkinTonePopoverOpenChange}
onSelectSkinTone={fun.onEmojiSkinToneDefaultChange}
/>
)}
</FunGridHeader>
<FunGridRowGroup
aria-labelledby={section.header.key}
colCount={section.colCount}
rowCount={section.rowCount}
rowGroupOffset={section.rowGroup.rowGroupOffset}
rowGroupSize={section.rowGroup.rowGroupSize}
>
{section.rowGroup.rows.map(row => {
return (
<Row
key={row.key}
<FunGridHeader
id={section.header.key}
headerOffset={section.header.headerOffset}
headerSize={section.header.headerSize}
>
<FunGridHeaderText>
{getTitleForSection(
i18n,
section.id as FunEmojisSection
)}
</FunGridHeaderText>
{section.id ===
EmojiPickerCategory.SmileysAndPeople && (
<SectionSkinToneHeaderPopover
i18n={i18n}
rowIndex={row.rowIndex}
cells={row.cells}
focusedCellKey={focusedCellKey}
emojiSkinToneDefault={fun.emojiSkinToneDefault}
onSelectEmoji={handleSelectEmoji}
onEmojiSkinToneDefaultChange={
open={skinTonePopoverOpen}
onOpenChange={handleSkinTonePopoverOpenChange}
onSelectSkinTone={
fun.onEmojiSkinToneDefaultChange
}
/>
);
})}
</FunGridRowGroup>
</FunGridScrollerSection>
);
})}
</FunGridContainer>
</FunKeyboard>
)}
</FunScroller>
)}
</FunGridHeader>
<FunGridRowGroup
aria-labelledby={section.header.key}
colCount={section.colCount}
rowCount={section.rowCount}
rowGroupOffset={section.rowGroup.rowGroupOffset}
rowGroupSize={section.rowGroup.rowGroupSize}
>
{section.rowGroup.rows.map(row => {
return (
<Row
key={row.key}
i18n={i18n}
rowIndex={row.rowIndex}
cells={row.cells}
focusedCellKey={focusedCellKey}
emojiSkinToneDefault={fun.emojiSkinToneDefault}
onSelectEmoji={handleSelectEmoji}
onEmojiSkinToneDefaultChange={
fun.onEmojiSkinToneDefaultChange
}
/>
);
})}
</FunGridRowGroup>
</FunGridScrollerSection>
);
})}
</FunGridContainer>
</FunKeyboard>
)}
</FunScroller>
</Tooltip.Provider>
</FunPanelBody>
</FunPanel>
);
@@ -572,8 +576,8 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element {
return getEmojiVariantByParentKeyAndSkinTone(emojiParent.key, skinTone);
}, [emojiParent, skinTone]);
const handlePress = useCallback(
(event: PressEvent) => {
const handleClick = useCallback(
(event: PointerEvent) => {
if (emojiHasSkinToneVariants && emojiSkinToneDefault == null) {
setPopoverOpen(true);
return;
@@ -585,7 +589,7 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element {
skinTone,
};
const shouldClose =
(event.pointerType === 'keyboard' || event.pointerType === 'virtual') &&
event.nativeEvent.pointerType !== 'mouse' &&
!(event.ctrlKey || event.metaKey);
onSelectEmoji(emojiSelection, shouldClose);
},
@@ -654,12 +658,20 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element {
colIndex={props.colIndex}
rowIndex={props.rowIndex}
>
<TooltipTrigger>
<FunTooltip
side="top"
content={`:${emojiShortNameDisplay}:`}
collisionBoundarySelector=".FunScroller__Viewport"
collisionPadding={6}
// `skipDelayDuration=0` doesn't work with `disableHoverableContent`
// FIX: https://github.com/radix-ui/primitives/pull/3562
// disableHoverableContent
>
<FunItemButton
ref={popoverTriggerRef}
excludeFromTabOrder={!props.isTabbable}
aria-label={emojiName}
onPress={handlePress}
onClick={handleClick}
onLongPress={handleLongPress}
onContextMenu={handleContextMenu}
longPressAccessibilityDescription={i18n(
@@ -668,9 +680,7 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element {
>
<FunStaticEmoji role="presentation" size={32} emoji={emojiVariant} />
</FunItemButton>
<FunTooltip placement="top">{`:${emojiShortNameDisplay}:`}</FunTooltip>
</TooltipTrigger>
d
</FunTooltip>
{emojiHasSkinToneVariants && (
<Popover
data-fun-overlay

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { Range } from '@tanstack/react-virtual';
import { defaultRangeExtractor, useVirtualizer } from '@tanstack/react-virtual';
import type { PointerEvent } from 'react';
import React, {
memo,
useCallback,
@@ -11,7 +12,6 @@ import React, {
useState,
useId,
} from 'react';
import type { PressEvent } from 'react-aria';
import { VisuallyHidden } from 'react-aria';
import { LRUCache } from 'lru-cache';
import { FunItemButton } from '../base/FunItem';
@@ -356,8 +356,8 @@ export function FunPanelGifs({
[]
);
const handlePressGif = useCallback(
(_event: PressEvent, gifSelection: FunGifSelection) => {
const handleClickGif = useCallback(
(_event: PointerEvent, gifSelection: FunGifSelection) => {
onFunSelectGif(gifSelection);
onSelectGif(gifSelection);
setSelectedItemKey(null);
@@ -520,7 +520,7 @@ export function FunPanelGifs({
itemOffset={item.start}
itemLane={item.lane}
isTabbable={isTabbable}
onPressGif={handlePressGif}
onClickGif={handleClickGif}
fetchGif={fetchGif}
/>
);
@@ -542,16 +542,16 @@ const Item = memo(function Item(props: {
itemOffset: number;
itemLane: number;
isTabbable: boolean;
onPressGif: (event: PressEvent, gifSelection: FunGifSelection) => void;
onClickGif: (event: PointerEvent, gifSelection: FunGifSelection) => void;
fetchGif: typeof tenorDownload;
}) {
const { onPressGif, fetchGif } = props;
const { onClickGif, fetchGif } = props;
const handlePress = useCallback(
async (event: PressEvent) => {
onPressGif(event, { gif: props.gif });
const handleClick = useCallback(
async (event: PointerEvent) => {
onClickGif(event, { gif: props.gif });
},
[props.gif, onPressGif]
[props.gif, onClickGif]
);
const descriptionId = `FunGifsPanelItem__GifDescription--${props.gif.id}`;
@@ -606,7 +606,7 @@ const Item = memo(function Item(props: {
>
<FunItemButton
aria-label={props.gif.title}
onPress={handlePress}
onClick={handleClick}
excludeFromTabOrder={!props.isTabbable}
>
{src != null && (

View File

@@ -1,6 +1,6 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { CSSProperties } from 'react';
import type { CSSProperties, PointerEvent } from 'react';
import React, {
memo,
useCallback,
@@ -9,7 +9,6 @@ import React, {
useRef,
useState,
} from 'react';
import type { PressEvent } from 'react-aria';
import type {
StickerPackType,
StickerType,
@@ -344,8 +343,8 @@ export function FunPanelStickers({
return searchInput.length > 0;
}, [searchInput]);
const handlePressSticker = useCallback(
(event: PressEvent, stickerSelection: FunStickerSelection) => {
const handleClickSticker = useCallback(
(event: PointerEvent, stickerSelection: FunStickerSelection) => {
onFunSelectSticker(stickerSelection);
onSelectSticker(stickerSelection);
if (!(event.ctrlKey || event.metaKey)) {
@@ -356,8 +355,8 @@ export function FunPanelStickers({
[onFunSelectSticker, onSelectSticker, onClose]
);
const handlePressTimeSticker = useCallback(
(event: PressEvent, style: FunTimeStickerStyle) => {
const handleClickTimeSticker = useCallback(
(event: PointerEvent, style: FunTimeStickerStyle) => {
onSelectTimeSticker?.(style);
if (!(event.ctrlKey || event.metaKey)) {
onClose();
@@ -492,8 +491,8 @@ export function FunPanelStickers({
cells={row.cells}
stickerLookup={stickerLookup}
focusedCellKey={focusedCellKey}
onPressSticker={handlePressSticker}
onPressTimeSticker={handlePressTimeSticker}
onClickSticker={handleClickSticker}
onClickTimeSticker={handleClickTimeSticker}
/>
);
})}
@@ -515,11 +514,11 @@ const Row = memo(function Row(props: {
stickerLookup: StickerLookup;
cells: ReadonlyArray<CellLayoutNode>;
focusedCellKey: CellKey | null;
onPressSticker: (
event: PressEvent,
onClickSticker: (
event: PointerEvent,
stickerSelection: FunStickerSelection
) => void;
onPressTimeSticker: (event: PressEvent, style: FunTimeStickerStyle) => void;
onClickTimeSticker: (event: PointerEvent, style: FunTimeStickerStyle) => void;
}): JSX.Element {
return (
<FunGridRow rowIndex={props.rowIndex}>
@@ -537,8 +536,8 @@ const Row = memo(function Row(props: {
colIndex={cell.colIndex}
stickerLookup={props.stickerLookup}
isTabbable={isTabbable}
onPressSticker={props.onPressSticker}
onPressTimeSticker={props.onPressTimeSticker}
onClickSticker={props.onClickSticker}
onClickTimeSticker={props.onClickTimeSticker}
/>
);
})}
@@ -553,28 +552,28 @@ const Cell = memo(function Cell(props: {
rowIndex: number;
stickerLookup: StickerLookup;
isTabbable: boolean;
onPressSticker: (
event: PressEvent,
onClickSticker: (
event: PointerEvent,
stickerSelection: FunStickerSelection
) => void;
onPressTimeSticker: (event: PressEvent, style: FunTimeStickerStyle) => void;
onClickTimeSticker: (event: PointerEvent, style: FunTimeStickerStyle) => void;
}): JSX.Element {
const { onPressSticker, onPressTimeSticker } = props;
const { onClickSticker, onClickTimeSticker } = props;
const stickerLookupItem = props.stickerLookup[props.value];
const handlePress = useCallback(
(event: PressEvent) => {
const handleClick = useCallback(
(event: PointerEvent) => {
if (stickerLookupItem.kind === 'sticker') {
onPressSticker(event, {
onClickSticker(event, {
stickerPackId: stickerLookupItem.sticker.packId,
stickerId: stickerLookupItem.sticker.id,
stickerUrl: stickerLookupItem.sticker.url,
});
} else if (stickerLookupItem.kind === 'timeSticker') {
onPressTimeSticker(event, stickerLookupItem.style);
onClickTimeSticker(event, stickerLookupItem.style);
}
},
[stickerLookupItem, onPressSticker, onPressTimeSticker]
[stickerLookupItem, onClickSticker, onClickTimeSticker]
);
return (
@@ -590,7 +589,7 @@ const Cell = memo(function Cell(props: {
? (stickerLookupItem.sticker.emoji ?? '')
: stickerLookupItem.style
}
onPress={handlePress}
onClick={handleClick}
>
{stickerLookupItem.kind === 'sticker' && (
<FunSticker