Skip to content

Commit 1aa3086

Browse files
authored
Fix animation issues (#22)
1 parent 615c099 commit 1aa3086

4 files changed

Lines changed: 119 additions & 107 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@types/node": "^22.19.17",
2020
"@types/react": "^18.0.0",
2121
"@types/react-dom": "^18.0.0",
22+
"motion": "^12.38.0",
2223
"react": "^18.1.0",
2324
"react-dom": "^18.1.0",
2425
"typescript": "^5.9.3"

pnpm-lock.yaml

Lines changed: 63 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/views/DailyHelper/DailyHelper.tsx

Lines changed: 54 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import {
2-
RefObject,
3-
createRef,
4-
useEffect,
5-
useMemo,
6-
useRef,
7-
useState,
8-
} from 'react'
1+
import { useEffect, useMemo, useRef, useState } from 'react'
92

103
import Alert from '@mui/material/Alert'
114
import AlertTitle from '@mui/material/AlertTitle'
@@ -21,9 +14,9 @@ import ListItem from '@mui/material/ListItem'
2114
import ListItemButton from '@mui/material/ListItemButton'
2215
import ListItemText from '@mui/material/ListItemText'
2316
import ListSubheader from '@mui/material/ListSubheader'
17+
import { AnimatePresence, motion } from 'motion/react'
2418
import PullRequest from './PullRequest'
2519
import SettingsIcon from '@mui/icons-material/Settings'
26-
import Slide from '@mui/material/Slide'
2720
import Stack from '@mui/material/Stack'
2821
import Tooltip from '@mui/material/Tooltip'
2922
import Typography from '@mui/material/Typography'
@@ -63,12 +56,6 @@ export default function DailyHelper() {
6356
setIsPullRequestsWithoutLabelsHidden,
6457
] = useState(false)
6558
const [hiddenLabels, setHiddenLabels] = useState(new Set<string>()) // always keep them lowercase
66-
const [pullRequestRefs, setPullRequestRefs] = useState<
67-
RefObject<HTMLElement>[]
68-
>([])
69-
const [isPullRequestInViewport, setIsPullRequestInViewport] = useState<
70-
Map<string, boolean>
71-
>(new Map())
7259
const [activeFilter, setActiveFilter] = useState<
7360
'mustReview' | 'myPrs' | 'myWork' | null
7461
>(null)
@@ -90,8 +77,6 @@ export default function DailyHelper() {
9077
)
9178
const sortDir = sortDirs[sortField]
9279

93-
const main = useRef(null)
94-
9580
const handleInvalidTokenError = () => {
9681
setIsInvalidToken(true)
9782
}
@@ -115,17 +100,10 @@ export default function DailyHelper() {
115100
.then(pullRequests => {
116101
setPullRequests(pullRequests)
117102
setIsLoadingAnimationPlaying(false)
118-
setPullRequestRefs(
119-
Array(pullRequests.length)
120-
.fill(null)
121-
.map((_, index) => pullRequestRefs[index] || createRef()),
122-
)
123103
})
124104
setShouldLoad(false)
125105
}
126-
127-
isMandatoryDataPresent && setPullRequestRefs(pullRequestRefs)
128-
}, [shouldLoad, teamName, pullRequestRefs, isInvalidToken, orgName])
106+
}, [shouldLoad, teamName, isInvalidToken, orgName])
129107

130108
useEffect(() => {
131109
if (!settingsHandler.loadGithubToken()) return
@@ -140,18 +118,6 @@ export default function DailyHelper() {
140118
})
141119
}, [shouldLoad])
142120

