Skip to content

Onglet - DsfrTabItem

🌟 Introduction

Le composant DsfrTabItem représente un onglet individuel dans un ensemble d'onglets. Il gère les interactions utilisateur telles que les clics et les commandes clavier pour naviguer entre les onglets. Ce composant offre également une intégration avec des icônes pour une expérience utilisateur enrichie et intuitive.

Ce composant doit s’utiliser dans un DsfrTabs.

📐 Structure

  • <DsfrTabItem> : Un élément de liste représentant un onglet.
    • Contient un bouton pour activer l'onglet.
    • Gère les commandes clavier pour permettre la navigation entre les onglets.

🛠️ Props

PropriétéTypeDescriptionValeur par défaut
panelIdstringID unique du panneau de contenu associé à cet onglet.obligatoire
tabIdstringID unique de l'onglet, utilisé pour l'accessibilité.obligatoire
iconstringNom de l'icône à afficher dans l'onglet (facultatif).undefined

📡 Événements

  • click : Événement émis lorsque l'onglet est cliqué, envoie l’index de l’onglet (number, entier commençant à 0).
  • next : Événement émis lorsque l'utilisateur appuie sur la touche "flèche droite" ou "flèche bas".
  • previous : Événement émis lorsque l'utilisateur appuie sur la touche "flèche gauche" ou "flèche haut".
  • first : Événement émis lorsque l'utilisateur appuie sur la touche "Home".
  • last : Événement émis lorsque l'utilisateur appuie sur la touche "End".

🧩 Slots

  • default : Slot pour insérer le contenu de l'onglet.

📝 Exemples

vue
<script lang="ts" setup>
import { ref } from 'vue'

import DsfrButton from '../../DsfrButton/DsfrButton.vue'
import DsfrTabContent from '../DsfrTabContent.vue'
import DsfrTabItem from '../DsfrTabItem.vue'
import DsfrTabs from '../DsfrTabs.vue'

const tabListName = 'Liste d’onglet'
const title1 = 'Titre 1'
const tabTitles = [
  { title: title1, icon: 'ri-checkbox-circle-line', tabId: 'tab-0', panelId: 'tab-content-0' },
  { title: 'Titre 2', icon: 'ri-checkbox-circle-line', tabId: 'tab-1', panelId: 'tab-content-1' },
  { title: 'Titre 3', icon: 'ri-checkbox-circle-line', tabId: 'tab-2', panelId: 'tab-content-2' },
  { title: 'Titre 4', icon: 'ri-checkbox-circle-line', tabId: 'tab-3', panelId: 'tab-content-3' },
]

const activeTab = ref(0)
const selectPrevious = async () => {
  const newIndex = activeTab.value === 0 ? tabTitles.length - 1 : activeTab.value - 1
  activeTab.value = newIndex
}
const selectNext = async () => {
  const newIndex = activeTab.value === tabTitles.length - 1 ? 0 : activeTab.value + 1
  activeTab.value = newIndex
}
const selectFirst = async () => {
  activeTab.value = 0
}
const selectLast = async () => {
  activeTab.value = tabTitles.length - 1
}
</script>

