Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
7b909fb7
Unverified
Commit
7b909fb7
authored
Jan 04, 2025
by
Chris Curry
Committed by
GitHub
Jan 04, 2025
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: scroll to top (#4244)
parent
d81174ad
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
93 additions
and
3 deletions
+93
-3
PagedMemoList.tsx
web/src/components/PagedMemoList/PagedMemoList.tsx
+29
-3
ScrollToTop.tsx
web/src/components/ScrollToTop.tsx
+64
-0
No files found.
web/src/components/PagedMemoList/PagedMemoList.tsx
View file @
7b909fb7
import
{
Button
}
from
"@usememos/mui"
;
import
{
ArrowDownIcon
,
LoaderIcon
}
from
"lucide-react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useRef
,
useState
,
useMemo
}
from
"react"
;
import
{
useLocation
}
from
"react-router-dom"
;
import
PullToRefresh
from
"react-simple-pull-to-refresh"
;
import
ScrollToTop
from
"@/components/ScrollToTop"
;
import
{
DEFAULT_LIST_MEMOS_PAGE_SIZE
}
from
"@/helpers/consts"
;
import
useResponsiveWidth
from
"@/hooks/useResponsiveWidth"
;
import
{
Routes
}
from
"@/router"
;
import
{
useMemoList
,
useMemoStore
}
from
"@/store/v1"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
...
...
@@ -26,11 +29,33 @@ const PagedMemoList = (props: Props) => {
const
{
md
}
=
useResponsiveWidth
();
const
memoStore
=
useMemoStore
();
const
memoList
=
useMemoList
();
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
[
containerRightOffset
,
setContainerRightOffset
]
=
useState
(
0
);
const
[
state
,
setState
]
=
useState
<
State
>
({
isRequesting
:
true
,
// Initial request
nextPageToken
:
""
,
});
const
sortedMemoList
=
props
.
listSort
?
props
.
listSort
(
memoList
.
value
)
:
memoList
.
value
;
const
location
=
useLocation
();
const
shouldShowScrollToTop
=
useMemo
(
()
=>
[
Routes
.
ROOT
,
Routes
.
EXPLORE
,
Routes
.
ARCHIVED
].
includes
(
location
.
pathname
as
Routes
)
||
location
.
pathname
.
startsWith
(
"/u/"
),
[
location
.
pathname
],
);
useEffect
(()
=>
{
const
updateOffset
=
()
=>
{
if
(
containerRef
.
current
)
{
const
rect
=
containerRef
.
current
.
getBoundingClientRect
();
const
offset
=
window
.
innerWidth
-
rect
.
right
;
setContainerRightOffset
(
offset
);
}
};
updateOffset
();
window
.
addEventListener
(
"resize"
,
updateOffset
);
return
()
=>
window
.
removeEventListener
(
"resize"
,
updateOffset
);
},
[]);
const
fetchMoreMemos
=
async
(
nextPageToken
:
string
)
=>
{
setState
((
state
)
=>
({
...
state
,
isRequesting
:
true
}));
...
...
@@ -56,7 +81,7 @@ const PagedMemoList = (props: Props) => {
},
[
props
.
filter
,
props
.
pageSize
]);
const
children
=
(
<>
<
div
ref=
{
containerRef
}
className=
"flex flex-col justify-start items-start w-full max-w-full"
>
{
sortedMemoList
.
map
((
memo
)
=>
props
.
renderer
(
memo
))
}
{
state
.
isRequesting
&&
(
<
div
className=
"w-full flex flex-row justify-center items-center my-4"
>
...
...
@@ -77,7 +102,8 @@ const PagedMemoList = (props: Props) => {
<
p
className=
"mt-2 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
)
}
</>
<
ScrollToTop
enabled=
{
shouldShowScrollToTop
}
className=
"fixed bottom-6"
style=
{
{
right
:
`calc(1rem + ${containerRightOffset}px)`
}
}
/>
</
div
>
);
// In case of md screen, we don't need pull to refresh.
...
...
web/src/components/ScrollToTop.tsx
0 → 100644
View file @
7b909fb7
import
clsx
from
"clsx"
;
import
{
ArrowUpIcon
}
from
"lucide-react"
;
import
{
useEffect
,
useState
}
from
"react"
;
interface
ScrollToTopProps
{
className
?:
string
;
style
?:
React
.
CSSProperties
;
enabled
?:
boolean
;
}
const
ScrollToTop
=
({
className
,
style
,
enabled
=
true
}:
ScrollToTopProps
)
=>
{
const
[
isVisible
,
setIsVisible
]
=
useState
(
false
);
const
[
shouldRender
,
setShouldRender
]
=
useState
(
false
);
useEffect
(()
=>
{
const
handleScroll
=
()
=>
{
const
shouldBeVisible
=
window
.
scrollY
>
400
;
if
(
shouldBeVisible
!==
isVisible
)
{
if
(
shouldBeVisible
)
{
setShouldRender
(
true
);
setTimeout
(()
=>
setIsVisible
(
true
),
50
);
}
else
{
setIsVisible
(
false
);
setTimeout
(()
=>
setShouldRender
(
false
),
200
);
}
}
};
if
(
enabled
)
{
window
.
addEventListener
(
"scroll"
,
handleScroll
);
return
()
=>
window
.
removeEventListener
(
"scroll"
,
handleScroll
);
}
},
[
enabled
,
isVisible
]);
const
scrollToTop
=
()
=>
{
window
.
scrollTo
({
top
:
0
,
behavior
:
"smooth"
,
});
};
if
(
!
enabled
||
!
shouldRender
)
{
return
null
;
}
return
(
<
button
onClick=
{
scrollToTop
}
className=
{
clsx
(
"p-3 bg-primary dark:bg-primary-dark hover:bg-primary-darker dark:hover:bg-primary-darker rounded-full shadow-lg"
,
"transition-all duration-200"
,
"opacity-0 scale-95"
,
isVisible
&&
"opacity-100 scale-100"
,
className
,
)
}
style=
{
style
}
aria
-
label=
"Scroll to top"
>
<
ArrowUpIcon
className=
"w-5 h-5 text-white"
/>
</
button
>
);
};
export
default
ScrollToTop
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment