feat(chatbot): composant ChatModeToggle (radiogroup) et état partagé du mode
Toggle sobre 2 options (role radiogroup, flèches/Home/End, focus visible, reduced-motion), étiquette de mode sur les réponses approfondies, et useChatProfondeur (état par clé, desktop et mobile synchronisés). Libellés lus dans utils/chatProfondeur.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vcVGz8i4WpZm2dixKUiSD
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
80f69565aa
commit
4c57f30f6a
@@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<span v-if="profondeur === 'approfondi'" class="chat-mode-badge">{{ CHAT_PROFONDEUR_LIBELLES.approfondi }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Étiquette sobre sur une réponse donnée en mode approfondi (rien en mode rapide).
|
||||||
|
defineProps<{ profondeur?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chat-mode-badge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 1px solid var(--nav-bg-alt);
|
||||||
|
font-size: 0.66rem;
|
||||||
|
font-family: var(--nav-font);
|
||||||
|
color: var(--nav-text-muted);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chat-mode" role="radiogroup" aria-label="Mode de réponse">
|
||||||
|
<button
|
||||||
|
v-for="(p, i) in CHAT_PROFONDEURS"
|
||||||
|
:key="p"
|
||||||
|
:ref="(el) => (boutons[i] = el as HTMLButtonElement | null)"
|
||||||
|
type="button"
|
||||||
|
role="radio"
|
||||||
|
class="chat-mode-option"
|
||||||
|
:class="{ active: modelValue === p }"
|
||||||
|
:aria-checked="modelValue === p"
|
||||||
|
:tabindex="modelValue === p ? 0 : -1"
|
||||||
|
:title="CHAT_PROFONDEUR_AIDES[p]"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="choisir(p)"
|
||||||
|
@keydown="onKey($event, i)"
|
||||||
|
>{{ CHAT_PROFONDEUR_LIBELLES[p] }}</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Toggle 2 options partagé par les 5 chatbots. Libellés : utils/chatProfondeur.ts.
|
||||||
|
// radiogroup : flèches déplacent la sélection, Tab entre/sort du groupe (roving tabindex).
|
||||||
|
const props = defineProps<{ modelValue: 'rapide' | 'approfondi'; disabled?: boolean }>()
|
||||||
|
const emit = defineEmits<{ 'update:modelValue': [value: 'rapide' | 'approfondi'] }>()
|
||||||
|
|
||||||
|
const boutons = ref<(HTMLButtonElement | null)[]>([])
|
||||||
|
|
||||||
|
function choisir(p: ChatProfondeur) {
|
||||||
|
if (!props.disabled) emit('update:modelValue', p)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKey(e: KeyboardEvent, i: number) {
|
||||||
|
const n = CHAT_PROFONDEURS.length
|
||||||
|
let j = -1
|
||||||
|
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') j = (i + 1) % n
|
||||||
|
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') j = (i - 1 + n) % n
|
||||||
|
else if (e.key === 'Home') j = 0
|
||||||
|
else if (e.key === 'End') j = n - 1
|
||||||
|
if (j < 0) return
|
||||||
|
e.preventDefault()
|
||||||
|
choisir(CHAT_PROFONDEURS[j])
|
||||||
|
nextTick(() => boutons.value[j]?.focus())
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chat-mode {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--nav-bg-alt);
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
.chat-mode-option {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--nav-text-muted);
|
||||||
|
font-family: var(--nav-font);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
.chat-mode-option:hover:not(.active):not(:disabled) { color: var(--nav-text); }
|
||||||
|
.chat-mode-option.active {
|
||||||
|
background: var(--nav-primary);
|
||||||
|
color: var(--nav-text-on-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.chat-mode-option:focus-visible { outline: 2px solid var(--nav-primary); outline-offset: 2px; }
|
||||||
|
.chat-mode-option:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.chat-mode-option { transition: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Mode de profondeur courant d'un chatbot (M3). État partagé par clé via useState :
|
||||||
|
* desktop et mobile d'un même chatbot (clés 'accueil', 'agences'…) restent synchronisés.
|
||||||
|
* Volontairement non persisté : le mode est un choix par question, 'rapide' à chaque visite.
|
||||||
|
*/
|
||||||
|
export function useChatProfondeur(key: string | (() => string) = 'defaut') {
|
||||||
|
const k = typeof key === 'function' ? key : () => key
|
||||||
|
const store = useState<Record<string, ChatProfondeur>>('aep-chat-profondeur', () => ({}))
|
||||||
|
return computed<ChatProfondeur>({
|
||||||
|
get: () => store.value[k()] ?? CHAT_PROFONDEUR_DEFAUT,
|
||||||
|
set: (v) => { store.value = { ...store.value, [k()]: normaliserProfondeur(v) } },
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user