All files / src/components/common AddListModal.vue

89.58% Statements 43/48
95% Branches 38/40
81.25% Functions 13/16
93.18% Lines 41/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151        10x             10x         10x 10x 10x 10x 10x 10x   10x   10x 10x 9x 9x 9x 9x 9x 9x 9x         1x       1x 1x 1x         2x 2x 2x         10x   10x         1x           1x     2x             1x         1x 264x         1x                 1x               3x     1x               1x           1x     1x                 23x                            
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { presetService, type Preset } from '../../services/preset'
 
const props = defineProps<{
  open: boolean
  initialPresetId?: string | null
  initialPresetEmoji?: string | null
  initialPresetName?: string | null
}>()
 
const emit = defineEmits<{
  close: []
  create: [name: string, emoji: string, presetId: string | null]
}>()
 
const name = ref('')
const selectedEmoji = ref('')
const selectedPresetId = ref<string | null>(null)
const selectedPresetName = ref<string | null>(null)
const inputRef = ref<HTMLInputElement>()
const presets = ref<Preset[]>([])
 
const emojis = ['πŸ›’', '🏠', 'πŸŽ‚', '🎁', 'πŸ’Š', '🐾', '🧹', 'πŸ•', 'πŸ“¦', '🌿', 'πŸ‹οΈ', '✈️']
 
watch(() => props.open, async (isOpen) => {
  if (isOpen) {
    name.value = ''
    selectedEmoji.value = props.initialPresetEmoji || emojis[0]!
    selectedPresetId.value = props.initialPresetId ?? null
    selectedPresetName.value = props.initialPresetName ?? null
    await nextTick()
    inputRef.value?.focus()
    presetService.getAll().then(p => { presets.value = p }).catch(() => {})
  }
}, { immediate: true })
 
function selectPreset(preset: Preset | null) {
  Iif (preset === null) {
    selectedPresetId.value = null
    selectedPresetName.value = null
  } else {
    selectedPresetId.value = preset.id
    selectedPresetName.value = preset.name
    selectedEmoji.value = preset.emoji
  }
}
 
function handleCreate() {
  Iif (!name.value.trim()) return
  emit('create', name.value.trim(), selectedEmoji.value, selectedPresetId.value)
  emit('close')
}
</script>
 
<template>
  <Teleport to="body">
    <Transition name="backdrop">
      <div v-if="open" class="fixed inset-0 z-50 bg-ctp-crust/60 backdrop-blur-sm" @click="$emit('close')" />
    </Transition>
 
    <Transition name="sheet">
      <div v-if="open" class="fixed bottom-0 left-0 right-0 z-50" :style="{ paddingBottom: 'env(safe-area-inset-bottom)' }">
        <div class="bg-ctp-mantle border-t border-ctp-surface0 rounded-t-3xl p-6 shadow-2xl shadow-ctp-crust/50 max-w-lg mx-auto">
          <div class="w-10 h-1 bg-ctp-surface1 rounded-full mx-auto mb-5" />
 
          <h2 class="text-lg font-semibold text-ctp-text mb-5">Neue Liste</h2>
 
          <!-- Name -->
          <div class="relative mb-5">
            <input
              ref="inputRef"
              v-model="name"
              type="text"
              placeholder="Name der Liste…"
              maxlength="50"
              class="w-full px-4 py-3 rounded-xl bg-ctp-surface0 border border-ctp-surface1 text-ctp-text placeholder-ctp-overlay0 outline-none transition-all duration-200 focus:border-ctp-teal focus:ring-2 focus:ring-ctp-teal/20"
              @keydown.enter="handleCreate"
            />
            <span class="absolute right-3 top-1/2 -translate-y-1/2 text-[10px] text-ctp-overlay0 tabular-nums">{{ name.length }}/50</span>
          </div>
 
          <!-- Emoji -->
          <p class="text-xs font-medium text-ctp-subtext0 mb-2.5">Symbol</p>
          <div class="flex flex-wrap gap-2 mb-5">
            <button
              v-for="emoji in emojis"
              :key="emoji"
              class="w-10 h-10 rounded-xl flex items-center justify-center text-lg transition-all duration-150 border-2"
              :class="selectedEmoji === emoji ? 'bg-ctp-teal/10 border-ctp-teal scale-110' : 'bg-ctp-surface0 border-transparent hover:bg-ctp-surface1'"
              @click="selectedEmoji = emoji"
            >{{ emoji }}</button>
          </div>
 
          <!-- Preset picker -->
          <div v-if="presets.length > 0" class="mb-5">
            <p class="text-xs font-medium text-ctp-subtext0 mb-2.5">
              Von Vorlage starten <span class="font-normal text-ctp-overlay0">(optional)</span>
            </p>
            <div class="flex gap-2 overflow-x-auto pb-1 scrollbar-none">
              <button
                @click="selectPreset(null)"
                class="shrink-0 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors"
                :class="selectedPresetId === null
                  ? 'bg-ctp-surface1 border-ctp-surface2 text-ctp-text'
                  : 'bg-ctp-surface0 border-transparent text-ctp-overlay0 hover:text-ctp-subtext0'"
              >Leer</button>
              <button
                v-for="preset in presets"
                :key="preset.id"
                @click="selectPreset(preset)"
                class="shrink-0 flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors"
                :class="selectedPresetId === preset.id
                  ? 'bg-ctp-teal/10 border-ctp-teal text-ctp-teal'
                  : 'bg-ctp-surface0 border-transparent text-ctp-subtext0 hover:bg-ctp-surface1'"
              >
                <span>{{ preset.emoji }}</span>
                {{ preset.name }}
                <span class="opacity-50">Β· {{ preset.itemCount }}</span>
              </button>
            </div>
          </div>
 
          <!-- Actions -->
          <div class="flex gap-3">
            <button
              class="flex-1 py-3 rounded-xl bg-ctp-surface0 text-ctp-subtext0 font-medium text-sm transition-colors hover:bg-ctp-surface1 active:scale-[0.98]"
              @click="$emit('close')"
            >Abbrechen</button>
            <button
              class="flex-1 py-3 rounded-xl bg-gradient-to-r from-ctp-teal to-ctp-sapphire text-ctp-crust font-semibold text-sm transition-all hover:shadow-lg hover:shadow-ctp-teal/30 active:scale-[0.98] disabled:opacity-40 disabled:cursor-not-allowed"
              :disabled="!name.trim()"
              @click="handleCreate"
            >{{ selectedPresetId ? `Aus Vorlage` : 'Erstellen' }}</button>
          </div>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>
 
<style scoped>
.backdrop-enter-active, .backdrop-leave-active { transition: opacity 0.3s ease; }
.backdrop-enter-from, .backdrop-leave-to { opacity: 0; }
.sheet-enter-active { transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); }
.sheet-leave-active { transition: transform 0.3s cubic-bezier(0.4, 0, 1, 1); }
.sheet-enter-from, .sheet-leave-to { transform: translateY(100%); }
.scrollbar-none { scrollbar-width: none; }
.scrollbar-none::-webkit-scrollbar { display: none; }
</style>