2025-02-19 17:31:33 -08:00
|
|
|
// Copyright 2025 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
2025-04-24 15:17:35 -07:00
|
|
|
import type { ForwardedRef, ReactNode } from 'react';
|
|
|
|
import React, { forwardRef, useEffect, useRef } from 'react';
|
|
|
|
import { type PressEvent, useLongPress } from 'react-aria';
|
2025-04-10 12:32:36 -07:00
|
|
|
import type { LongPressEvent } from '@react-types/shared';
|
2025-04-24 15:17:35 -07:00
|
|
|
import { Button } from 'react-aria-components';
|
|
|
|
import { mergeRefs } from '@react-aria/utils';
|
|
|
|
import { PressResponder } from '@react-aria/interactions';
|
|
|
|
import { strictAssert } from '../../../util/assert';
|
2025-02-19 17:31:33 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Button
|
|
|
|
*/
|
|
|
|
|
2025-04-10 12:32:36 -07:00
|
|
|
export type FunItemButtonLongPressProps = Readonly<
|
|
|
|
| {
|
|
|
|
longPressAccessibilityDescription?: never;
|
|
|
|
onLongPress?: never;
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
longPressAccessibilityDescription: string;
|
|
|
|
onLongPress: (event: LongPressEvent) => void;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
|
|
|
export type FunItemButtonProps = Readonly<
|
|
|
|
{
|
|
|
|
'aria-label': string;
|
2025-04-24 15:17:35 -07:00
|
|
|
excludeFromTabOrder: boolean;
|
2025-04-10 12:32:36 -07:00
|
|
|
onPress: (event: PressEvent) => void;
|
|
|
|
onContextMenu?: (event: MouseEvent) => void;
|
|
|
|
children: ReactNode;
|
|
|
|
} & FunItemButtonLongPressProps
|
|
|
|
>;
|
|
|
|
|
|
|
|
export const FunItemButton = forwardRef(function FunItemButton(
|
|
|
|
props: FunItemButtonProps,
|
2025-04-24 15:17:35 -07:00
|
|
|
outerRef: ForwardedRef<HTMLButtonElement>
|
2025-04-10 12:32:36 -07:00
|
|
|
): JSX.Element {
|
2025-04-24 15:17:35 -07:00
|
|
|
const { onContextMenu } = props;
|
|
|
|
const innerRef = useRef<HTMLButtonElement>(null);
|
2025-04-10 12:32:36 -07:00
|
|
|
|
|
|
|
const { longPressProps } = useLongPress({
|
|
|
|
isDisabled: props.onLongPress == null,
|
|
|
|
accessibilityDescription: props.longPressAccessibilityDescription,
|
|
|
|
onLongPress: props.onLongPress,
|
|
|
|
});
|
2025-02-19 17:31:33 -08:00
|
|
|
|
2025-04-24 15:17:35 -07:00
|
|
|
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]);
|
|
|
|
|
2025-02-19 17:31:33 -08:00
|
|
|
return (
|
2025-04-24 15:17:35 -07:00
|
|
|
<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>
|
2025-02-19 17:31:33 -08:00
|
|
|
);
|
2025-04-10 12:32:36 -07:00
|
|
|
});
|