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>
+