React web app : Mapping <Popover.Button> and <Popover.Panel> separately - javascript

I want to render this popover panel
<Popover.Panel className="absolute z-10 inset-x-0 transform shadow-xl pb-2 w-max">
<div>
<Dropdown subCategory={mainCatArray}/>
</div>
</Popover.Panel>
same as this popover button render in this below code. Because I want to render a separate popover panel with the popover button, please help me to do that. (I cannot render in one categoryObj because I want to render this popover button horizontally, but if I use a single map for the entire popover component popover button renders it vertically.
(Simply What I want to do is I want to render one <Popover.Panel> to one <Popover.Button>)
import { Fragment, useEffect, useState } from "react";
import { Popover, Transition } from "#headlessui/react";
import Dropdown from "./dropdown";
import { categoryObj } from "../../components/item/categoriesobj";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const CatDropDown = () => {
const [mainCatArray, setMainCatArray] = useState([]);
return (
<>
<Popover className="z-0 relative">
{({ open }) => (
<>
<div>
<div className=" z-10 bg-white">
<div className="w-2/3 flex gap-5 px-4 py-6 sm:px-6 lg:px-8 ">
{categoryObj.map((singleMainCategory) => (
<Popover.Button
className={classNames(
open ? "text-gray-900" : "text-gray-900",
"group bg-white rounded-md inline-flex items-center text-sm font-susty focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-white"
)}
>
<span
key={singleMainCategory.id}
onClick={() => {
setMainCatArray(singleMainCategory.subCategory);
}}
>
<span>{singleMainCategory.name}</span>
</span>
</Popover.Button>
))}
</div>
</div>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 -translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-1"
>
<Popover.Panel className="absolute z-10 inset-x-0 transform shadow-lg pb-2">
<div>
<Dropdown subCategory={mainCatArray}/>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</>
);
};
export default CatDropDown;

Related

How to transition div using Tailwindcss in React

I have a Menu panel next to my sider. I am trying to add a drawer type animation however I can't seem position open/close within the pink column
const album = ["Album1", "Album2", "Album3"];
export const Menu = () => {
const [open, setOpen] = useState(false);
return (
<div className="flex flex-1 flex-col p-3">
<h2 className="text-lg font-medium text-gray-900">Album</h2>
<div className="flex-1">
<div className="flex flex-1 flex-col space-y-3 py-3">
{album.map((name) => (
<button
key={name}
id={name}
className={`flex space-x-2 items-center`}
onClick={() => setOpen(true)}
>
<span>{name}</span>
</button>
))}
</div>
</div>
<aside
onClick={() => setOpen(false)} //temporary
className={`transform top-0 left-0 w-72 bg-blue-400 fixed h-full overflow-auto ease-in-out transition-all duration-1000 z-30 ${
open ? "translate-x-14" : "-translate-x-full"
}`}
>
hello
</aside>
</div>
);
};
Javascript. Just add a eventListener to the 'button' which will handle the actions. In the eventListener, remove or add a css class. For example, by default the position is '-100px right' with css set position to '300px right', also add a transition 'all 300ms ease'. Now you element start with no css, and when user press button add the class.
<div class="text-3xl font-bold underline transition-all duration-300" id="must-change">
Lorem Ipsu
</div>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded" id="press-me">
Button
</button>
<script>
const btn = document.querySelector("#press-me")
const mustChange = document.querySelector("#must-change")
btn.addEventListener("click", () => {
mustChange.classList.toggle("font-bold")
})
</script>
I set 'transition all' but you can use a specific transition if you want.

ReactJS perform a function before going to the route of link after clicking on a link

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>
);
}

How to map large data in nextjs when in viewport?

I want to make make a dropdown where a user can select an erc20 token from a tokenlist in Nextjs.
I tried a regular mapping function on the token list but then the site doesn't respond and is very slow because the tokenlist.json. I would like to render the data when in viewport. How can I achieve this?
I would like to make it fast, like in the token select modal in
Uniswap
I used nextjs Image and this loads the token image when in view but it is still slow because it needs to render the token name and symbol
This is how I fetch the tokenlist and render it:
import { Fragment, useEffect, useState } from 'react';
import { Combobox, Transition } from '#headlessui/react';
import { CheckIcon, SelectorIcon } from '#heroicons/react/solid';
import { PlusSmIcon } from '#heroicons/react/outline';
import axios from 'axios';
import tokensJson from '../web3/tokens.json';
import Image from 'next/image';
export default function SelectErc20() {
const [selected, setSelected] = useState(tokensJson.tokens[0]);
const [tokenlist, setTokenlist] = useState([]);
const [query, setQuery] = useState('');
const filteredTokens =
query === ''
? tokenlist
: tokenlist.filter((token) =>
token.name
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''))
);
useEffect(() => {
axios
.get('https://tokens.coingecko.com/uniswap/all.json')
.then((res) => {
setTokenlist(res.data.tokens);
})
.catch(setTokenlist(tokensJson.tokens));
}, []);
return (
<div className="flex items-center space-x-3">
<img src={selected.logoURI} alt="token" className="h-6 w-6" />
<div className="w-64">
<Combobox value={selected} onChange={setSelected}>
<div className="relative mt-1">
<div className="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-emerald-300 sm:text-sm">
<Combobox.Input
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
displayValue={(token) => token.name}
onChange={(event) => setQuery(event.target.value)}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<SelectorIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</Combobox.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<a
href="#"
className="relative mb-3 flex select-none items-center space-x-3 py-2 px-4 text-gray-700 hover:bg-neutral-100"
>
<PlusSmIcon className="h-5 w-5" />
<span>Add custom token</span>
</a>
{filteredTokens.length === 0 && query !== '' ? (
<div className="relative select-none py-2 px-4 text-gray-700">
<span>Nothing found..</span>
</div>
) : (
filteredTokens.map((token) => (
<Combobox.Option
key={token.address}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${
active ? 'bg-emerald-600 text-white' : 'text-gray-900'
}`
}
value={token}
>
{({ selected, active }) => (
<div className="flex items-center justify-between">
<div className="flex items-center truncate">
<Image
src={token.logoURI}
alt={token.name}
width="24"
height="24"
className="mr-3"
/>
<span
className={`block truncate ${
selected ? 'font-medium' : 'font-normal'
}`}
>
{token.name}
</span>
</div>
<span
className={`block text-xs text-gray-400 ${
selected ? 'font-medium' : 'font-normal'
} ${active ? 'text-white' : null}`}
>
{token.symbol}
</span>
{selected ? (
<span
className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
active ? 'text-white' : 'text-emerald-600'
}`}
>
<CheckIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
) : null}
</div>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Transition>
</div>
</Combobox>
</div>
</div>
);
}
It's because you're rendering too much HTML node, your navigator can't paint it.
In order to do what you need, you must use what we call a 'virtual list'.
There are few libraries to virtualize, you're not the first.
Look at for exemple React Window