<template>
  <div class="fr-container fr-my-2w">
    <DsfrTabs
      v-model="activeTab"
      :tab-list-name="tabListName"
    >
      <template #tab-items>
        <DsfrTabItem
          v-for="(tab, index) of tabTitles"
          :key="tab.tabId"
          :tab-id="tab.tabId"
          :panel-id="tab.panelId"
          :icon="tab.icon"
          @click="activeTab = index"
          @next="selectNext()"
          @previous="selectPrevious()"
          @first="selectFirst()"
          @last="selectLast()"
        >
          {{ tab.title }}
        </DsfrTabItem>
      </template>
      <DsfrTabContent
        panel-id="tab-content-0"
        tab-id="tab-0"
      >
        <div>Contenu 1 avec d'<em>autres composants</em></div>
      </DsfrTabContent>

      <DsfrTabContent
        panel-id="tab-content-1"
        tab-id="tab-1"
      >
        <div>Contenu 2 avec d'<strong>autres composants</strong></div>
      </DsfrTabContent>

      <DsfrTabContent
        panel-id="tab-content-2"
        tab-id="tab-2"
      >
        <div>Contenu 3 avec d'<em><strong>autres composants</strong></em></div>
      </DsfrTabContent>

      <DsfrTabContent
        panel-id="tab-content-3"
        tab-id="tab-3"
      >
        <div>
          <p>Contenu 4 avec beaucoup de contenus</p>
          <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Vitae fugit sit et eos a officiis adipisci nulla repellat cupiditate? Assumenda, explicabo ullam laboriosam ex sit corporis enim illum a itaque.</p>
          <p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quasi animi quis quos consectetur alias delectus recusandae sunt quisquam incidunt provident quidem, at voluptatibus id, molestias et? Temporibus perspiciatis aut voluptates.</p>
          <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quibusdam obcaecati at delectus iusto possimus! Molestiae, iusto veritatis. Nostrum magni officiis autem, in ullam aliquid, mollitia, commodi architecto vitae omnis vero.</p>
        </div>
      </DsfrTabContent>
    </DsfrTabs>
    <div style="display: flex; gap: 1rem; margin-block: 1rem;">
      <DsfrButton
        label="Activer le 1er onglet"
        :disabled="activeTab === 0"
        @click="activeTab = 0"
      />
      <DsfrButton
        label="Activer le 2è onglet"
        :disabled="activeTab === 1"
        @click="activeTab = 1"
      />
      <DsfrButton
        label="Activer le 3è onglet"
        :disabled="activeTab === 2"
        @click="activeTab = 2"
      />
      <DsfrButton
        label="Activer le dernier onglet"
        :disabled="activeTab === tabTitles.length - 1"
        @click="activeTab = tabTitles.length - 1"
      />
    </div>
  </div>
</template>

⚙️ Code source du composant

vue
<script setup lang="ts">
import { computed, inject, toRef } from 'vue'

import { registerTabKey } from './injection-key'

export type DsfrTabContentProps = {
  panelId: string
  tabId: string
}
const props = defineProps<DsfrTabContentProps>()

const values = { true: '100%', false: '-100%' }
const useTab = inject(registerTabKey)!
const { isVisible, asc } = useTab(toRef(() => props.tabId))
// @ts-expect-error this will be fine
const translateValueFrom = computed(() => values[String(asc?.value)])
// @ts-expect-error this will be fine
const translateValueTo = computed(() => values[String(!asc?.value)])
</script>

<template>
  <Transition
    name="slide-fade"
    mode="in-out"
  >
    <div
      v-show="isVisible"
      :id="panelId"
      class="fr-tabs__panel"
      :class="{
        'fr-tabs__panel--selected': isVisible,
      }"
      role="tabpanel"
      :aria-labelledby="tabId"
      :tabindex="isVisible ? 0 : -1"
    >
      <!-- @slot Slot par défaut pour le contenu de l’onglet. Sera dans `<div class="fr-tabs__panel">` -->
      <slot />
    </div>
  </Transition>
</template>

<style scoped>
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.3s ease-out;
}

.slide-fade-enter-from {
  transform: translateX(v-bind(translateValueFrom));
  opacity: 0;
}
.slide-fade-leave-to {
  transform: translateX(v-bind(translateValueTo));
  opacity: 0;
}
</style>
ts
export type DsfrTabItemProps = {
  panelId: string
  tabId: string
  icon?: string
}

export type DsfrTabContentProps = {
  panelId: string
  tabId: string
}

export type DsfrTabsProps = {
  modelValue: number
  tabListName: string
  tabTitles: (Partial<DsfrTabItemProps> & { title: string })[]
  tabContents?: string[]
}
ts
import type { InjectionKey, Ref } from 'vue'

type RegisterTab = (title: Ref<string>) => {
  isVisible: Ref<boolean>
  asc?: Ref<boolean>
}

export const registerTabKey: InjectionKey<RegisterTab> = Symbol('tabs')

Le DsfrTabItem utilise v-on:keydown pour gérer les interactions clavier et permettre la navigation entre les onglets. Le composant utilise également watch pour gérer la mise au point automatique lorsque l'onglet est sélectionné, améliorant ainsi l'accessibilité et l'expérience utilisateur.