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