How to hide a custom toast notification with Tailwind css and react

Im working on a custom toast notification. But the hard part to me is I've not been able to hide the toast notification correctly. When I make a click to show it the toast has an animation to the left the problem is when the toast is gonna hide just disapear and it does not slide to the right. Im working with react and tailwind css. Here is the code. also leave a link to the case. https://codesandbox.io/s/toast-notification-ucx2v?file=/src/components/toastNotification.jsx
I hope you can help me guys.
-----App component-----
import { useEffect, useState } from "react";
import ToastNotification from "./components/toastNotification";
import "./styles.css";
export default function App() {
const [showToast, setShowToast] = useState(false);
const addToCart = () => {
setShowToast(true);
};
useEffect(() => {
setTimeout(() => {
setShowToast(false);
}, 3000);
}, [showToast]);
return (
<div className="App">
<ToastNotification showNotification={showToast} />
<button
className="w-1/2 p-2 flex items-center justify-center rounded-md bg-indigo-400 text-white"
type="submit"
onClick={addToCart}
>
Add to cart
</button>
</div>
);
}
-----Toast component-----
import React, { useEffect, useState } from "react";
const ToastNotification = ({ showNotification }) => {
useEffect(() => {
setTimeout(() => {}, 5000);
}, []);
return (
<>
{showNotification === false ? null : (
<div className="static z-20">
<div
className={`${
showNotification
? "animate__animated animate__slideInRight absolute top-16 right-4 flex items-center text-white max-w-sm w-full bg-green-400 shadow-md rounded-lg overflow-hidden mx-auto"
: "animate__animated animate__slideOutRight absolute top-16 right-4 flex items-center text-white max-w-sm w-full bg-green-400 shadow-md rounded-lg mx-auto"
}`}
>
<div class="w-10 border-r px-2">
<i class="far fa-check-circle"></i>
</div>
<div class="flex items-center px-2 py-3">
<div class="mx-3">
<p>add to car</p>
</div>
</div>
</div>
</div>
)}
</>
);
};
export default ToastNotification;

Animate conditional components in Reactjs to slide in, fade in

I am trying to add some user experience with animations. The problem is the way I have my screen set up. Currently I don't render my sidebar when it's closed, and it looks great and it's responsive, but it seems that I cannot add animations because of the layout I've chosen. As conditionally rendered components don't seem to work with animations.
export default function Layout(props) {
const [open, setOpen] = useState(false);
const isMobile = useMediaQuery({ query: "(max-width: 768px)" });
return (
<div className="relative h-screen w-screen flex">
/// SIDEBAR
{open && (
<section className={"fixed top-0 left-0 bg-white h-screen z-20 w-64 md:relative md:w-1/3 delay-400 duration-500 ease-in-out transition-all transform "
+ (open ? " translate-x-0 " : " translate-x-full ")}></section>
)}
{open && isMobile && (
<div
onClick={(ev) => {
setOpen(false);
}}
className="fixed left-0 mt-0 bg-black bg-opacity-60 w-screen h-screen z-10"
></div>
)}
<div className="relative w-full h-full">
/// TOP BAR
<div className="absolute top-0 h-16 w-full bg-blue-600 flex flex-row items-center p-2 z-5">
<MenuIcon
onClick={(ev) => {
setOpen(!open);
}}
className="text-white"
width={30}
height={30}
></MenuIcon>
</div>
/// CONTENT
<div className="pt-16 h-full w-full overflow-y-auto">{props.children}</div>
</div>
</div>
);
}
Tried using something like this delay-400 duration-500 ease-in-out transition-all transform " + (isOpen ? " translate-x-0 " : " translate-x-full ")
Is there a way to do appearing/ disappearing animations with my current setup or do I have to change the layout entirely?
This is how it currently looks like

Categories