Skip to content

Contenu d’onglet - DsfrTabContent

🌟 Introduction

Le composant DsfrTabContent est conçu pour être utilisé comme panneau de contenu dans une interface à onglets. Il utilise une transition fluide pour animer l'affichage du contenu de l'onglet sélectionné.

Ce composant doit s’utiliser dans un DsfrTabs.

📐 Structure

  • <DsfrTabContent> : Le composant principal.
    • Affiche le contenu associé à un onglet.
    • Utilise une transition animée pour l'apparition et la disparition du contenu.
    • Utilise des classes pour gérer l'état de sélection de l'onglet.

🛠️ Props

PropriétéTypeDescriptionValeur par défaut
panelIdstringID unique pour le panneau, utilisé pour l'accessibilité.obligatoire
tabIdstringID unique de l'onglet associé, utilisé pour l'accessibilité.obligatoire

📡 Événements

Ce composant ne déclenche pas d'événements spécifiques.

🧩 Slots

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

📝 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')