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 ​

  • WButtonMore is 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 by WModal at the app root, with the slot keeping its context, and a confirm opened from an item with useOverlay takes its place — see Anchored confirm. The toggle slot swaps the "⋯" for another button, such as a WButtonInput: it gets toggle for the click, attrs for the button and active for its highlight.
  • WButtonDropdown is a button with an arrow next to it: the button slot holds the main action and the content slot the others. The content slot gets close.

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.

Herb bed

Last action: none

vue
<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.

vue
<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>
  • horizontalAlign lines the content up with the toggle: LEFT_INNER and RIGHT_INNER align their left or right edges, FILL makes the content as wide as the toggle, CENTER centres it, and the OUTER values put it beside the toggle.
  • Vertically it opens below the toggle, or above when there's more room there; top forces above. The content slot gets isTop, isLeft and isRight, to round the right corners or place an arrow.
  • parentElement anchors the content to another element than the toggle. updateAlign picks the sides again when the toggle moves while the dropdown is open.
  • dropdownClass goes 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 ​

ts
import WDropdownMenu from 'eco-vue-js/dist/components/DropdownMenu/WDropdownMenu.vue'

Props ​

PropTypeDefaultDescription
isOpenbooleanrequiredShows the menu.
parentElement(Pick<Element, "getBoundingClientRect"> & { contextElement?: Element | undefined; })—Element the menu is positioned against. Defaults to the element rendered by the toggle slot.
dropdownClassstring—Classes for the menu's content box. Defaults to w-max.
Inherited from src/components/Dropdown/types.ts (6)
PropTypeDefaultDescription
horizontalAlignHorizontalAlignrequiredHorizontal placement relative to the parent. When it does not fit the viewport, the next placement in order is tried.
topboolean—Prefers opening above the parent.
bottomboolean—Always opens below the parent.
updateAlignboolean—Picks the placement again as the parent moves, instead of keeping the first one.
emitUpdateboolean—Emits update:rect on scroll and resize instead of following the parent.
freezeboolean—Stops following the parent and keeps the current position, e.g. once the parent is leaving the page.

Events ​

EventPayloadDescription
update:rect—The parent moved on scroll or resize, with emitUpdate set.

Slots ​

SlotPropsDescription
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.
contentDropdownDefaultSlotScopeMenu 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 ​

ts
import WDropdownAdaptive from 'eco-vue-js/dist/components/DropdownMenu/WDropdownAdaptive.vue'

Props ​

PropTypeDefaultDescription
isOpenbooleanrequiredShows the dropdown.
parentElement(Pick<Element, "getBoundingClientRect"> & { contextElement?: Element | undefined; }) | null—Element the dropdown opens at. Defaults to the element rendered by the toggle slot.
horizontalAlignHorizontalAlign—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.
dialogboolean—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.
frameClassstring—Classes of the dropdown's box, replacing the default frame.
closeOnClickboolean—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 ​

EventPayloadDescription
close—The dropdown closed without isOpen turning false — a click outside, Escape, a swipe, or another dropdown taking its place.

Slots ​

SlotPropsDescription
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 ​

ts
import WButtonMore from 'eco-vue-js/dist/components/Button/WButtonMore.vue'

Props ​

PropTypeDefaultDescription
iconSVGComponent—Icon of the button. Defaults to three dots.
disabledboolean—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 ​

EventPayloadDescription
close—The menu closed — by a click on the button or inside the menu, or by something opened from it taking its place.

Slots ​

SlotPropsDescription
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 ​

ts
import WButtonDropdown from 'eco-vue-js/dist/components/Button/WButtonDropdown.vue'

Props ​

PropTypeDefaultDescription
semanticTypeSemanticType—Color scheme of the arrow button.
leftToggleboolean—Puts the arrow button before the button slot instead of after it.
disabledboolean—Disables the arrow button. Buttons in the button slot keep their own state.
tooltipTextstring—Tooltip over the whole button row.
horizontalAlignHorizontalAlignHorizontalAlign.LEFT_INNERHorizontal placement relative to the parent. When it does not fit the viewport, the next placement in order is tried.

Slots ​

SlotPropsDescription
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 ​

ts
import WMenuItem from 'eco-vue-js/dist/components/MenuItem/WMenuItem.vue'

Props ​

PropTypeDefaultDescription
disabledboolean—Grays the item out and ignores clicks.
hrefstring—URL of a plain link, when there is no to.
downloadstring—download attribute of the href link — the file name to save it as.
replaceboolean—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.
relstring—Native rel of the link, with href or to.
activeboolean—Marks the item as picked, with primary text and a check. false leaves room for the check, to line up with picked items.
tooltipTextstring—Tooltip text on the left of the item.
loadingboolean—Shows a spinner over the item and ignores clicks.
semanticTypeSemanticTypeSemanticType.PRIMARYColor of the item. Types other than primary and secondary color the text at rest — a red "Delete".
toRouteLocationRaw—Router location — renders a router link. Needs vue-router installed in the app.

Events ​

EventPayloadDescription
click(value: MouseEvent)The item was clicked, unless it is disabled or loading.

Slots ​

SlotPropsDescription
default—Content of the item, laid out in a row.

WButtonMoreItem ​

ts
import WButtonMoreItem from 'eco-vue-js/dist/components/Button/WButtonMoreItem.vue'

Props ​

PropTypeDefaultDescription
textstring—Label. The default slot replaces it.
iconSVGComponent—Icon after the label. The icon slot replaces it.
disabledboolean—Blocks clicks and dims the item.
hrefstring—Renders the item as a link to this URL.
downloadstring—Native download attribute, with href.
replaceboolean—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.
relstring—Native rel of the link, with href or to.
tooltipTextstring—Tooltip over the item.
semanticTypeSemanticType—Color of the item. Types other than primary and secondary color the text at rest — a red "Delete".
toRouteLocationRaw—Router location — renders a router link. Needs vue-router installed in the app.

Events ​

EventPayloadDescription
click(value: MouseEvent)The item was clicked.

Slots ​

SlotPropsDescription
default—Label. Replaces text.
icon—Icon after the label. Replaces icon.