Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/comment animation #4976

Merged
merged 8 commits into from
Dec 16, 2024
3 changes: 3 additions & 0 deletions src/common/enums/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ export const REFERRAL_QUERY_REFERRAL_KEY = 'referral'
export const REFERRAL_STORAGE_REFERRAL_CODE = '__REFERRAL_CODE'

export const HAS_SHOW_MOMENT_BANNER = '__HAS_SHOW_MOMENT_BANNER'

export const NEW_POST_COMMENT_MUTATION_RESULT =
'__NEW_POST_COMMENT_MUTATION_RESULT'
44 changes: 36 additions & 8 deletions src/components/Comment/DropdownActions/DeleteComment/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import gql from 'graphql-tag'
import { FormattedMessage } from 'react-intl'

import { TEST_ID } from '~/common/enums'
import { COMMENT_FEED_ID_PREFIX, TEST_ID } from '~/common/enums'
import {
Dialog,
toast,
Expand All @@ -15,6 +15,8 @@ import {
DeleteCommentMutation,
} from '~/gql/graphql'

import styles from './styles.module.css'

const DELETE_COMMENT = gql`
mutation DeleteComment($id: ID!) {
deleteComment(input: { id: $id }) {
Expand Down Expand Up @@ -42,20 +44,23 @@ const DeleteCommentDialog = ({
}: DeleteCommentDialogProps) => {
const { show, openDialog, closeDialog } = useDialogSwitch(true)
const { routerLang } = useRoute()
const commentId = comment.id
const { id, parentComment } = comment
const node =
comment.node.__typename === 'Article' ||
comment.node.__typename === 'Moment'
? comment.node
: undefined
const isArticle = comment.node.__typename === 'Article'
const isMoment = comment.node.__typename === 'Moment'
const isDescendantComment = parentComment !== null

const nodeId = parentComment ? `${parentComment.id}-${id}` : id

const [deleteComment] = useMutation<DeleteCommentMutation>(DELETE_COMMENT, {
variables: { id: commentId },
variables: { id: id },
optimisticResponse: {
deleteComment: {
id: commentId,
id: id,
state: 'archived' as any,
node:
isMoment && node?.__typename === 'Moment'
Expand Down Expand Up @@ -103,8 +108,31 @@ const DeleteCommentDialog = ({
},
})

const onDelete = async () => {
await deleteComment()
const playAnimationAndDelete = async () => {
// play animation
const commentElements = document.querySelectorAll(
`#${COMMENT_FEED_ID_PREFIX}${nodeId}`
)

if (commentElements.length > 0) {
commentElements.forEach((commentElement) => {
commentElement.parentElement?.addEventListener('animationend', () => {
commentElement.parentElement?.classList.add(styles.hideComment)
deleteComment()
})
})
}
if (commentElements.length > 0 && !isDescendantComment) {
commentElements.forEach((commentElement) => {
commentElement.parentElement?.classList.add(styles.deletedComment)
})
} else if (commentElements.length > 0 && isDescendantComment) {
commentElements.forEach((commentElement) => {
commentElement.parentElement?.classList.add(
styles.deletedDescendantComment
)
})
}

