Files
malio-layer-ui/app/components/malio/sidebar/Sidebar.vue
T
tristan 14c719da51 feat(sidebar) : état actif + survol des liens (fond primary/10, texte primary, semi-bold)
- Survol : highlight pleine largeur porté par le <li> (fond m-primary/10 +
  texte m-primary + semi-bold), espacement pt-1 pb-1. Couleur de base text-black
  sur le <li> et le <a> n'impose plus sa couleur (héritage) : sinon, sur les
  bandes pt-1/pb-1 hors du <a>, le fond passait bleu mais le texte restait noir.
- Lien actif : texte m-primary + semi-bold, sans fond (active-class avec
  !important car appliqué hors twMerge).
- Pas de transition sur le hover (texte + fond basculent ensemble, sans délai).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:09:35 +02:00

141 lines
3.6 KiB
Vue

<template>
<aside
:id="componentId"
:class="twMerge(
'relative flex h-full flex-col bg-m-bg',
collapsed ? 'w-[72px]' : 'w-[232px]',
sidebarClass,
)"
v-bind="$attrs"
>
<div :class="['px-[20px] py-[14px]', collapsed ? '' : 'mx-[10px] border-b-2 border-m-primary']">
<slot
v-if="collapsed"
name="logo-collapsed"
/>
<slot
v-else
name="logo"
/>
</div>
<nav class="flex-1 overflow-y-auto mb-4">
<div
v-for="(section, sectionIndex) in sections"
:key="sectionIndex"
:class="collapsed ? 'first:border-t-2 first:border-m-primary' : 'mx-[10px] border-t-2 border-m-primary first:border-t-0'"
>
<div
v-if="section.label"
:class="[
'flex items-center gap-2 pt-2 pb-3',
collapsed ? 'justify-center pt-[40px]' : '',
]"
>
<IconifyIcon
v-if="section.icon"
:icon="section.icon"
:width="24"
class="shrink-0 text-m-primary"
/>
<span
v-if="!collapsed"
class="text-[15px] font-bold uppercase text-m-primary"
>
{{ section.label }}
</span>
</div>
<ul>
<li
v-for="item in section.items"
:key="item.to"
:class="collapsed ? '' : 'text-black hover:bg-m-primary/10 hover:font-semibold hover:text-m-primary pt-1 pb-1'"
>
<NuxtLink
:to="item.to"
active-class="!text-m-primary font-semibold"
:class="twMerge(
'block truncate text-[15px] leading-[150%]',
collapsed ? 'px-3 text-center' : 'pl-[32px]',
)"
>
<span v-if="!collapsed">{{ item.label }}</span>
</NuxtLink>
</li>
</ul>
</div>
</nav>
<button
type="button"
:aria-label="collapsed ? 'Déplier le menu' : 'Plier le menu'"
:class="twMerge(
'absolute top-1/2 -translate-y-1/2 right-0 translate-x-1/2 z-10',
'flex h-8 w-8 items-center justify-center rounded-full border border-m-border bg-white shadow-sm',
'cursor-pointer transition-colors hover:bg-m-surface',
toggleClass,
)"
@click="toggleCollapse"
>
<IconifyIcon
:icon="collapsed ? 'mdi:chevron-right' : 'mdi:chevron-left'"
:width="18"
/>
</button>
</aside>
</template>
<script setup lang="ts">
import {computed, ref, useId} from 'vue'
import {Icon as IconifyIcon} from '@iconify/vue'
import {twMerge} from 'tailwind-merge'
defineOptions({name: 'MalioSidebar', inheritAttrs: false})
export type SidebarItem = {
label: string
to: string
}
export type SidebarSection = {
label?: string
icon?: string
items: SidebarItem[]
}
const props = withDefaults(defineProps<{
sections: SidebarSection[]
modelValue?: boolean
id?: string
sidebarClass?: string
toggleClass?: string
}>(), {
modelValue: undefined,
id: '',
sidebarClass: '',
toggleClass: '',
})
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const generatedId = useId()
const componentId = computed(() => props.id || `malio-sidebar-${generatedId}`)
const isControlled = computed(() => props.modelValue !== undefined)
const localValue = ref(false)
const collapsed = computed(() =>
isControlled.value ? props.modelValue! : localValue.value,
)
function toggleCollapse() {
const newValue = !collapsed.value
if (!isControlled.value) {
localValue.value = newValue
}
emit('update:modelValue', newValue)
}
</script>