Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 1 addition & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { RouterProvider } from 'react-router-dom';
import router from '@/routes';
import ScrollToTop from '@/components/shared/ScrollToTop';

const App = () => {
useEffect(() => {
Expand All @@ -25,6 +26,7 @@ const App = () => {
return (
<div className="min-h-screen flex flex-col bg-white dark:bg-gray-900">
<RouterProvider router={router} />
<ScrollToTop />
</div>
);
};
Expand Down
56 changes: 56 additions & 0 deletions src/components/shared/ScrollToTop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronUp } from 'lucide-react';

const ScrollToTop = () => {
const [isVisible, setIsVisible] = useState(false);

useEffect(() => {
const toggleVisibility = () => {
if (window.scrollY > 300) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};

window.addEventListener('scroll', toggleVisibility);

return () => {
window.removeEventListener('scroll', toggleVisibility);
};
}, []);

const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
};

return (
<AnimatePresence>
{isVisible && (
<motion.button
onClick={scrollToTop}
className="fixed bottom-6 right-6 sm:bottom-8 sm:right-8 z-50 p-3 sm:p-4 bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
aria-label="Scroll to top"
initial={{ opacity: 0, scale: 0.5, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.5, y: 20 }}
whileHover={{ scale: 1.1, y: -2 }}
whileTap={{ scale: 0.95 }}
transition={{
type: 'spring',
stiffness: 500,
damping: 30,
}}
>
<ChevronUp className="h-5 w-5 sm:h-6 sm:w-6" aria-hidden="true" />
</motion.button>
)}
</AnimatePresence>
);
};

export default ScrollToTop;
27 changes: 0 additions & 27 deletions src/pages/News/NewsDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -361,33 +361,6 @@ const NewsDetailPage: React.FC = () => {
</div>
</div>
)}

{/* Back to Top Button */}
<div className="fixed bottom-8 right-8 z-50">
<motion.button
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
className="p-3 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400"
aria-label="Back to top"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 15l7-7 7 7"
/>
</svg>
</motion.button>
</div>
</div>

{/* Image Modal */}
Expand Down