Use useVirtualList from VueUse to render HeadlessUI's Listbox component - javascript

I want to use VueUse's virtual scroller composable to render the HeadlessUI's listbox content.
This is what I tried:
<Listbox v-model="selectedIcon">
<div class="relative mt-1">
<ListboxButton class="bg-white rounded-lg cursor-default shadow-md text-left w-full py-2 pr-10 pl-3 relative sm:text-sm focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-white focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-offset-orange-300 focus-visible:ring-offset-2">
<span class="block truncate">test</span>
<span class="flex pr-2 inset-y-0 right-0 absolute items-center pointer-events-none">
<SelectorIcon class="h-5 text-gray-400 w-5" aria-hidden="true" />
</span>
</ListboxButton>
<ListboxOptions v-bind="containerProps" class="bg-white rounded-md shadow-lg ring-black mt-1 text-base w-full max-h-60 h-full py-1 ring-1 ring-opacity-5 absolute overflow-auto sm:text-sm focus:outline-none">
<div v-bind="wrapperProps">
<ListboxOption v-slot="{ active, selected }" v-for="icon in list" :key="icon.data.name" :value="icon">
<li>
<span>{{ icon.data.name }}</span>
</li>
</ListboxOption>
</div>
</ListboxOptions>
</div>
</Listbox>
And this is the virtual scroller composable:
const { list, containerProps, wrapperProps } = useVirtualList(icons, { itemHeight: 40 })
The problem is that when I try to open the listbox, I get this error:
Uncaught (in promise) TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'.
at watch.immediate (index.mjs:1322)
at callWithErrorHandling (runtime-core.esm-bundler.js:6737)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:6746)
at Array.job (runtime-core.esm-bundler.js:7154)
at flushPostFlushCbs (runtime-core.esm-bundler.js:6938)
at flushJobs (runtime-core.esm-bundler.js:6983)
at flushJobs (runtime-core.esm-bundler.js:6991)
I also get this warning:
[Vue warn]: Unhandled error during execution of watcher callback
at <ApplicationIconSelector key=0 >
at <Anonymous as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" ... >
at <ForcePortalRoot force=false >
at <PortalGroup target= <div class=​"fixed z-10 inset-0 overflow-y-auto" id=​"headlessui-dialog-4" role=​"dialog" aria-modal=​"true" aria-labelledby=​"headlessui-dialog-title-8">​…​</div>​<div class=​"flex min-h-screen text-center px-4 pt-4 pb-20 items-end justify-center sm:​p-0 sm:​block">​<div id=​"headlessui-dialog-overlay-6" aria-hidden=​"true" class=​"bg-gray-975 bg-opacity-85 inset-0 transition-opacity fixed">​</div>​<!-- This element is to trick the browser into centering the modal contents. --><span class=​"hidden sm:​h-screen sm:​inline-block sm:​align-middle" aria-hidden=​"true">​&ZeroWidthSpace;​</span>​<div class=​"rounded-lg shadow-xl text-left transform transition-all text-gray-850 inline-block align-bottom sm:​max-w-lg sm:​my-8 sm:​w-full sm:​align-middle dark:​text-gray-200">​<div class=​"rounded-t-lg bg-gray-25 px-4 pt-5 pb-4 sm:​p-6 sm:​pb-4 dark:​bg-gray-925">​…​</div>​<div class=​"bg-gray-75 py-3 px-3 sm:​flex sm:​flex-row-reverse dark:​bg-gray-900">​…​</div>​flex<button class=​"flex-y-center justify-center p-2 px-3 font-medium text-sm transition duration-75 select-none cursor-pointer focus:​outline-none rounded-md bg-gray-75 text-gray-850 hover:​bg-gray-100 active:​bg-gray-175 dark:​text-gray-250 dark:​bg-gray-875 dark:​hover:​bg-gray-850 dark:​active:​bg-gray-825 w-full">​…​</button>​flex</div>​</div>​</div>​</div>​ >
at <Portal>
at <ForcePortalRoot force=true >
at <Dialog as="div" class="fixed z-10 inset-0 overflow-y-auto" ref="el" ... >
at <Anonymous onBeforeEnter=fn<onBeforeEnter> onAfterEnter=fn<onAfterEnter> onBeforeLeave=fn<onBeforeLeave> ... >
at <Anonymous as="template" show=true data=null >
at <AddEditApplication modelValue=true onUpdate:modelValue=fn data=null >
at <Index onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {__v_skip: true} > >
at <RouterView>
at <App>

