How to properly handle mouse events with AlpineJS for my menu? - javascript

I'm working on a mega dropdown menu component with alpine.js and Tailwind CSS. Right now I have met some difficulties and I can't make the mouse events work. Below is my code and the red block is the dropdown mega menu. As you can see, when you move the cursor on the Product menu item, the mega menu is shown. After that if you move the cursor down a little bit on the mega menu, the menu is still shown. At this state, if you move out of the mega menu, the mega menu is close. The problem is that when you move on to the Product menu and then move your cursor right to the *Pricing" menu item, the dropdown is still shown, which is not correct. When the user moves out of the Product menu, how can I test if the destination is the mega dropdown or other menu items like Pricing (and close the mega dropdown in this case) using alpine.js?
<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/2.8.2/alpine.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.1.2/tailwind.min.css" rel="stylesheet"/>
<div class="border-b border-gray-200 relative h-20 flex py-0 items-stretch" x-data="{isProdMenuOpen: false, isLearnMenuOpen: false}">
<div class="flex container mx-auto items-center justify-between h-20"><img class="flex-none h-6" src="https://sitebulb.com/images/icons/logo.svg" />
<ul class="flex items-center justify-between flex-none">
<li class="h-20 border-b border-l border-r border-transparent mx-6 flex items-center relative" #mouseenter="isProdMenuOpen = true">
<!--div.h-20.w-full.flex.items-center.border-b.border-transparent(class="hover:border-red")--><a class="font-semibold flex-none" href="">Product</a>
<div class="h-full absolute -left-6 -right-6 top-0 border-r border-l border-gray-200">
<div class="w-full absolute bottom-0 bg-black z-100 inset-x-0" style="transform: translate(0px, 2px); height:4px;"></div>
</div>
</li>
<li class="h-20 flex items-center mx-6"><a class="font-semibold flex-none" href="">Pricing</a></li>
<li class="h-20 flex items-center mx-6"><a class="font-semibold flex-none" href="">About</a></li>
</ul>
<div class="flex items-center justify-between flex-none"><button class="bg-white rounded border px-3 py-2 text-sm font-medium border-gray-200">Login</button><button class="bg-white rounded bg-green-400 text-white text-sm font-medium px-3 py-2 ml-2 hover:bg-green-500">Free Trial</button></div>
</div><!-- Popup Menu Items -->
<div class="flex flex-row items-start border-b border-gray-200 w-screen h-40 absolute left-0 top-20 bg-red-400" id="prodmenu" x-show="isProdMenuOpen" #mouseenter="isProdMenuOpen = true" #mouseleave="isProdMenuOpen = false"></div>
</div>

