Skip to content

Commit

Permalink
feat(client): show tooltip when mouse enter in desktop.
Browse files Browse the repository at this point in the history
  • Loading branch information
meowtec committed Apr 26, 2023
1 parent 2c11ad3 commit 5824d67
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 10 deletions.
63 changes: 63 additions & 0 deletions packages/web/src/utils/use-bool-delay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useMemo, useRef, useState } from 'react';

/**
* A powerful hook to handle boolean state with delay.
* @param value the initial value
* @param options
* @returns
*/
export function useBoolDelay(value: boolean, options: {
/**
* The delay to change the state to true. in milliseconds.
*/
toTrueDelay: number;
/**
* The delay to change the state to false. in milliseconds.
*/
toFalseDelay: number;
}) {
const [state, setState] = useState(value);
const toTrueTimeoutRef = useRef<number | null>(null);
const toFalseTimeoutRef = useRef<number | null>(null);

const fns = useMemo(() => {
const clearTimeoutRef = (ref: React.MutableRefObject<number | null>) => {
if (ref.current != null) {
window.clearTimeout(ref.current);
ref.current = null;
}
};

const createDelayTo = (target: boolean) => () => {
const selfTimeoutRef = target ? toTrueTimeoutRef : toFalseTimeoutRef;
const oppositeTimeoutRef = target ? toFalseTimeoutRef : toTrueTimeoutRef;

if (selfTimeoutRef.current) return;

clearTimeoutRef(oppositeTimeoutRef);

selfTimeoutRef.current = window.setTimeout(() => {
setState(target);
selfTimeoutRef.current = null;
}, target ? options.toTrueDelay : options.toFalseDelay);
};

const createImmediateTo = (target: boolean) => () => {
clearTimeoutRef(toTrueTimeoutRef);
clearTimeoutRef(toFalseTimeoutRef);

setState(target);
};

const delayToTrue = createDelayTo(true);
const delayToFalse = createDelayTo(false);
const setTrue = createImmediateTo(true);
const setFalse = createImmediateTo(false);

return {
delayToTrue, delayToFalse, setTrue, setFalse,
};
}, [options.toFalseDelay, options.toTrueDelay]);

return [state, fns] as const;
}
36 changes: 26 additions & 10 deletions packages/web/src/views/chat/message/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import clsx from 'clsx';
import { useRef, useState } from 'react';
import { useClickAway, useLongPress } from 'react-use';
import { useRef } from 'react';
import {
useClickAway, useLongPress,
} from 'react-use';
import useMergedRef from '@react-hook/merged-ref';
import copy from 'copy-to-clipboard';
import { MailReceive, MailSendDetailed, MailType } from '#/types';
import Tooltip, { useTooltip } from '#/components/tooltip';
import { useLongText } from '#/services/file';
import { download } from '#/utils/download';
import { useBoolDelay } from '#/utils/use-bool-delay';
import { showToast } from '#/components/toast/command';
import FileMessage from './file-message';
import TextMessage from './text-message';
Expand All @@ -23,7 +26,10 @@ const longPressOptions = {

export default function Message({ message }: MessageProps) {
const ref = useRef<HTMLDivElement | null>(null);
const [showDialog, setShowDialog] = useState(false);
const [tooltipVisible, tooltipVisibleMutates] = useBoolDelay(false, {
toTrueDelay: 0,
toFalseDelay: 500,
});
const isReceive = 'sender' in message;
const { data } = message;

Expand All @@ -46,20 +52,26 @@ export default function Message({ message }: MessageProps) {
});

const onLongPress = () => {
setShowDialog(true);
tooltipVisibleMutates.setTrue();
};

const handleMouseEnter = () => {
tooltipVisibleMutates.setTrue();
};

const handleMouseLeave = () => {
tooltipVisibleMutates.delayToFalse();
};

const longPressEvent = useLongPress(onLongPress, longPressOptions);

useClickAway(ref, () => {
setTimeout(() => {
setShowDialog(false);
}, 500);
tooltipVisibleMutates.delayToFalse();
});

const handleCopy = () => {
copy(text);
setShowDialog(false);
tooltipVisibleMutates.setFalse();
showToast({
content: 'Copied',
});
Expand All @@ -68,7 +80,7 @@ export default function Message({ message }: MessageProps) {
const handleSave = () => {
if (data.type !== MailType.file) return;
download(`/api/file/${data.content.id}`, data.content.name);
setShowDialog(false);
tooltipVisibleMutates.setFalse();
};

const mergedRef = useMergedRef(ref, tooltipProps.floating.reference);
Expand All @@ -78,6 +90,8 @@ export default function Message({ message }: MessageProps) {
<div
{...longPressEvent}
ref={mergedRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={clsx('message', isReceive ? 'is-receive' : 'is-send')}
>
{
Expand All @@ -91,9 +105,11 @@ export default function Message({ message }: MessageProps) {
)
}
</div>
{showDialog && (
{tooltipVisible && (
<Tooltip
{...tooltipProps}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
visible
>
{
Expand Down

0 comments on commit 5824d67

Please sign in to comment.