Uncaught (in promise) TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'.
Looks like it is attempting to bind itself to something other than a native DOM element. In the example on the VueUse site they show native DOM elements with the v-bind directive. However, your code uses v-bind on non-native DOM elements, you have it on a VNode (the ListBox component).
Even looking at the source code you can see that the binding of containerProps expects an HTMLElement.
const containerRef: Ref = ref<HTMLElement | null>()
I've not used HeadlessUI before, but looking at the source code for the ListBoxOptions component it appears that it does not render any elements outside of what is passed into its default slot; aka your <div v-bind="wrapperProps">. Is your list of classes even rendering on anything? Hard to tell how your code is running without an example.
I recommend creating another div, nested inside of ListBoxOptions, that wraps the <div v-bind="wrapperProps"> element. On this new div move the v-bind="containerProps" from the ListBoxOptions onto it; see below.
<ListboxOptions>
<div v-bind="containerProps" class="bg-white rounded-md shadow-lg ring-black mt-1 text-base w-full max-h-60 h-full py-1 ring-1 ring-opacity-5 absolute overflow-auto sm:text-sm focus:outline-none">
<div v-bind="wrapperProps">
<ListboxOption v-slot="{ active, selected }" v-for="icon in list" :key="icon.data.name" :value="icon">
<li>
<span>{{ icon.data.name }}</span>
</li>
</ListboxOption>
</div>
</div>
</ListboxOptions>
I think that may fix your issue.

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

JS click event listener in laravel livewire component?

I'm trying to add some JS to add a click event to an element within a livewire component. The click event works as expected on first load, however as soon as I run a wire:click, the JS click events no longer work?
I can see that livewire is removing / updating the dom elements when I click the wire:click element so unsure whether this may have something to do with it.
What am I doing wrong? How should I be registering click events to livewire elements in JS and ensure they always work?
My javascript:
document.addEventListener('livewire:load', function () {
let pb_responsive_button = [...document.querySelectorAll('.js-pb-responsive')];
pb_responsive_button.map(function (btn) {
btn.addEventListener("click", function () {
console.log('click');
});
});
});
livewire:
<button wire:click="addComponentActive(true)">
<span class="font-bold text-sm">Add Page Block</span>
</button>
public function addComponentActive(bool $bool)
{
$this->add_component_active = $bool;
}
my view:
#if ($add_component_active === true)
<section class="bg-white shadow-lg rounded-sm mb-8 w-full h-[calc(100%-64px)] absolute z-50">
.....
</section>
#endif
<section class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
<div class="border border-gray-200 rounded">
<div class="bg-gray-200 p-5 flex justify-between">
<div class="bg-white border border-gray-200 rounded p-2">
<span class="font-bold text-sm">Homepage</span>
</div>
<div class="bg-blue-600 text-white border border-gray-200 rounded p-2">
<button wire:click="addComponentActive(true)">
<span class="font-bold text-sm">Add Page Block</span>
</button>
</div>
<div class="flex justify-between items-center gap-2">
<div class="js-pb-responsive js-pb-mobile bg-white border border-gray-200 rounded p-2">M</div>
<div class="js-pb-responsive js-pb-tablet bg-white border border-gray-200 rounded p-2">T</div>
<div class="js-pb-responsive js-pb-desktop bg-white border border-gray-200 rounded p-2">D</div>
</div>
</div>
</div>
</section>
UPDATE:
I've updated it to show and hide the blocks rather than display it in a conditional if statement which ensures the click events still work, since the element still exists on page.
<section class="{{$add_component_active === true ? 'block' : 'hidden'}}"> ... </section>
Surely there is a better way to handle this. I notice I have a livewire:load eventListener? Is there another event I should be listening to when the livewire view is reloaded to reinitise the JS events?
Any help would be much appreciated.
Livewire works hand-in-hand with Alpine.js. You should focus on Alpine if you want strictly front-end functionality https://alpinejs.dev/directives/on
<button #click="alert('Hello World!')">Say Hi</button>

How to set up Vue 3 parent component to emit event to child component

