This is a Vue component, BpDropdown, for implementing dropdowns. It’s used in the navigation components in conjunctions with BpDirectional.

Usage

Props

  • delay (default: 0) – If hoverable, how long to delay before closing after the the mouse leaves the dropdown.
  • hoverable (default: false) – When set, the dropdown will open on hover, otherwise it will only open on click.
  • href (required) – The href for the link that’s overloaded to also open the dropdown.
  • id (required) – Required ID attribute value for accessibility purposes.
  • label (required) – Required next for the link.
  • labelClass (default: ‘’) – Allows adding additional classes to the link used to open the dropdown.
  • transition (default: ‘dropdown__transition’) – The name of a vue transition to use for the dropdown.

Slots

  • default – The default slot is used for the content within the dropdown.
  • link – Optional slot for providing more markup for the link than just ``.
  • button – Optional slot for overriding content of the button used to open the dropdown. The default value is an SVG chevron pointing downward.

Events

  • open – Emitted without a payload when the dropdown is opened.
  • close – Emitted without a payload when the dropdown is closed.
<bp-dropdown href="/about" label="Our Company" id="js-about">
    <bp-directional>
        <div class="container linkGroups">
            <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 &amp; 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 &amp; 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 &amp; 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 &amp; 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-directional>
</bp-dropdown>
  • Content:
    <template>
        <bp-directional>
            <div
                ref="dropdown"
                class="dropdown"
                :class="{'-open': expanded}"
                v-on="{ mouseleave, focusout, mouseover, mouseup, mousedown }"
            >
                <a
                    ref="link"
                    :aria-controls="id"
                    :aria-expanded="String(expanded)"
                    class="dropdown__link"
                    :class="labelClass"
                    :href="href"
                    @click.prevent="click"
                    @keydown.space.prevent="click"
                >
                    <slot name="link">{{ label }}</slot>
                    <button
                        :aria-controls="id"
                        :aria-expanded="String(expanded)"
                        class="dropdown__button"
                        @click.prevent.stop="click"
                    >
                        <slot name="button">
                            <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>
                </a>
                <transition :name="transition">
                    <div
                        v-if="expanded"
                        :id="id"
                        class="dropdown__content"
                        @keydown.esc.prevent="close"
                    >
                        <slot />
                    </div>
                </transition>
            </div>
        </bp-directional>
    </template>
    
    <script>
    import BpDirectional from '../../utilities/directional/BpDirectional'
    
    const Timer = function () {
        return {
            timeout: null,
            start (callback, delay) {
                if (!this.timeout) {
                    this.timeout = setTimeout(callback, delay)
                }
            },
            clear () {
                if (this.timeout) {
                    clearTimeout(this.timeout)
                    this.timeout = null
                }
            },
        }
    }
    
    export default {
        components: {
            BpDirectional,
        },
        props: {
            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' },
        },
        data: () => ({
            timer: new Timer(),
            expanded: false,
        }),
    
        destroyed () {
            removeExternalClickListener(this.externalClick)
        },
        methods: {
            mouseleave (evt) {
                if (this.hoverable) {
                    this.timer.start(() => this.close(false), this.delay)
                }
            },
    
            mouseup (evt) {
                console.log('mouseup', evt)
            },
    
            mousedown (evt) {
                console.log('mousedown', evt)
            },
    
            focusout (evt) {
                console.log('focusout', evt)
                if (evt.relatedTarget && this.expanded && !this.$el.contains(evt.relatedTarget)) {
                    this.close(false)
                }
            },
            click (evt) {
                console.log('click', evt)
                if (this.expanded) {
                    this.close()
                } else {
                    this.open()
                }
            },
            mouseover () {
                if (this.hoverable) {
                    this.timer.clear()
                    if (!this.expanded) {
                        this.open()
                    }
                }
            },
    
            externalClick (evt) {
                console.log('externalClick', evt)
                if (evt.srcElement && !this.$el.contains(evt.srcElement)) {
                    this.close(false)
                }
            },
    
            open () {
                this.$emit('open')
                this.timer.clear()
                this.expanded = true
                addEventListener('click', this.externalClick)
            },
            close (refocus = true) {
                this.$emit('close')
                this.timer.clear()
                this.expanded = false
                if (refocus) {
                    this.$refs.link.focus()
                }
                removeEventListener('click', this.externalClick)
            },
        },
    }
    </script>
    
  • URL: /components/raw/dropdown/BpDropdown.vue
  • Filesystem Path: resources/styles/atoms/dropdown/BpDropdown.vue
  • Size: 4.4 KB
  • Content:
    .dropdown {
        position: relative;
    
        &__link {
            display: inline-block;
            padding: $thin-padding;
            z-index: 2;
        }
    
        &__button {
            background: transparent;
            border: 0;
            padding: 0;
        }
    
        &__icon {
            height: 1em;
            vertical-align: middle;
            width: 1em;
        }
    
    
        &__content {
            position: absolute;
            z-index: 1;
        }
    
        &.-open {
    
            .dropdown__content {
                background-color: $white;
                box-shadow: $low-shadow;
            }
        }
    
        &.-rightEdge {
            .dropdown__content {
                left: auto;
                right: 0;
            }
        }
    
        &.-fullWidth {
            position: static;
    
            .dropdown__content {
                left: 0;
                right: 0;
            }
        }
    
        &__transition {
            &-enter-active {
                transform-origin: top;
                transition: opacity $moderate, transform $moderate;
                transform: rotateX(0);
            }
            &-enter,
            &-leave-to {
                opacity: 0;
                transform: rotateX(90deg);
            }
        }
    }
    
  • URL: /components/raw/dropdown/dropdown.scss
  • Filesystem Path: resources/styles/atoms/dropdown/dropdown.scss
  • Size: 1.1 KB