From b6cfe0933d3f8fe6bc0c2635a963d4e350c06ce0 Mon Sep 17 00:00:00 2001
From: Josh Perez <60019601+josh-signal@users.noreply.github.com>
Date: Wed, 29 Sep 2021 16:59:37 -0400
Subject: [PATCH] Adds open/close animations to dialogs and modals
---
ACKNOWLEDGMENTS.md | 49 ++++
package.json | 4 +-
ts/components/App.tsx | 10 +
ts/components/ConfirmationDialog.tsx | 97 ++++---
ts/components/ForwardMessageModal.tsx | 356 ++++++++++++++------------
ts/components/Modal.tsx | 67 ++++-
ts/hooks/useAnimated.tsx | 37 +++
ts/hooks/useReducedMotion.ts | 34 +++
ts/util/lint/exceptions.json | 147 +++++++++++
yarn.lock | 58 ++++-
10 files changed, 635 insertions(+), 224 deletions(-)
create mode 100644 ts/hooks/useAnimated.tsx
create mode 100644 ts/hooks/useReducedMotion.ts
diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md
index 272d5988e..ed5dc9a54 100644
--- a/ACKNOWLEDGMENTS.md
+++ b/ACKNOWLEDGMENTS.md
@@ -9,6 +9,30 @@ Signal Desktop makes use of the following open source projects.
License: MIT
+## @react-spring/web
+
+ MIT License
+
+ Copyright (c) 2018-present Paul Henschel, react-spring, all contributors
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
## @sindresorhus/is
MIT License
@@ -471,6 +495,31 @@ Signal Desktop makes use of the following open source projects.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+## bezier-easing
+
+ Copyright (c) 2014 Gaƫtan Renaudeau
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
## blob-util
Apache License
diff --git a/package.json b/package.json
index f07e283fa..01707280d 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
},
"dependencies": {
"@popperjs/core": "2.9.2",
+ "@react-spring/web": "9.2.4",
"@signalapp/signal-client": "0.9.5",
"@sindresorhus/is": "0.8.0",
"abort-controller": "3.0.0",
@@ -80,6 +81,7 @@
"axe-core": "4.1.4",
"backbone": "1.4.0",
"better-sqlite3": "https://github.com/signalapp/better-sqlite3#2fa02d2484e9f9a10df5ac7ea4617fb2dff30006",
+ "bezier-easing": "2.1.0",
"blob-util": "2.0.2",
"blueimp-load-image": "5.14.0",
"blurhash": "1.1.3",
@@ -212,7 +214,7 @@
"@types/pino": "6.3.6",
"@types/pino-multi-stream": "5.1.0",
"@types/quill": "1.3.10",
- "@types/react": "16.8.5",
+ "@types/react": "16.8.6",
"@types/react-dom": "16.8.2",
"@types/react-measure": "2.0.5",
"@types/react-redux": "7.1.2",
diff --git a/ts/components/App.tsx b/ts/components/App.tsx
index 65e022afc..25eef2eda 100644
--- a/ts/components/App.tsx
+++ b/ts/components/App.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { ComponentProps, useEffect } from 'react';
+import { Globals } from '@react-spring/web';
import classNames from 'classnames';
import { AppViewType } from '../state/ducks/app';
@@ -10,6 +11,7 @@ import { Install } from './Install';
import { StandaloneRegistration } from './StandaloneRegistration';
import { ThemeType } from '../types/Util';
import { usePageVisibility } from '../hooks/usePageVisibility';
+import { useReducedMotion } from '../hooks/useReducedMotion';
type PropsType = {
appView: AppViewType;
@@ -84,6 +86,14 @@ export const App = ({
document.body.classList.toggle('page-is-visible', isPageVisible);
}, [isPageVisible]);
+ // A11y settings for react-spring
+ const prefersReducedMotion = useReducedMotion();
+ useEffect(() => {
+ Globals.assign({
+ skipAnimation: prefersReducedMotion,
+ });
+ }, [prefersReducedMotion]);
+
return (
{
- const cancelAndClose = React.useCallback(() => {
+ const { close, renderAnimation } = useAnimated(
+ {
+ from: { opacity: 0, transform: 'scale(0.25)' },
+ enter: { opacity: 1, transform: 'scale(1)' },
+ leave: { opacity: 0, onRest: () => onClose() },
+ config: {
+ duration: 150,
+ },
+ },
+ onClose
+ );
+
+ const cancelAndClose = useCallback(() => {
if (onCancel) {
onCancel();
}
- onClose();
- }, [onCancel, onClose]);
+ close();
+ }, [close, onCancel]);
- const handleCancel = React.useCallback(
- (e: React.MouseEvent) => {
+ const handleCancel = useCallback(
+ (e: MouseEvent) => {
if (e.target === e.currentTarget) {
cancelAndClose();
}
@@ -80,40 +94,43 @@ export const ConfirmationDialog = React.memo(
const hasActions = Boolean(actions.length);
return (
-
- {children}
-
-
+ {renderAnimation(
+
- {cancelText || i18n('confirmation-dialog--Cancel')}
-
- {actions.map((action, i) => (
- {
- action.action();
- onClose();
- }}
- data-action={i}
- variant={getButtonVariant(action.style)}
- >
- {action.text}
-
- ))}
-
-
+ {children}
+
+
+ {cancelText || i18n('confirmation-dialog--Cancel')}
+
+ {actions.map((action, i) => (
+ {
+ action.action();
+ close();
+ }}
+ data-action={i}
+ variant={getButtonVariant(action.style)}
+ >
+ {action.text}
+
+ ))}
+
+
+ )}
+
);
}
);
diff --git a/ts/components/ForwardMessageModal.tsx b/ts/components/ForwardMessageModal.tsx
index 5ca7f7ec2..0de436740 100644
--- a/ts/components/ForwardMessageModal.tsx
+++ b/ts/components/ForwardMessageModal.tsx
@@ -30,6 +30,7 @@ import { SearchInput } from './SearchInput';
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
import { assert } from '../util/assert';
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
+import { useAnimated } from '../hooks/useAnimated';
export type DataPropsType = {
attachments?: Array
;
@@ -198,13 +199,28 @@ export const ForwardMessageModal: FunctionComponent = ({
[contactLookup, selectedContacts, setSelectedContacts]
);
+ const { close, renderAnimation } = useAnimated(
+ {
+ from: { opacity: 0, transform: 'translateY(48px)' },
+ enter: { opacity: 1, transform: 'translateY(0px)' },
+ leave: {
+ opacity: 0,
+ transform: 'translateY(48px)',
+ },
+ config: {
+ duration: 200,
+ },
+ },
+ onClose
+ );
+
const handleBackOrClose = useCallback(() => {
if (isEditingMessage) {
setIsEditingMessage(false);
} else {
- onClose();
+ close();
}
- }, [isEditingMessage, onClose, setIsEditingMessage]);
+ }, [isEditingMessage, close, setIsEditingMessage]);
const rowCount = filteredConversations.length;
const getRow = (index: number): undefined | Row => {
@@ -249,182 +265,188 @@ export const ForwardMessageModal: FunctionComponent = ({
{i18n('GroupV2--cannot-send')}
)}
-
-
-
+
+ {renderAnimation(
+
+
+ {isEditingMessage ? (
+ setIsEditingMessage(false)}
+ type="button"
+ >
+
+
+ ) : (
+
+ )}
+
{i18n('forwardMessage')}
+
{isEditingMessage ? (
-
setIsEditingMessage(false)}
- type="button"
- >
-
-
- ) : (
-
- )}
-
{i18n('forwardMessage')}
-
- {isEditingMessage ? (
-
- {linkPreview ? (
-
-
+ {linkPreview ? (
+
+ removeLinkPreview()}
+ title={linkPreview.title}
+ />
+
+ ) : null}
+ {attachmentsToForward && attachmentsToForward.length ? (
+ removeLinkPreview()}
- title={linkPreview.title}
+ onCloseAttachment={(attachment: AttachmentType) => {
+ const newAttachments = attachmentsToForward.filter(
+ currentAttachment => currentAttachment !== attachment
+ );
+ setAttachmentsToForward(newAttachments);
+ }}
/>
-
- ) : null}
- {attachmentsToForward && attachmentsToForward.length ? (
-
{
- const newAttachments = attachmentsToForward.filter(
- currentAttachment => currentAttachment !== attachment
- );
- setAttachmentsToForward(newAttachments);
- }}
- />
- ) : null}
-
-
{
- setMessageBodyText(messageText);
- onEditorStateChange(messageText, bodyRanges, caretLocation);
- }}
- onPickEmoji={onPickEmoji}
- onSubmit={forwardMessage}
- onTextTooLong={onTextTooLong}
- />
-
-
+ {
+ setMessageBodyText(messageText);
+ onEditorStateChange(
+ messageText,
+ bodyRanges,
+ caretLocation
+ );
+ }}
+ onPickEmoji={onPickEmoji}
+ onSubmit={forwardMessage}
+ onTextTooLong={onTextTooLong}
/>
+
+
+
-
- ) : (
-
-
{
- setSearchTerm(event.target.value);
- }}
- ref={inputRef}
- value={searchTerm}
- />
- {candidateConversations.length ? (
-
- {({ contentRect, measureRef }: MeasuredComponentProps) => {
- // We disable this ESLint rule because we're capturing a bubbled
- // keydown event. See [this note in the jsx-a11y docs][0].
- //
- // [0]: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/c275964f52c35775208bd00cb612c6f82e42e34f/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events
- /* eslint-disable jsx-a11y/no-static-element-interactions */
- return (
-
-
{
- if (
- disabledReason !==
- ContactCheckboxDisabledReason.MaximumContactsSelected
- ) {
- toggleSelectedConversation(conversationId);
- }
- }}
- onSelectConversation={shouldNeverBeCalled}
- renderMessageSearchResult={() => {
- shouldNeverBeCalled();
- return
;
- }}
- rowCount={rowCount}
- shouldRecomputeRowHeights={false}
- showChooseGroupMembers={shouldNeverBeCalled}
- startNewConversationFromPhoneNumber={
- shouldNeverBeCalled
- }
- />
-
- );
- /* eslint-enable jsx-a11y/no-static-element-interactions */
+ ) : (
+
+
{
+ setSearchTerm(event.target.value);
}}
-
- ) : (
-
- {i18n('noContactsFound')}
-
- )}
-
- )}
-
-
- {Boolean(selectedContacts.length) &&
- selectedContacts.map(contact => contact.title).join(', ')}
-
-
- {isEditingMessage || !isMessageEditable ? (
-
- ) : (
-
setIsEditingMessage(true)}
- />
- )}
+ {candidateConversations.length ? (
+
+ {({ contentRect, measureRef }: MeasuredComponentProps) => {
+ // We disable this ESLint rule because we're capturing a bubbled
+ // keydown event. See [this note in the jsx-a11y docs][0].
+ //
+ // [0]: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/c275964f52c35775208bd00cb612c6f82e42e34f/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
+ return (
+
+
{
+ if (
+ disabledReason !==
+ ContactCheckboxDisabledReason.MaximumContactsSelected
+ ) {
+ toggleSelectedConversation(conversationId);
+ }
+ }}
+ onSelectConversation={shouldNeverBeCalled}
+ renderMessageSearchResult={() => {
+ shouldNeverBeCalled();
+ return
;
+ }}
+ rowCount={rowCount}
+ shouldRecomputeRowHeights={false}
+ showChooseGroupMembers={shouldNeverBeCalled}
+ startNewConversationFromPhoneNumber={
+ shouldNeverBeCalled
+ }
+ />
+
+ );
+ /* eslint-enable jsx-a11y/no-static-element-interactions */
+ }}
+
+ ) : (
+
+ {i18n('noContactsFound')}
+
+ )}
+
+ )}
+
+
+ {Boolean(selectedContacts.length) &&
+ selectedContacts.map(contact => contact.title).join(', ')}
+
+
+ {isEditingMessage || !isMessageEditable ? (
+
+ ) : (
+ setIsEditingMessage(true)}
+ />
+ )}
+
-
+ )}
>
);
diff --git a/ts/components/Modal.tsx b/ts/components/Modal.tsx
index ae28069fe..2261f5f54 100644
--- a/ts/components/Modal.tsx
+++ b/ts/components/Modal.tsx
@@ -1,7 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
-import React, { useRef, useState, ReactElement, ReactNode } from 'react';
+import React, { ReactElement, ReactNode, useRef, useState } from 'react';
import Measure, { ContentRect, MeasuredComponentProps } from 'react-measure';
import classNames from 'classnames';
import { noop } from 'lodash';
@@ -10,6 +10,7 @@ import { LocalizerType } from '../types/Util';
import { ModalHost } from './ModalHost';
import { Theme } from '../util/theme';
import { getClassNamesFor } from '../util/getClassNamesFor';
+import { useAnimated } from '../hooks/useAnimated';
import { useHasWrapped } from '../hooks/useHasWrapped';
type PropsType = {
@@ -18,9 +19,12 @@ type PropsType = {
hasXButton?: boolean;
i18n: LocalizerType;
moduleClassName?: string;
- noMouseClose?: boolean;
onClose?: () => void;
title?: ReactNode;
+};
+
+type ModalPropsType = PropsType & {
+ noMouseClose?: boolean;
theme?: Theme;
};
@@ -36,8 +40,51 @@ export function Modal({
onClose = noop,
title,
theme,
-}: Readonly
): ReactElement {
+}: Readonly): ReactElement {
+ const { close, renderAnimation } = useAnimated(
+ {
+ from: { opacity: 0, transform: 'translateY(48px)' },
+ enter: { opacity: 1, transform: 'translateY(0px)' },
+ leave: {
+ opacity: 0,
+ transform: 'translateY(48px)',
+ },
+ config: {
+ duration: 200,
+ },
+ },
+ onClose
+ );
+
+ return (
+
+ {renderAnimation(
+
+ {children}
+
+ )}
+
+ );
+}
+
+export function ModalWindow({
+ children,
+ hasStickyButtons,
+ hasXButton,
+ i18n,
+ moduleClassName,
+ onClose = noop,
+ title,
+}: Readonly): JSX.Element {
const modalRef = useRef(null);
+
const bodyRef = useRef(null);
const [scrolled, setScrolled] = useState(false);
const [hasOverflow, setHasOverflow] = useState(false);
@@ -56,10 +103,10 @@ export function Modal({
}
return (
-
+ <>
{/* We don't want the click event to propagate to its container node. */}
- {/* eslint-disable jsx-a11y/no-static-element-interactions */}
- {/* eslint-disable jsx-a11y/click-events-have-key-events */}
+ {/* eslint-disable-next-line max-len */}
+ {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */}
- {/* eslint-enable jsx-a11y/no-static-element-interactions */}
- {/* eslint-enable jsx-a11y/click-events-have-key-events */}
{hasHeader && (
{hasXButton && (
@@ -81,9 +126,7 @@ export function Modal({
type="button"
className={getClassName('__close-button')}
tabIndex={0}
- onClick={() => {
- onClose();
- }}
+ onClick={onClose}
/>
)}
{title && (
@@ -122,7 +165,7 @@ export function Modal({
)}
-
+ >
);
}
diff --git a/ts/hooks/useAnimated.tsx b/ts/hooks/useAnimated.tsx
new file mode 100644
index 000000000..48f484d20
--- /dev/null
+++ b/ts/hooks/useAnimated.tsx
@@ -0,0 +1,37 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React, { useState, ReactElement } from 'react';
+import { animated, useTransition, UseTransitionProps } from '@react-spring/web';
+import cubicBezier from 'bezier-easing';
+
+export function useAnimated
>(
+ props: UseTransitionProps,
+ onClose: () => unknown
+): {
+ close: () => unknown;
+ renderAnimation: (children: ReactElement) => JSX.Element;
+} {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const transitions = useTransition(isOpen, {
+ ...props,
+ leave: {
+ ...props.leave,
+ onRest: () => onClose(),
+ },
+ config: {
+ duration: 200,
+ easing: cubicBezier(0.17, 0.17, 0, 1),
+ ...props.config,
+ },
+ });
+
+ return {
+ close: () => setIsOpen(false),
+ renderAnimation: children =>
+ transitions((style, item) =>
+ item ? {children} : null
+ ),
+ };
+}
diff --git a/ts/hooks/useReducedMotion.ts b/ts/hooks/useReducedMotion.ts
new file mode 100644
index 000000000..b1fdb3da9
--- /dev/null
+++ b/ts/hooks/useReducedMotion.ts
@@ -0,0 +1,34 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { useEffect, useState } from 'react';
+
+function getReducedMotionQuery(): MediaQueryList {
+ return window.matchMedia('(prefers-reduced-motion: reduce)');
+}
+
+// Inspired by .
+export function useReducedMotion(): boolean {
+ const initialQuery = getReducedMotionQuery();
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(
+ initialQuery.matches
+ );
+
+ useEffect(() => {
+ const query = getReducedMotionQuery();
+
+ function changePreference() {
+ setPrefersReducedMotion(query.matches);
+ }
+
+ changePreference();
+
+ query.addEventListener('change', changePreference);
+
+ return () => {
+ query.removeEventListener('change', changePreference);
+ };
+ });
+
+ return prefersReducedMotion;
+}
diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json
index eab9f9155..20e3d6071 100644
--- a/ts/util/lint/exceptions.json
+++ b/ts/util/lint/exceptions.json
@@ -240,6 +240,153 @@
"updated": "2018-09-18T19:19:27.699Z",
"reasonDetail": "What's being eval'd is a static string"
},
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/animated/dist/react-spring-animated.cjs.dev.js",
+ "line": " const instanceRef = React.useRef(null);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/animated/dist/react-spring-animated.cjs.dev.js",
+ "line": " const observerRef = React.useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/animated/dist/react-spring-animated.cjs.prod.js",
+ "line": " const instanceRef = React.useRef(null);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/animated/dist/react-spring-animated.cjs.prod.js",
+ "line": " const observerRef = React.useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/animated/dist/react-spring-animated.esm.js",
+ "line": " const instanceRef = useRef(null);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/animated/dist/react-spring-animated.esm.js",
+ "line": " const observerRef = useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.cjs.dev.js",
+ "line": " const layoutId = React.useRef(0);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.cjs.dev.js",
+ "line": " const ctrls = React.useRef([...state.ctrls]);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.cjs.dev.js",
+ "line": " const usedTransitions = React.useRef(null);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.cjs.prod.js",
+ "line": " const layoutId = React.useRef(0);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.cjs.prod.js",
+ "line": " const ctrls = React.useRef([...state.ctrls]);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.cjs.prod.js",
+ "line": " const usedTransitions = React.useRef(null);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.esm.js",
+ "line": " const layoutId = useRef(0);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.esm.js",
+ "line": " const ctrls = useRef([...state.ctrls]);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/core/dist/react-spring-core.esm.js",
+ "line": " const usedTransitions = useRef(null);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/shared/dist/react-spring-shared.cjs.dev.js",
+ "line": " const committed = React.useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/shared/dist/react-spring-shared.cjs.dev.js",
+ "line": " const prevRef = React.useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/shared/dist/react-spring-shared.cjs.prod.js",
+ "line": " const committed = React.useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/shared/dist/react-spring-shared.cjs.prod.js",
+ "line": " const prevRef = React.useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/shared/dist/react-spring-shared.esm.js",
+ "line": " const committed = useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
+ {
+ "rule": "React-useRef",
+ "path": "node_modules/@react-spring/shared/dist/react-spring-shared.esm.js",
+ "line": " const prevRef = useRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-09-27T21:37:06.339Z"
+ },
{
"rule": "DOM-innerHTML",
"path": "node_modules/@sindresorhus/is/dist/index.js",
diff --git a/yarn.lock b/yarn.lock
index 3845a8a69..308dd489d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1671,6 +1671,51 @@
react-lifecycles-compat "^3.0.4"
warning "^3.0.0"
+"@react-spring/animated@~9.2.0":
+ version "9.2.4"
+ resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.2.4.tgz#062ecc0fdfef89f2541a42d8500428b70035f879"
+ integrity sha512-AfV6ZM8pCCAT29GY5C8/1bOPjZrv/7kD0vedjiE/tEYvNDwg9GlscrvsTViWR2XykJoYrDfdkYArrldWpsCJ5g==
+ dependencies:
+ "@react-spring/shared" "~9.2.0"
+ "@react-spring/types" "~9.2.0"
+
+"@react-spring/core@~9.2.0":
+ version "9.2.4"
+ resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.2.4.tgz#275a4a065e3a315a4f5fb28c9a6f62ce718c25d6"
+ integrity sha512-R+PwyfsjiuYCWqaTTfCpYpRmsP0h87RNm7uxC1Uxy7QAHUfHEm2sAHn+AdHPwq/MbVwDssVT8C5yf2WGcqiXGg==
+ dependencies:
+ "@react-spring/animated" "~9.2.0"
+ "@react-spring/shared" "~9.2.0"
+ "@react-spring/types" "~9.2.0"
+
+"@react-spring/rafz@~9.2.0":
+ version "9.2.4"
+ resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.2.4.tgz#44793e9adc14dd0dcd1573d094368af11a89d73a"
+ integrity sha512-SOKf9eue+vAX+DGo7kWYNl9i9J3gPUlQjifIcV9Bzw9h3i30wPOOP0TjS7iMG/kLp2cdHQYDNFte6nt23VAZkQ==
+
+"@react-spring/shared@~9.2.0":
+ version "9.2.4"
+ resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.2.4.tgz#f9cc66ac5308a77293330a18518e34121f4008c1"
+ integrity sha512-ZEr4l2BxmyFRUvRA2VCkPfCJii4E7cGkwbjmTBx1EmcGrOnde/V2eF5dxqCTY3k35QuCegkrWe0coRJVkh8q2Q==
+ dependencies:
+ "@react-spring/rafz" "~9.2.0"
+ "@react-spring/types" "~9.2.0"
+
+"@react-spring/types@~9.2.0":
+ version "9.2.4"
+ resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.2.4.tgz#2365ce9d761f548a9adcb2cd68714bf26765a5de"
+ integrity sha512-zHUXrWO8nweUN/ISjrjqU7GgXXvoEbFca1CgiE0TY0H/dqJb3l+Rhx8ecPVNYimzFg3ZZ1/T0egpLop8SOv4aA==
+
+"@react-spring/web@9.2.4":
+ version "9.2.4"
+ resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.2.4.tgz#c6d5464a954bfd0d7bc90117050f796a95ebfa08"
+ integrity sha512-vtPvOalLFvuju/MDBtoSnCyt0xXSL6Amyv82fljOuWPl1yGd4M1WteijnYL9Zlriljl0a3oXcPunAVYTD9dbDQ==
+ dependencies:
+ "@react-spring/animated" "~9.2.0"
+ "@react-spring/core" "~9.2.0"
+ "@react-spring/shared" "~9.2.0"
+ "@react-spring/types" "~9.2.0"
+
"@signalapp/signal-client@0.9.5":
version "0.9.5"
resolved "https://registry.yarnpkg.com/@signalapp/signal-client/-/signal-client-0.9.5.tgz#b133305aa39c00ceae8743316efd0cc0bfe02e2d"
@@ -2844,10 +2889,10 @@
"@types/prop-types" "*"
"@types/react" "*"
-"@types/react@*", "@types/react@16.8.5":
- version "16.8.5"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.5.tgz#03b9a6597bc20f6eaaed43f377a160f7e41c2b90"
- integrity sha512-8LRySaaSJVLNZb2dbOGvGmzn88cbAfrgDpuWy+6lLgQ0OJFgHHvyuaCX4/7ikqJlpmCPf4uazJAZcfTQRdJqdQ==
+"@types/react@*", "@types/react@16.8.6":
+ version "16.8.6"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.6.tgz#fa1de3fe56cc9b6afeddc73d093d7f30fd5e31cc"
+ integrity sha512-bN9qDjEMltmHrl0PZRI4IF2AbB7V5UlRfG+OOduckVnRQ4VzXVSzy/1eLAh778IEqhTnW0mmgL9yShfinNverA==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
@@ -4732,6 +4777,11 @@ bcrypt-pbkdf@^1.0.0:
bindings "^1.5.0"
tar "^6.1.0"
+bezier-easing@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
+ integrity sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY=
+
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"