Files
calebburke.dev/web/src/components/flashcard-decks/FlashcardDeckTreeNode.vue
T
2026-06-28 23:58:29 -07:00

127 lines
3.1 KiB
Vue

<template>
<div>
<div
class="deck-node d-flex align-center px-3 py-2 rounded"
:class="{ selected: isSelected }"
@click="$emit('select', deck)"
>
<v-btn
icon
variant="text"
density="comfortable"
size="default"
class="mr-1"
@click.stop="expanded = !expanded"
>
<v-icon size="default">
{{ expanded ? "mdi-chevron-down" : "mdi-chevron-right" }}
</v-icon>
</v-btn>
<v-icon
size="default"
class="mr-2 text-medium-emphasis"
:icon="expanded && hasChildren ? 'mdi-folder-open-outline' : 'mdi-folder-outline'"
/>
<span
class="flex-grow-1 text-body-1"
:class="{ 'font-weight-medium': isSelected }"
>
{{ deck.name }}
</span>
<div
class="kebab-btn"
@click.stop
>
<FlashcardDeckKebab
:flashcard-deck-id="deck.id"
@created="$emit('created', $event)"
@updated="$emit('updated', $event)"
@deleted="$emit('deleted', $event)"
/>
</div>
</div>
<div
v-show="expanded"
class="pl-5"
>
<draggable
v-model="children"
:group="{ name: 'decks' }"
item-key="id"
@change="onChange"
>
<template #item="{ element }">
<FlashcardDeckTreeNode
v-model:children="element.children"
:deck="element"
:selected-id="selectedId"
@select="$emit('select', $event)"
@move="$emit('move', $event)"
@created="$emit('created', $event)"
@updated="$emit('updated', $event)"
@deleted="$emit('deleted', $event)"
/>
</template>
</draggable>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue"
import draggable from "vuedraggable"
import type { DeckNode } from "@/components/flashcard-decks/FlashcardDeckTree.vue"
import FlashcardDeckKebab from "@/components/flashcard-decks/FlashcardDeckKebab.vue"
const props = defineProps<{
deck: DeckNode
selectedId?: number | null
}>()
const children = defineModel<DeckNode[]>("children", { required: true })
const emit = defineEmits<{
select: [deck: DeckNode]
move: [payload: { deckId: number; newParentId: number | null }]
created: [flashcardDeckId: number]
updated: [flashcardDeckId: number]
deleted: [flashcardDeckId: number]
}>()
const expanded = ref(false)
const isSelected = computed(() => props.selectedId === props.deck.id)
const hasChildren = computed(() => children.value.length > 0)
function onChange(event: { added?: { element: DeckNode } }) {
if (event.added) {
emit("move", { deckId: event.added.element.id, newParentId: props.deck.id })
}
}
</script>
<style scoped>
.deck-node {
cursor: grab;
user-select: none;
}
.deck-node:hover {
background: rgb(var(--v-theme-hoverColor));
}
.deck-node.selected {
background: rgb(var(--v-theme-lightprimary));
}
.kebab-btn {
opacity: 0;
transition: opacity 0.15s;
}
.deck-node:hover .kebab-btn {
opacity: 1;
}
</style>