feat(mail) : arbre des dossiers repliable — chevrons, sous-dossiers masqués par défaut

Seuls les dossiers racine sont affichés au départ ; chevron pour déplier/replier
chaque dossier ayant des sous-dossiers. Le clic sur le nom sélectionne toujours le dossier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 08:05:00 +02:00
parent 17b5fa2340
commit c6fa5a534e
2 changed files with 56 additions and 17 deletions

View File

@@ -19,14 +19,34 @@ const { t } = useI18n()
const currentDepth = computed(() => props.depth ?? 0)
// Dossiers dépliés (repliés par défaut → seuls les dossiers racine sont visibles).
const expanded = ref<Set<string>>(new Set())
function isExpanded(path: string): boolean {
return expanded.value.has(path)
}
function toggleExpanded(path: string): void {
const next = new Set(expanded.value)
if (next.has(path)) {
next.delete(path)
} else {
next.add(path)
}
expanded.value = next
}
function hasChildren(folder: MailFolderDto): boolean {
return !!folder.children && folder.children.length > 0
}
function handleSelect(path: string): void {
emit('select', path)
}
function paddingStyle(): Record<string, string> {
const depth = currentDepth.value
if (depth <= 0) return {}
return { paddingLeft: `${0.75 + depth * 0.75}rem` }
return { paddingLeft: `${0.5 + depth * 0.75}rem` }
}
</script>
@@ -41,19 +61,34 @@ function paddingStyle(): Record<string, string> {
<template v-else>
<div v-for="folder in folders" :key="folder.path">
<button
type="button"
class="w-full text-left"
@click="handleSelect(folder.path)"
<div
class="flex items-center gap-1 rounded-md pr-2 py-1.5 text-sm transition-colors"
:class="
selectedPath === folder.path
? 'bg-primary-100 text-primary-700 font-medium'
: 'text-neutral-700 hover:bg-neutral-100'
"
:style="paddingStyle()"
>
<div
class="flex items-center gap-2 rounded-md px-3 py-1.5 text-sm transition-colors"
:class="
selectedPath === folder.path
? 'bg-primary-100 text-primary-700 font-medium'
: 'text-neutral-700 hover:bg-neutral-100'
"
:style="paddingStyle()"
<button
v-if="hasChildren(folder)"
type="button"
class="flex-shrink-0 rounded p-0.5 hover:bg-neutral-200"
:aria-label="isExpanded(folder.path) ? t('mail.folderTree.collapse') : t('mail.folderTree.expand')"
@click.stop="toggleExpanded(folder.path)"
>
<Icon
:name="isExpanded(folder.path) ? 'material-symbols:keyboard-arrow-down' : 'material-symbols:chevron-right'"
size="16"
class="text-neutral-400"
/>
</button>
<span v-else class="inline-block w-[22px] flex-shrink-0" />
<button
type="button"
class="flex flex-1 items-center gap-2 text-left min-w-0"
@click="handleSelect(folder.path)"
>
<Icon
:name="getFolderIcon(folder.path)"
@@ -72,11 +107,11 @@ function paddingStyle(): Record<string, string> {
>
{{ folder.unreadCount > 99 ? '99+' : folder.unreadCount }}
</span>
</div>
</button>
</button>
</div>
<MailFolderTree
v-if="folder.children && folder.children.length > 0"
v-if="hasChildren(folder) && isExpanded(folder.path)"
:folders="folder.children"
:selected-path="selectedPath"
:depth="currentDepth + 1"