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('')
Related
I am attempting to set up slot machine-style animation using Vue 3 TailwindCSS and HeadlessUI. So far, I have a basic green square set up to slide in from top and slide out the bottom based on cycles in a for-loop called when clicking the "click to transition" button. The resetIsShowing() function also handles the randomization of a sequential array (1-10). My goal is to display one of the 10 random numbers on the green square on each cycle. To do this, I refactored the main div displaying the square to pass a random number on each cycle:
<div v-for="number in numbers" :key="number.id" class="h-full w-full rounded-md bg-green-500 shadow-lg">
{{ number }}
</div>
But when I add list property to the above div, then I get the following error:
The current component <TransitionChild /> is rendering a "template".
However we need to passthrough the following props:
- ref
If I change "template" to "ref" in the TransitionRoot, then I lose the slide down effect that I am trying to achieve. How can I set this up so that numbers appear on the square on each cycle?
Here is the full code:
<template>
<div class="flex flex-col items-center py-16">
<div class="h-72 w-72">
<div class="flex justify-center items-center w-full h-full p-4 text-9xl rounded-md shadow-lg border-solid border-2 border-sky-500">
<TransitionRoot
appear
:show="isShowing"
as="template"
enter="transition ease-in-out duration-300 transform"
enter-from="-translate-y-full opacity-0"
enter-to="translate-y-0 opacity-100"
leave="transition ease-in-out duration-300 transform"
leave-from="translate-y-0 opacity-100"
leave-to="translate-y-full opacity-0"
>
<div v-for="number in numbers" :key="number.id" class="h-full w-full rounded-md bg-green-500 shadow-lg">
{{ number }}
</div>
</TransitionRoot>
</div>
</div>
<button
#click="resetIsShowing"
class="mt-8 flex transform items-center rounded-full bg-black bg-opacity-20 px-3 py-2 text-sm font-medium text-white transition hover:scale-105 hover:bg-opacity-30 focus:outline-none active:bg-opacity-40"
>
<svg viewBox="0 0 20 20" fill="none" class="h-5 w-5 opacity-70">
<path
d="M14.9497 14.9498C12.2161 17.6835 7.78392 17.6835 5.05025 14.9498C2.31658 12.2162 2.31658 7.784 5.05025 5.05033C7.78392 2.31666 12.2161 2.31666 14.9497 5.05033C15.5333 5.63385 15.9922 6.29475 16.3266 7M16.9497 2L17 7H16.3266M12 7L16.3266 7"
stroke="currentColor"
stroke-width="1.5"
/>
</svg>
<span class="ml-3">Click to transition</span>
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { TransitionRoot } from '#headlessui/vue'
const isShowing = ref(true)
const numbers = ref([])
const numberArray = ref(Array.from({length: 10}, (e, i)=> i + 1))
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function resetIsShowing() {
for (let i = 0; i < numberArray.value.length; i++) {
const index = Math.floor(Math.random() * numberArray.value.length)
await sleep(800)
isShowing.value = false
setTimeout(() => {
isShowing.value = true
}, 500)
numbers.value = numberArray.value.filter(r => r === (index + 1))
}
}
</script>
RESOLVED
<template>
<div class="flex flex-col items-center py-16">
<div class="h-72 w-72">
<div class="flex justify-center items-center w-full h-full p-4 rounded-md shadow-lg border-solid border-2 border-sky-500">
<TransitionRoot
appear
:show="isShowing"
as="template"
enter="transition ease-in-out duration-[400ms]"
enter-from="-translate-y-full opacity-0"
enter-to="translate-y-0 opacity-100"
leave="transition ease-in-out duration-[400ms]"
leave-from="translate-y-0 opacity-100"
leave-to="translate-y-full opacity-0"
>
<div class="h-full w-full rounded-md pt-16">
<span v-if="numbers > 0" class="text-9xl">
{{ numbers }}
</span>
</div>
</TransitionRoot>
</div>
</div>
<button
#click="resetIsShowing"
class="mt-8 flex transform items-center rounded-full bg-black bg-opacity-20 px-3 py-2 text-sm font-medium text-white transition hover:scale-105 hover:bg-opacity-30 focus:outline-none active:bg-opacity-40"
>
<svg viewBox="0 0 20 20" fill="none" class="h-5 w-5 opacity-70">
<path
d="M14.9497 14.9498C12.2161 17.6835 7.78392 17.6835 5.05025 14.9498C2.31658 12.2162 2.31658 7.784 5.05025 5.05033C7.78392 2.31666 12.2161 2.31666 14.9497 5.05033C15.5333 5.63385 15.9922 6.29475 16.3266 7M16.9497 2L17 7H16.3266M12 7L16.3266 7"
stroke="currentColor"
stroke-width="1.5"
/>
</svg>
<span class="ml-3">Click to transition</span>
</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { TransitionRoot } from '#headlessui/vue'
const isShowing = ref(true)
const numbers = ref([])
const numberArray = ref(Array.from({length: 10}, (e, i)=> i + 1))
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function resetIsShowing() {
for (let i = 0; i < numberArray.value.length; i++) {
await sleep(1200)
const index = Math.floor(Math.random() * numberArray.value.length)
// const index = numberArray.value[i]
numbers.value = numberArray.value.filter(r => r === (index + 1))[0]
// numbers.value = numberArray.value.filter(r => r === (index))[0]
console.log(numbers.value)
isShowing.value = false
setTimeout(() => {
isShowing.value = true
}, 500)
}
}
</script>
I'm building out filters for a job board where by I have three types of filters: location, category, and employment type. Switching between the categories filter sorts the job board between the selected category quite fine.
Location and Employment filters is where the issue occurs. They are both dropdown menus whereby the state renders nothing the first time you click them. Only when you click one of the dropdown options again does it actually render. I've attached photos to visually show what the job board looks like and the dropdown options, as well as the devtools open to show you what states render.
Here is the code sandbox for a more interactive look: codesandbox.io/s/mutable-pond-h08wst?file=/src/App.js
App.js
import React, {useState, useEffect} from 'react'
import jobData from "./assets/data.json"
import JobBoardComponent from './components/JobBoardComponent';
import NavBar from './components/NavBar'
import EmailBar from './components/EmailBar'
import BannerPage from "./components/BannerPage"
import FuncFilter from "./components/FuncFilter"
import LocFilter from "./components/LocFilter"
import ConFilter from './components/ConFilter';
import Filters from "./components/Filters"
function App() {
const [jobs, setJobs] = useState([])
const [filtered, setFiltered] = useState([])
const [activeCategory, setActiveCategory] = useState("all")
const [activeLocation, setActiveLocation] = useState("all")
const [activeContract, setActiveContract] = useState("all")
const [selectedId, setSelectedId] = useState("")
const sortedJobs = jobData.sort((a, b) => Number(b.id) - parseFloat(a.id))
useEffect(() => {
setJobs(sortedJobs)
}, [])
function handleClick(id) {
if (selectedId !== id) {
setSelectedId(id)
} else
setSelectedId("")
}
console.log(filtered)
return (
<div className="App w-auto">
<NavBar />
<BannerPage />
<EmailBar />
<Filters
jobs={jobs}
setFiltered={setFiltered}
activeLocation={activeLocation}
setActiveLocation={setActiveLocation}
activeCategory={activeCategory}
setActiveCategory={setActiveCategory}
activeContract={activeContract}
setActiveContract={setActiveContract}
/>
<div>
{activeCategory === "all" ? jobs.map((job) => <JobBoardComponent handleClick={handleClick} selected={selectedId === job.id} job={job} key={job.id} />)
: filtered.map((job) => <JobBoardComponent handleClick={handleClick} selected={selectedId === job.id} job={job} key={job.id} />)}
</div>
</div>
);
}
export default App;
FuncFilter
import LocFilter from "./LocFilter"
import ConFilter from "./ConFilter"
function FuncFilter({setActiveCategory, activeCategory, activeLocation, setActiveLocation, activeContract, setActiveContract, setFiltered, jobs}) {
useEffect(() => {
if (activeCategory === "all") {
setFiltered(jobs)
return
}
const filtered = jobs.filter((job) => job.category.includes(activeCategory))
setFiltered(filtered)
}, [activeCategory])
const obj = jobs.map(function(job) {
return job.category
})
function markCategory() {
const marketingCount = []
for (const jobCat of obj) {
if (jobCat === "Marketing") {
marketingCount.push(jobCat)
}
}
return marketingCount.length
}
function cusCategory() {
const cusCount = []
for (const jobCat of obj) {
if (jobCat === "Customer Service") {
cusCount.push(jobCat)
}
}
return cusCount.length
}
function creativeCategory() {
const creativeCount = []
for (const jobCat of obj) {
if (jobCat === "Creative") {
creativeCount.push(jobCat)
}
}
return creativeCount.length
}
function webCategory() {
const webCount = []
for (const jobCat of obj) {
if (jobCat === "Web Development") {
webCount.push(jobCat)
}
}
return webCount.length
}
function salesCategory() {
const salesCount = []
for (const jobCat of obj) {
if (jobCat === "Sales") {
salesCount.push(jobCat)
}
}
return salesCount.length
}
function peopleCategory() {
const peopleCount = []
for (const jobCat of obj) {
if (jobCat === "People & HR") {
peopleCount.push(jobCat)
}
}
return peopleCount.length
}
function opsCategory() {
const opsCount = []
for (const jobCat of obj) {
if (jobCat === "Biz Ops") {
opsCount.push(jobCat)
}
}
return opsCount.length
}
function finCategory() {
const finCount = []
for (const jobCat of obj) {
if (jobCat === "Finance") {
finCount.push(jobCat)
}
}
return finCount.length
}
function proCategory() {
const proCount = []
for (const jobCat of obj) {
if (jobCat === "Product") {
proCount.push(jobCat)
}
}
return proCount.length
}
const resetLocation = event => {
setActiveLocation(1);
};
const resetContract = event => {
setActiveContract(1);
};
const resetCategory = event => {
setActiveCategory(1)
};
//1. Marketing DONE
// 2. Biz Ops DONE
// 3. Sales DONE
// 4. Finance DONE
// 5. Product
// 6. Web Development DONE
// 7. Creative DONE
// 8. People & HR DONE
// 9. Customer Service DONE
return (
<div className="mb-20 -mt-20">
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => setActiveCategory("all")}>All</button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Marketing");}}>Marketing ({markCategory()}) </button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Customer Service");}}>Customer Service ({cusCategory()})</button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Creative");}}>Creative ({creativeCategory()})</button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Web Development");}}>Web Development ({webCategory()})</button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("People & HR");}}>People & HR ({peopleCategory()})</button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Sales");}}>Sales ({salesCategory()}) </button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Biz Ops");}}> Biz Ops ({opsCategory()}) </button>
<button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Finance");}}>Finance ({finCategory()}) </button>
<button className="hidden text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
onClick={() => {resetLocation(); resetContract(); setActiveCategory("Product");}}> Product ({proCategory()}) </button>
<LocFilter
jobs={jobs}
setFiltered={setFiltered}
activeLocation={activeLocation}
setActiveLocation={setActiveLocation}
activeCategory={activeCategory}
setActiveCategory={setActiveCategory}
activeContract={activeContract}
setActiveContract={setActiveContract}
/>
<ConFilter
jobs={jobs}
setFiltered={setFiltered}
activeLocation={activeLocation}
setActiveLocation={setActiveLocation}
activeCategory={activeCategory}
setActiveCategory={setActiveCategory}
activeContract={activeContract}
setActiveContract={setActiveContract}
/>
</div>
)
}
export default FuncFilter
LocFilter.js
import { Fragment } from 'react'
import { Menu, Transition } from '#headlessui/react'
import { ChevronDownIcon } from '#heroicons/react/solid'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
function LocFilter({setActiveLocation, activeLocation, activeCategory, setActiveCategory, activeContract, setActiveContract, setFiltered, jobs}) {
useEffect(() => {
const filtered = jobs.filter((job) => job.location.includes(activeLocation))
setFiltered(filtered)
}, [activeLocation])
const resetContract = event => {
setActiveContract(1);
};
const resetCategory = event => {
setActiveCategory(1);
};
return (
<Menu as="div" className="mb-20 relative inline-block text-left">
<div>
<Menu.Button
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Locations
<ChevronDownIcon className="-mr-1 ml-2 h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<a
// active category cannot be 0
// if active category is a category, it works to select a location filter
// you cannot click that exact category right after though, as AC is labeled as that category
onClick={() => {resetCategory(); resetContract(); setActiveLocation("USA"); }}
className={classNames(
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
'block px-4 py-2 text-sm'
)}
>
USA
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
onClick={() => {resetCategory(); resetContract(); setActiveLocation("Asia");}}
className={classNames(
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
'block px-4 py-2 text-sm'
)}
>
Asia
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
onClick={() => {resetCategory(); resetContract(); setActiveLocation("Europe");}}
className={classNames(
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
'block px-4 py-2 text-sm'
)}
>
Europe
</a>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
)
}
export default LocFilter
ConFilter,js
import { Fragment } from 'react'
import { Menu, Transition } from '#headlessui/react'
import { ChevronDownIcon } from '#heroicons/react/solid'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
function ConFilter({ setActiveCategory, activeLocation, activeCategory, setActiveContract, setActiveLocation, activeContract, setFiltered, jobs}) {
useEffect(() => {
const filtered = jobs.filter((job) => job.contract.includes(activeContract))
setFiltered(filtered)
}, [activeContract])
const resetLocation = event => {
setActiveLocation(1);
};
const resetCategory = event => {
setActiveCategory(1);
};
return (
<Menu as="div" className="mb-20 relative inline-block text-left">
<div>
<Menu.Button
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Employment
<ChevronDownIcon className="-mr-1 ml-2 h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<a
// active category cannot be 0
// if active category is a category, it works to select a location filter
// you cannot click that exact category right after though, as AC is labeled as that category
onClick={() => {resetCategory(); resetLocation(); setActiveContract("Remote");}}
className={classNames(
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
'block px-4 py-2 text-sm'
)}
>
Remote
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
onClick={() => {resetCategory(); resetLocation(); setActiveContract("Part Time");}}
className={classNames(
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
'block px-4 py-2 text-sm'
)}
>
Part Time
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
onClick={() => {resetCategory(); resetLocation(); setActiveContract("Full Time");}}
className={classNames(
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
'block px-4 py-2 text-sm'
)}
>
Full Time
</a>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
)
}
export default ConFilter```
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 not entirely sure how to toggle the popover based on a boolean, in my case: if there are search results. I thought I could just use the open attribute. Any help is appreciated!
To sum up this component:
Type in a search term in the input
An API call is being made and results are returned from te API
The PopoverPanel should then be opened
<template>
<form :action="action" method="GET" class="flex-1">
<Popover :open="searchResults.length > 0" class="relative w-full">
<PopoverOverlay
:class="`bg-black ${open ? 'fixed inset-0 opacity-10' : 'opacity-0'}`"
#click="() => emptySearchResults()"
/>
<div class="relative">
<label for="search" class="sr-only">Search...</label>
<input
id="search"
v-model="searchTerm"
type="search"
placeholder="Search..."
class="h-10 w-full px-4 py-2 text-sm rounded-lg placeholder-gray-400 focus:outline-none"
>
</div>
<PopoverPanel class="absolute top-full right-0 w-full mt-3 py-2 px-2 bg-gray-700 rounded-lg shadow-lg">
<a
v-for="result in searchResults"
:key="result.id"
:href="result.url"
class="flex items-center font-semibold p-2 text-sm text-white leading-none rounded-lg hover:bg-gray-600"
>
<span class="mr-3 py-1 px-2 text-xs text-gray-900 leading-none bg-gray-300 rounded-full">
{{ result.module }}
</span>
{{ result.title }}
</a>
</PopoverPanel>
</Popover>
</form>
</template>
<script setup>
import { ref, watch } from 'vue';
import { Popover, PopoverPanel } from '#headlessui/vue';
import debounce from '#/Helpers/debounce';
import xhr from '#/Helpers/xhr';
const searchTerm = ref('');
const searchResults = ref([]);
const getSearchResults = debounce(async (value) => {
if (value === '') return;
const { data } = await xhr.post('search', { q: value });
const { results } = await data;
searchResults.value = results;
}, 250);
const emptySearchResults = () => {
searchTerm.value = '';
searchResults.value = [];
};
watch(searchTerm, (value) => getSearchResults(value));
</script>
I have 3 button. I want to add red background when one of them is clicked. By default the first button is active. When I click on the second or the third one first button should lose it's background styles red to gray. Also I want to add slide in animation. ex, If I'm currently on first button and click on the 2nd button, background should animate left to right.
My current code:
import React from "react";
const TopOptions = () => {
return (
<>
<div className="flex flex-col border-b shadow-lg">
<div className="flex flex-col px-4">
<p className="text-xs pl-6 pt-4">PUNE</p>
<div className="flex flex-row items-center">
<select className="pl-2">
<option value="Kothrud Outlet">Kothrud Outlet</option>
</select>
</div>
</div>
<div className=" bg-gray-100 my-5 text-center flex flex-row items-center justify-evenly text-sm rounded-md mx-4">
<button className="w-full py-3 font-bold ">DELIVERY</button>
<button className="w-full py-3 font-bold text-gray-600">
TAKEAWAY
</button>
<button className="w-full py-3 font-bold text-gray-600">
DINE IN
</button>
</div>
</div>
</>
);
};
export default TopOptions;
You can use state for each individual button's class. After clicking each button you have to change that button's state as per following:
import React, {useState} from "react";
const TopOptions = () => {
const [btnClass, setBtnClass] = useState({"delivery": "btn-danger", "takeaway": "", "dinein": ""});
const handleClick = (e) => {
// after handling the click event add following code
let updatedBtnClass = {"delivery": "", "takeaway": "", "dinein": ""};
updatedBtnClass[e.target.name] = "btn-danger"; // for red background class
setBtnClass(updatedBtnClass);
}
return (
<>
<div className="flex flex-col border-b shadow-lg">
<div className="flex flex-col px-4">
<p className="text-xs pl-6 pt-4">PUNE</p>
<div className="flex flex-row items-center">
<select className="pl-2">
<option value="Kothrud Outlet">Kothrud Outlet</option>
</select>
</div>
</div>
<div
className=" bg-gray-100 my-5 text-center flex flex-row items-center justify-evenly text-sm rounded-md mx-4">
<button name={"delivery"} className={"w-full py-3 font-bold " + btnClass.delivery} onClick={handleClick}>
DELIVERY
</button>
<button name={"takeaway"} className={"w-full py-3 font-bold " + btnClass.takeaway} onClick={handleClick}>
TAKEAWAY
</button>
<button name={"dinein"} className={"w-full py-3 font-bold " + btnClass.dinein} onClick={handleClick}>
DINE IN
</button>
</div>
</div>
</>
);
};
export default TopOptions;
I would approach this by giving each button a number, initialise a state variable with the number of the first button and change this state on each btn press:
import React, {useState} from "react";
export const TopOptions = () => {
const [clickedButton, setClickedButton] = useState(0);
const [yPos, setYPos] = useState(0);
const buttons = ["DELIVERY", "TAKEAWAY", "DINE IN"];
const speed = 10;
let direction = 0;
const updateState = () => {
setYPos(yPos + (speed * direction));
}
const clickHandler = (index) => {
direction = index > clickedButton ? 1 : -1;
let duration = Math.abs(clickedButton - index) * 1000; // 1sec
setClickedButton(index);
const animRef = requestAnimationFrame(updateState);
setTimeout(() => {
cancelAnimationFrame(animRef);
}, duration);
}
return (
<>
<div className="flex flex-col border-b shadow-lg">
<div className="flex flex-col px-4">
<p className="text-xs pl-6 pt-4">PUNE</p>
<div className="flex flex-row items-center">
<select className="pl-2">
<option value="Kothrud Outlet">Kothrud Outlet</option>
</select>
</div>
</div>
<div
className=" bg-gray-100 my-5 text-center flex flex-row items-center justify-evenly text-sm rounded-md mx-4">
{buttons.map((btn, index) => (
<button className={"w-full py-3 font-bold " + (clickedButton === index ? "" : "text-gray-600")}
onClick={() => {
clickHandler(index);
}} key={"btn" + index}
>{btn}</button>
))}
</div>
<image src={"yourbackgroundimage.jpg"}
style={"position:fixed; width: 100vw; height: 100vh; top: 0; left: " + yPos}/>
{
/*
This is not a god example, just to show how to use yPos now.
assuming left btn is curently active:
if clicked on middle button it lets the image slide from left to right for one second
if clicked on right button it lets the image slide from left to right for two second
*/
}
</div>
</>
);
};