143-
const setPullRequestInViewport = (
144-
pullRequestId: string,
145-
isVisible: boolean,
146-
) => {
147-
if (isVisible) {
148-
isPullRequestInViewport.set(pullRequestId, true)
149-
} else {
150-
isPullRequestInViewport.delete(pullRequestId)
151-
}
152-
setIsPullRequestInViewport(isPullRequestInViewport)
153-
}
154-
155121
const reviewRequiredFilteredIds = useMemo(() => {
156122
if (!isReviewFilterActive || !viewerLogin) return null
157123
const filtered = applyReviewRequiredFilter(
@@ -177,27 +143,6 @@ export default function DailyHelper() {
177143
pr.requestedReviewers.some(u => u.login === viewerLogin) ||
178144
pr.reviews.some(r => r.reviewer.login === viewerLogin))
179145

180-
const visibleCount = useMemo(
181-
() =>
182-
pullRequests.filter(
183-
pr =>
184-
getVisibility(pr.labels) &&
185-
(!reviewRequiredFilteredIds ||
186-
reviewRequiredFilteredIds.has(pr.id)) &&
187-
(!isMyPrsFilterActive || pr.author.login === viewerLogin) &&
188-
(!isMyWorkFilterActive || isMyWork(pr)),
189-
).length,
190-
[
191-
pullRequests,
192-
reviewRequiredFilteredIds,
193-
isMyPrsFilterActive,
194-
isMyWorkFilterActive,
195-
viewerLogin,
196-
hiddenLabels,
197-
isPullRequestsWithoutLabelsHidden,
198-
],
199-
)
200-
201146
const sortedPullRequests = useMemo(() => {
202147
const filters = settingsHandler.loadFilters()
203148
const getStateRank = (pr: (typeof pullRequests)[number]): number => {
@@ -231,6 +176,30 @@ export default function DailyHelper() {
231176
})
232177
}, [pullRequests, sortField, sortDir])
233178

179+
const visiblePullRequests = useMemo(
180+
() =>
181+
isLoadingAnimationPlaying
182+
? sortedPullRequests
183+
: sortedPullRequests.filter(
184+
pr =>
185+
getVisibility(pr.labels) &&
186+
(!reviewRequiredFilteredIds ||
187+
reviewRequiredFilteredIds.has(pr.id)) &&
188+
(!isMyPrsFilterActive || pr.author.login === viewerLogin) &&
189+
(!isMyWorkFilterActive || isMyWork(pr)),
190+
),
191+
[
192+
sortedPullRequests,
193+
isLoadingAnimationPlaying,
194+
reviewRequiredFilteredIds,
195+
isMyPrsFilterActive,
196+
isMyWorkFilterActive,
197+
viewerLogin,
198+
hiddenLabels,
199+
isPullRequestsWithoutLabelsHidden,
200+
],
201+
)
202+
234203
const allLabels = new Map<string, LabelWithCount>()
235204
const pullRequestsWithLabels = pullRequests.filter(pr => pr.labels.length)
236205
const pullRequestsWithoutLabelsCount =
@@ -258,9 +227,7 @@ export default function DailyHelper() {
258227
setTeamName(newTeamName)
259228
setLoadingProgress(0)
260229
isValidToken && setIsLoadingAnimationPlaying(true)
261-
setPullRequests(generateDummyPullRequests(isPullRequestInViewport.size))
262-
setPullRequestRefs([])
263-
setIsPullRequestInViewport(new Map())
230+
setPullRequests(generateDummyPullRequests(pullRequests.length))
264231
setShouldLoad(true)
265232
}
266233

@@ -308,9 +275,10 @@ export default function DailyHelper() {
308275
height: 'calc(100vh - 64px)',
309276
mt: '64px',
310277
overflowY: 'auto',
278+
overflowX: 'hidden',
311279
}}
312280
>
313-
<Box ref={main} sx={{ mx: 'auto', px: 1 }} maxWidth={1150}>
281+
<Box sx={{ mx: 'auto', px: 1 }} maxWidth={1150}>
314282
{isInvalidToken && (
315283
<Alert severity="error">
316284
<AlertTitle>Authorization error</AlertTitle>
@@ -383,9 +351,9 @@ export default function DailyHelper() {
383351
lineHeight={1}
384352
sx={{ fontVariantNumeric: 'tabular-nums' }}
385353
>
386-
{visibleCount === pullRequests.length
354+
{visiblePullRequests.length === pullRequests.length
387355
? pullRequests.length
388-
: `${visibleCount} / ${pullRequests.length}`}
356+
: `${visiblePullRequests.length} / ${pullRequests.length}`}
389357
</Typography>
390358
<Typography
391359
variant="caption"
@@ -409,38 +377,32 @@ export default function DailyHelper() {
409377
/>
410378
</Stack>
411379
</Stack>
412-
<Stack spacing={0.5}>
413-
{sortedPullRequests.map((pr, index) => (
414-
<Slide
415-
key={pr.id}
416-
direction="up"
417-
in={
418-
isLoadingAnimationPlaying ||
419-
(getVisibility(pr.labels) &&
420-
(!reviewRequiredFilteredIds ||
421-
reviewRequiredFilteredIds.has(pr.id)) &&
422-
(!isMyPrsFilterActive ||
423-
pr.author.login === viewerLogin) &&
424-
(!isMyWorkFilterActive || isMyWork(pr)))
425-
}
426-
timeout={400}
427-
mountOnEnter
428-
unmountOnExit
429-
appear={false}
430-
container={main.current}
431-
enter={isPullRequestInViewport.has(pr.id)}
432-
exit={false} // disable exit transitions as they lag when multiple are played at once
433-
>
434-
<Box ref={pullRequestRefs[index]}>
380+
<Stack spacing={0.5} useFlexGap>
381+
<AnimatePresence mode="popLayout">
382+
{visiblePullRequests.map((pr, index) => (
383+
<motion.div
384+
key={pr.id}
385+
initial={
386+
isLoadingAnimationPlaying ? false : { opacity: 0 }
387+
}
388+
animate={{ opacity: 1 }}
389+
exit={{
390+
opacity: 0,
391+
x: '100%',
392+
transition: { duration: 0.5, ease: 'easeIn' },
393+
}}
394+
transition={{
395+
duration: 0.2,
396+
delay: Math.min(index * 0.02, 0.1),
397+
}}
398+
>
435399
<PullRequest
436-
customRef={pullRequestRefs[index]}
437-
setIsInViewport={setPullRequestInViewport}
438400
isLoading={isLoadingAnimationPlaying}
439401
{...pr}
440402
/>
441-
</Box>
442-
</Slide>
443-
))}
403+
</motion.div>
404+
))}
405+
</AnimatePresence>
444406
</Stack>
445407
</>
446408
)}

src/views/DailyHelper/PullRequest.tsx

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RefObject, useEffect, useState } from 'react'
1+
import { useEffect, useState } from 'react'
22
import { useTheme } from '@mui/material/styles'
33

44
import Card from '@mui/material/Card'
@@ -19,17 +19,11 @@ import Stack from '@mui/material/Stack'
1919
import Typography from '@mui/material/Typography'
2020
import { dataFetcher } from '../../helpers/dataFetcher'
2121
import { settingsHandler } from '../../helpers/settingsHandler'
22-
import useOnScreen from '../../helpers/useOnScreen'
23-
2422
type PullRequestProps = {
25-
customRef: RefObject<HTMLElement>
26-
setIsInViewport: (id: string, visible: boolean) => void
2723
isLoading: boolean
2824
} & PullRequest
2925

3026
export default function PullRequest({
31-
customRef,
32-
setIsInViewport,
3327
isLoading,
3428
id,
3529
title,
@@ -59,11 +53,6 @@ export default function PullRequest({
5953
const [isLastCommitChecksLoading, setIsLastCommitChecksLoading] =
6054
useState(false)
6155

62-
const isInViewport = useOnScreen(customRef)
63-
useEffect(() => {
64-
setIsInViewport(id, isInViewport)
65-
}, [isInViewport, id, setIsInViewport])
66-
6756
const handleCommitChecksReload = async () => {
6857
setIsLastCommitChecksLoading(true)
6958
setLastCommitChecks(
@@ -206,7 +195,6 @@ export default function PullRequest({
206195
isLoading={isLoading}
207196
author={author}
208197
createdAt={createdAt}
209-
isDraft={isDraft}
210198
reviews={reviews}
211199
requestedReviewers={requestedReviewers}
212200
contributors={contributors}

0 commit comments

Comments
 (0)