From 9bfaa6dc2a89f4211456f5d7657d007b5a7afa8e Mon Sep 17 00:00:00 2001 From: Tomasz Palys Date: Thu, 22 Jun 2023 17:38:41 +0200 Subject: [PATCH] [web] Update button label after successful copy Summary: Change button label after successful action. Depends on D8246 Test Plan: Click copy button and check if the label changes. Click it repeatedly and check if the label changes back only after 2s. from the last click. Click it and close modal - nothing should break. Reviewers: bartek, kamil, inka Reviewed By: kamil Subscribers: ashoat Differential Revision: https://phab.comm.dev/D8294 --- lib/hooks/useResettingState.js | 26 +++++++++++++++++++ .../view-invite-link-modal.react.js | 17 +++++++++--- web/utils/tooltip-action-utils.js | 19 +++++--------- 3 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 lib/hooks/useResettingState.js diff --git a/lib/hooks/useResettingState.js b/lib/hooks/useResettingState.js new file mode 100644 index 0000000000..5f1021eeb0 --- /dev/null +++ b/lib/hooks/useResettingState.js @@ -0,0 +1,26 @@ +// @flow + +import _debounce from 'lodash/debounce.js'; +import * as React from 'react'; + +import type { SetState } from '../types/hook-types.js'; + +function useResettingState( + initialState: (() => T) | T, + duration: number, +): [T, SetState] { + const [value, setValue] = React.useState(initialState); + const resetStatusAfterTimeout = React.useRef( + _debounce(() => setValue(initialState), duration), + ); + React.useEffect(() => resetStatusAfterTimeout.current.cancel, []); + + const setNewValue = React.useCallback((newValue: (T => T) | T) => { + setValue(newValue); + resetStatusAfterTimeout.current(); + }, []); + + return React.useMemo(() => [value, setNewValue], [setNewValue, value]); +} + +export { useResettingState }; diff --git a/web/invite-links/view-invite-link-modal.react.js b/web/invite-links/view-invite-link-modal.react.js index c5c1e9477e..5fa2b19093 100644 --- a/web/invite-links/view-invite-link-modal.react.js +++ b/web/invite-links/view-invite-link-modal.react.js @@ -5,6 +5,7 @@ import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; import { inviteLinkUrl } from 'lib/facts/links.js'; +import { useResettingState } from 'lib/hooks/useResettingState.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import type { InviteLink } from 'lib/types/link-types.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; @@ -18,6 +19,7 @@ type Props = { +inviteLink: InviteLink, }; +const copiedMessageDurationMs = 2000; function ViewInviteLinkModal(props: Props): React.Node { const { inviteLink } = props; const threadInfo = useSelector( @@ -27,9 +29,16 @@ function ViewInviteLinkModal(props: Props): React.Node { const { popModal } = useModalContext(); const url = inviteLinkUrl(inviteLink.name); - const copyLink = React.useCallback(() => { - navigator.clipboard.writeText(url); - }, [url]); + const [copied, setCopied] = useResettingState(false, copiedMessageDurationMs); + const copyLink = React.useCallback(async () => { + try { + await navigator.clipboard.writeText(url); + setCopied(true); + } catch (e) { + setCopied(false); + } + }, [setCopied, url]); + const buttonText = copied ? 'Copied!' : 'Copy'; return ( {url} diff --git a/web/utils/tooltip-action-utils.js b/web/utils/tooltip-action-utils.js index 27c98c4c67..9096590ff4 100644 --- a/web/utils/tooltip-action-utils.js +++ b/web/utils/tooltip-action-utils.js @@ -1,10 +1,10 @@ // @flow import invariant from 'invariant'; -import _debounce from 'lodash/debounce.js'; import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; +import { useResettingState } from 'lib/hooks/useResettingState.js'; import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js'; import { useCanEditMessage } from 'lib/shared/edit-messages-utils.js'; import { createMessageReply } from 'lib/shared/message-utils.js'; @@ -120,18 +120,11 @@ function useMessageCopyAction( ): ?MessageTooltipAction { const { messageInfo } = item; - const [successful, setSuccessful] = React.useState(false); - const resetStatusAfterTimeout = React.useRef( - _debounce(() => setSuccessful(false), copiedMessageDurationMs), + const [successful, setSuccessful] = useResettingState( + false, + copiedMessageDurationMs, ); - const onSuccess = React.useCallback(() => { - setSuccessful(true); - resetStatusAfterTimeout.current(); - }, []); - - React.useEffect(() => resetStatusAfterTimeout.current.cancel, []); - return React.useMemo(() => { if (messageInfo.type !== messageTypes.TEXT) { return null; @@ -140,7 +133,7 @@ function useMessageCopyAction( const onClick = async () => { try { await navigator.clipboard.writeText(messageInfo.text); - onSuccess(); + setSuccessful(true); } catch (e) { setSuccessful(false); } @@ -150,7 +143,7 @@ function useMessageCopyAction( onClick, label: successful ? 'Copied!' : 'Copy', }; - }, [messageInfo.text, messageInfo.type, onSuccess, successful]); + }, [messageInfo.text, messageInfo.type, setSuccessful, successful]); } function useMessageReactAction(