So i have this case, where i have a list that is hidden by default, and when you click a button it toggles the visibility.
this is my controller
import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["collapsible", "information"]
toggle() {
this.element.classList.toggle("collapsible__open");
}
connect() {
this.ensureCollapsibleHidden();
super.connect();
}
ensureCollapsibleHidden() {
this.element.classList.remove("collapsible__open");
this.collapsibleTarget.classList.add("collapsible");
}
}
this works perfectly fine.
But, inside the collapsible list i have other collapsible items.
this is what i want to achieve
this is the code
header[
class="w-full flex justify-end items-center relative"
data-controller="collapse"
]
button[
data-action="click->collapse#toggle"
]
| show
div[class="collapsible "]
header
h1[class="text-md" style="color: #8D95B6" ]
= t 'informational.actions'
ul[class="my-2 text-sm space-y-3 flex flex-col"]
li[
class="flex w-full justify-between items-center text-black"
data-controller="collapse"
data-action="mouseover->collapse#toggle mouseout->collapse#toggle" <------------ this mess stuff up
]
= t 'inbox.snooze'
svg class="w-4 h-4" fill="none" stroke="currentColor" viewbox=("0 0 24 24") xmlns="http://www.w3.org/2000/svg"
path d=("M9 5l7 7-7 7") stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
Inner collapsible component
ul[
class="collapsible"
style="right: -9.7rem"
]
li[class="block px-4 py-1"]
| 1 Hour
li[class="block px-4 py-1"]
| 4 Hours
with the current implementation, both collapsibles show when i click on show
I'm assuming you want a first click to show the first menu and then a second click to show the sub-menu?
If so, you'll want to add a data element like aria-expanded to the first menu and then set it to true when it's first expanded. Then use that as a condition in your stimulus controller to set the visibility of the second sub-menu to true only when the first one has aria-expanded set to true and you detect a click.
Related
I am using vue to make a menu which can show and hide the description when click the title.
The original menu is like this showing the title
The expanded menu is like this expanding the menu description
<li class="cursor-pointer p-2 flex" v-for="(menu, index) in menus" :key="index" >
<div class="w-full flex items-center justify-between px-2" #click="handleSelect(menu,index)">
<div class="text-sm w-full ">
<i class="el-icon-document" ></i> {{ menu.name }}
</div>
</div>
<!-- menu show and hide-->
<ul class="py-4 px-1 rounded-b border-gray-100" v-show="menu.showed">
{{ menu.description }}
</ul>
</li>
handleSelect(menu,index){
console.log('select',index,menu.showed);
this.menus[index].showed=!menu.showed
},
menus:[
{
name:'Introduction',
showed: false,
decription:''
},
{
name:'Feedback Is Important',
showed: false,
decription:''
},
{
name:'Client Briefing and Debriefing',
showed: false,
decription:''
},
]
my expected result is that the expanded status of the menu could show and hide when clicking the menu title. However, clicking the first menu could change the 'showed' variable but doesn't show the expansion, only clicking the second menu will show the first menu.
It seems like the v-show not working, any solution or idea to solve it or better way to deal with menu show-up.
Your data needs to be reactive. Put your menus array inside the data section when using options API , or inside a setup script when using composition API
I am creating a component in Vue3 called <Dropdown>.
The Dropdown component uses other components in turn: Menu, MenuItems, MenuItem and MenuButton from the headlessui/vue library.
My idea is that you can put any content in the Dropdown, that's why I created a slot called #contentdropdown.
The problem is that when I pass this slot content to the Dropdown component, Vue gives me the following error:
< MenuItems /> is missing a parent < Menu /> component
This is my Dropdown componente code:
<template>
<Menu as="div" class="relative inline-block text-left">
<div>
<MenuButton class="btn inline-flex justify-center w-full text-sm" :class="'btn-'+variant">
Options
<ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" aria-hidden="true" />
</MenuButton>
</div>
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
<slot name="contentdropdown"></slot>
</transition>
</Menu>
</template>
<script>
import { Menu, MenuButton } from '#headlessui/vue'
import { ChevronDownIcon } from '#heroicons/vue/solid'
import { vVariantProp } from '../../../../constants'
import { reactive, computed } from 'vue';
export default {
name: 'dropdown',
props: {
...vVariantProp,
},
setup(props) {
props = reactive(props);
return {
}
},
};
</script>
Why does it need the parent component called Menu?, if in fact I am already painting the slot inside the component and also importing it inside the Dropdown component.
This is how I pass to the Dropdown component the content through its #contentdropdown slot:
<Dropdown v-bind="{'variant':'primary'}">
<template #contentdropdown>
<MenuItems class="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 class="py-1">
<MenuItem>
Subitem1
</MenuItem>
<MenuItem>
Subitem2
</MenuItem>
</div>
</MenuItems>
</template>
</Dropdown>
The error <MenuItems /> is missing a parent <Menu /> component is not a Vue specific error. It is an error thrown by headlessui/vue - source
MenuItems component (as well as MenuButon etc - see doc) is designed to be used inside Menu component. It is using inject to tap into state provideded by the Menu component. There is nothing you can do about it - it is designed that way
Problem is that slot content (everything inside <template #contentdropdown> in the last code example) in Vue is always rendered in parent scope
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope.
This means that MenuItems rendered as slot content has no access to data provideded by the Menu component rendered inside your Dropdown component
I don't see any way to overcome this limitation. You'll need to change your design (or describe your use case to headlessui/vue maintainers and ask them to implement alternative approach to share MenuContext with child components - for example using slot props)
I have a VueJS view that creates collapsed contents using Bootstrap Vue Collapse Component.
The data is dynamic and can contains hundreds of items, which is why you see in the code below it was created via a v-for loop in Vue.
<div class="inventory-detail" v-for="(partNumberGroup,index) in inventory" :key="index" >
<b-button block v-b-toggle="partNumberGroup.partNumber" v-bind:id="partNumberGroup.partNumber" variant="primary"
#click="(evt) =>{isActive = !isActive && evt.target.id == partNumberGroup.partNumber}">
<i v-bind:id="partNumberGroup.partNumber" class="float-right fa" :class="{ 'fa-plus': !isActive, 'fa-minus': isActive }"></i>
{{ partNumberGroup.partNumber }}
</b-button>
<div class="inventory-detail__card" v-for="item in partNumberGroup.items">
<b-collapse v-bind:id="partNumberGroup.partNumber" >
<b-card>
<!--Accordion/Collapse content -->
</b-card>
</b-collapse>
</div>
</div>
This works fairly well in that I can individually expand and collapse each content separately. However, the one issue I'm facing is each time I click the icon fa-minus (-) orfa-plus (+), all of them changed as per the images below.
Any tips on how I should implementing this? in my code I tried the dynamic CSS class switching but I still lack the ability to switch on specific element.
I feel like the solution to this is to somehow conditionally apply dynamic CSS class or somehow able to use the attribute 'aria-expanded'.
You can try something like this. Whenever somebody clicks on the icon, set its index as activeIndex (using the setActiveIndex method). Then you can set the class accordingly by comparing the activeIndex with current index
<i
#click="setActiveIndex(index)"
v-bind:id="partNumberGroup.partNumber"
class="float-right fa"
:class="{ 'fa-plus': !isActive(index), 'fa-minus': isActive(index) }">.
</i>
then in the script part:
...
data() {
return {
activeIndex: -1
}
},
methods: {
/* set active index on click */
setActiveIndex(index) {
this.activeIndex = index;
},
/* check if index is active or not */
isActive(index) {
return index === this.activeIndex;
}
}
I'm using the Algolia search widget (VueJS) from Algolia, and everything works fine, but on focus the search input field displays an unwanted inner border (outline) that I cannot remove so far.
I cannot directly define 'outline:none;' on the ais-search-box element, because that has no effect (it's being replaced in the DOM I think is the reason).
How can I get rid of this inner border/outline?
My code is:
<ais-search-box class="shadow appearance-none border rounded px-3 text-gray-700 mb-2 leading-tight focus:outline-none focus:shadow-outline" placeholder="Search" autofocus>
<div slot="submit-icon">
<i class="fas fa-search text-xl p-2 text-blue-400"></i>
</div>
</ais-search-box>
I have some similar issues, when i work with PrimeVue in my Vue.js project. Most of the components are replaced in the DOM during render. My approach is to identify the type or class of this element after it is rendered. Then I give it´s parent a class and construct my CSS non scoped in my main vue-file.
If you give your parent the class shadow, your css could look like this:
.shadow input[type="text"]:focus {
outline: none;
}
I have below navbar in Vue. When I pass an array to it it doesn't show the menu.
<template>
<div>
<div class="mt-5 px-2">
<li v-for="item in items" :key="item.name">
<a
href="item.link"
class="group flex items-center px-2 py-2 text-base leading-6 font-medium rounded-md text-white bg-gray-900 focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150"
>
<svg
class="mr-4 h-6 w-6 text-gray-300 group-hover:text-gray-300 group-focus:text-gray-300 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6"
/>
</svg>
{{ item.name }}
</a>
</li>
</div>
</div>
<!--
items: [
{ name: 'Foo' },
{ link: 'Bar' }
]
-->
</template>
<script>
export default {
name: 'Nav',
props: ['items'],
data() {
return {
parents: [],
};
},
};
</script>
<style scoped></style>
Your items-array you show as example data is unsuitable for the template:
the items-array contains two objects, one with a single property name, the other with a single property link. But based on the template it looks as if name and link should be part of the same object, e.g.:
items: [
{ name: 'Foo1', 'link': 'Bar1' },
{ name: 'Foo2', 'link': 'Bar2' }
]
With the original array passed as by your example, there will likely be an error shown in the browser's console due to item.link not being defined for the first object, and thus probably stopping the rendering process of Vue.
The main problem in your example is that you named your component Nav, but HTML5 already has this element which will cause a collision between your component and HTML's element, just rename into something else.
After you change the name make sure that the array that you pass to that component looks like this - [{name: 'a', link: 'http://a.com'}, {name: 'b', link: 'http://b.com'}].