Highlight the active nav item on click using Tailwind CSS in React - javascript

I have a navigation bar like this:
<nav className="bg-white shadow dark:bg-gray-800">
<div className="container flex items-center justify-center p-6 mx-auto text-gray-600 capitalize dark:text-gray-300">
<Link
to="/home"
className="text-gray-800 transition-colors duration-300 transform dark:text-gray-200 border-b-2 border-blue-500 mx-1.5 sm:mx-6"
>
home
</Link>
<Link
to="/invoices"
className="border-b-2 border-transparent hover:text-gray-800 transition-colors duration-300 transform dark:hover:text-gray-200 hover:border-blue-500 mx-1.5 sm:mx-6"
>
features
</Link>
<Link
to="/forms"
className="border-b-2 border-transparent hover:text-gray-800 transition-colors duration-300 transform dark:hover:text-gray-200 hover:border-blue-500 mx-1.5 sm:mx-6"
>
forms
</Link>
<Link
to="/newForm"
className="border-b-2 border-transparent hover:text-gray-800 transition-colors duration-300 transform dark:hover:text-gray-200 hover:border-blue-500 mx-1.5 sm:mx-6"
>
form2
</Link>
</nav>
I want to change the nav item when it's clicked, but everywhere in Stackoverflow that I've searched I only found solutions for NestJS, Next.js, etc., not React.

You should use active variant on className in tailwindCSS.
for example:
<Link
to="/home"
className="text-gray-300 active:text-blue-500"
>
home
</Link>