I am attempting to set up a button in a Vue 3 Tailwind parent component to trigger a HeadlessUI transition event in a child component. My goal is to enable the button in the parent to emit an event, while the child component "watches" for the event before triggering the transition event as part of the callback function in the watch. So far, I have the parent component set up to trigger the emit, while the child component is set up to watch for the "transition" event. However, the event is not being executed. I'm afraid I don't have the watch in the child component set up correctly, so as to watch for the button click in the parent component. How can I go about enabling the child component to watch for the click of the button in the parent component?
Here is my code so far:
Parent:
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<div class="min-h-full">
<Disclosure as="nav" class="bg-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<button type="button" #click="transition" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Click to transition</button>
</div>
</div>
</div>
</div>
</div>
</Disclosure>
<main>
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div class="px-4 py-6 sm:px-0">
<HelloWorld :event="transition" />
</div>
</div>
</main>
</div>
</template>
<script setup>
import { Disclosure, DisclosureButton, DisclosurePanel, Menu, MenuButton, MenuItem, MenuItems } from '#headlessui/vue'
import { BellIcon, MenuIcon, XIcon } from '#heroicons/vue/outline'
import HelloWorld from './components/HelloWorld.vue'
</script>
Child:
<template>
<div class="flex flex-col items-center py-16">
<div class="w-96 h-96">
<TransitionRoot
appear
:show="isShowing"
as="template"
enter="transform transition duration-[400ms]"
enter-from="opacity-0 rotate-[-120deg] scale-50"
enter-to="opacity-100 rotate-0 scale-100"
leave="transform duration-200 transition ease-in-out"
leave-from="opacity-100 rotate-0 scale-100 "
leave-to="opacity-0 scale-95 "
>
<div class="w-full h-full bg-gray-400 rounded-md shadow-lg" />
</TransitionRoot>
</div>
</div>
</template>
<script setup>
import { ref, toRefs, watch } from 'vue'
import { TransitionRoot } from '#headlessui/vue'
const props = defineProps({
transition: Function
})
const { transition } = toRefs(props)
const isShowing = ref(true)
watch(transition, () => {
isShowing.value = false
setTimeout(() => {
isShowing.value = true
}, 500)
})
</script>
events should go up and state should go down.
make your child component to watch a property and the button in parent should change the state of that property
update:
const { transition } = toRefs(props)
you might be losing reactivity here.
more info: https://stackoverflow.com/a/64926664/420096
update2:
the way you made it should work, but point directly to the prop is fine too:
https://codesandbox.io/s/relaxed-sea-y95x6c?file=/src/App.vue
Based on Sombriks' feedback, here is the answer:
Parent:
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<div class="min-h-full">
<Disclosure as="nav" class="bg-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<button type="button" #click="transition" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Click to transition</button>
</div>
</div>
</div>
</div>
</div>
</Disclosure>
<main>
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div class="px-4 py-6 sm:px-0">
<HelloWorld :show="show" />
</div>
</div>
</main>
</div>
</template>
<script setup>
import { Disclosure, DisclosureButton, DisclosurePanel, Menu, MenuButton, MenuItem, MenuItems } from '#headlessui/vue'
import { BellIcon, MenuIcon, XIcon } from '#heroicons/vue/outline'
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
const show = ref(true)
const transition = () => {
show.value = !show.value
}
</script>
Child:
<template>
<div class="flex flex-col items-center py-16">
<div class="w-96 h-96">
<TransitionRoot
appear
:show="isShowing"
as="template"
enter="transform transition duration-[400ms]"
enter-from="opacity-0 rotate-[-120deg] scale-50"
enter-to="opacity-100 rotate-0 scale-100"
leave="transform duration-200 transition ease-in-out"
leave-from="opacity-100 rotate-0 scale-100 "
leave-to="opacity-0 scale-95 "
>
<div class="w-full h-full bg-gray-400 rounded-md shadow-lg" />
</TransitionRoot>
</div>
</div>
</template>
<script setup>
import { ref, toRefs, watch } from 'vue'
import { TransitionRoot } from '#headlessui/vue'
const props = defineProps({
show: Boolean
})
const { show } = toRefs(props)
const isShowing = ref(true)
watch(show, () => {
isShowing.value = false
setTimeout(() => {
isShowing.value = true
}, 500)
})
</script>

I'm trying to build a dropdown when cursor hovers on a navbar element pointing to that element with an arrow up

