- Add LetterTemplate.icon field and 7th template 'Mindre parkeringsskada' (🅿️) - Create TemplatePicker.vue component: modal overlay with 2-column card grid, emits 'select' and 'close' events, closes on overlay click - Add '✨ Visa mallar' pill button above textarea in ComposePage - Clicking button opens TemplatePicker modal, selecting a template fills textarea and closes modal - Style button as pill/badge with light blue background and icon - Add 7 Vitest tests for TemplatePicker (renders cards, emits events, close behavior, parking damage template) - Add 4 Vitest tests for ComposePage template picker integration - Add 2 Playwright E2E tests (opens picker, fills textarea and closes)
151 lines
2.8 KiB
Vue
151 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { templates, type LetterTemplate } from '@/data/templates'
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'select', template: LetterTemplate): void
|
|
(e: 'close'): void
|
|
}>()
|
|
|
|
function handleSelect(template: LetterTemplate) {
|
|
emit('select', template)
|
|
emit('close')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="modal-overlay" @click.self="emit('close')">
|
|
<div class="modal">
|
|
<div class="modal__header">
|
|
<h2 class="modal__title">Välj en mall</h2>
|
|
<button class="modal__close" @click="emit('close')">×</button>
|
|
</div>
|
|
<p class="modal__subtitle">
|
|
Klicka på en mall för att fylla i meddelandetexten.
|
|
</p>
|
|
<div class="modal__grid">
|
|
<button
|
|
v-for="t in templates"
|
|
:key="t.name"
|
|
class="modal__card"
|
|
@click="handleSelect(t)"
|
|
>
|
|
<span class="modal__card-icon">{{ t.icon }}</span>
|
|
<span class="modal__card-name">{{ t.name }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.modal-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.modal {
|
|
background: #fff;
|
|
border-radius: 1rem;
|
|
width: 100%;
|
|
max-width: 28rem;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.modal__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 1.5rem 1.5rem 0;
|
|
}
|
|
|
|
.modal__title {
|
|
margin: 0;
|
|
font-size: 1.25rem;
|
|
color: #1a202c;
|
|
}
|
|
|
|
.modal__close {
|
|
background: none;
|
|
border: none;
|
|
font-size: 1.5rem;
|
|
color: #a0aec0;
|
|
cursor: pointer;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.25rem;
|
|
transition:
|
|
color 0.15s,
|
|
background 0.15s;
|
|
}
|
|
|
|
.modal__close:hover {
|
|
color: #4a5568;
|
|
background: #f7fafc;
|
|
}
|
|
|
|
.modal__subtitle {
|
|
margin: 0.5rem 0 0;
|
|
padding: 0 1.5rem;
|
|
font-size: 0.875rem;
|
|
color: #718096;
|
|
}
|
|
|
|
.modal__grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 0.75rem;
|
|
padding: 1.25rem 1.5rem 1.5rem;
|
|
}
|
|
|
|
.modal__card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 1.25rem 0.75rem;
|
|
background: #f7fafc;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 0.75rem;
|
|
cursor: pointer;
|
|
transition:
|
|
border-color 0.15s,
|
|
background 0.15s,
|
|
transform 0.1s;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.modal__card:hover {
|
|
border-color: #4299e1;
|
|
background: #ebf8ff;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.modal__card:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.modal__card-icon {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.modal__card-name {
|
|
font-size: 0.8125rem;
|
|
font-weight: 600;
|
|
color: #4a5568;
|
|
text-align: center;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
@media (max-width: 400px) {
|
|
.modal__grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|