Files
skycel 64b1a33d10 feat(mobile): add BottomBar navigation and CheminLibre drawer (Story 3.7)
- Add ZoneCard component for zone display with status indicators
- Add CheminLibre drawer with vertical zone cards and path decoration
- Add BottomBar with Map, Progress, and Settings buttons
- Add ProgressDetail modal showing visited sections
- Add SettingsDrawer with language, consent, and reset options
- Add i18n translations for zone, cheminLibre, bottomBar, settings
- Add --bottom-bar-height CSS variable for spacing
- Modify layouts to include BottomBar on mobile (< 768px)
- Support safe-area-inset for iOS devices
- Touch targets minimum 48x48px for WCAG compliance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-07 04:29:55 +01:00

195 lines
6.5 KiB
Vue

<script setup lang="ts">
const { t } = useI18n()
const showCheminLibre = ref(false)
const showProgress = ref(false)
const showSettings = ref(false)
const progressionStore = useProgressionStore()
</script>
<template>
<div class="bottom-bar-container md:hidden">
<!-- Bottom Bar fixe -->
<nav
class="bottom-bar fixed bottom-0 inset-x-0 z-40 bg-sky-dark-50 border-t border-sky-dark-100 safe-bottom"
>
<div class="flex items-center justify-around h-16">
<!-- Bouton Carte -->
<button
type="button"
class="bottom-bar-btn flex flex-col items-center justify-center min-w-12 min-h-12 p-2 text-sky-text-muted hover:text-sky-accent transition-colors"
:class="{ 'text-sky-accent': showCheminLibre }"
:aria-label="t('bottomBar.map')"
@click="showCheminLibre = true"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
<span class="text-xs font-ui mt-1">{{ t('bottomBar.map') }}</span>
</button>
<!-- Bouton Progression -->
<button
type="button"
class="bottom-bar-btn flex flex-col items-center justify-center min-w-12 min-h-12 p-2 text-sky-text-muted hover:text-sky-accent transition-colors"
:class="{ 'text-sky-accent': showProgress }"
:aria-label="t('bottomBar.progress')"
@click="showProgress = true"
>
<!-- Cercle de progression -->
<div class="relative w-6 h-6">
<svg class="w-full h-full -rotate-90" viewBox="0 0 36 36">
<circle
cx="18"
cy="18"
r="14"
fill="none"
stroke="currentColor"
stroke-width="3"
class="text-sky-dark-100"
/>
<circle
cx="18"
cy="18"
r="14"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
class="text-sky-accent"
:stroke-dasharray="`${progressionStore.completionPercent * 0.88}, 100`"
/>
</svg>
<span
class="absolute inset-0 flex items-center justify-center text-[8px] font-ui font-bold"
>
{{ progressionStore.completionPercent }}
</span>
</div>
<span class="text-xs font-ui mt-1">{{ t('bottomBar.progress') }}</span>
</button>
<!-- Bouton Paramètres -->
<button
type="button"
class="bottom-bar-btn flex flex-col items-center justify-center min-w-12 min-h-12 p-2 text-sky-text-muted hover:text-sky-accent transition-colors"
:class="{ 'text-sky-accent': showSettings }"
:aria-label="t('bottomBar.settings')"
@click="showSettings = true"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
<span class="text-xs font-ui mt-1">{{ t('bottomBar.settings') }}</span>
</button>
</div>
</nav>
<!-- Drawer Chemin Libre -->
<Teleport to="body">
<Transition name="slide-up">
<div v-if="showCheminLibre" class="fixed inset-x-0 bottom-16 top-0 z-50">
<!-- Overlay -->
<div class="absolute inset-0 bg-black/50" @click="showCheminLibre = false" />
<!-- Drawer content -->
<div class="absolute inset-x-0 bottom-0 max-h-[80vh] bg-sky-dark-50 rounded-t-2xl">
<CheminLibre @close="showCheminLibre = false" />
</div>
</div>
</Transition>
<!-- Modal Progression -->
<Transition name="fade">
<div
v-if="showProgress"
class="fixed inset-0 z-50 flex items-end justify-center md:items-center"
>
<div class="absolute inset-0 bg-black/50" @click="showProgress = false" />
<div
class="relative w-full max-w-sm bg-sky-dark-50 rounded-t-2xl md:rounded-2xl p-6 safe-bottom"
>
<ProgressDetail @close="showProgress = false" />
</div>
</div>
</Transition>
<!-- Drawer Paramètres -->
<Transition name="slide-up">
<div v-if="showSettings" class="fixed inset-x-0 bottom-16 top-0 z-50">
<div class="absolute inset-0 bg-black/50" @click="showSettings = false" />
<div
class="absolute inset-x-0 bottom-0 max-h-[60vh] bg-sky-dark-50 rounded-t-2xl p-6 safe-bottom overflow-y-auto"
>
<SettingsDrawer @close="showSettings = false" />
</div>
</div>
</Transition>
</Teleport>
</div>
</template>
<style scoped>
.safe-bottom {
padding-bottom: max(env(safe-area-inset-bottom), 1rem);
}
.bottom-bar {
height: var(--bottom-bar-height, 64px);
padding-bottom: env(safe-area-inset-bottom);
}
.bottom-bar-btn {
min-width: 48px;
min-height: 48px;
}
.slide-up-enter-active,
.slide-up-leave-active {
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.slide-up-enter-from,
.slide-up-leave-to {
transform: translateY(100%);
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
.slide-up-enter-active,
.slide-up-leave-active,
.fade-enter-active,
.fade-leave-active {
transition: none;
}
}
</style>