import { VExpansionPanels, VExpansionPanel, VExpansionPanelTitle, VExpansionPanelText, VDataTable, VIcon, VRow, VCol, VSelect, VBtn, VCombobox, VContainer, VListItem, VChip, VDivider, VSheet, VCard, VCardItem, VCardTitle, VCardText, VBadge, VTooltip } from 'vuetify/components' import type { StoryObj } from '@storybook/vue3' import { VuetifyItems, itemsChips } from './VuetifyItems' import { mdiCheckboxMarkedCircleOutline, mdiLinkVariant, mdiAlertCircleOutline, mdiMagnify, mdiFilterVariant, mdiInformationOutline } from '@mdi/js' import { computed, ref, watch } from 'vue' const checkIcon = mdiCheckboxMarkedCircleOutline const linkIcon = mdiLinkVariant const iconAlert = mdiAlertCircleOutline const searchIcon = mdiMagnify const filterIcon = mdiFilterVariant const infoIcon = mdiInformationOutline export default { title: 'Accessibilité/Design System/Vuetify', } export const VuetifyPanel: StoryObj = { render: () => { return { components: { VExpansionPanels, VExpansionPanel, VExpansionPanelTitle, VExpansionPanelText, VListItem, VDataTable, VIcon, VContainer, VSelect, VBtn, VCombobox, VChip, VDivider, VSheet, VCard, VCardItem, VCardTitle, VCardText, VBadge, VTooltip, VRow, VCol, }, setup() { // Configuration de base const menuProps = ref({ top: false }) const itemValue = ref(0) const activeBtnIndex = ref('null') const search = ref([]) const searchString = ref('') // Filtrage par conformité const conformityFilter = ref('all') const conformityOptions = [ { title: 'Tous les composants', value: 'all' }, { title: 'Natifs Conformes', value: 'conform' }, { title: 'Natifs Non conformes', value: 'non-conform' }, { title: 'Alternatifs Conformes', value: 'alternative' }, ] // Tri des items par ordre alphabétique const items = computed(() => { return itemsChips.sort((a, b) => a.text.toLowerCase().localeCompare(b.text.toLowerCase())) }) const itemsString = computed(() => { const sortedItems = itemsChips.sort((a, b) => a.text.toLowerCase().localeCompare(b.text.toLowerCase())) return sortedItems.map(o => o.text) }) // Récupération du composant sélectionné const cardItem = computed(() => { return itemValue.value > 0 ? VuetifyItems[0].items[itemValue.value - 1] : { name: '', value: 0, errorImportants: [], errorIndeterminated: [], errorSolutionImportant: [], errorSolutionIndeterminated: [], solution: [], } }) // Vérification si un composant est conforme ou non const isComponentConform = (item) => { return item && (item.errorImportants.length === 0 && item.errorIndeterminated.length === 0) } // Vérification si un composant a une solution alternative avec href const hasAlternativeSolution = (item) => { return item && item.solution && item.solution.some(sol => sol.href) } // Détermine le statut du composant (conforme, alternatif, non conforme) const getComponentStatus = (item) => { if (isComponentConform(item)) return 'conform' if (hasAlternativeSolution(item)) return 'alternative' return 'non-conform' } // Détermine la couleur du composant selon son statut const getComponentColor = (item) => { const status = getComponentStatus(item) if (status === 'conform') return 'success' if (status === 'alternative') return '#a05bb6' return 'error' } // Détermine l'icône du composant selon son statut const getComponentIcon = (item) => { return getComponentStatus(item) === 'conform' || getComponentStatus(item) === 'alternative' ? checkIcon : iconAlert } // Filtrage des composants par conformité const filteredComponents = computed(() => { // Si on veut tous les composants if (conformityFilter.value === 'all') { return items.value } // Filtrer selon la conformité sélectionnée return items.value.filter((item) => { const component = VuetifyItems[0].items.find(c => c.value === item.value) const status = getComponentStatus(component) switch (conformityFilter.value) { case 'conform': return status === 'conform' case 'non-conform': return status === 'non-conform' case 'alternative': return status === 'alternative' default: return true } }) }) // Liste des composants filtrés pour affichage const displayedComponents = computed(() => { return VuetifyItems[0].items.filter((component) => { // Filtrage par conformité if (conformityFilter.value !== 'all') { const status = getComponentStatus(component) switch (conformityFilter.value) { case 'conform': return status === 'conform' case 'non-conform': return status === 'non-conform' case 'alternative': return status === 'alternative' default: return true } } return true }) }) // Surveillance des changements de filtre watch(conformityFilter, () => { // Réinitialiser la sélection quand on change de filtre itemValue.value = 0 search.value = [] }) return { VuetifyItems, menuProps, items: filteredComponents, itemsString, checkIcon, iconAlert, linkIcon, searchIcon, filterIcon, infoIcon, itemValue, cardItem, activeBtnIndex, search, searchString, conformityFilter, conformityOptions, isComponentConform, hasAlternativeSolution, getComponentStatus, getComponentColor, getComponentIcon, displayedComponents, // Les propriétés filterIcon et searchIcon sont déjà définies à la ligne 115 } }, template: `

Audit d'accessibilité des composants Vuetify

Composant natif conforme Composant natif non conforme Composant alternatif conforme

Composants {{ conformityFilter === 'all' ? '' : conformityFilter === 'conform' ? 'conformes' : 'non conformes' }}

Aucun composant ne correspond aux critères de filtrage.
{{ component.name }}

{{ cardItem.name }}

Erreurs bloquantes {{ cardItem.errorImportants.length }} erreur(s) Erreurs indéterminées {{ cardItem.errorIndeterminated.length }} erreur(s)
Erreurs bloquantes TANAGURU

Pas d'erreur d'accessibilité bloquante relevée à ce jour

{{ item.match('[0-9.]+')?.join('') || '' }} {{ item.replace(/[0-9.]/g, '') }}

Solution : {{ cardItem.errorSolutionImportant[0] }}

Erreurs indéterminées TANAGURU

Pas d'erreur d'accessibilité indéterminée relevée à ce jour

{{ item.match('[0-9.]+')?.join('') || '' }} {{ item.replace(/[0-9.]/g, '') }}

Solution : {{ cardItem.errorSolutionIndeterminated[0] }}

Solutions recommandées
Aucune solution recommandée pour le moment.
{{ item.name }}
`, } }, tags: ['!dev'], } export const Legende: StoryObj = { args: { }, render: (args) => { return { components: { VIcon, VRow, VCol }, setup() { return { args, checkIcon, iconAlert } }, template: `

L'étude porte sur 25 composants à date du 19/06/2025

L'étude relève que la majorité des composants garantissent l'accessibilité numérique par défaut tel que défini par le RGAA.
Nous identifions néanmoins pour certains composants qu'un travail d'ajustement est nécessaire. Ce travail peut être réalisé directement par les produits.
Dans une démarche d'accélération de la fabrication, nous proposons pour chaque composant relevant d'un manque sur le sujet des solutions afin d'en faciliter
la mise en conformité. Ces solutions peuvent être de nature : composant ou recommandation.


Pour chaque composant, nous indiquons les erreurs remontées par l'outil Tanaguru : Erreurs bloquantes et indéterminées.
Ces erreurs doivent être analysées contextuellement. Pour rappel, l'accessibilité numérique ne se mesure par sur des composants seuls dénués de contexte.
Une appréciation de la part du développeur est nécessaire pour en évaluer la pertinence.

`, } }, tags: ['!dev'], }