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>
|