Only animate typing when viewing conversation

This commit is contained in:
ayumi-signal 2023-10-02 16:18:28 -04:00 committed by GitHub
parent c9af8d3ce2
commit 69c0cad14c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 26 deletions

View file

@ -1384,6 +1384,11 @@ $message-padding-horizontal: 12px;
&--with-reactions {
padding-bottom: 15px;
}
&--typing {
flex-direction: row-reverse;
overflow-y: clip;
}
}
.module-message__container-outer--typing-bubble {
@ -1391,16 +1396,8 @@ $message-padding-horizontal: 12px;
}
.module-message__typing-avatar-container {
align-items: center;
display: flex;
flex-direction: row-reverse;
justify-content: center;
margin-inline-end: 8px;
overflow-y: clip;
&--with-reactions {
padding-bottom: 15px;
}
}
.module-message__typing-avatar {
@ -2848,6 +2845,10 @@ button.module-image__border-overlay:focus {
padding-inline: 1px;
}
.module-message__typing-animation-container .module-typing-animation {
width: 30px;
}
.module-typing-animation__dot {
border-radius: 50%;

View file

@ -44,8 +44,8 @@ export type TypingBubblePropsType = {
const SPRING_CONFIG = {
mass: 1,
tension: 986,
friction: 64,
tension: 439,
friction: 42,
precision: 0,
velocity: 0,
};
@ -53,17 +53,15 @@ const SPRING_CONFIG = {
const AVATAR_ANIMATION_PROPS: Record<'visible' | 'hidden', object> = {
visible: {
opacity: 1,
scale: 1,
width: '28px',
x: '0px',
top: '0px',
},
hidden: {
opacity: 0.5,
scale: 0.5,
width: '4px', // Match value of module-message__typing-avatar margin-inline-start
x: '14px',
top: '30px',
top: '34px',
},
};
@ -71,6 +69,7 @@ function TypingBubbleAvatar({
conversationId,
contact,
visible,
shouldAnimate,
getPreferredBadge,
onContactExit,
showContactModal,
@ -80,6 +79,7 @@ function TypingBubbleAvatar({
conversationId: string;
contact: TypingContactType | undefined;
visible: boolean;
shouldAnimate: boolean;
getPreferredBadge: PreferredBadgeSelectorType;
onContactExit: (id: string | undefined) => void;
showContactModal: (contactId: string, conversationId?: string) => void;
@ -89,7 +89,9 @@ function TypingBubbleAvatar({
const [springProps, springApi] = useSpring(
{
config: SPRING_CONFIG,
from: AVATAR_ANIMATION_PROPS[visible ? 'hidden' : 'visible'],
from: shouldAnimate
? AVATAR_ANIMATION_PROPS[visible ? 'hidden' : 'visible']
: {},
to: AVATAR_ANIMATION_PROPS[visible ? 'visible' : 'hidden'],
onRest: () => {
if (!visible) {
@ -138,6 +140,7 @@ function TypingBubbleAvatar({
function TypingBubbleGroupAvatars({
conversationId,
typingContactIds,
shouldAnimate,
getConversation,
getPreferredBadge,
showContactModal,
@ -153,6 +156,7 @@ function TypingBubbleGroupAvatars({
| 'theme'
> & {
typingContactIds: ReadonlyArray<string>;
shouldAnimate: boolean;
}): ReactElement {
const [allContactsById, setAllContactsById] = useState<
Map<string, TypingContactType>
@ -195,7 +199,7 @@ function TypingBubbleGroupAvatars({
// Avatars are rendered Right-to-Left so the leftmost avatars can render on top.
return (
<div className="module-message__typing-avatar-container">
<div className="module-message__author-avatar-container module-message__author-avatar-container--typing">
{typingContactsOverflowCount > 0 && (
<div
className="module-message__typing-avatar module-message__typing-avatar--overflow-count
@ -228,6 +232,7 @@ function TypingBubbleGroupAvatars({
i18n={i18n}
theme={theme}
visible={visibleContactIds.has(contactId)}
shouldAnimate={shouldAnimate}
/>
))}
</div>
@ -241,12 +246,10 @@ const OUTER_DIV_ANIMATION_PROPS: Record<'visible' | 'hidden', object> = {
const BUBBLE_ANIMATION_PROPS: Record<'visible' | 'hidden', object> = {
visible: {
opacity: 1,
scale: 1,
top: '0px',
},
hidden: {
opacity: 0.5,
scale: 0.5,
top: '30px',
},
};
@ -269,13 +272,17 @@ export function TypingBubble({
() => Object.keys(typingContactIdTimestamps),
[typingContactIdTimestamps]
);
const [shouldAnimate, setShouldAnimate] = useState(false);
const prevTypingContactIds = React.useRef<
ReadonlyArray<string> | undefined
>();
const isSomeoneTyping = useMemo(
() => typingContactIds.length > 0,
[typingContactIds]
);
const [outerDivStyle, outerDivSpringApi] = useSpring(
{
from: OUTER_DIV_ANIMATION_PROPS[isSomeoneTyping ? 'hidden' : 'visible'],
to: OUTER_DIV_ANIMATION_PROPS[isSomeoneTyping ? 'visible' : 'hidden'],
config: SPRING_CONFIG,
},
@ -283,7 +290,6 @@ export function TypingBubble({
);
const [typingAnimationStyle, typingAnimationSpringApi] = useSpring(
{
from: BUBBLE_ANIMATION_PROPS[isSomeoneTyping ? 'hidden' : 'visible'],
to: BUBBLE_ANIMATION_PROPS[isSomeoneTyping ? 'visible' : 'hidden'],
config: SPRING_CONFIG,
onRest: () => {
@ -336,6 +342,23 @@ export function TypingBubble({
typingContactIdTimestamps,
]);
// Only animate when the user observes a change in typing contacts, not when first
// switching to a conversation.
useEffect(() => {
if (shouldAnimate) {
return;
}
if (!prevTypingContactIds.current) {
prevTypingContactIds.current = typingContactIds;
return;
}
if (prevTypingContactIds.current !== typingContactIds) {
setShouldAnimate(true);
}
}, [shouldAnimate, typingContactIds]);
if (!isVisible) {
return null;
}
@ -360,6 +383,7 @@ export function TypingBubble({
<TypingBubbleGroupAvatars
conversationId={conversationId}
typingContactIds={typingContactIds}
shouldAnimate={shouldAnimate}
getConversation={getConversation}
getPreferredBadge={getPreferredBadge}
showContactModal={showContactModal}

View file

@ -2688,6 +2688,14 @@
"reasonCategory": "usageTrusted",
"updated": "2022-11-03T14:21:47.456Z"
},
{
"rule": "React-useRef",
"path": "ts/components/conversation/TypingBubble.tsx",
"line": " const prevTypingContactIds = React.useRef<",
"reasonCategory": "usageTrusted",
"updated": "2023-09-28T21:48:57.488Z",
"reasonDetail": "Used to track change of typing contacts while a conversation is actively viewed."
},
{
"rule": "React-useRef",
"path": "ts/components/conversation/WaveformScrubber.tsx",
@ -2804,6 +2812,13 @@
"reasonCategory": "usageTrusted",
"updated": "2021-10-22T00:52:39.251Z"
},
{
"rule": "React-useRef",
"path": "ts/hooks/useScrollLock.tsx",
"line": " const onUserInterruptRef = useRef(onUserInterrupt);",
"reasonCategory": "usageTrusted",
"updated": "2023-09-19T17:05:51.321Z"
},
{
"rule": "React-useRef",
"path": "ts/hooks/useSizeObserver.tsx",
@ -2853,13 +2868,6 @@
"reasonCategory": "usageTrusted",
"updated": "2023-07-25T21:55:26.191Z"
},
{
"rule": "React-useRef",
"path": "ts/hooks/useScrollLock.tsx",
"line": " const onUserInterruptRef = useRef(onUserInterrupt);",
"reasonCategory": "usageTrusted",
"updated": "2023-09-19T17:05:51.321Z"
},
{
"rule": "React-useRef",
"path": "ts/quill/formatting/menu.tsx",