internal/dashboard/frontend/src/components/SidebarList.vue

<script setup lang="ts">
import { IconChevronRight } from "@tabler/icons-vue";
import type { ItemList } from "src/types";
import { onMounted, ref } from "vue";
import SidebarListItem from "./SidebarListItem.vue";

const props = defineProps<{
  title: string;
  items: ItemList[];
  type: "ip" | "port" | "country" | "city";
  selectedItem?: any;
  open?: boolean;
}>();

defineEmits<{
  (e: "click", value: any): void;
  (e: "exclude", value: any): void;
}>();

const detailsRef = ref<HTMLDetailsElement | null>(null);

onMounted(() => {
  if (props.open && detailsRef.value) {
    detailsRef.value.open = true;
  }
});
</script>

<template>
  <details ref="detailsRef" class="sidebar-list" name="sidebar-accordion">
    <summary
      class="sticky top-0 z-10 flex shrink-0 cursor-pointer items-center justify-between bg-stone-900 p-2 text-xs font-bold"
    >
      <div class="flex items-center gap-1">
        <IconChevronRight class="icon transition-transform" size="14" />
        <span>{{ title }} ({{ items.length }})</span>
      </div>
      <slot name="header-actions"></slot>
    </summary>
    <div class="sidebar-list-content flex flex-col gap-0.5 px-1.5 py-1">
      <SidebarListItem
        v-for="item in items"
        :key="item.value"
        :type="type"
        :value="item.value"
        :label="item.label"
        :count="item.count"
        :is-selected="selectedItem === item.value"
        @click="$emit('click', item.value)"
        @exclude="$emit('exclude', item.value)"
      />
    </div>
  </details>
</template>

<style scoped>
@reference "src/style.css";

summary {
  @apply list-none appearance-none;

  &::-webkit-details-marker {
    @apply hidden;
  }
}

.sidebar-list {
  @apply transition-none;
}

.sidebar-list[open] {
  @apply mb-0 min-h-0 flex-1 overflow-y-auto border-b border-stone-800;

  &:last-child {
    @apply border-b-0;
  }

  summary {
    @apply border-b border-stone-800;
  }

  .icon {
    @apply rotate-90;
  }
}

.sidebar-list-content {
  @apply pb-2;
}
</style>