Dropdown menu
For a menu of actions, use a ready-made button. For anything else anchored to an element — a column picker, a filter, a date range — build it on WDropdownMenu.
Action menus
WButtonMoreis the "⋯" button: its default slot is the menu, and any click closes it. Only one menu is open at a time, and on phones it opens as a bottom sheet. The menu is rendered byWModalat the app root, with the slot keeping its context, and a confirm opened from an item withuseOverlaytakes its place — see Anchored confirm. Thetoggleslot swaps the "⋯" for another button, such as aWButtonInput: it getstogglefor the click,attrsfor the button andactivefor its highlight.WButtonDropdownis a button with an arrow next to it: thebuttonslot holds the main action and thecontentslot the others. Thecontentslot getsclose.
Menu items are WButtonMoreItems: a text (or the default slot), an icon, and to or href to make the item a link. A disabled item can explain itself with tooltipText. semanticType colors an item, like the red Delete below.
Last action: none
<template>
<div class="flex items-center gap-6">
<span>Herb bed</span>
<WButtonMore>
<WButtonMoreItem
text="Rename"
:icon="markRaw(IconEdit)"
@click="action = 'Rename'"
/>
<WButtonMoreItem
text="Duplicate"
:icon="markRaw(IconCopy)"
@click="action = 'Duplicate'"
/>
<WButtonMoreItem
text="Archive"
:icon="markRaw(IconArchiveBook)"
disabled
tooltip-text="Only the head gardener can archive a bed"
/>
<WButtonMoreItem
text="Delete"
:icon="markRaw(IconTrash)"
:semantic-type="SemanticType.NEGATIVE"
@click="action = 'Delete'"
/>
</WButtonMore>
<WButtonDropdown>
<template #button>
<WButton @click="action = 'Export as CSV'">
Export as CSV
</WButton>
</template>
<template #content="{close}">
<WButtonMoreItem
text="Export as JSON"
@click="action = 'Export as JSON'; close()"
/>
<WButtonMoreItem
text="Export as PDF"
@click="action = 'Export as PDF'; close()"
/>
</template>
</WButtonDropdown>
<WButtonMore>
<template #toggle="{toggle, active, attrs}">
<WButtonInput
:icon="markRaw(IconSort)"
tooltip-text="Sort beds"
:active="active"
v-bind="attrs"
@click="toggle"
/>
</template>
<WButtonMoreItem
text="Sort by name"
@click="action = 'Sort by name'"
/>
<WButtonMoreItem
text="Sort by planting date"
@click="action = 'Sort by planting date'"
/>
</WButtonMore>
</div>
<p class="text-description mt-4 text-sm">
Last action: {{ action ?? 'none' }}
</p>
</template>
<script lang="ts" setup>
import {markRaw, ref} from 'vue'
import {SemanticType} from 'eco-vue-js/dist/utils/SemanticType'
import WButton from 'eco-vue-js/dist/components/Button/WButton.vue'
import WButtonDropdown from 'eco-vue-js/dist/components/Button/WButtonDropdown.vue'
import WButtonInput from 'eco-vue-js/dist/components/Button/WButtonInput.vue'
import WButtonMore from 'eco-vue-js/dist/components/Button/WButtonMore.vue'
import WButtonMoreItem from 'eco-vue-js/dist/components/Button/WButtonMoreItem.vue'
import IconArchiveBook from 'eco-vue-js/dist/assets/icons/IconArchiveBook'
import IconCopy from 'eco-vue-js/dist/assets/icons/IconCopy'
import IconEdit from 'eco-vue-js/dist/assets/icons/IconEdit'
import IconSort from 'eco-vue-js/dist/assets/icons/IconSort'
import IconTrash from 'eco-vue-js/dist/assets/icons/IconTrash'
const action = ref<string>()
</script>Custom dropdown
WDropdownMenu renders the toggle slot in place and, while isOpen is true, the content slot in a layer on <body>, positioned against the toggle. It has no state of its own: the parent opens and closes it, usually with a WClickOutside around the content to close it on a click elsewhere. It is built on WDropdown, which also works without a toggle, e.g. against a text selection.
<template>
<WDropdownMenu
:is-open="isOpen"
:horizontal-align="HorizontalAlign.LEFT_INNER"
>
<template #toggle>
<WButton
:semantic-type="SemanticType.SECONDARY"
class="w-max"
@click="isOpen = !isOpen"
>
Columns: {{ visible.length }} of {{ COLUMNS.length }}
</WButton>
</template>
<template #content>
<WClickOutside
class="surface-raised my-2 grid w-64 gap-1 rounded-xl p-3 shadow-md border border-line-raised"
@click="isOpen = false"
>
<WCheckbox
v-for="column in COLUMNS"
:key="column"
:model-value="visible.includes(column)"
:title="column"
no-margin
@update:model-value="visible = $event ? [...visible, column] : visible.filter(item => item !== column)"
/>
</WClickOutside>
</template>
</WDropdownMenu>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import {HorizontalAlign} from 'eco-vue-js/dist/utils/HorizontalAlign'
import {SemanticType} from 'eco-vue-js/dist/utils/SemanticType'
import WButton from 'eco-vue-js/dist/components/Button/WButton.vue'
import WCheckbox from 'eco-vue-js/dist/components/Checkbox/WCheckbox.vue'
import WClickOutside from 'eco-vue-js/dist/components/ClickOutside/WClickOutside.vue'
import WDropdownMenu from 'eco-vue-js/dist/components/DropdownMenu/WDropdownMenu.vue'
const COLUMNS = ['Name', 'Species', 'Height', 'Kind', 'Watered']
const isOpen = ref(false)
const visible = ref(['Name', 'Species', 'Height'])
</script>horizontalAlignlines the content up with the toggle:LEFT_INNERandRIGHT_INNERalign their left or right edges,FILLmakes the content as wide as the toggle,CENTERcentres it, and theOUTERvalues put it beside the toggle.- Vertically it opens below the toggle, or above when there's more room there;
topforces above. Thecontentslot getsisTop,isLeftandisRight, to round the right corners or place an arrow. parentElementanchors the content to another element than the toggle.updateAlignpicks the sides again when the toggle moves while the dropdown is open.dropdownClassgoes on the positioned box, for its width or max height.
WDropdownAdaptive opens its content with the overlay manager, like the list's sort and filter menus: a dropdown in the standard w-dropdown-frame, or a bottom sheet on phones. Only one is open at a time, and it emits close when it closes on its own — a click outside, Escape, a swipe, or another dropdown taking its place. Without horizontalAlign it is centered on the toggle with a tip. The sheet starts with the header slot, or else a copy of the toggle with unclickable false. Opened from inside a dropdown with closeOnClick it takes that one's place; from any other, such as a filter, it opens over it.
WMenuItem is a row for the content of a custom menu — the row that WButtonMoreItem and the list menus are built from. It is a button, a router link with to, or a plain link with href (and download for a file). active marks the picked option with a check; false keeps room for the check, so that the rows of a picker line up. loading shows a spinner over it while its action runs.
API
WDropdownMenu
import WDropdownMenu from 'eco-vue-js/dist/components/DropdownMenu/WDropdownMenu.vue'Props
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | required | Shows the menu. |
parentElement | (Pick<Element, "getBoundingClientRect"> & { contextElement?: Element | undefined; }) | — | Element the menu is positioned against. Defaults to the element rendered by the toggle slot. |
dropdownClass | string | — | Classes for the menu's content box. Defaults to w-max. |
Inherited from src/components/Dropdown/types.ts (6)
| Prop | Type | Default | Description |
|---|---|---|---|
horizontalAlign | HorizontalAlign | required | Horizontal placement relative to the parent. When it does not fit the viewport, the next placement in order is tried. |
top | boolean | — | Prefers opening above the parent. |
bottom | boolean | — | Always opens below the parent. |
updateAlign | boolean | — | Picks the placement again as the parent moves, instead of keeping the first one. |
emitUpdate | boolean | — | Emits update:rect on scroll and resize instead of following the parent. |
freeze | boolean | — | Stops following the parent and keeps the current position, e.g. once the parent is leaving the page. |
Events
| Event | Payload | Description |
|---|---|---|
update:rect | — | The parent moved on scroll or resize, with emitUpdate set. |
Slots
| Slot | Props | Description |
|---|---|---|
toggle | { isTop: boolean; unclickable: undefined; } | Element that opens the menu, and that it is positioned against. isTop is true while the menu is open above it. |
content | DropdownDefaultSlotScope | Menu content, rendered while open. isTop, isLeft and isRight tell where it opened relative to the parent, atBottom that it sits in the lower half of the viewport. |
WDropdownAdaptive
import WDropdownAdaptive from 'eco-vue-js/dist/components/DropdownMenu/WDropdownAdaptive.vue'Props
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | required | Shows the dropdown. |
parentElement | (Pick<Element, "getBoundingClientRect"> & { contextElement?: Element | undefined; }) | null | — | Element the dropdown opens at. Defaults to the element rendered by the toggle slot. |
horizontalAlign | HorizontalAlign | — | Aligns the dropdown to the parent without a tip, such as a field's menu with HorizontalAlign.FILL. Otherwise it is centered on the parent with a tip pointing at it. |
dialog | boolean | — | The content is a small form, such as a filter: the header slot is its title on every screen, and the frame pads the content and sizes to it. |
frameClass | string | — | Classes of the dropdown's box, replacing the default frame. |
closeOnClick | boolean | — | A click on the content closes the layer, as in a menu. What is opened from it takes its place; from a dropdown without it, such as a filter, it stays over it. |
Events
| Event | Payload | Description |
|---|---|---|
close | — | The dropdown closed without isOpen turning false — a click outside, Escape, a swipe, or another dropdown taking its place. |
Slots
| Slot | Props | Description |
|---|---|---|
toggle | { isTop: boolean; unclickable: boolean | undefined; } | Element that opens the dropdown, which it points at. isTop is true while the dropdown is open above it. On phones it is repeated at the top of the bottom sheet, unless there is a header — unclickable is true for the one on the page and false for the copy in the sheet. |
header | — | Heading of the bottom sheet on phones, instead of the copy of toggle. With dialog, the title of the dropdown too. |
content | — | Content of the dropdown, which brings its own padding, unless it is a dialog. A click inside closes it with closeOnClick. |
WButtonMore
import WButtonMore from 'eco-vue-js/dist/components/Button/WButtonMore.vue'Props
| Prop | Type | Default | Description |
|---|---|---|---|
icon | SVGComponent | — | Icon of the button. Defaults to three dots. |
disabled | boolean | — | Blocks opening and dims the button. |
anchor | (Pick<Element, "getBoundingClientRect"> & { contextElement?: Element | undefined; }) | — | Element the menu is positioned against instead of the button, aligned to its right edge — for a menu opened at a cursor or row. |
Events
| Event | Payload | Description |
|---|---|---|
close | — | The menu closed — by a click on the button or inside the menu, or by something opened from it taking its place. |
Slots
| Slot | Props | Description |
|---|---|---|
default | — | Menu items, usually WButtonMoreItem. A click inside closes the menu. Only one menu is open at a time, and on phones it is a bottom sheet. A confirm opened from an item with useOverlay takes the menu's place at the same anchor. |
toggle | { toggle: () => void; active: boolean; attrs: { 'aria-expanded': boolean; }; } | Replaces the three-dots button, such as with WButtonInput: call toggle on click, bind attrs to the button and active to its highlight — active stays off while the menu is open at a custom anchor. |
WButtonDropdown
import WButtonDropdown from 'eco-vue-js/dist/components/Button/WButtonDropdown.vue'Props
| Prop | Type | Default | Description |
|---|---|---|---|
semanticType | SemanticType | — | Color scheme of the arrow button. |
leftToggle | boolean | — | Puts the arrow button before the button slot instead of after it. |
disabled | boolean | — | Disables the arrow button. Buttons in the button slot keep their own state. |
tooltipText | string | — | Tooltip over the whole button row. |
horizontalAlign | HorizontalAlign | HorizontalAlign.LEFT_INNER | Horizontal placement relative to the parent. When it does not fit the viewport, the next placement in order is tried. |
Slots
| Slot | Props | Description |
|---|---|---|
button | — | Buttons joined to the arrow button. They keep their own click handlers. |
content | { close: () => void; } | Menu content, usually WButtonMoreItem. A click inside closes the menu, and so does close. On phones it is a bottom sheet. |
WMenuItem
import WMenuItem from 'eco-vue-js/dist/components/MenuItem/WMenuItem.vue'Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | — | Grays the item out and ignores clicks. |
href | string | — | URL of a plain link, when there is no to. |
download | string | — | download attribute of the href link — the file name to save it as. |
replace | boolean | — | With to, replaces the current history entry instead of pushing a new one. |
target | "_self" | "_blank" | "_parent" | "_top" | — | Native target of the link, with href or to. |
rel | string | — | Native rel of the link, with href or to. |
active | boolean | — | Marks the item as picked, with primary text and a check. false leaves room for the check, to line up with picked items. |
tooltipText | string | — | Tooltip text on the left of the item. |
loading | boolean | — | Shows a spinner over the item and ignores clicks. |
semanticType | SemanticType | SemanticType.PRIMARY | Color of the item. Types other than primary and secondary color the text at rest — a red "Delete". |
to | RouteLocationRaw | — | Router location — renders a router link. Needs vue-router installed in the app. |
Events
| Event | Payload | Description |
|---|---|---|
click | (value: MouseEvent) | The item was clicked, unless it is disabled or loading. |
Slots
| Slot | Props | Description |
|---|---|---|
default | — | Content of the item, laid out in a row. |
WButtonMoreItem
import WButtonMoreItem from 'eco-vue-js/dist/components/Button/WButtonMoreItem.vue'Props
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | — | Label. The default slot replaces it. |
icon | SVGComponent | — | Icon after the label. The icon slot replaces it. |
disabled | boolean | — | Blocks clicks and dims the item. |
href | string | — | Renders the item as a link to this URL. |
download | string | — | Native download attribute, with href. |
replace | boolean | — | With to, replaces the current history entry instead of pushing a new one. |
target | "_self" | "_blank" | "_parent" | "_top" | — | Native target of the link, with href or to. |
rel | string | — | Native rel of the link, with href or to. |
tooltipText | string | — | Tooltip over the item. |
semanticType | SemanticType | — | Color of the item. Types other than primary and secondary color the text at rest — a red "Delete". |
to | RouteLocationRaw | — | Router location — renders a router link. Needs vue-router installed in the app. |
Events
| Event | Payload | Description |
|---|---|---|
click | (value: MouseEvent) | The item was clicked. |
Slots
| Slot | Props | Description |
|---|---|---|
default | — | Label. Replaces text. |
icon | — | Icon after the label. Replaces icon. |