This is a Vue component, BpDropdown, for implementing dropdowns. It’s used in the navigation components in conjunctions with BpDirectional.
hoverable
, how long to delay before closing after the the mouse leaves the dropdown.
<!-- Default -->
<bp-dropdown label="Our Company" id="js-about">
<nav class="dropdown__menu menu">
<ul class="menu__list">
<li class="menu__item">
<a class="menu__link" href=""><span>Sit a</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Amet voluptas?</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Sit exercitationem?</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Dolor magni</span></a>
</li>
</ul>
</nav>
</bp-dropdown>
<!-- Hover -->
<bp-dropdown-hoverable label="Our Company" id="js-about" :delay="250">
<nav class="dropdown__menu menu">
<ul class="menu__list">
<li class="menu__item">
<a class="menu__link" href=""><span>Sit a</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Amet voluptas?</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Sit exercitationem?</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Dolor magni</span></a>
</li>
</ul>
</nav>
</bp-dropdown-hoverable>
<!-- Large -->
<bp-dropdown label="Our Company" id="js-about">
<div class="container linkGroups">
<input type="text">
<div class="linkGroup">
<div class="linkGroup__heading">Seacoast</div>
<ul class="linkGroup__list -wide">
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Bar Harbor</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Camden</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Castine</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Damariscotta</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Kennebunkport</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Old Orchard Beach</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Portland</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Rockland</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Stonington</a></li>
</ul>
</div>
<div class="linkGroup">
<div class="linkGroup__heading">Parks</div>
<ul class="linkGroup__list">
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Acadia</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Baxter</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Camden Hills</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Popham Beach</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Quoddy Head</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Reid</a></li>
</ul>
</div>
<div class="linkGroup">
<div class="linkGroup__heading">Activities</div>
<ul class="linkGroup__list">
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Agricultural Attractions</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Arts & Culture</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Camping</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Fishing</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Guide Services</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Hiking & Climbing</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Hunting</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Lighthouses & Sightseeing</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Shopping</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Spas, Health & Wellness</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Wildlife Watching</a></li>
<li class="linkGroup__item"><a class="linkGroup__link" href="#">Winter Activities</a></li>
</ul>
</div>
</div>
</bp-dropdown>
<!-- Link -->
<bp-dropdown-link href="/about" label="Our Company" id="js-about">
<nav class="dropdown__menu menu">
<ul class="menu__list">
<li class="menu__item">
<a class="menu__link" href=""><span>Sit a</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Amet voluptas?</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Sit exercitationem?</span></a>
</li>
<li class="menu__item">
<a class="menu__link" href=""><span>Dolor magni</span></a>
</li>
</ul>
</nav>
</bp-dropdown-link>
<template>
<div
class="dropdown"
:class="{'-open': isOpen}"
>
<button
ref="button"
:aria-controls="id"
:aria-expanded="String(isOpen)"
class="dropdown__button"
:class="labelClass"
@click.prevent="toggle"
>
<slot name="button">
{{ label }}
<svg
class="dropdown__icon"
viewBox="0 0 24 24"
stroke-width="1"
stroke="currentColor"
fill="none"
><polyline points="6 9 12 15 18 9" /></svg>
</slot>
</button>
<div
v-show="isOpen"
:id="id"
ref="dropdown"
class="dropdown__content"
>
<slot />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import useOpenable from '@resources/js/components/UseOpenable.vue'
const dropdown = ref(null)
const button = ref(null)
defineProps({
id: { type: String, required: true },
label: { type: String, required: true },
labelClass: { type: String, default: '' },
transition: { type: String, default: 'dropdown__transition' },
})
const { isOpen, toggle } = useOpenable(dropdown, button)
</script>
<template>
<div
class="dropdown"
:class="{'-open': isOpen}"
@mouseleave="delayClose(delay)"
@mouseenter="keepOpen"
>
<button
ref="button"
:aria-controls="id"
:aria-expanded="String(isOpen)"
class="dropdown__button"
:class="labelClass"
@click.prevent="toggle"
@mouseenter="open"
>
<slot name="button">
{{ label }}
<svg
class="dropdown__icon"
viewBox="0 0 24 24"
stroke-width="1"
stroke="currentColor"
fill="none"
><polyline points="6 9 12 15 18 9" /></svg>
</slot>
</button>
<div
v-show="isOpen"
:id="id"
ref="dropdown"
class="dropdown__content"
>
<slot />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import useOpenable from '@resources/js/components/UseOpenable.vue'
const dropdown = ref(null)
const button = ref(null)
defineProps({
delay: { type: Number, default: 0 },
id: { type: String, required: true },
label: { type: String, required: true },
labelClass: { type: String, default: '' },
transition: { type: String, default: 'dropdown__transition' },
})
const { isOpen, open, keepOpen, delayClose, toggle } = useOpenable(dropdown, button)
</script>
<template>
<div
ref="dropdown"
class="dropdown"
:class="{'-open': isOpen}"
>
<a
ref="link"
:aria-controls="id"
:aria-expanded="String(isOpen)"
class="dropdown__link"
:class="labelClass"
:href="href"
@click.prevent="toggle"
>
<slot name="link">
{{ label }}
<svg
class="dropdown__icon"
viewBox="0 0 24 24"
stroke-width="1"
stroke="currentColor"
fill="none"
><polyline points="6 9 12 15 18 9" /></svg>
</slot>
</a>
<div
v-show="isOpen"
:id="id"
ref="dropdown"
class="dropdown__content"
>
<slot />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import useOpenable from '@resources/js/components/UseOpenable.vue'
const dropdown = ref(null)
const link = ref(null)
defineProps({
delay: { type: Number, default: 0 },
hoverable: { type: Boolean, default: false },
href: { type: String, required: true },
id: { type: String, required: true },
label: { type: String, required: true },
labelClass: { type: String, default: '' },
transition: { type: String, default: 'dropdown__transition' },
})
const { toggle, isOpen } = useOpenable(dropdown, link)
</script>
@use "/resources/styles/common" as *;
@use "/resources/styles/config";
.dropdown {
$b: &;
--dropdown-color: var(--color, #{config.$white});
--dropdown-color-contrast: var(--color-contrast, #{config.$plain-text});
position: relative;
&__button,
&__link {
color: config.$link;
display: inline-block;
padding: config.$thin-padding;
z-index: 2;
}
&__icon {
height: 1em;
vertical-align: middle;
width: 1em;
}
&__content {
--color: var(--dropdown-color-contrast);
--color-hover: #{config.$secondary};
background-color: var(--dropdown-color);
box-shadow: config.$low-shadow;
color: var(--dropdown-color-contrast);
position: absolute;
z-index: 1;
}
&.-rightEdge {
#{$b}__content {
left: auto;
right: 0;
}
}
&.-fullWidth {
position: static;
#{$b}__content {
left: 0;
right: 0;
}
}
&__transition {
&-enter-active {
transform-origin: top;
transition: opacity var(--duration-moderate), transform var(--duration-moderate);
transform: rotateX(0);
}
&-enter,
&-leave-to {
opacity: 0;
transform: rotateX(90deg);
}
}
}