I have a question related to tailwindcss and React. I have a property that if a <NavItem /> is focused then the background and the text will change. <LanguageSwitcher /> is another component, and if it is focused, I want it to not change focus state of other items in navbar. <LanguageSwitcher /> doesn't affect URL.
Here's my code for item in navigation bar and images of how <LanguageSwitcher /> affects focus state of other items:
import React from "react";
import { Link } from "react-router-dom";
interface Props {
title: string;
url: string;
icon: JSX.Element;
}
const NavItem: React.FC<Props> = ({ title, url, icon }) => {
//https://tailwindcss.com/docs/hover-focus-and-other-states#pseudo-classes
return (
<Link
to={url}
className="text-center items-center flex-col justify-center font-medium px-1 text-white capitalize hover:font-semibold select-none group"
>
<div className="flex items-center justify-center mb-1 rounded-3xl px-2 py-1 group-hover:bg-btnHover group-focus:bg-btnActive group-focus:text-textActive transition-colors duration-75">
{icon}
</div>
<span>{title}</span>
</Link>
);
};
export default NavItem;
Is it possible? If so, how can I do it, maybe with useLocation from react-router-dom or directly with tailwindcss?
"Focus" generally means the UI element the user can currently interact with. I think you are conflating "focus state" with "active state". I'm guessing you want the Link to remain "active" while other UI elements have, or might not have, "focus", and are being interacted with. Switch to using the NavLink component and conditionally apply the "active" state CSS classname.
Here's an example*:
import React from "react";
import { NavLink } from "react-router-dom";
interface Props {
title: string;
url: string;
icon: JSX.Element;
}
const NavItem: React.FC<Props> = ({ title, url, icon }) => {
//https://tailwindcss.com/docs/hover-focus-and-other-states#pseudo-classes
return (
<NavLink
to={url}
className="text-center items-center flex-col justify-center font-medium px-1 text-white capitalize hover:font-semibold select-none group"
>
{({ isActive }: { isActive: boolean }) => (
<>
<div
className={
[
"flex items-center justify-center mb-1 rounded-3xl px-2 py-1 group-hover:bg-btnHover transition-colors duration-75"
isActive ? "group-focus:bg-btnActive group-focus:text-textActive" : null
]
.filter(Boolean)
.join(" ")
}
>
{icon}
</div>
<span>{title}</span>
</>
)}
</NavLink>
);
};
export default NavItem;
*Note: I've just guessed/plucked the CSS classnames that looked like they were related to the "focus/active" CSS styling, you will want to double-check the actual classes in your implementation.
Related
I'm trying to achieve a parallax effect similar to this one using framer-motion in my Next.js app:
https://www.nicyiallouris.com
This is my current ProjectCard component:
"use client";
import { asText } from "#prismicio/helpers";
import { motion, useScroll, useTransform } from "framer-motion";
import Image from "next/image";
import Link from "next/link";
import type { ProjectDocumentData } from "../../../.slicemachine/prismicio";
export const ProjectCard = ({
href,
project,
index,
}: {
href: string;
project: ProjectDocumentData;
index: number;
}) => {
const { scrollYProgress } = useScroll();
const y = useTransform(scrollYProgress, [0, 1], ["0%", `-${index * 10}%`]);
if (!project.cover.url) return null;
return (
<motion.div className="sticky top-0 flex min-h-[70vh] items-center justify-center overflow-hidden">
<Link
href={href}
aria-label={asText(project.title)}
className="absolute z-10"
>
<h3 className="relative border-b border-brand font-brand text-lg uppercase">
{asText(project.title)}
</h3>
</Link>
<Image
src={project.cover.url}
width={project.cover.widescreen.dimensions?.width}
height={project.cover.widescreen.dimensions?.height}
alt="Project Image"
className="absolute inset-0 h-full w-full object-cover"
placeholder="blur"
blurDataURL={`${project.cover.url}&blur=200`}
/>
</motion.div>
);
};
This isn't working though, the scroll effect is different, and the translate effect is pulling the project cards up above the footer. How can I get this done? Code is available here:
https://github.com/thejessewinton/ryan-spacone-www
i have an issue on my project i'am working on, i have a SIDEBAR and a TOPHEADER(NAVBAR).., now the issue starts when i want to have a FIXED SIDEBAR without it obscuring other page elements like TopHeader/Navbar and body content to the left side of the page.. tried adding ml-20 on topHeader div but that didnt help as it just opens up a whitespace on the left,,also tried using Space-x-20 class that also didnt help because it only pushed the body content to the right just like iwanted but theres another issue , the TopHeader is stuck on the left ..on this project iam using Tailwindcss to style the project would appreciate the help.
This is how it looks like with the "Fixed left-0 "
enter image description here
enter image description here
How I want it to look: enter image description here
enter image description here
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { HiMenuAlt3 } from "react-icons/hi";
import { AiFillHome, AiOutlinePoweroff } from "react-icons/ai";
import { FiPlay } from "react-icons/fi";
import {
BsDisplay,
BsQuestionCircle,
BsTrophy,
BsTwitter,
} from "react-icons/bs";
import { MdOutlineGames } from "react-icons/md";
import { SiDiscord } from "react-icons/si";
import { BiNews } from "react-icons/bi";
import { TopHeader } from "../../components/TopHeader";
export const Sidebar = ({ children }) => {
const menus = [
{ name: "Home", link: "/", icon: AiFillHome },
{ name: "Play", link: "/", icon: FiPlay },
{ name: "Watch", link: "/", icon: BsDisplay },
{ name: "Leaderboard", link: "/", icon: BsTrophy, margin: true },
{ name: "Games", link: "/", icon: MdOutlineGames },
{ name: "News", link: "/", icon: BiNews },
{ name: "F.a.q", link: "/", icon: BsQuestionCircle, margin: true },
{ name: "Logout", link: "/", icon: AiOutlinePoweroff },
{ name: "Discord", link: "/", icon: SiDiscord, margin: true },
{ name: "Twitter", link: "/", icon: BsTwitter },
];
const [open, setOpen] = useState(true);
return (
<section className="flex">
<div
className={`fixed left-0 bg-black min-h-screen ${
open ? "w-72" : "w-16"
} duration-500 text-white px-4`}
>
<div className="py-3 flex justify-end">
<HiMenuAlt3
size={26}
className="cursor-pointer"
onClick={() => setOpen(!open)}
/>
</div>
<div className="mt-4 flex flex-col gap-4 relative">
{menus?.map((menu, i) => (
<Link
to={menu?.link}
key={i}
className={`${
menu?.margin && "mt-5"
} flex items-center text-sm gap-3.5 font-medium p-2 hover:bg-indigo-600 rounded-md`}
>
<div>{React.createElement(menu?.icon, { size: "20" })}</div>
<h2
style={{
transitionDelay: `${i + 3}00ms`,
}}
className={`whitespace-pre duration-500 ${
!open && "opacity-0 translate-x-28 overflow-hidden"
}`}
>
{menu?.name}
</h2>
</Link>
))}
</div>
</div>
<div className="bg-gray-800 w-full p-3">
<>
<TopHeader />
{children}
</>
</div>
</section>
);
};
import React from "react";
import { AiOutlineSearch } from "react-icons/ai";
import { BiUserCircle } from "react-icons/bi";
import { HiOutlineFlag } from "react-icons/hi";
import { IoMdNotificationsOutline } from "react-icons/io";
import "tw-elements";
export const TopHeader = () => {
return (
<div className="max-w-auto mx-auto px-4 mb-5 bg-black text-white">
<div className=" flex justify-between">
<div className="flex space-x-52">
{/*Logo*/}
<div>
<h1 className="flex items-center py-4 px-2 font-semibold">LOGO</h1>
</div>
{/* Search Input */}
<div className="hidden md:flex items-center space-x-1">
<div className="bg-gray-300 text-white rounded-full flex items-center px-2">
<AiOutlineSearch size={20} />
<input
className="bg-gray-300 p-2 rounded-full focus:outline-none"
type="text"
placeholder="Search Games"
/>
</div>
</div>
<div className="flex items-center space-x-3">
{/* Notification Button */}
{/*<button className="py-2 px-2 text-white">
<IoMdNotificationsOutline size={30} />
</button> */}
{/* RightSidebar Button */}
<button className="py-2 px-2 text-white">
<HiOutlineFlag size={30} />
</button>
{/* User Button*/}
<button className="py-2 px-2 text-white">
<BiUserCircle size={30} />
</button>
</div>
</div>
</div>
</div>
);
};
i finally found a good fix for my issue i had above , instead of using tailwind default " fixed left-0 " , i ended up using StickyNode and it kind of solved every bit of problems i had by wrapping my sidebar with stickynode component like this:
<Sticky>
<Sidebar />
</Sticky>
I've created my navbar such that, when I open it in a mobile device, body gets overflow-hidden class applied. When the navbar is closed, overflow-hidden is removed from body.
Problem encountered: The function which removes overflow-hidden from body doesn't get run when I click on the link and it navigates to another view. Which means, on the new page, body has overflow hidden so the user can't really scroll anywhere. Opening and closing the navbar fixes that since it removes overflow hidden from the body.
I'm using Laravel Breeze with ReactJS.
// NavLink.js
import React, { useEffect, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { Link } from "#inertiajs/inertia-react";
export default function NavLink({ href, active, children, className = "" }) {
return (
<Link
href={href}
className={
(active
? "inline-flex items-center px-1 pt-1 border-b-2 border-[#ed8686] text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-[#E05D5D] transition duration-150 ease-in-out dark:text-gray-400"
: "inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out dark:text-gray-300 dark:hover:text-gray-500 dark:hover:border-gray-300 dark:focus:outline-none dark:focus:text-gray-500 dark:focus:border-gray-300") +
` ${className}`
}
>
{children}
</Link>
);
}
// NavBar.js
import ApplicationLogo from "#/Components/ApplicationLogo";
import DarkModeButton from "#/Components/DarkModeButton";
import NavLink from "#/Components/NavLink";
import { Link } from "#inertiajs/inertia-react";
import React, { useEffect, useRef, useState } from "react";
import { MdClose, MdMenu } from "react-icons/md";
const NavBar = ({ dark, setDark }) => {
const [scrollTop, setScrollTop] = useState("");
const navRef = useRef();
const mobileNavRef = useRef();
const toggleNav = (e) => {
e.preventDefault();
const navBar = mobileNavRef.current;
navBar.classList.toggle("active");
document.body.classList.toggle("overflow-hidden");
};
const hideNav = (e) => {
e.preventDefault();
const navBar = mobileNavRef.current;
navBar.classList.remove("active");
document.body.classList.remove("overflow-hidden");
};
// TODO: Too many renders, change to remove extra renders
useEffect(() => {
if (scrollTop >= 80) {
navRef.current.classList.add("scrolled");
} else {
navRef.current.classList.remove("scrolled");
}
const onScroll = (e) => {
setScrollTop(e.target.documentElement.scrollTop);
};
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, [scrollTop]);
return (
<>
<nav className="main-nav" ref={navRef}>
<Link to="/" className="h-full w-auto block py-4 mx-auto">
<ApplicationLogo className="h-full w-auto" dark={!!dark} />
</Link>
<MdMenu
className="fixed right-4 top-4 text-lg cursor-pointer lg:hidden dark:text-white"
onClick={(e) => toggleNav(e)}
/>
<ul className="hidden lg:flex flex-row items-center justify-center">
<li>
<NavLink
href={route("home")}
active={route().current("home")}
children="Home"
className="text-lg my-2 mx-4"
/>
</li>
<li>
<NavLink
href={route("gallery")}
children="Gallery"
active={route().current("gallery")}
className="text-lg my-2 mx-4"
/>
</li>
<li>
<NavLink
// href={route("about")}
children="About"
active={route().current("about")}
className="text-lg my-2 mx-4"
/>
</li>
<li className="cursor-pointer flex items-center justify-center">
<DarkModeButton dark={dark} setDark={setDark} />
</li>
</ul>
</nav>
<ul
className="w-full md:w1/2 lg:w-1/4 flex items-center justify-center fixed inset-0 bg-white h-full flex-col mobile-nav lg:hidden z-50 dark:bg-gray-900"
ref={mobileNavRef}
>
<MdClose
className="absolute top-4 right-4 text-lg cursor-pointer lg:hidden dark:text-white"
onClick={(e) => hideNav(e)}
/>
<Link to="/" onClick={(e) => hideNav(e)}>
<ApplicationLogo
className="h-32 mb-2 w-auto lg:hidden"
dark={dark}
/>
</Link>
<li>
<NavLink
href={route("home")}
active={route().current("home")}
children="Home"
className="text-lg my-2 mx-4"
onClick={(e) => hideNav(e)}
/>
</li>
<li>
<NavLink
href={route("gallery")}
children="Gallery"
active={route().current("gallery")}
className="text-lg my-2 mx-4"
onClick={(e) => hideNav(e)}
/>
</li>
<li>
<NavLink
// href={route("about")}
children="About"
active={route().current("about")}
className="text-lg my-2 mx-4"
onClick={(e) => hideNav(e)}
/>
</li>
<li className="cursor-pointer">
<DarkModeButton dark={dark} setDark={setDark} />
</li>
</ul>
</>
);
};
export default NavBar;
I've made the function hideNav() which has e.preventDefault() but I'm guessing it doesn't work because it doesn't apply directly on the Link tag? I'm not sure
Unsure how to tackle this problem
I've thought about using useNavigate but I am not using react router dom, inertiajs is handling the routing for me, I just set the routes in laravel web.php file.
I've come up with a solution,
It's not the ideal solution but this is what I can think of
I added a prop to NavLink component and based on if that prop is true, I add onclick event handler that removes overflow class from body
import React from "react";
import { Link } from "#inertiajs/inertia-react";
import { Inertia } from "#inertiajs/inertia";
export default function NavLink({
href,
active,
children,
className = "",
mobileLink = false,
}) {
return (
<Link
href={href}
className={
(active
? "inline-flex items-center px-1 pt-1 border-b-2 border-[#ed8686] text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-[#E05D5D] transition duration-150 ease-in-out dark:text-gray-400"
: "inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out dark:text-gray-300 dark:hover:text-gray-500 dark:hover:border-gray-300 dark:focus:outline-none dark:focus:text-gray-500 dark:focus:border-gray-300") +
` ${className}`
}
onClick={
mobileLink
? (e) => {
e.preventDefault();
document.body.classList.remove("overflow-hidden");
Inertia.visit(href);
}
: undefined
}
>
{children}
</Link>
);
}
To make the accordion component with Headless UI, I have used Disclosure component. But I have a problem to control the collapse/expand state for it's siblings.
So, I want to close other siblings when I open one, but Disclosure component is only supporting internal render props, open and close. So, I can't control it outside of the component and can't close others when I open one.
import { Disclosure } from '#headlessui/react'
import { ChevronUpIcon } from '#heroicons/react/solid'
export default function Example() {
return (
<div className="w-full px-4 pt-16">
<div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2">
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
<span>What is your refund policy?</span>
<ChevronUpIcon
className={`${
open ? 'rotate-180 transform' : ''
} h-5 w-5 text-purple-500`}
/>
</Disclosure.Button>
<Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
If you're unhappy with your purchase for any reason, email us
within 90 days and we'll refund you in full, no questions asked.
</Disclosure.Panel>
</>
)}
</Disclosure>
<Disclosure as="div" className="mt-2">
{({ open }) => (
<>
<Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
<span>Do you offer technical support?</span>
<ChevronUpIcon
className={`${
open ? 'rotate-180 transform' : ''
} h-5 w-5 text-purple-500`}
/>
</Disclosure.Button>
<Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
No.
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
</div>
)
}
How do we control the close/open state outside of the component?
I don't think so it's possible using HeadlessUI, although you can create your own Disclosure like component.
Lift the state up to the parent component by creating a disclosures state that stores all the information about the disclosures.
Loop over the disclosures using map and render them.
Render a button that toggles the isClose property of the disclosures and also handles the aria attributes.
On button click, toggle the isOpen value of the clicked disclosure and close all the other disclosures.
Checkout the snippet below:
import React, { useState } from "react";
import { ChevronUpIcon } from "#heroicons/react/solid";
export default function Example() {
const [disclosures, setDisclosures] = useState([
{
id: "disclosure-panel-1",
isOpen: false,
buttonText: "What is your refund policy?",
panelText:
"If you're unhappy with your purchase for any reason, email us within 90 days and we'll refund you in full, no questions asked."
},
{
id: "disclosure-panel-2",
isOpen: false,
buttonText: "Do you offer technical support?",
panelText: "No."
}
]);
const handleClick = (id) => {
setDisclosures(
disclosures.map((d) =>
d.id === id ? { ...d, isOpen: !d.isOpen } : { ...d, isOpen: false }
)
);
};
return (
<div className="w-full px-4 pt-16">
<div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2 space-y-2">
{disclosures.map(({ id, isOpen, buttonText, panelText }) => (
<React.Fragment key={id}>
<button
className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75"
onClick={() => handleClick(id)}
aria-expanded={isOpen}
{...(isOpen && { "aria-controls": id })}
>
{buttonText}
<ChevronUpIcon
className={`${
isOpen ? "rotate-180 transform" : ""
} h-5 w-5 text-purple-500`}
/>
</button>
{isOpen && (
<div className="px-4 pt-4 pb-2 text-sm text-gray-500">
{panelText}
</div>
)}
</React.Fragment>
))}
</div>
</div>
);
}
it is possible just you need to add some extra props selectors to the Disclosure.Button Component, in this case, I am adding aria-label='panel' like so...
import { Disclosure } from '#headlessui/react'
function MyDisclosure() {
return (
<Disclosure>
<Disclosure.Button aria-label="panel" className="py-2">
Is team pricing available?
</Disclosure.Button>
<Disclosure.Panel className="text-gray-500">
Yes! You can purchase a license that you can share with your entire
team.
</Disclosure.Panel>
</Disclosure>
)
}
next you need to select the following with "querySelectorAll" like...
<button
type='button'
onClick={() => {
const panels = [...document.querySelectorAll('[aria-expanded=true][aria-label=panel]')]
panels.map((panel) => panel.click())
}}
>
</button>
with this, you just need to change 'aria-expanded' to either 'true' or 'false' to expand or collapse
There's a way to do this with React (assuming you're using #headlessui/react) via useState:
const [disclosureState, setDisclosureState] = useState(0);
function handleDisclosureChange(state: number) {
if (state === disclosureState) {
setDisclosureState(0); // close all of them
} else {
setDisclosureState(state); // open the clicked disclosure
}
}
And in each Disclosure component, just pass an onClick callback to the Disclosure.Button:
<Disclosure.Button onClick={() => handleDisclosureChange(N)} />
Where N is the index of the clicked Disclosure (using 1 as the first Disclosure, since 0 handles all disclosures closed).
Finally, conditionally render the Disclosure.Panel based on the disclosureState:
{
disclosureState === N && (<Disclosure.Panel />)
}
Where N is the index of the clicked Disclosure. Using this method you can open just 1 disclosure at a time, and clicking an open disclosure will close all of them.
I am implementing a modal in my react app but face the weird error, Error: Expected `onClick` listener to be a function, instead got a value of `object` type.. What's confusing here is the fact that I have implemented the modal in another part of the application using the same logic and it doesn't produce such error.
Below are important snippets of the code.
index.js
import React, { useState } from "react";
import ProfileSave from "../../components/modal/profileSave";
const test = () => {
const [saveModal, setSaveModal] = useState(false);
const [save, setSave] = useState(false);
return (
<>
<button className="w-[165px] h-[60px] text-center px-4 py-2 text-sm text-white bg-[#3A76BF] rounded-md font-bold text-[16px]" onClick={saveShowModal}>
Save
</button>
{saveModal && (
<ProfileSave
open={saveModal}
handleClose={() => setSaveModal(!saveModal)}
handleSave={handleSave}
/>
)}
</>
export default test
profileSave.js
import React from "react";
const ProfileSave = (open, handleClose, handleModal) => {
return open ? (
<div className="fixed inset-0 z-10 overflow-y-auto">
<div
className="fixed inset-0 w-full h-full bg-black opacity-40"
onClick={handleClose}
></div>
</div>
) : (
""
);
};
export default ProfileSave;
You neet to extract the props that you component recive
Try this :
import React from "react";
const ProfileSave = ({open, handleClose, handleModal}) => {
return open ? (
<div className="fixed inset-0 z-10 overflow-y-auto">
<div
className="fixed inset-0 w-full h-full bg-black opacity-40"
onClick={handleClose}
></div>
</div>
) : (
""
);
};
export default ProfileSave;