I am using NextJS and Tailwind CSS. I already built the dropdown but I have an issue building the arrow pointing to the specific element. I built the arrow in a previous version using a div rotated at 45deg but then I couldn't make my dropdown take the full screen. Now I have it full screen and I'm wondering how to make the arrow (div) point to the specific element I'm hovering on. I made sure to hide some details about the website because of confidentiality matters. Here's what I'm trying to achieve:
Here's the code I have so far:
import React from 'react'
import { navLinks } from '../data/navdata'
const DropdownHover = ({ index }) => {
return (
<div className="group-hover:block absolute left-0 w-full hidden text-dark-gray mt-8 bg-pink" aria-labelledby="dropdownButton">
<div className="justify-center">
<div className="flex py-10 px-20 text-sm justify-between">
{navLinks[index].hover.map((link, index) => {
return (
<div className="" key={index}>
<p className="text-navbar-gray py-2 uppercase font-semibold">{link.name}</p>
{link.links.map((sublink, index) => {
return(<p className="" key={index}><a className="bg-pink hover:text-red py-2 block whitespace-no-wrap" href={sublink.path}>{sublink.name}</a></p>)
})}
</div>
)
})}
</div>
<div className="flex text-sm px-20 py-10">
<div className='pr-40'>
<p className="text-navbar-gray py-2 uppercase font-semibold">Dummy Data</p>
<p className=""><a className="bg-pink hover:text-red py-2 block whitespace-no-wrap" href="#">Dummy Data</a></p>
<p className=""><a className="bg-pink hover:text-red py-2 block whitespace-no-wrap" href="#">Dummy Data</a></p>
<p className=""><a className="bg-pink hover:text-red py-2 block whitespace-no-wrap" href="#">Dummy Data</a></p>
</div>
<div>
<p className="text-navbar-gray py-2 uppercase font-semibold">Dummy Data</p>
<p className=""><a className="bg-pink hover:text-red py-2 block whitespace-no-wrap" href="#">Dummy Data</a></p>
<p className=""><a className="bg-pink hover:text-red py-2 block whitespace-no-wrap" href="#">Dummy Data</a></p>
<p className=""><a className="bg-pink hover:text-red py-2 block whitespace-no-wrap" href="#">Dummy Data</a></p>
</div>
</div>
</div>
</div>
)
}
export default DropdownHover
in CSS you can simply manipulate left property of the arrow if its positioned absolute:
li :nth-child(1):hover .arrow{
left:20rem;
}
li :nth-child(2):hover .arrow{
left:30rem;
}
but if you want to make it completely responsive, first get OffsetX of every li element then add an event listener on mouseclick.
for a better understanding you can check the following project on code pen.
https://codepen.io/piyushpd139/pen/gOYvZPG

Add class to the last child of element after page load

My code:
<script>
fetch('http://127.0.0.1:8000/chatbot/fetch/')
.then(response => response.json())
.then(function (data) {
console.log(data)
if (data.choices !== '') {
const htmlAnswer = data.answer.map(answer => {
return `
<button
class="max-w-sm py-3 px-4 focus:outline-none text-blue-500 hover:bg-blue-100 text-center transition ease-in-out duration-300">
🧑🏻‍🎨 ${answer}
</button>
`
}).join('')
document.getElementById('server-message').innerHTML = `
<div class="flex flex-col shadow rounded-lg my-3 max-w-sm border hover:border-indigo-300 hover:shadow-lg transition ease-in-out duration-300">
<div class="mb-0 py-3 px-4 max-w-sm bg-gray-200 rounded-lg rounded-b-none select-none">
${data.question}
</div>
${htmlAnswer}
</div>
`
}
document.addEventListener("DOMContentLoaded", function(){
document.getElementById("server-message").lastElementChild.classList.add('rounded-b-lg');
})
})
</script>
I want to add class to the last child of the parent. I am rendering both parent and child elements from JSON. How can I get it work?
Probably it should start looking for a child element after these are rendered into the HTML. I don't know how to do it.
I need it in vanilla js.
maybe this will help. I check the last item in map function and add the class to it
const answerLength = data.answer.length
const htmlAnswer = data.answer.map((answer,index)=> {
// if this is the last element then add the class
if(index+1 ==answerLength){
return `
<button
class="max-w-sm py-3 px-4 focus:outline-none text-blue-500 hover:bg-blue-100 text-center transition ease-in-out duration-300 rounded-b-lg">
🧑🏻‍🎨 ${answer}
</button>
`
}
else{
return `
<button
class="max-w-sm py-3 px-4 focus:outline-none text-blue-500 hover:bg-blue-100 text-center transition ease-in-out duration-300">
🧑🏻‍🎨 ${answer}
</button>
`
}
}).join('')

Categories

Resources