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

<script setup lang="ts">
import { IconX } from "@tabler/icons-vue";
import { icons } from "utils/icons";

defineProps<{
  value: string | number;
  label?: string;
  count?: number;
  isSelected?: boolean;
  type: "ip" | "port" | "country" | "city";
}>();

const emit = defineEmits<{
  (e: "click"): void;
  (e: "exclude"): void;
}>();
</script>

<template>
  <div
    class="group ip-list-item"
    :class="{
      selected: isSelected,
    }"
  >
    <div
      @click="$emit('click')"
      class="label cursor-pointer truncate px-1.5 py-1"
    >
      {{ label ?? value }}
    </div>
    <div class="px-1.5 py-1 text-right text-stone-500">
      <span v-if="count !== undefined"> ({{ count }}) </span>
      <span v-else>  </span>
    </div>
    <div
      class="px-1.5 py-1 group-hover:opacity-100"
      :class="{
        'opacity-20': !isSelected,
      }"
    >
      <div class="flex items-center justify-end gap-0.5">
        <router-link
          :to="`/${type}/${value}`"
          class="ip-list-btn"
          title="View IP Details"
        >
          <component :is="icons.externalLink" size="14" />
        </router-link>
        <button
          @click.stop="$emit('exclude')"
          class="ip-list-btn"
          title="Filter out IP"
        >
          <IconX size="14" />
        </button>
      </div>
    </div>
  </div>
</template>

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

.ip-list-item {
  @apply grid grid-cols-[1fr_auto_auto] items-center rounded px-1 text-xs transition-colors hover:bg-stone-300/10;
}

.ip-list-item.selected {
  @apply bg-primary-500/10 ring-primary-300/30 ring-1;

  .label {
    @apply text-primary-300;
  }
}

.ip-list-btn {
  @apply text-stone-500 group-hover:opacity-100 hover:text-stone-200;
}
</style>