bilhej/frontend/src/components/TemplatePicker.vue
Joakim Mörling 96508d63cd feat: add template picker modal to compose page
- 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)
2026-05-14 17:39:21 +02:00

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')">&times;</button>
</div>
<p class="modal__subtitle">
Klicka 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>