Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
144 changes: 144 additions & 0 deletions src/common/components/BackToTop.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
.back-to-top {
position: fixed;
bottom: 3rem;
right: 2rem;
left: auto;
transform: none;
z-index: 98;
width: 56px;
height: 56px;
border-radius: 50%;
background: var(--color-brand-primary);
border: 3px solid #fff;
color: #fff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
line-height: 1;
padding: 0;
box-shadow: 0 8px 24px rgba(0, 242, 254, 0.35), inset 0 0 20px rgba(255, 255, 255, 0.1);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
animation: slideUpButton 0.4s ease-out;
flex-shrink: 0;
backdrop-filter: blur(10px);
}

/* Footer variant - inline button */
.back-to-top--footer {
position: static;
bottom: auto;
right: auto;
width: 52px;
height: 52px;
margin: 0 0 0 auto;
animation: none;
}

@keyframes slideUpButton {
from {
opacity: 0;
transform: translateY(40px) scale(0.8);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}

.back-to-top:hover {
background: var(--color-brand-primary);
box-shadow: 0 12px 32px rgba(0, 242, 254, 0.5), inset 0 0 20px rgba(255, 255, 255, 0.2);
transform: translateY(-6px) scale(1.08);
border-color: #00f2fe;
}

.back-to-top--footer:hover {
transform: scale(1.08);
}

.back-to-top:active {
transform: translateY(-2px) scale(0.96);
box-shadow: 0 4px 16px rgba(0, 242, 254, 0.4), inset 0 0 20px rgba(255, 255, 255, 0.1);
}

/* Footer back-to-top container */
.footer-back-to-top {
margin-left: auto;
display: flex;
align-items: center;
padding: 0 1rem;
}

/* Arrow icon — perfectly centered inside the circle */
.back-to-top-icon {
width: 24px;
height: 24px;
stroke-width: 3;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}

/* Home Page Specific Styles */
body.home-page .back-to-top {
background: rgba(0, 242, 254, 0.95);
box-shadow: 0 6px 20px rgba(0, 242, 254, 0.4);
}

body.home-page .back-to-top:hover {
background: var(--color-brand-primary);
box-shadow: 0 10px 28px rgba(0, 242, 254, 0.55);
}

@media screen and (max-width: 768px) {
.back-to-top {
bottom: 2.5rem;
right: 1.5rem;
width: 52px;
height: 52px;
font-size: 1.3rem;
}

.footer-back-to-top {
padding: 0 0.5rem;
}
}

@media screen and (max-width: 480px) {
.back-to-top {
bottom: 2rem;
right: 1rem;
width: 48px;
height: 48px;
font-size: 1.1rem;
border: 2px solid #fff;
}

.back-to-top--footer {
width: 48px;
height: 48px;
}

.footer-back-to-top {
padding: 0;
flex: 1;
text-align: right;
}
}

@media screen and (max-height: 700px) {
.back-to-top {
bottom: 2rem;
}
}

@media screen and (max-height: 600px) {
.back-to-top {
bottom: 1.5rem;
}
}
53 changes: 53 additions & 0 deletions src/common/components/BackToTop.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useState, useEffect } from 'react';
import { FiArrowUp } from 'react-icons/fi';
import './BackToTop.css';

const BackToTop = ({ isInFooter = false }) => {
const [isVisible, setIsVisible] = useState(isInFooter);

const toggleVisibility = () => {
if (isInFooter) {
setIsVisible(true);
} else if (window.pageYOffset > 400) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};

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

useEffect(() => {
if (!isInFooter) {
window.addEventListener('scroll', toggleVisibility);

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

return (
<>
{isVisible && (
<button
aria-label="Back to top"
className={`back-to-top ${isInFooter ? 'back-to-top--footer' : ''}`}
title="Back to top"
type="button"
onClick={scrollToTop}
>
<FiArrowUp className="back-to-top-icon" />
<span className="sr-only">Back to top</span>
</button>
)}
</>
);
};

export default BackToTop;
2 changes: 2 additions & 0 deletions src/common/footer/ExtendedFooter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FaInstagram, FaXTwitter } from 'react-icons/fa6';
import { IoAddSharp, IoLogoRss } from 'react-icons/io5';
import { MdManageSearch } from 'react-icons/md';
import { Link } from 'react-router-dom';
import BackToTop from 'common/components/BackToTop';

const ExtendedFooter = () => {
const { showShareModal, setShowShareModal } = useSearchContext();
Expand Down Expand Up @@ -58,6 +59,7 @@ const ExtendedFooter = () => {
return (
<div className="app-footer--home app-footer">
<div className="app-footer-body">
<BackToTop />
<div className="body-primary">
<h3>
<span className="sr-only">ReactPlay</span>
Expand Down
20 changes: 11 additions & 9 deletions src/common/footer/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ const Footer = () => {
return (
<footer className="app-footer text-center">
<hr className="separater" />
<div className="flex justify-center items-center py-2">
<p className="app-footer-text">
ReactPlay - The MIT License (MIT) Copyright &copy;
{new Date().getFullYear()} . Powered by{' '}
<Link className="text-link-default" to="/tech-stacks">
Top Notch Tech Stacks
</Link>
.
</p>
<div className="footer-content-wrapper">
<div className="flex justify-center items-center py-2">
<p className="app-footer-text">
ReactPlay - The MIT License (MIT) Copyright &copy;
{new Date().getFullYear()} . Powered by{' '}
<Link className="text-link-default" to="/tech-stacks">
Top Notch Tech Stacks
</Link>
.
</p>
</div>
</div>
</footer>
);
Expand Down
10 changes: 10 additions & 0 deletions src/common/header/HeaderNav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FaXTwitter, FaDiscord } from 'react-icons/fa6';
import { BiMoney } from 'react-icons/bi';
import { IoAddSharp, IoShareSocial, IoHeartSharp } from 'react-icons/io5';
import { MdManageSearch, MdClose, MdEvent } from 'react-icons/md';
import { AiOutlineHome } from 'react-icons/ai';
import SocialShare from 'common/components/SocialShare';
import { GoX } from 'react-icons/go';
import { Modal, Box, Typography, Menu } from '@mui/material';
Expand Down Expand Up @@ -51,6 +52,15 @@ const HeaderNav = ({ showBrowse }) => {
const modalClose = () => setShowShareModal(!showShareModal);

const NavLinks = [
{
type: 'Link',
testId: 'home-btn',
title: 'Home',
to: '/',
icon: AiOutlineHome,
iconClass: 'icon home-icon',
label: 'Home'
},
{
type: 'Link',
testId: 'leaderboard-btn',
Expand Down
2 changes: 2 additions & 0 deletions src/common/playideas/PlayIdeas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RiChatNewLine } from 'react-icons/ri';
import LevelBadge from 'common/components/LevelBadge';
import './playIdeas.css';
import { PLAY_DOC_LINK } from 'constants';
import BackToTop from 'common/components/BackToTop';

const PlayIdeas = () => {
const [ideas, setIdeas] = useState([]);
Expand Down Expand Up @@ -57,6 +58,7 @@ const PlayIdeas = () => {
<main className="app-body app-body-overflow-hidden">
<div className="playideas-container">
<div className="playideas-header">
<BackToTop />
<div>
<h1 className="header-title">
Play Ideas
Expand Down
1 change: 1 addition & 0 deletions src/common/routing/RouteDefs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Testimonials
} from 'common';
import PlayList from 'common/playlists/PlayList';
import BackToTop from 'common/components/BackToTop';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { NhostClient, NhostReactProvider } from '@nhost/react';
import BadgesDashboard from 'common/badges-dashboard';
Expand Down
Loading