From 703b12428bbcec6b091c86716bd7f5badfe81f35 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello <fabio@manganiello.tech> Date: Sat, 8 Mar 2025 01:28:18 +0100 Subject: [PATCH] =?UTF-8?q?=EE=AD=BF=20=20Added=20support=20for=20dropdown?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Dropdown.vue | 86 ++++++++++++++++++++++++ frontend/src/components/DropdownItem.vue | 48 +++++++++++++ frontend/src/mixins/Dropdowns.vue | 16 +++++ 3 files changed, 150 insertions(+) create mode 100644 frontend/src/components/Dropdown.vue create mode 100644 frontend/src/components/DropdownItem.vue create mode 100644 frontend/src/mixins/Dropdowns.vue diff --git a/frontend/src/components/Dropdown.vue b/frontend/src/components/Dropdown.vue new file mode 100644 index 0000000..ce0c100 --- /dev/null +++ b/frontend/src/components/Dropdown.vue @@ -0,0 +1,86 @@ +<template> + <div class="dropdown"> + <button class="dropdown__button" @click="show"> + <slot name="button" /> + </button> + + <div class="dropdown__container" ref="container"> + <div class="dropdown__content"> + <slot /> + </div> + </div> + </div> +</template> + +<script lang="ts"> +export default { + computed: { + container() { + return this.$refs.container as HTMLElement; + }, + }, + + methods: { + show() { + this.container.classList.add('show'); + }, + }, +} +</script> + +<style lang="scss" scoped> +@use "@/styles/common.scss" as *; + +.dropdown { + position: relative; + display: inline-block; + + &__button { + background: none; + border: none; + cursor: pointer; + + &:focus, + &:hover { + color: var(--color-hover); + outline: none; + } + } + + &__container { + position: absolute; + display: none; + top: 100%; + right: 0; + z-index: 20; + + &.show { + display: block; + } + } + + &__content { + min-width: 10rem; + display: flex; + background-color: var(--color-background); + border: 1px solid var(--color-border); + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.5); + border-radius: 0.25rem; + padding: 0.25rem; + flex-direction: column; + animation: dropdown 0.25s ease-out; + } + + @keyframes dropdown { + from { + opacity: 0; + transform: translateY(-0.5rem); + } + + to { + opacity: 1; + transform: translateY(0); + } + } +} +</style> diff --git a/frontend/src/components/DropdownItem.vue b/frontend/src/components/DropdownItem.vue new file mode 100644 index 0000000..0a68eb8 --- /dev/null +++ b/frontend/src/components/DropdownItem.vue @@ -0,0 +1,48 @@ +<template> + <div class="dropdown__item" @click="$emit('click')"> + <div class="dropdown__item__icon"> + <slot name="icon" /> + </div> + <div class="dropdown__item__text"> + <slot /> + </div> + </div> +</template> + +<script lang="ts"> +export default { + emits: ['click'], +} +</script> + +<style lang="scss" scoped> +.dropdown__item { + display: flex; + flex: 1; + padding: 0.5rem 0; + cursor: pointer; + + &:hover { + color: var(--color-hover); + + :deep(a) { + background-color: inherit; + color: var(--color-hover); + } + } + + &__icon { + margin-right: 0.5rem; + } + + &__text { + display: flex; + flex: 1; + + :deep(a) { + flex: 1; + text-decoration: none; + } + } +} +</style> diff --git a/frontend/src/mixins/Dropdowns.vue b/frontend/src/mixins/Dropdowns.vue new file mode 100644 index 0000000..84863a0 --- /dev/null +++ b/frontend/src/mixins/Dropdowns.vue @@ -0,0 +1,16 @@ +<script lang="ts"> +export default { + methods: { + installDropdownHandler() { + document.addEventListener('click', (event) => { + if (!(event.target as HTMLElement).closest('.dropdown__button')) { + document.querySelectorAll('.dropdown__container').forEach((content) => { + content.classList.remove('show'); + }); + } + }); + }, + }, +} +</script> +