thats-me/frontend/_src/components/ModalCard.vue
2026-04-22 12:57:10 +02:00

171 lines
3.6 KiB
Vue

<template>
<Transition name="modal-card">
<div v-if="open" class="modal-card glass--panel" :class="{ 'modal-card--dark': isDark }">
<!-- Header: title + close -->
<div class="modal-card__header">
<span class="modal-card__title">{{ title }}</span>
<button class="modal-card__close" @click="$emit('close')">
<q-icon name="close" size="22px" />
</button>
</div>
<!-- Tabs (optional) -->
<div v-if="tabs.length" class="modal-card__tabs">
<div class="modal-card__tabs-scroll">
<button
v-for="tab in tabs"
:key="tab.value"
class="modal-card__tab"
:class="{ 'modal-card__tab--active': modelValue === tab.value }"
@click="$emit('update:modelValue', tab.value)"
>
<q-icon v-if="tab.icon" :name="tab.icon" size="16px" />
<span>{{ tab.label }}</span>
</button>
</div>
</div>
<!-- Content -->
<div class="modal-card__body">
<slot />
</div>
</div>
</Transition>
</template>
<script setup>
import { computed } from 'vue'
import { useQuasar } from 'quasar'
defineProps({
open: { type: Boolean, default: false },
title: { type: String, default: '' },
tabs: { type: Array, default: () => [] },
modelValue: { type: String, default: '' }
})
defineEmits(['close', 'update:modelValue'])
const $q = useQuasar()
const isDark = computed(() => $q.dark.isActive)
</script>
<style scoped>
.modal-card {
position: fixed;
top: 48px;
left: 12px;
right: 12px;
bottom: 12px;
z-index: 30;
display: flex;
flex-direction: column;
border-radius: 20px;
border: 1px solid rgba(128, 128, 128, 0.15);
overflow: hidden;
}
/* Header */
.modal-card__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 20px 12px;
flex-shrink: 0;
}
.modal-card__title {
font-size: 20px;
font-weight: 700;
}
.modal-card__close {
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: rgba(128, 128, 128, 0.1);
color: inherit;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.15s;
}
.modal-card__close:hover {
background: rgba(128, 128, 128, 0.2);
}
/* Tabs */
.modal-card__tabs {
flex-shrink: 0;
padding: 0 16px;
border-bottom: 1px solid rgba(128, 128, 128, 0.1);
}
.modal-card__tabs-scroll {
display: flex;
gap: 4px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
padding-bottom: 8px;
}
.modal-card__tabs-scroll::-webkit-scrollbar {
display: none;
}
.modal-card__tab {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: none;
background: rgba(128, 128, 128, 0.08);
color: inherit;
font-family: inherit;
font-size: 13px;
font-weight: 500;
border-radius: 10px;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s;
opacity: 0.6;
}
.modal-card__tab:hover {
opacity: 0.85;
background: rgba(128, 128, 128, 0.14);
}
.modal-card__tab--active {
opacity: 1;
background: rgba(128, 128, 128, 0.18);
}
.modal-card--dark .modal-card__tab--active {
background: rgba(255, 255, 255, 0.12);
}
/* Body */
.modal-card__body {
flex: 1;
overflow-y: auto;
padding: 20px;
-webkit-overflow-scrolling: touch;
}
/* Transition */
.modal-card-enter-active,
.modal-card-leave-active {
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.3s ease;
}
.modal-card-enter-from,
.modal-card-leave-to {
transform: translateY(30px) scale(0.96);
opacity: 0;
}
</style>