I am using tailwind ui to create a page using Vue.js - v.2
I've looked over this SO thread and I believe I have my transitions in the correct spot.
When I click the menu to show the <MobileSidebar> component, everything works great. When I close the <MobileSidebar> component, the component is just removed from the screen. I do not see the sidebar "slide" off of the browser.
I'm passing the open state as a prop; emitting the status. That seems to be working fine as well.
How can I allow the transition to render before the element is hidden/removed from view?
App.vue
<template>
<MobileSidebar
:open="sidebarOpen"
#toggle-sidebar="toggleSidebar"
/>
</template>
<script>
...
data: () => ({
sidebarOpen: false,
}),
methods: {
toggleSidebar() {
this.sidebarOpen = !this.sidebarOpen;
},
}
</script>
MobileSidebar.vue
<template>
<div
class="fixed inset-0 flex z-40 lg:hidden"
role="dialog"
aria-modal="true"
v-show="open"
>
<transition
enter-active-class="transition-opacity ease-in-out duration-300"
enter-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-opacity ease-in-out duration-300"
leave-class="opacity-100"
leave-to-class="opacity-0"
>
<div
class="fixed inset-0 bg-gray-600 bg-opacity-75"
aria-hidden="true"
v-show="open"
></div>
</transition>
<transition
enter-active-class="transition ease-in-out duration-300 transform"
enter-class="-translate-x-full"
enter-to-class="translate-x-0"
leave-active-class="transition ease-in-out duration-300 transform"
leave-class="translate-x-0"
leave-to-class="-translate-x-full"
>
<div
class="relative flex-1 flex flex-col max-w-xs w-full bg-white focus:outline-none"
v-show="open"
>
...
</div>
</transition>
</div>
</template>
<script>
...
methods: {
toggleSidebar() {
this.$emit("toggle-sidebar");
},
},
props: {
open: {
type: Boolean,
required: true,
default: false,
},
},
</script>
The issue is still that I didn't have a <transition> wrapping the outter-most div.
This is working properly:
MobileSidebar.vue
<template>
<transition
enter-active-class="transition-opacity ease-linear duration-300"
enter-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-opacity ease-linear duration-300"
leave-class="opacity-100"
leave-to-class="opacity-0"
>
<div
class="fixed inset-0 flex z-40 lg:hidden"
role="dialog"
aria-modal="true"
v-show="open"
>
...
</div>
</transition>
Related
<script setup>
import Logo from "../assets/svgs/Logo";
import Menu from "../assets/svgs/Menu";
const hover = Boolean
let boxTop
if (hover === true) {
boxTop = 'boxTop--0'
} else {
boxTop = 'boxTop--20'
}
</script>
<template>
<nav class=" w-full fixed flex items-center">
<div class="w-full flex h-full">
<div class="px-6 flex items-center justify-center h-full relative "
#mouseover="hover = true" #mouseleave="hover = false"
>
<Menu class="w-10 fill-white"/>
<div class="absolute w-full h-full bg-black" :style="boxTop"></div>
</div>
</div>
</nav>
</template>
I want to make it so when the mouse enters the div, the css class "boxTop--0" gets added, and, when the mouse leaves the div, the css class "boxTop--20" gets added. However, I can't figure out how to do it with compositon api.
I would be very greatful for any help.
Please take a look at following snippet:
You can create reactive state (boxTop) with ref and create function for changing state.
const { ref } = Vue
const app = Vue.createApp({
el: "#demo",
setup() {
const boxTop = ref(null)
const hover = (val) => {
if (val) {
boxTop.value = 'boxTop--0'
} else {
boxTop.value = 'boxTop--20'
}
}
return { boxTop, hover }
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3"></script>
<div id="demo">
<nav class=" w-full fixed flex items-center">
<div class="w-full flex h-full">
<div class="px-6 flex items-center justify-center h-full relative "
#mouseenter="hover(true)" #mouseleave="hover(false)"
>
hover me
<div class="absolute w-full h-full bg-black" :style="boxTop">
{{ boxTop }}
</div>
</div>
</div>
</nav>
</div>
Something like this should do the trick, using a computed.
<script setup>
import Menu from "../assets/svgs/Menu"
const hover = ref(true)
const boxTop = computed(() => hover.value ? 'boxTop--0' : 'boxTop--20')
</script>
<template>
<nav class="fixed flex items-center w-full">
<div class="flex w-full h-full">
<div class="relative flex items-center justify-center h-24 h-full px-6 bg-green-500" #mouseover="hover = true" #mouseleave="hover = false">
<Menu class="w-10 fill-white" />
<div class="absolute w-full h-full bg-black" :class="boxTop"></div>
</div>
</div>
</nav>
</template>
<style scoped>
.boxTop--0 {
background-color: red;
}
.boxTop--20 {
background-color: teal;
}
</style>
I've used :class="boxTop" and some classes to help me debug what you have tried.
I tried adding a simple rectangle on top of a canvas using Fabric.js in Vue 3
I was able to load rectangle on top of canvas inside a page, where as it doesn't seem to work inside a component.
I tried putting a 2 second timeout as well in component, but it doesn't render anything.
Is there any workaround or am I making any mistake?
<template>
<TransitionRoot as="template" :show="showCanvas">
<Dialog as="div" class="fixed z-10 inset-0" #close="showCanvas = false">
<div class="w-full" style="width: 60%!important; height:auto">
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0"
enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
<DialogOverlay class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</TransitionChild>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"></span>
<TransitionChild as="template" enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
<div
class="bg-white">
<div>
<div class="mt-3 text-center sm:mt-5">
<!-- This example requires Tailwind CSS v2.0+ -->
<div class="fixed inset-0 overflow-y-auto z-50 flex justify-center items-center">
<div class=" bg-white m-auto rounded-lg shadow-md" role="dialog" aria-modal="true"
aria-labelledby="modal- headline" style="width: 550px !important;height:auto;">
<canvas id="canvas" ></canvas>
</div>
</div>
<div class="flex w-full mx-auto absolute bottom-56 justify-center z-50">
<button type="button"
class="w-50 mr-2 inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:col-start-2 sm:text-sm"
>
Save
</button>
<button type="button"
class="mt-3 w-50 ml-2 inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:col-start-1 sm:text-sm"
#click="closePopup" ref="cancelButtonRef">
Cancel
</button>
</div>
</div>
</div>
</div>
</TransitionChild>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { fabric } from 'fabric'
import { ref, watch, reactive, onMounted } from 'vue';
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '#headlessui/vue'
export default {
components:
{
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
},
props: {
showCanvas: {
required: true,
type: Boolean
},
},
setup(props, context) {
reactive(props.showCanvas) // Make the prop reactive so that we can watch for changes
let canvas = undefined
watch(() => props.showCanvas, (selection, prevSelection) => {
if (selection == true) {
bindImage()
}
})
function bindImage()
{
canvas = new fabric.Canvas('canvas');
setTimeout(function(){
var rectangle = new fabric.Rect({
width: 300,
height: 200,
fill: '',
stroke: 'green',
strokeWidth: 5
});
console.log("Rectangle>>>>", rectangle);
// Render the Rect in canvas
canvas.add(rectangle);
}, 2000);
}
function closePopup()
{
context.emit("closeCanvas")
}
return {
closePopup,
}
}
}
</script>
<style>
</style>
You should put them into the onMounted block, for example:
<template>
<canvas width="600" height="600" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>
<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'
function init() {
let canvas = new fabric.Canvas('canvas') // 实例化fabric,并绑定到canvas元素上
// 圆
let circle = new fabric.Circle({
left: 100,
top: 100,
radius: 50,
})
canvas.add(circle)
}
onMounted(() => {
init()
})
</script>
I have a simple Vue3 application that is making heavy use of TailwindUI components. I'm trying to place a TinyMce editor inside of a slide over component and that works fine. The issue is the entry animation.
On first entry, it slides in like it's supposed to. However, if it is closed and reopened the entry animation is gone. The whole time the exit animation continues to work without issue. Is there a way I can do this and keep the animation intact?
Here is a CodeSandBox with the issue reproduced in it's simplest form.
Here is the relevant code:
App.vue
<template>
<button #click="open = true">Open Menu</button>
<SlideOver :open="open" #close="open = false" />
</template>
<script>
import { ref } from "vue";
import SlideOver from "./components/slide-over.vue";
export default {
name: "App",
components: {
SlideOver,
},
setup() {
const open = ref(false);
return { open };
},
};
</script>
slide-over.vue
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<TransitionRoot as="template" :show="open">
<Dialog
as="div"
static
class="fixed inset-0 overflow-hidden"
#close="$emit('close')"
:open="open"
>
<div class="absolute inset-0 overflow-hidden">
<DialogOverlay class="absolute inset-0" />
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
<TransitionChild
as="template"
enter="transform transition ease-in-out duration-500 sm:duration-700"
enter-from="translate-x-full"
enter-to="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leave-from="translate-x-0"
leave-to="translate-x-full"
>
<div class="w-screen max-w-md">
<div
class="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll"
>
<div class="px-4 sm:px-6">
<div class="flex items-start justify-between">
<DialogTitle class="text-lg font-medium text-gray-900">
Panel title
</DialogTitle>
<div class="ml-3 h-7 flex items-center">
<button
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
#click="$emit('close')"
>
<span class="sr-only">Close panel</span>
<XIcon class="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div class="mt-6 relative flex-1 px-4 sm:px-6">
<TinyMceEditor api-key="no-api-key" />
</div>
</div>
</div>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
} from "#headlessui/vue";
import { XIcon } from "#heroicons/vue/outline";
import TinyMceEditor from "#tinymce/tinymce-vue";
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
XIcon,
TinyMceEditor,
},
props: {
open: {
type: Boolean,
default: false,
},
},
setup() {},
};
</script>
In my opinion, this is a problem with loading the TinyMce Editor (I don't know exactly what the problem is). I added a delay in loading the editor after opening the modal using watchEffect based on the props open with setTimeout in it and v-if on the TinyMceEditor tag. It may not be a perfect and aesthetic solution, but the animation works smoothly.
Here is a code in codesandbox.io.
And code here: slide-over.vue (App.vue stays the same)
<template>
<TransitionRoot as="template" :show="open">
<Dialog
as="div"
static
class="fixed inset-0 overflow-hidden"
#close="$emit('close')"
:open="open"
>
<div class="absolute inset-0 overflow-hidden">
<DialogOverlay class="absolute inset-0" />
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
<TransitionChild
as="template"
enter="transform transition ease-in-out duration-500 sm:duration-700"
enter-from="translate-x-full"
enter-to="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leave-from="translate-x-0"
leave-to="translate-x-full"
>
<div class="w-screen max-w-md">
<div
class="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll"
>
<div class="px-4 sm:px-6">
<div class="flex items-start justify-between">
<DialogTitle class="text-lg font-medium text-gray-900">
Panel title
</DialogTitle>
<div class="ml-3 h-7 flex items-center">
<button
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
#click="$emit('close')"
>
<span class="sr-only">Close panel</span>
<XIcon class="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div class="mt-6 relative flex-1 px-4 sm:px-6">
<TinyMceEditor v-if="loadEditor" api-key="no-api-key" />
</div>
</div>
</div>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { ref, watchEffect } from "vue";
import {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
} from "#headlessui/vue";
import { XIcon } from "#heroicons/vue/outline";
import TinyMceEditor from "#tinymce/tinymce-vue";
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
XIcon,
TinyMceEditor,
},
props: {
open: {
type: Boolean,
default: false,
},
},
setup(props) {
const loadEditor = ref(false);
watchEffect(() => {
if (props.open) {
setTimeout(function () {
loadEditor.value = true;
}, 10);
} else {
loadEditor.value = false;
}
});
return { loadEditor };
}
};
</script>
I'm using this code
var ComponentClass = Vue.extend(Notification);
var instance = new ComponentClass({
propsData: {
notification: notification
}
});
instance.$mount();
this.$refs.notificationContainer.appendChild(instance.$el);
to programmatically create a component when a new notification is received through pusher, this is my notification component
<template>
<transition
enter-active-class="transition ease-out duration-100 transform"
enter-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="transition ease-in duration-75 transform"
leave-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<div class="bg-gray-100 px-4 py-2 border-0 rounded relative mb-4 text-gray-700 flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<img
class="h-10 w-10 rounded-full"
:src="notification.user.profile_photo_url"
:alt="notification.user.username"
>
</div>
<span class="inline-block align-middle flex-1 ml-2 mr-4">
<strong>{{notification.user.username}}</strong> {{notification.message}}
</span>
<button class="focus:outline-none">
<i
class="fad fa-times text-red-600"
></i>
</button>
</div>
</transition>
</template>
<script>
export default {
props: {
notification: Object
}
}
</script>
but for some reason the transition doesnt run when the component is created
Fixed by adding appear to the transition element
I'm, using Vue and Nuxt to build an application, however, I'm getting the following error on compiling the app.
Here is my code:
<template>
<nav class="flex items-center bg-green w-screen justify-between flex-wrap p-6">
<section class="flex items-center flex-no-shrink mr-6">
<nuxt-link to="/" class="block lg:inline-block text-white uppercase lg:mt-0 no-underline mr-4">Ben John Bagley</nuxt-link>
</section>
<section class="block lg:hidden">
<button class="flex items-center p-2" #click="toggle">
<i class="fal fa-bars text-white"></i>
</button>
</section>
<section class="block text-left lg:flex w-full lg:items-center lg:w-auto lg:text-right" :class="open ? 'block': 'hidden'">
<nuxt-link to="/about" class="block mt-3 lg:inline-block text-white lg:mt-0 no-underline mr-4">About</nuxt-link>
<nuxt-link to="/works" class="block mt-3 lg:inline-block text-white lg:mt-0 no-underline mr-4">Works</nuxt-link>
<nuxt-link to="/contact" class="inline-block text-sm px-4 py-2 leading-none text-white border border-green-dark bg-green-dark hover:bg-green-darker hover:border-green-darker rounded no-underline mt-4 mr-4 lg:mt-0">Contact</nuxt-link>
</section>
</nav>
</template>
<script>
export default {
data: function () {
open: false,
},
methods: {
toggle() {
this.open = !this.open
}
}
}
</script>
I'm sure it's a simple fix, however, I'm sure the comma is needed here.
This
data: function () {
open: false,
}
should be
data: function () {
return {
open: false
};
}
data is a function so you have to return a value which in this case is an object.