toast.success({
message: isMoment ? (
Expand Down Expand Up @@ -171,7 +199,7 @@ const DeleteCommentDialog = ({
text={<FormattedMessage defaultMessage="Delete" id="K3r6DQ" />}
color="red"
onClick={() => {
onDelete()
playAnimationAndDelete()
closeDialog()
}}
/>
Expand All @@ -181,7 +209,7 @@ const DeleteCommentDialog = ({
text={<FormattedMessage defaultMessage="Delete" id="K3r6DQ" />}
color="red"
onClick={() => {
onDelete()
playAnimationAndDelete()
closeDialog()
}}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.hideComment {
display: none;
}

.deletedComment {
overflow: hidden;
animation:
slide-up-fade 1.6s,
maintain-padding 1.6s;
}

.deletedDescendantComment {
overflow: hidden;
animation: slide-up-fade 1.6s;
}

@keyframes slide-up-fade {
0% {
max-height: 500px;
background: var(--color-insufficient-red);
}

/* 1s later */
62.5% {
background: var(--color-insufficient-red);
}

100% {
display: none;
max-height: 0;
background: var(--color-white);
}
}

@keyframes maintain-padding {
0% {
padding-right: var(--sp16);
padding-left: var(--sp16);
margin-right: calc(-1 * var(--sp16));
margin-left: calc(-1 * var(--sp16));
}

99% {
padding-right: var(--sp16);
padding-left: var(--sp16);
margin-right: calc(-1 * var(--sp16));
margin-left: calc(-1 * var(--sp16));
}

100% {
padding-right: 0;
padding-left: 0;
margin-right: 0;
margin-left: 0;
}
}
27 changes: 24 additions & 3 deletions src/components/Comment/Feed/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import classNames from 'classnames'
import React from 'react'

import { COMMENT_FEED_ID_PREFIX, TEST_ID } from '~/common/enums'
import { toPath } from '~/common/utils'
import {
COMMENT_FEED_ID_PREFIX,
NEW_POST_COMMENT_MUTATION_RESULT,
TEST_ID,
} from '~/common/enums'
import { sessionStorage, toPath } from '~/common/utils'
import {
Avatar,
AvatarSize,
Expand Down Expand Up @@ -80,11 +84,28 @@ const BaseCommentFeed = ({
moment: node,
})

const newPostCommentMutationResult = sessionStorage.get(
NEW_POST_COMMENT_MUTATION_RESULT
)

const isNewPostComment = newPostCommentMutationResult === id

const commentClasses = classNames({
[styles.comment]: true,
[styles.playSlideDownFade]: isNewPostComment,
[styles.momentComment]: isMoment,
})

return (
<article
className={styles.comment}
className={commentClasses}
id={`${COMMENT_FEED_ID_PREFIX}${nodeId}`}
data-test-id={TEST_ID.ARTICLE_COMMENT_FEED}
onAnimationEnd={() => {
if (isNewPostComment) {
sessionStorage.remove(NEW_POST_COMMENT_MUTATION_RESULT)
}
}}
>
<header className={styles.header}>
<section className={styles.left}>
Expand Down
58 changes: 58 additions & 0 deletions src/components/Comment/Feed/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,61 @@
.momentComment {
padding: var(--sp8) 0;

@media (--sm-up) {
padding: var(--sp12) 0;
}
}

.playSlideDownFade {
overflow: hidden;
animation:
slide-down-fade 1.6s,
maintain-padding 1.6s;
}

@keyframes slide-down-fade {
0% {
max-height: 0;
background: var(--color-green-lighter);
opacity: 0;
}

/* 1s later */
62.5% {
background: var(--color-green-lighter);
}

100% {
max-height: 500px;
background: var(--color-white);
opacity: 1;
}
}

@keyframes maintain-padding {
0% {
padding-right: var(--sp16);
padding-left: var(--sp16);
margin-right: calc(-1 * var(--sp16));
margin-left: calc(-1 * var(--sp16));
}

/* keep the padding and margin not change */
99% {
padding-right: var(--sp16);
padding-left: var(--sp16);
margin-right: calc(-1 * var(--sp16));
margin-left: calc(-1 * var(--sp16));
}

100% {
padding-right: 0;
padding-left: 0;
margin-right: 0;
margin-left: 0;
}
}

.header {
@mixin flex-start-space-between;

Expand Down
42 changes: 30 additions & 12 deletions src/components/Comment/FooterActions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,31 @@ const BaseFooterActions = ({
)} `
: ''

const [isHiding, setIsHiding] = useState(false)

const handleHideForm = () => {
// Start hide animation
setIsHiding(true)
}

const handleHideComplete = () => {
// End hide animation
setIsHiding(false)

if (editor === activeEditor) {
setActiveEditor(null)
}
setShowForm(false)
}

const handleReplyButtonClick = () => {
if (showForm) {
handleHideForm()
} else {
toggleShowForm()
}
}

return (
<>
<footer
Expand Down Expand Up @@ -232,12 +257,7 @@ const BaseFooterActions = ({
{...buttonProps}
{...replyButtonProps}
{...replyCustomButtonProps}
onClick={() => {
if (editor === activeEditor) {
setActiveEditor(null)
}
toggleShowForm()
}}
onClick={handleReplyButtonClick}
/>
</Media>
</>
Expand Down Expand Up @@ -275,14 +295,12 @@ const BaseFooterActions = ({
replyToId={comment.id}
parentId={comment.parentComment?.id || comment.id}
submitCallback={submitCallback}
closeCallback={() => {
if (editor === activeEditor) {
setActiveEditor(null)
}
setShowForm(false)
}}
closeCallback={handleHideForm}
isInCommentDetail={isInCommentDetail}
defaultContent={defaultContent}
playAnimation
isHiding={isHiding}
onHideComplete={handleHideComplete}
/>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@ import dynamic from 'next/dynamic'
import { useContext, useEffect, useRef, useState } from 'react'
import { FormattedMessage } from 'react-intl'

import { MAX_ARTICLE_COMMENT_LENGTH } from '~/common/enums'
import { dom, formStorage, sanitizeContent, stripHtml } from '~/common/utils'
import {
MAX_ARTICLE_COMMENT_LENGTH,
NEW_POST_COMMENT_MUTATION_RESULT,
} from '~/common/enums'
import {
dom,
formStorage,
sanitizeContent,
sessionStorage,
stripHtml,
} from '~/common/utils'
import {
Dialog,
SpinnerBlock,
Expand Down Expand Up @@ -93,27 +102,35 @@ const CommentForm: React.FC<CommentFormProps> = ({
await putComment({
variables: { input },
update: (cache, mutationResult) => {
const newComment = mutationResult.data?.putComment

if (!newComment) {
return
}

sessionStorage.set(NEW_POST_COMMENT_MUTATION_RESULT, newComment.id)

if (!!parentId && !isInCommentDetail) {
updateArticleComments({
cache,
articleId,
commentId: parentId,
type: 'addSecondaryComment',
comment: mutationResult.data?.putComment,
comment: newComment,
})
} else if (!!parentId && isInCommentDetail) {
updateCommentDetail({
cache,
commentId: parentId || '',
type: 'add',
comment: mutationResult.data?.putComment,
comment: newComment,
})
} else {
updateArticleComments({
cache,
articleId,
type: 'add',
comment: mutationResult.data?.putComment,
comment: newComment,
})
}
},
Expand Down
Loading
Loading