Clean up: remove duplicate TagInput, delete quasar-migration branch
- Removed src/components/shared/TagInput.vue (exact duplicate of src/components/TagInput.vue) - Deleted feature/quasar-migration branch from local and remote - Verified build + deploy: design intact Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e6f20d871
commit
141ed947d6
|
|
@ -1,137 +0,0 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Array, default: () => [] }, // current tag labels
|
||||
allTags: { type: Array, default: () => [] }, // { label, color, category }
|
||||
getColor: { type: Function, default: () => '#6b7280' },
|
||||
placeholder:{ type: String, default: 'Ajouter un tag…' },
|
||||
canCreate: { type: Boolean, default: true },
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'create'])
|
||||
|
||||
const query = ref('')
|
||||
const focused = ref(false)
|
||||
const inputEl = ref(null)
|
||||
|
||||
const filtered = computed(() => {
|
||||
const q = query.value.trim().toLowerCase()
|
||||
if (!q) return props.allTags.filter(t => !props.modelValue.includes(t.label)).slice(0, 12)
|
||||
return props.allTags
|
||||
.filter(t => !props.modelValue.includes(t.label) && t.label.toLowerCase().includes(q))
|
||||
.slice(0, 12)
|
||||
})
|
||||
|
||||
const showCreate = computed(() => {
|
||||
if (!props.canCreate) return false
|
||||
const q = query.value.trim()
|
||||
if (!q || q.length < 2) return false
|
||||
return !props.allTags.some(t => t.label.toLowerCase() === q.toLowerCase())
|
||||
})
|
||||
|
||||
function addTag (label) {
|
||||
if (!label || props.modelValue.includes(label)) return
|
||||
emit('update:modelValue', [...props.modelValue, label])
|
||||
query.value = ''
|
||||
}
|
||||
|
||||
function removeTag (label) {
|
||||
emit('update:modelValue', props.modelValue.filter(t => t !== label))
|
||||
}
|
||||
|
||||
function createAndAdd () {
|
||||
const label = query.value.trim()
|
||||
if (!label) return
|
||||
emit('create', label)
|
||||
addTag(label)
|
||||
}
|
||||
|
||||
function onBlur () {
|
||||
setTimeout(() => { focused.value = false }, 180)
|
||||
}
|
||||
|
||||
function onKeydown (e) {
|
||||
if (e.key === 'Enter' && query.value.trim()) {
|
||||
e.preventDefault()
|
||||
if (filtered.value.length) addTag(filtered.value[0].label)
|
||||
else if (showCreate.value) createAndAdd()
|
||||
}
|
||||
if (e.key === 'Backspace' && !query.value && props.modelValue.length) {
|
||||
removeTag(props.modelValue[props.modelValue.length - 1])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ti-wrap" :class="{ 'ti-focused': focused }">
|
||||
<!-- Existing tags as chips -->
|
||||
<span v-for="t in modelValue" :key="t" class="ti-chip" :style="'background:'+getColor(t)">
|
||||
{{ t }}
|
||||
<button class="ti-chip-rm" @click.stop="removeTag(t)">×</button>
|
||||
</span>
|
||||
<!-- Input -->
|
||||
<input ref="inputEl" class="ti-input" type="text"
|
||||
v-model="query" :placeholder="modelValue.length ? '' : placeholder"
|
||||
@focus="focused=true" @blur="onBlur" @keydown="onKeydown" />
|
||||
<!-- Dropdown -->
|
||||
<div v-if="focused && (filtered.length || showCreate)" class="ti-dropdown">
|
||||
<div v-for="t in filtered" :key="t.label" class="ti-option" @mousedown.prevent="addTag(t.label)">
|
||||
<span class="ti-opt-dot" :style="'background:'+getColor(t.label)"></span>
|
||||
<span class="ti-opt-label">{{ t.label }}</span>
|
||||
<span class="ti-opt-cat">{{ t.category }}</span>
|
||||
</div>
|
||||
<div v-if="showCreate" class="ti-option ti-option-create" @mousedown.prevent="createAndAdd">
|
||||
<span class="ti-create-plus">+</span>
|
||||
<span>Créer « <strong>{{ query.trim() }}</strong> »</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ti-wrap {
|
||||
display:flex; flex-wrap:wrap; gap:3px; align-items:center;
|
||||
background:#181c2e; border:1px solid rgba(255,255,255,0.06); border-radius:6px;
|
||||
padding:3px 6px; min-height:28px; position:relative; cursor:text;
|
||||
transition: border-color 0.12s;
|
||||
}
|
||||
.ti-wrap.ti-focused { border-color:rgba(99,102,241,0.4); }
|
||||
.ti-chip {
|
||||
display:inline-flex; align-items:center; gap:2px;
|
||||
font-size:0.58rem; font-weight:600; color:#fff;
|
||||
padding:1px 6px; border-radius:10px; white-space:nowrap;
|
||||
}
|
||||
.ti-chip-rm {
|
||||
background:none; border:none; color:rgba(255,255,255,0.6); cursor:pointer;
|
||||
font-size:0.7rem; padding:0 1px; margin-left:1px; line-height:1;
|
||||
}
|
||||
.ti-chip-rm:hover { color:#fff; }
|
||||
.ti-input {
|
||||
flex:1; min-width:60px; background:none; border:none; outline:none;
|
||||
color:#e2e4ef; font-size:0.72rem; padding:2px 0;
|
||||
}
|
||||
.ti-input::placeholder { color:#7b80a0; }
|
||||
.ti-dropdown {
|
||||
position:absolute; top:100%; left:0; right:0; z-index:50;
|
||||
background:#181c2e; border:1px solid rgba(99,102,241,0.3); border-radius:6px;
|
||||
max-height:180px; overflow-y:auto; box-shadow:0 8px 24px rgba(0,0,0,0.45);
|
||||
margin-top:2px;
|
||||
}
|
||||
.ti-dropdown::-webkit-scrollbar { width:3px; }
|
||||
.ti-dropdown::-webkit-scrollbar-thumb { background:rgba(255,255,255,0.1); }
|
||||
.ti-option {
|
||||
display:flex; align-items:center; gap:6px;
|
||||
padding:5px 10px; cursor:pointer; font-size:0.72rem; color:#e2e4ef;
|
||||
transition:background 0.1s;
|
||||
}
|
||||
.ti-option:hover { background:rgba(99,102,241,0.12); }
|
||||
.ti-opt-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
||||
.ti-opt-label { flex:1; }
|
||||
.ti-opt-cat { font-size:0.55rem; color:#7b80a0; }
|
||||
.ti-option-create { color:#6366f1; font-weight:600; border-top:1px solid rgba(255,255,255,0.06); }
|
||||
.ti-create-plus {
|
||||
width:18px; height:18px; border-radius:50%; background:rgba(99,102,241,0.2);
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
font-size:0.75rem; font-weight:800; color:#6366f1; flex-shrink:0;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user