Skip to content

Commit afddd8b

Browse files
Scroll to Top (#140)
1 parent acbae4c commit afddd8b

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

src/app/[locale]/layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getMessages } from "next-intl/server";
1010
import { routing } from "@/config/i18n/navigation";
1111
import { inter, poppins } from "@/lib/fonts";
1212
import { cn } from "@/lib/utils";
13+
import { JumpToTop } from "@/components/custom/jump-to-top";
1314
import AppProvider from "@/components/providers";
1415

1516
import "@/base/styles/globals.css";
@@ -58,7 +59,10 @@ export default async function RootLayout({ children, params }: RootLayoutProps)
5859
)}
5960
>
6061
<NextIntlClientProvider messages={messages}>
61-
<AppProvider>{children}</AppProvider>
62+
<AppProvider>
63+
{children}
64+
<JumpToTop />
65+
</AppProvider>
6266
</NextIntlClientProvider>
6367

6468
{process.env.NEXT_PUBLIC_GA_ID && <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID} />}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { AnimatePresence, motion } from "framer-motion";
5+
import { ArrowUp } from "lucide-react";
6+
7+
import { cn } from "@/lib/utils";
8+
9+
export const JumpToTop = () => {
10+
const [isVisible, setIsVisible] = useState(false);
11+
12+
const toggleVisibility = () => {
13+
if (window.scrollY > 300) {
14+
setIsVisible(true);
15+
} else {
16+
setIsVisible(false);
17+
}
18+
};
19+
20+
const scrollToTop = () => {
21+
window.scrollTo({
22+
top: 0,
23+
behavior: "smooth",
24+
});
25+
};
26+
27+
useEffect(() => {
28+
window.addEventListener("scroll", toggleVisibility);
29+
return () => {
30+
window.removeEventListener("scroll", toggleVisibility);
31+
};
32+
}, []);
33+
34+
return (
35+
<AnimatePresence>
36+
{isVisible && (
37+
<motion.button
38+
initial={{ opacity: 0, scale: 0.8, y: 20 }}
39+
animate={{ opacity: 1, scale: 1, y: 0 }}
40+
exit={{ opacity: 0, scale: 0.8, y: 20 }}
41+
transition={{ duration: 0.2 }}
42+
onClick={scrollToTop}
43+
className={cn(
44+
"fixed right-8 bottom-8 z-50",
45+
"flex h-12 w-12 items-center justify-center rounded-full shadow-lg",
46+
"bg-primary text-primary-foreground",
47+
"hover:bg-primary/90 transition-colors duration-200",
48+
"focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none"
49+
)}
50+
aria-label="Scroll to top"
51+
>
52+
<ArrowUp className="h-6 w-6" />
53+
</motion.button>
54+
)}
55+
</AnimatePresence>
56+
);
57+
};

0 commit comments

Comments
 (0)