feat(aep): carte AEP — push Gitea 2026-04-28

This commit is contained in:
Jules Neny
2026-04-28 14:00:05 +02:00
commit 21c44d8193
86 changed files with 31855 additions and 0 deletions

162
components/TopSearchBar.vue Normal file
View File

@@ -0,0 +1,162 @@
<template>
<!-- Barre de recherche animée : compacte par défaut, s'étend au focus -->
<div class="top-search-wrapper" :class="{ expanded: isFocused || hasValue }">
<label class="top-search-label" aria-label="Rechercher une organisation">
<!-- Icône loupe -->
<svg
class="top-search-icon"
width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<!-- Input texte -->
<input
ref="inputEl"
v-model="localValue"
type="search"
placeholder="Rechercher une organisation…"
class="top-search-input"
autocomplete="off"
@focus="isFocused = true"
@blur="isFocused = false"
@input="onInput"
/>
<!-- Bouton clear -->
<button
v-if="hasValue"
type="button"
class="top-search-clear"
aria-label="Effacer la recherche"
@click.stop="clear"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</label>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const inputEl = ref<HTMLInputElement | null>(null)
const isFocused = ref(false)
const localValue = ref(props.modelValue)
watch(() => props.modelValue, (v) => { localValue.value = v })
const hasValue = computed(() => localValue.value.length > 0)
function onInput() {
emit('update:modelValue', localValue.value)
}
function clear() {
localValue.value = ''
emit('update:modelValue', '')
nextTick(() => inputEl.value?.focus())
}
</script>
<style scoped>
.top-search-wrapper {
display: flex;
align-items: center;
position: relative;
flex-shrink: 0;
}
.top-search-label {
display: flex;
align-items: center;
gap: 6px;
border: 1.5px solid var(--nav-bg-alt);
border-radius: 20px;
background: var(--nav-bg);
padding: 5px 10px;
cursor: text;
width: 44px;
overflow: hidden;
transition: width 0.25s ease, border-color 0.2s, background 0.2s;
}
.top-search-wrapper.expanded .top-search-label {
width: 280px;
border-color: var(--nav-primary);
background: var(--nav-surface);
}
@media (max-width: 640px) {
.top-search-wrapper.expanded .top-search-label {
width: calc(100vw - 80px);
}
}
.top-search-icon {
color: var(--nav-text-muted);
flex-shrink: 0;
width: 16px;
height: 16px;
transition: color 0.2s;
}
.top-search-wrapper.expanded .top-search-icon {
color: var(--nav-primary-solid);
}
.top-search-input {
border: none;
outline: none;
background: transparent;
color: var(--nav-text);
font-size: 13px;
width: 100%;
min-width: 0;
opacity: 0;
transition: opacity 0.2s;
}
.top-search-wrapper.expanded .top-search-input {
opacity: 1;
}
.top-search-input::placeholder {
color: var(--nav-text-muted);
}
/* Masquer le bouton clear natif des navigateurs */
.top-search-input::-webkit-search-cancel-button {
display: none;
}
.top-search-clear {
display: flex;
align-items: center;
justify-content: center;
color: var(--nav-text-muted);
flex-shrink: 0;
padding: 1px;
border-radius: 50%;
transition: color 0.15s, background 0.15s;
background: transparent;
border: none;
cursor: pointer;
}
.top-search-clear:hover {
color: var(--nav-text);
background: var(--nav-bg-alt);
}
</style>