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
840b16f0
Commit
840b16f0
authored
Jan 08, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: tweak back to top button
parent
012ca1d7
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
68 additions
and
101 deletions
+68
-101
PagedMemoList.tsx
web/src/components/PagedMemoList/PagedMemoList.tsx
+68
-37
ScrollToTop.tsx
web/src/components/ScrollToTop.tsx
+0
-64
No files found.
web/src/components/PagedMemoList/PagedMemoList.tsx
View file @
840b16f0
import
{
Button
}
from
"@usememos/mui"
;
import
{
ArrowDownIcon
,
LoaderIcon
}
from
"lucide-react"
;
import
{
useEffect
,
useRef
,
useState
,
useMemo
}
from
"react"
;
import
{
useLocation
}
from
"react-router-dom"
;
import
{
ArrowDownIcon
,
ArrowUpIcon
,
LoaderIcon
,
SlashIcon
}
from
"lucide-react"
;
import
{
useEffect
,
useMemo
,
useRef
,
useState
}
from
"react"
;
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"
;
...
...
@@ -30,32 +28,15 @@ const PagedMemoList = (props: Props) => {
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
(
const
shouldShowBackToTop
=
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
sortedMemoList
=
props
.
listSort
?
props
.
listSort
(
memoList
.
value
)
:
memoList
.
value
;
const
fetchMoreMemos
=
async
(
nextPageToken
:
string
)
=>
{
setState
((
state
)
=>
({
...
state
,
isRequesting
:
true
}));
...
...
@@ -88,21 +69,29 @@ const PagedMemoList = (props: Props) => {
<
LoaderIcon
className=
"animate-spin text-zinc-500"
/>
</
div
>
)
}
{
!
state
.
isRequesting
&&
state
.
nextPageToken
&&
(
{
!
state
.
isRequesting
&&
(
<>
{
!
state
.
nextPageToken
&&
sortedMemoList
.
length
===
0
?
(
<
div
className=
"w-full mt-12 mb-8 flex flex-col justify-center items-center italic"
>
<
Empty
/>
<
p
className=
"mt-2 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
)
:
(
<
div
className=
"w-full flex flex-row justify-center items-center my-4"
>
{
state
.
nextPageToken
&&
(
<>
<
Button
variant=
"plain"
onClick=
{
()
=>
fetchMoreMemos
(
state
.
nextPageToken
)
}
>
{
t
(
"memo.load-more"
)
}
<
ArrowDownIcon
className=
"ml-2
w-4 h-auto"
/>
<
ArrowDownIcon
className=
"ml-1
w-4 h-auto"
/>
</
Button
>
</
div
>
{
shouldShowBackToTop
&&
<
SlashIcon
className=
"mx-1 w-4 h-auto opacity-40"
/>
}
</>
)
}
{
!
state
.
isRequesting
&&
!
state
.
nextPageToken
&&
sortedMemoList
.
length
===
0
&&
(
<
div
className=
"w-full mt-12 mb-8 flex flex-col justify-center items-center italic"
>
<
Empty
/>
<
p
className=
"mt-2 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
{
shouldShowBackToTop
&&
<
BackToTop
/>
}
</
div
>
)
}
<
ScrollToTop
enabled=
{
shouldShowScrollToTop
}
className=
"fixed bottom-6"
style=
{
{
right
:
`calc(1rem + ${containerRightOffset}px)`
}
}
/>
</>
)
}
</
div
>
);
...
...
@@ -130,4 +119,46 @@ const PagedMemoList = (props: Props) => {
);
};
const
BackToTop
=
()
=>
{
const
t
=
useTranslate
();
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
);
setIsVisible
(
true
);
}
else
{
setShouldRender
(
false
);
setIsVisible
(
false
);
}
}
};
window
.
addEventListener
(
"scroll"
,
handleScroll
);
return
()
=>
window
.
removeEventListener
(
"scroll"
,
handleScroll
);
},
[
isVisible
]);
const
scrollToTop
=
()
=>
{
window
.
scrollTo
({
top
:
0
,
behavior
:
"smooth"
,
});
};
if
(
!
shouldRender
)
{
return
null
;
}
return
(
<
Button
variant=
"plain"
onClick=
{
scrollToTop
}
>
{
t
(
"router.back-to-top"
)
}
<
ArrowUpIcon
className=
"ml-1 w-4 h-auto"
/>
</
Button
>
);
};
export
default
PagedMemoList
;
web/src/components/ScrollToTop.tsx
deleted
100644 → 0
View file @
012ca1d7
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