There's a solution for this that is independent of Alpine or Javascript at all. Using Tailwind's group class on the li, making the popup a child of the group li and removing relative positioning from the li so the child div can go full width will give the same effect I believe you are looking for and fully controlled by CSS.
There will need to be some fudging around to make a similar effect that you are doing with the different color underline on the li in this example I used a border and relative positioning of the anchor tag to offset the border's height. Also, you will need to extend your variants to allow display to be manipulated in group-hover in the code snippet here on SO below I'm just using the class generated by that config to fake it. Here's a Tailwind play that shows the actual config as well https://play.tailwindcss.com/EYY0alaQuB?file=config
.group:hover .group-hover\:flex {
display: flex;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.1.2/tailwind.min.css" rel="stylesheet" />
<div class="border-b border-gray-200 relative h-20 flex py-0 items-stretch">
<div class="flex container mx-auto items-center justify-between h-20"><img class="flex-none h-6" src="https://sitebulb.com/images/icons/logo.svg" />
<ul class="flex items-center justify-between flex-none">
<li class="h-20 border-b-4 border-l border-r border-gray-200 px-6 flex items-center group" style="border-bottom: 4px solid black">
<a class="font-semibold flex-none relative top-1" href="">Product</a>
<!-- Popup Menu Items -->
<div class="hidden group-hover:flex flex-row items-start border-b border-gray-200 w-screen h-40 absolute left-0 top-20 bg-red-400" id="prodmenu"></div>
</li>
<li class="h-20 flex items-center mx-6 border-b-4 border-transparent"><a class="font-semibold flex-none relative top-1" href="">Pricing</a></li>
<li class="h-20 flex items-center mx-6 border-b-4 border-transparent"><a class="font-semibold flex-none relative top-1" href="">About</a></li>
</ul>
<div class="flex items-center justify-between flex-none">
<button class="bg-white rounded border px-3 py-2 text-sm font-medium border-gray-200">Login</button>
<button class="rounded bg-green-400 text-white text-sm font-medium px-3 py-2 ml-2 hover:bg-green-500">Free Trial</button>
</div>
</div>
</div>

Related

I have to click 2 times to the link in mobile view of my Next JS application in order to change routes

I am building a Next JS application,
On the desktop view, there is no problem, but when it changes to the mobile view after a scroll event or changing routes when I want to click on a link/button, etc. I need to click two times on it. I am suspicious that after an event, the focus on the page is being lost, but there is no overflow or something. I couldn't figure out where the focus was gone.
Here is an example of my code
<Link href={storyUrl}>
<a className="group inline-flex flex-col mb-4 md:mb-11 space-y-2 ">
<div className="flex items-center gap-2">
{isDeleted && (
<div className="group">
<Info className="text-slate-400" />
<div className="hidden rounded-lg p-2 text-xs bg-gray-100 absolute z-50 group-hover:block shadow-md w-48 mt-5">
This story has been deleted or in draft
</div>
</div>
)}
{draft && (
<span className="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-purple-50 text-purple-800">
Draft
</span>
)}
<h2
className={`text-slate-900 text-3xl font-semibold leading-9 tracking-md transition ease-in-out duration-150 group-hover:text-purple-700 ${
isDeleted ? 'line-through' : ''
}`}
>
{title}
</h2>
</div>
<p className="text-slate-500 text-sm tracking-sm line-clamp-2 break-words w-full xl:w-[600px] ">
{infoText}
</p>
</a>
</Link>
Thanks for any responses in advance
I tried to change the Links to the router.push but as I thought it was irrelevant.

How to keep div size when showing submenu on top Tailwind and React

Using Tailwind an React - When clicking on an icon I show a submenu but it happens that when the container div is shown it becomes bigger in its height automatically.
How can I display this submenu on top of the other elements so that it doesn't make its container div bigger? I tried with fixed but it moves from the place I require it to be displayed.
This is the code:
Table where I show the submenu when clicking:
<div className="flex flex-wrap m-5 mx-auto w-full pr-2 pl-2">
<div className="w-full px-4">
<div className="flex flex-wrap">
<div className="flex flex-col min-w-0 break-words w-full mb-6 shadow-lg rounded bg-white">
<div className="block w-full overflow-x-auto">
<table className="items-center w-full bg-transparent border-collapse">
<thead className="bg-sky-50">
<tr>
<th className="px-6 align-middle border border-solid py-3 text-xs uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold bg-blueGray-50 text-blueGray-500 border-blueGray-100">
Opciones
</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-slate-100">
<td className="border-t-0 px-6 align-middle border-l-0 text-center border-r-0 text-xs whitespace-nowrap p-4">
<div>
<OptionsDropDown
showDropdown= { showDropdown }
setShowDropdown = { setShowDropdown }
dataDropDown= { dataDropDown }
/>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
Menu Component, Here I have the icon and what I show when I click on it but it widens the parent div:
<div className="flex justify-center">
<button ref={ref}
className="flex items-center
text-black py-2 px-2
text-2xl hover:text-cyan-700"
onClick={ ()=> setShowDropdown(!showDropdown) }>
<FiMoreVertical/>
</button>
{
showDropdown && (
<div className="relative bg-white text-base z-50 py-2 rounded shadow-lg">
{
dataDropDown.map( (option, index) => (
<div key={index}>
{option.edit}
{option.delete}
</div>
))
}
</div>
)
}
</div>
Here is a screenshot of what it looks like right now on click, making the size of the parent div higher:
Here it is before clicking:
Clicking the container div makes it wide:
Using abosulte on submenu
You're most likely looking for absolute positioning using a relative positioning on the direct parent so that the position of the absolute element is anchored to its immediate parent.
Something like this should work:
<div className="relative flex justify-center">
{/* NOTE: the relative positioning above */}
<button ref={ref}
className="flex items-center
text-black py-2 px-2
text-2xl hover:text-cyan-700"
onClick={ ()=> setShowDropdown(!showDropdown) }>
<FiMoreVertical/>
</button>
{
showDropdown && (
<div className="absolute right-0 top-1/2 -translate-y-1/2 bg-white text-base z-50 py-2 rounded shadow-lg">
{/* which allows this to anchor */}
{
dataDropDown.map( (option, index) => (
<div key={index}>
{option.edit}
{option.delete}
</div>
))
}
</div>
)
}
</div>
although I expect you'll have to tweak it slightly to get the results that you want. It might also be worth looking into the blur event to automatically hide the popup once it loses focus.

Jquery mouseenter and mouseleave is deprecated in Next.js, Tailwind

enter image description here
I wanted to create useEffect variables and put it to true/false when user hovers on parent div.
I want to use that hover variable in a child div with image and resize image when user hovers on parent div with id="infoCard".
Code-
const [hover, setHover] = useState(false);
<div
className="flex flex-col md:flex-row font-inter py-7 px-2 border-b rounded-xl cursor-pointer hover:shadow-lg pr-6 transition duration-200 ease-out first:border-t hover:bg-red-100 mb-2"
id="infoCard"
>
<div className="relative h-40 w-64 md:h-52 md:w-80 flex-shrink-0 ml-6">
<Image
src={img}
layout="fill"
objectFit="cover"
className={`rounded-2xl scale-95 ${hover ? "scale-100" : ""
} transform transition duration-200 ease-out`}
/>
</div>
<div className="flex flex-col flex-grow pl-5 ml-2 mt-2 md:mt-0">
<div className="flex justify-between">
<p>
{location} {city}
</p>
<HeartIcon className="h-7 cursor-pointer" />
</div>
<h4 className="text-xl">{title}</h4>
<div className="border-b w-10 pt-2" />
<p className="pt-2 text-sm text-gray-500 flex-grow">
{numberOfGuest}
{description}
</p>
<div className="flex justify-between items-end">
<p className="flex items-center">
<StarIcon className="h-5 text-red-400" />
{star}
</p>
<div>
<p className="text-lg pb-2 font-semibold lg:text-2xl">{price}</p>
<p className="text-right font-light">{total}</p>
</div>
</div>
</div>
</div>
You could do this without the hover state with just CSS. Take a look at the documentation on group-hover classes here. They work like this:
<div class="group">
<img class="transform scale-95 group-hover:scale-100" />
</div>
Here's a minimal example of how you could do this. Note that you also need to extend the variants of the scale classes in the tailwind.config.js file as it's not included by default.
As a side note: You don't typically use jQuery in a React-based projects. If you need to detect hover, React has built-in mouse events. Read more here.

Vuejs css class leave not applied on nested transition

I am using VueJs 3 and I want to make a modal login form with animations.
I want the first animation to be the background opacity and the second (but at the same time) the form translating from the top to the center.
I use one transition component for the background and one nested
transition component for the form.
This is my template code
<template>
<transition name="popup">
<div v-if="showLogin" class="absolute top-0 right-0 bottom-0 left-0 bg-black bg-opacity-50 flex flex-row flex-nowrap justify-center items-center select-none" #click="toggleLoginPopup(false)">
<transition name="popup-inside" appear>
<div class="shadow-lg bg-white rounded-sm p-4 border border-gray-300" #click.stop="">
<div class="text-xl pb-4 text-center">
Sign In
</div>
<label class="block text-lg">
Username
</label>
<input type="text" class="w-full h-8 p-2 border border-black shadow-inner"/>
<label class="block text-lg pt-4">
Password
</label>
<input type="password" class="w-full h-8 p-2 border border-black shadow-inner"/>
<a class="block hover:cursor-pointer bg-purple-600 hover:bg-purple-800 text-xl w-full text-center rounded-sm text-white p-2 mt-4">
Login
</a>
</div>
</transition>
</div>
</transition>
</template>
And this is the code for the CSS
<style scoped>
.popup-enter-active, .popup-leave-active, .popup-inside-enter-active, .popup-inside-leave-active {
transition: all 5s ease;
}
.popup-enter-from, .popup-leave-to {
opacity: 0;
}
.popup-inside-enter-from, .popup-inside-leave-to {
transform: translate(0px, -100px);
}
</style>
Everything works fine when entering but when leaving the classes are not applied to the nested transition.

Alpinejs Accordion and Toggle on click

I’m using Alpinejs and Tailwind.
I'm trying to create an accordion with a toggle on each tab, but I want that toggle to trigger at the same time as when a user clicks to open each part of the accordion... here's what I got so far:
<ul class="block mb-4" x-data="{pay:null}">
<li class="flex flex-col">
<div #click="pay !== 'cc' ? pay = 'cc' : pay = null" class="cursor-pointer px-5 py-3 flex items-center bg-blue-50 border text-xl font-semibold border-blue-800 text-blue-800 inline-block hover:shadow rounded-t">
<button type="button" class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 bg-gray-200" x-data="{ on: false }" aria-pressed="false" :aria-pressed="on.toString()" #click="on = !on" x-state:on="Enabled" x-state:off="Not Enabled" :class="{ 'bg-orange-500': on, 'bg-white': !(on) }">
<span class="sr-only">Credit Card</span>
<span aria-hidden="true" class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200 translate-x-0" x-state:on="Enabled" x-state:off="Not Enabled" :class="{ 'translate-x-5': on, 'translate-x-0': !(on) }"></span>
</button>
<span class="ml-2">Credit / Debit Card</span>
</div>
<p x-show="pay == 'cc'" class="bg-white border-l border-r border-blue-800 py-4 px-2">
This is made with Alpine JS and Tailwind CSS
</p>
</li>
<li class="flex align-center flex-col">
<h4 #click="pay !== 1 ? pay = 1 : pay = null" class="cursor-pointer px-5 py-3 bg-indigo-400 text-white text-center inline-block hover:opacity-75 hover:shadow hover:-mb-3">Accordion item 2</h4>
<p x-show="pay == 1" class="border py-4 px-2">
There's no external CSS or JS
</p>
</li>
<li class="flex align-center flex-col">
<h4 #click="pay !== 2 ? pay = 2 : pay = null" :class="{'cursor-pointer px-5 py-3 bg-indigo-500 text-white text-center inline-block hover:opacity-75 hover:shadow hover:-mb-3': true, 'rounded-b': pay != 2}">Accordion item 3</h4>
<p x-show="pay == 2" :class="{'border py-4 px-2': true, 'rounded-b': pay == 2}">
Pretty cool huh?
</p>
</li>
</ul>
https://codepen.io/kennyk3/pen/eYBwEXN
I'm pretty green, so bear with me. I actually just want to comment but don't have enough reputation.
First of all, you need to remove bg-gray-200 from your <button class> because you're binding :class="{ bg-{color} } for on: true and on: false states. Another one is bg-orange-500 and ring-orange-500 isn't a defined utility on Tailwind CSS. Maybe you mean bg-yellow-500 and ring-yellow-500? Other than that, what is x-state? I can't find it in Alpine.js docs.
Okay, about your requested problem, your code actually has 2 issues.
You want a parent component to communicate with a child component, like a nested component. Alpine.js doesn't do that. Either you merge both components holding multiple properties in the parent component so that you only have one x-data or you $dispatch('customEvent') on your <div> at the #click event (together with pay toggling) and listen to the #customEvent.window on your <button> component.
Alternatively, you can install Alpine Magic Helpers for the $component/$parent communication or install Spruce for saving all states.
Because of your <button> is nested in your <div>, you're firing both #click events every time you #click the <button>. This is why your code looks like the <button> component communicates with your <ul> component which is untrue. You're firing two different #click events. Because of this behavior, if you $dispatch('customEvent') from your <div> component and listen to the #customEvent.window on your <button>, you'll end up toggling your on state twice. Try add #click.debounce.250 on your <button> together with the #customEvent.window and you'll see it toggles to and fro.
As for that, you need to remove #click event on your <button> while listening to the #customEvent.window so that the toggling happens once, or better you merge both x-data making your <ul> holding multiple properties (no need to $dispatch('customEvent') on your <div> and #click on your <button>, just one #click on your <div>).
In my implementation, I make x-data to hold both:
<ul class="block mb-4" x-data="{pay:null, on: false}">
<li class="flex flex-col">
<div #click="pay !== 'cc' ? pay = 'cc' : pay = null, on = !on" class="cursor-pointer px-5 py-3 flex items-center bg-blue-50 border text-xl font-semibold border-blue-800 text-blue-800 inline-block hover:shadow rounded-t">
<button type="button" class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500" aria-pressed="false" :aria-pressed="on.toString()" x-state:on="Enabled" x-state:off="Not Enabled" :class="{ 'bg-yellow-500': on, 'bg-gray-200': !(on) }">
<span class="sr-only">Credit Card</span>
<span aria-hidden="true" class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200 translate-x-0" x-state:on="Enabled" x-state:off="Not Enabled" :class="{ 'translate-x-5': on, 'translate-x-0': !(on) }"></span>
</button>
<span class="ml-2">Credit / Debit Card</span>
</div>
<p x-show="pay == 'cc'" class="bg-white border-l border-r border-blue-800 py-4 px-2">
This is made with Alpine JS and Tailwind CSS
</p>
</li>
<li class="flex align-center flex-col">
<h4 #click="pay !== 1 ? pay = 1 : pay = null" class="cursor-pointer px-5 py-3 bg-indigo-400 text-white text-center inline-block hover:opacity-75 hover:shadow hover:-mb-3">Accordion item 2</h4>
<p x-show="pay == 1" class="border py-4 px-2">
There's no external CSS or JS
</p>
</li>
<li class="flex align-center flex-col">
<h4 #click="pay !== 2 ? pay = 2 : pay = null" :class="{'cursor-pointer px-5 py-3 bg-indigo-500 text-white text-center inline-block hover:opacity-75 hover:shadow hover:-mb-3': true, 'rounded-b': pay != 2}">Accordion item 3</h4>
<p x-show="pay == 2" :class="{'border py-4 px-2': true, 'rounded-b': pay == 2}">
Pretty cool huh?
</p>
</li>
</ul>
https://codepen.io/wanahmadfiras/pen/jOVgBrz
Accordions (among other components) are now part of official AlpineJS documentation. Use them for perfect out of the box accordions.

Categories

Resources