You really need to define how you want to determine whether the class is added or not first.
Seems to me you'd want to instead loop through your items:
const isCurrentPage = (href: string) => {
// return true if `href` is the current path, many ways you could do this
}
// loop through your items and conditionally add the class if active...
['/home', '/invoices', '/forms'].map(href => <Link key={href} href={href} className={isCurrentPage(href) ? 'active' : ''} />
Your isCurrentPage function depends on your app logic and setup; you could probably rely on some logic like window.location.pathname.indexOf(href) === 0 to start.

Related

NextJs nesting a component into another (not FC)

I built a module, then broke it up into components and trying to put it back together.
The components work. Some components go inside the div of another component.
I have tried putting them into a div scaffold, but i was then writing more css to bring the module together.
I thought we just do this:
<CompOne>
<CompTwo />
</CompOne>
This gives an error:
Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes'.
So maybe the above is write i need to typescript? sorry couldnt find a working example on this.
function WaldoEye() {
return (
<WaldoEyeball>
<WaldoRetina/>
</WaldoEyeball>
)
}
export default WaldoEye
function WaldoEyeball() {
return (
<div className="
flex
relative
items-center
opacity-90
w-40 h-40
rounded-full
shadow-[inset_0_-15px_15px_-3px_rgba(5,10,255,0.3)]
bg-gradient-to-r
from-slate-50
via-slate-100
to-slate-50
">
<div className="
absolute
flex
items-center
opacity-90
w-40 h-40
rounded-full
shadow-[0_60px_55px_-25px_rgba(255,255,255,0.4)]
">
</div>
</div>
)
}
export default WaldoEyeball
function WaldoRetina() {
return (
<>
<div className="
flex
items-center
relative
mx-auto
w-16
h-16
rounded-full
border-2
border-sky-500
bg-gradient-radial
from-cyan-500
via-sky-300
to-sky-500
">
</div>
</>
)
}
export default WaldoRetina
So maybe the above is write i need to typescript? sorry couldnt find a working example on this.
Currently the WaldoEyeball component doesn't expect any children. You will need to adjust the props in the WaldoEyeball component so it accepts children.
const WaldoEyeball: FC = ({ children }) => {
return (
<div className="
flex
relative
items-center
opacity-90
w-40 h-40
rounded-full
shadow-[inset_0_-15px_15px_-3px_rgba(5,10,255,0.3)]
bg-gradient-to-r
from-slate-50
via-slate-100
to-slate-50
">
<div className="
absolute
flex
items-center
opacity-90
w-40 h-40
rounded-full
shadow-[0_60px_55px_-25px_rgba(255,255,255,0.4)]
">
{children} // drop children somewhere to render it
</div>
</div>
);
}

Flow direction for divs displayed on hovering

I have a following set of images each with its own div and I want to show a popup with information on hover. Refer the following component code-
function Skill({ directionLeft, skill }: Props) {
return (
//group hover filter class in motion img used for special effect
<div className='group relative flex cursor-pointer'>
<motion.div
initial={{
x: directionLeft ? -200 : 200,
opacity: 0
}}
transition={{ duration: 0 }}
whileInView={{ opacity: 1, x: 0 }}
className='w-16 h-16 rounded-full border border-gray-500 object-cover xl:w-20 xl:h-20 filter group-hover:grayscale transition duration-300 ease-in-out'
>
<Image
src={urlFor(skill.skillImage).url()}
height={108} width={192} alt='' className=' w-16 h-16 rounded-full border border-gray-500 object-cover xl:w-20 xl:h-20 filter group-hover:grayscale transition duration-300 ease-in-out' />
</motion.div>
<div className='absolute opacity-0 group-hover:opacity-90 transition duration-300 ease-in-out z-20 group-hover:bg-[#1e313f] h-54 xl:w-72 rounded-lg p-5'>
<div className='flex flex-col items-center justify-center h-full'>
{/*loop over if multiple skill supports*/}
{skill.relevantWorkDone.map((relevantWorkDone, index) => (
<p key={index} className='uppercase tracking-[5px] text-gray-200 text-sm overflow-y-auto opacity-100 z-20= justify-center'>
-{relevantWorkDone}
</p>
))}
</div>
</div>
</div>
)
}
Now I want to adjust the hovering of the popup messages according to the location of the parent div in super parent grid.
Eg- instead of 1
I want something like 2-
The image div being hovered on is the one in the one in the last column if the 2nd last row.
I know I can pass a prop with the grid location like I and create a function depending upon the remainder to make it flow either left or right and if I is in the first half or second half of total elements to make it flow from either top or bottom.
But I don't know what tailwind CSS classes to use to achieve this.
Any help would be appreciated.

State Update prevents classList.toggle()

I am making a timeslot-picker component and want to toggle the class of each individual timeslot-item so it changes color and also add the timeslot information to an array of objects. If I update the state array with an onClick function the classList.toggle function doesn't seem to work, as none of the timeslots change color. I would appreciate any help on how to combine both functionalites. This is the part of my component required for the functionality:
<ul className="w-[180px] h-[400px] overflow-y-scroll flex flex-col gap-1 pt-4 relative">
<div className="text-center ">{day.day}</div>
{timeslots.map((timeslot) => (
<li key={Math.random()} className="mt-3">
<button
onClick={(e) => {
e.currentTarget.classList.toggle('bg-violet-500');
setRequestedTimeslots([
...requestedTimeslots,
{
day: day.day,
start: timeslot.start,
end: timeslot.end,
},
]);
}}
className="px-4 py-2 bg-[#F5F5F5] rounded-xl w-full text-center"
>
{timeslot.start} - {timeslot.end}
</button>
</li>
))}
</ul>

React way of selecting elements and adding event listeners

I'm building a portfolio site with gatsby where I have a sidebar on the left with some links (Home, about, work, etc.) and on the right there's the main content which is one long strip of sections. Clicking a link simply scrolls to that section. I would like for a link to be styled differently when the user reaches that section. I've found a solution in vanilla javascript to apply active state that works, but I don't know how to do this the react way.
useEffect(() => {
const navbarlinks = document.querySelectorAll("nav a");
const navbarlinksActive = () => {
let position = window.scrollY + 200;
navbarlinks.forEach((navbarlink) => {
let section = document.querySelector(navbarlink.hash);
if (position >= section.offsetTop && position <= section.offsetTop + section.offsetHeight) {
navbarlink.classList.add("active");
} else {
navbarlink.classList.remove("active");
}
});
};
window.addEventListener("load", navbarlinksActive);
document.addEventListener("scroll", navbarlinksActive);
return () => {
window.removeEventListener("load", navbarlinksActive);
document.removeEventListener("scroll", navbarlinksActive);
};
}, []);
I'm selecting all the nav links, then looping through them, getting the hash property, which will match the id of the corresponding section which is then selected. Then the class is added or removed based on its position. This code is in the sidebar component where my links, which are gatsby Link elements, also are.
return (
<nav>
<Link
className="hover:text-clr-accent flex items-center py-2 px-4 transition"
to="#home"
>
<i className="fa-solid fa-house w-6 text-center text-lg"></i>
<span className="pl-2 text-lg">Home</span>
</Link>
<Link
className="hover:text-clr-accent flex items-center py-2 px-4 transition"
to="#resume"
>
<i className="fa-solid fa-file w-6 text-center text-lg"></i>
<span className="pl-2 text-lg">Resume</span>
</Link>
<Link
className="hover:text-clr-accent flex items-center py-2 px-4 transition"
to="#about"
>
<i className="fa-solid fa-address-card w-6 text-center text-lg"></i>
<span className="pl-2 text-lg">About me</span>
</Link>
<Link
className="hover:text-clr-accent flex items-center py-2 px-4 transition"
to="#work"
>
<i className="fa-solid fa-briefcase w-6 text-center text-lg"></i>
<span className="pl-2 text-lg">My work</span>
</Link>
</nav>
);
Instead of directly adding/removing the active class set the active link Id to state and render based on that. Otherwise react may overwrite your classes by rendering
Don't use the load event listener instead just call navbarlinksActive(), the window is already loaded by the time your effect is called.

Prevent react from restarting the css animation on route change

In my react app I have a file called SecondaryLayout.js
const SecondaryLayout = ({children, className, ...rest}) => {
return (
<>
<Sidebar />
<main className={['seconday_layout__container', className].join('')} {...rest}>
{children}
</main>
</>
);
};
Sidebar.js:
const Sidebar = () => {
const {user} = useSelector(state => state.auth);
return (
<div className="bg-white py-4 px-2 fixed h-screen shadow-lg secondary_layout__sidebar">
<div className="h-full flex flex-col justify-between items-center">
<Link to="/"><h1 className="font-pacifico text-white px-2.5 py-1 bg-blue-700 rounded-full text-xl -mb-5">P</h1></Link>
<div className="flex flex-col space-y-2">
<NavLink to="/user/dashboard" className="px-2.5 py-1.5 rounded-lg shadow-md" activeClassName="bg-blue-700 shadow-lg text-white"><TemplateIcon className="inline w-4 h-4 -mt-1"/></NavLink>
<NavLink to="/user/listings" className="px-2.5 py-1.5 rounded-lg shadow-md" activeClassName="bg-blue-700 shadow-lg text-white"><ViewListIcon className="inline w-4 h-4 -mt-1"/></NavLink>
<NavLink to="/user/address" className="px-2.5 py-1.5 rounded-lg shadow-md" activeClassName="bg-blue-700 shadow-lg text-white"><LocationMarkerIcon className="inline w-4 h-4 -mt-1"/></NavLink>
<NavLink to="/user/profile" className="px-2.5 py-1.5 rounded-lg shadow-md" activeClassName="bg-blue-700 shadow-lg text-white"><CogIcon className="inline w-4 h-4 -mt-1"/></NavLink>
</div>
<Avatar name={user.username} image={user.image} />
</div>
</div>
);
};
The sidebar has a css animation, and 3 NavLinks, every time I click on a link, the css animation restarts, the behavior that I want is the sidebar to only fade in once, and stay fixed even when I click on a navlink, I tried to wrap my sidebar component with React.memo() but that didn't fix the issue
Edit:
Let's say the user navigates to /user/dashboard, or /user/profile, all these routes should always render the sidebar
Example
Dashboard.js
import React from 'react';
import { Helmet } from 'react-helmet';
import SecondaryLayout from '../../layouts/SecondaryLayout';
const Dashboard = () => {
return (
<SecondaryLayout>
<Helmet>
<title>My Dashboard</title>
</Helmet>
Dashboard
</SecondaryLayout>
);
};
export default Dashboard;
I think I managed to fix the issue, what I was doing is to wrap every component by the secondary layout, what I have done is to move the Secondary Layout to wrap around the route component itself, and not around the component being rendered by the route, i.e: <Route .... /></Route .... />

Categories

Resources