Skip to content

FileUpload

Composant FileUpload pour le téléchargement de fichiers avec support du drag & drop, validation des formats et tailles, et accessibilité complète selon les normes W3C.

Description

FileUpload est un composant de téléchargement de fichiers enrichi qui permet de sélectionner des fichiers via un bouton de parcours ou par glisser-déposer. Il offre la validation automatique des formats et tailles, l'affichage des aperçus d'images, la gestion de la progression, et respecte toutes les normes d'accessibilité.

Exemples d'utilisation

Utilisation simple (sans SuFormField)

Upload de base

vue
<script setup>
import { ref } from 'vue'

const files = ref([])
</script>

<template>
  <SuFileUpload 
    placeholder="Sélectionnez vos fichiers ou glissez-les ici"
    v-model="files"
  />
</template>

Upload multiple

vue
<script setup>
import { ref } from 'vue'

const images = ref([])
</script>

<template>
  <SuFileUpload 
    :multiple="true"
    :maxFiles="5"
    accept="image/*"
    placeholder="Sélectionnez jusqu'à 5 images"
    v-model="images"
  />
</template>

Avec validation de taille

vue
<script setup>
import { ref } from 'vue'

const documents = ref([])
</script>

<template>
  <SuFileUpload 
    :maxSize="10 * 1024 * 1024"
    accept=".pdf,.doc,.docx"
    placeholder="Documents (max 10MB)"
    v-model="documents"
  />
</template>

Tailles

vue
<template>
  <SuFileUpload 
    size="sm" 
    placeholder="Petit upload"
  />
  
  <SuFileUpload 
    size="md" 
    placeholder="Upload moyen"
  />
  
  <SuFileUpload 
    size="lg" 
    placeholder="Grand upload"
  />
</template>

États

vue
<template>
  <SuFileUpload 
    placeholder="État par défaut"
  />
  
  <SuFileUpload 
    state="error"
    placeholder="État d'erreur"
  />
  
  <SuFileUpload 
    state="success"
    placeholder="État de succès"
  />
  
  <SuFileUpload 
    state="warning"
    placeholder="État d'avertissement"
  />
</template>

Variantes visuelles

vue
<template>
  <SuFileUpload 
    variant="default"
    placeholder="Style par défaut"
  />
  
  <SuFileUpload 
    variant="dashed"
    placeholder="Bordures pointillées"
  />
  
  <SuFileUpload 
    variant="solid"
    placeholder="Bordures pleines"
  />
  
  <SuFileUpload 
    variant="minimal"
    placeholder="Style minimaliste"
  />
</template>

Avec progression

vue
<script setup>
import { ref } from 'vue'

const files = ref([])

const handleFileUpload = (file) => {
  // Simuler un upload avec progression
  file.status = 'uploading'
  file.progress = 0
  
  const interval = setInterval(() => {
    file.progress += 10
    
    if (file.progress >= 100) {
      clearInterval(interval)
      file.status = 'success'
    }
  }, 200)
}
</script>

<template>
  <SuFileUpload 
    :multiple="true"
    :showProgress="true"
    placeholder="Upload avec progression"
    v-model="files"
    @upload="handleFileUpload"
  />
</template>

États spéciaux

vue
<template>
  <SuFileUpload 
    :disabled="true"
    placeholder="Upload désactivé"
  />
  
  <SuFileUpload 
    :readonly="true"
    placeholder="Lecture seule"
  />
  
  <SuFileUpload 
    :loading="true"
    placeholder="Chargement..."
  />
</template>

Utilisation avec SuFormField

Le composant SuFileUpload peut être utilisé avec SuFormField pour bénéficier d'une structure de formulaire complète avec label, message et gestion des états.

Usage basique avec SuFormField

vue
<template>
  <SuFormField 
    label="Documents" 
    message="Formats acceptés : PDF, DOC, DOCX"
  >
    <template #default="slotProps">
      <SuFileUpload 
        accept=".pdf,.doc,.docx"
        placeholder="Sélectionnez vos documents"
        v-bind="slotProps"
        v-model="documents"
      />
    </template>
  </SuFormField>
</template>

Avec destructuration des slot props

vue
<template>
  <SuFormField 
    label="Images"
    :required="true"
    message="Téléchargez vos images"
  >
    <template #default="{ fieldId, messageId, state, disabled }">
      <SuFileUpload 
        :id="fieldId"
        :aria-describedby="messageId"
        :state="state"
        :disabled="disabled"
        :multiple="true"
        accept="image/*"
        placeholder="Glissez vos images ici"
        v-model="images"
      />
    </template>
  </SuFormField>
</template>

Validation avec états

vue
<script setup>
import { ref, computed } from 'vue'

const cv = ref([])
const cvError = ref('')

const validateCV = () => {
  if (cv.value.length === 0) {
    cvError.value = 'Le CV est obligatoire'
  } else {
    cvError.value = ''
  }
}

const cvState = computed(() => {
  if (cv.value.length === 0) return 'default'
  return cvError.value ? 'error' : 'success'
})
</script>

<template>
  <SuFormField 
    label="CV"
    :required="true"
    :state="cvState"
    :message="cvError || 'Format PDF ou Word, maximum 5MB'"
  >
    <template #default="slotProps">
      <SuFileUpload 
        accept=".pdf,.doc,.docx"
        :maxSize="5 * 1024 * 1024"
        :maxFiles="1"
        placeholder="Téléchargez votre CV"
        v-bind="slotProps"
        v-model="cv"
        @change="validateCV"
      />
    </template>
  </SuFormField>
</template>

Upload multiple avec FormField

vue
<template>
  <SuFormField 
    label="Portfolio"
    message="Images de vos réalisations (max 10 fichiers)"
  >
    <template #default="slotProps">
      <SuFileUpload 
        :multiple="true"
        :maxFiles="10"
        :maxSize="20 * 1024 * 1024"
        accept="image/*,.pdf"
        placeholder="Glissez vos fichiers ici"
        v-bind="slotProps"
        v-model="portfolio"
      />
    </template>
  </SuFormField>
</template>

Label et message personnalisés

vue
<script setup>
import { DocumentArrowUpIcon, InformationCircleIcon } from '@heroicons/vue/24/outline'
</script>

<template>
  <SuFormField 
    label="Documents justificatifs" 
    :required="true"
    message="Téléchargez vos documents"
  >
    <template #label="{ label, required, htmlFor }">
      <label 
        :for="htmlFor" 
        class="flex items-center gap-2 font-bold text-gray-900"
      >
        <DocumentArrowUpIcon class="w-4 h-4" />
        {{ label }}
        <span v-if="required" class="text-red-500">*</span>
      </label>
    </template>
    
    <template #default="slotProps">
      <SuFileUpload 
        accept=".pdf,.jpg,.png"
        :maxSize="10 * 1024 * 1024"
        :multiple="true"
        placeholder="PDF, JPG ou PNG"
        v-bind="slotProps"
        v-model="documents"
      />
    </template>
    
    <template #message="{ state }">
      <div class="flex items-center gap-2 text-sm text-gray-600">
        <InformationCircleIcon class="w-4 h-4" />
        <span v-if="state === 'error'">⚠️ Format ou taille invalide</span>
        <span v-else>Maximum 10MB par fichier</span>
      </div>
    </template>
  </SuFormField>
</template>

Gestion des erreurs

vue
<script setup>
import { ref } from 'vue'

const files = ref([])
const uploadError = ref('')

const handleError = (error, file) => {
  uploadError.value = error
  console.error('Erreur upload:', error, file)
}

const handleUpload = (file) => {
  uploadError.value = ''
  console.log('Fichier ajouté:', file)
}
</script>

<template>
  <SuFormField 
    label="Fichiers"
    :state="uploadError ? 'error' : 'default'"
    :message="uploadError || 'Glissez vos fichiers ou cliquez pour parcourir'"
  >
    <template #default="slotProps">
      <SuFileUpload 
        :multiple="true"
        v-bind="slotProps"
        v-model="files"
        @upload="handleUpload"
        @error="handleError"
      />
    </template>
  </SuFormField>
</template>

Formulaire de candidature

vue
<script setup>
import { ref } from 'vue'

const formData = ref({
  cv: [],
  lettreMotivation: [],
  portfolio: []
})

const handleCVUpload = (file) => {
  console.log('CV uploadé:', file)
  // Démarrer l'upload vers le serveur
}

const handleError = (error, file) => {
  alert(`Erreur: ${error}`)
}
</script>

<template>
  <form class="application-form">
    <h2>Candidature</h2>
    
    <SuFormField 
      label="CV"
      :required="true"
      message="Format PDF ou Word, maximum 5MB"
    >
      <template #default="slotProps">
        <SuFileUpload 
          accept=".pdf,.doc,.docx"
          :maxSize="5 * 1024 * 1024"
          :maxFiles="1"
          placeholder="Téléchargez votre CV"
          v-bind="slotProps"
          v-model="formData.cv"
          @upload="handleCVUpload"
          @error="handleError"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Lettre de motivation"
      message="Format PDF ou Word, maximum 5MB"
    >
      <template #default="slotProps">
        <SuFileUpload 
          accept=".pdf,.doc,.docx"
          :maxSize="5 * 1024 * 1024"
          :maxFiles="1"
          placeholder="Téléchargez votre lettre"
          v-bind="slotProps"
          v-model="formData.lettreMotivation"
          @error="handleError"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Portfolio"
      message="Images et PDF, max 10 fichiers de 20MB"
    >
      <template #default="slotProps">
        <SuFileUpload 
          accept="image/*,.pdf"
          :maxSize="20 * 1024 * 1024"
          :maxFiles="10"
          :multiple="true"
          placeholder="Vos réalisations"
          v-bind="slotProps"
          v-model="formData.portfolio"
          @error="handleError"
        />
      </template>
    </SuFormField>
    
    <button type="submit">Envoyer</button>
  </form>
</template>

Champ désactivé

vue
<template>
  <SuFormField 
    label="Upload désactivé"
    :disabled="true"
    message="Cette fonctionnalité est temporairement désactivée"
  >
    <template #default="slotProps">
      <SuFileUpload 
        placeholder="Non disponible"
        v-bind="slotProps"
      />
    </template>
  </SuFormField>
</template>

API

Props

PropTypeDefaultDescription
modelValueUploadedFile[][]Liste des fichiers (v-model)
multiplebooleanfalseSélection multiple
acceptstringundefinedTypes MIME ou extensions acceptés
maxSizenumber10485760Taille max par fichier (10MB)
maxFilesnumber5Nombre max de fichiers
size'sm' | 'md' | 'lg''md'Taille du composant
state'default' | 'error' | 'success' | 'warning''default'État visuel
disabledbooleanfalseDésactive le composant
readonlybooleanfalseMode lecture seule
requiredbooleanfalseChamp requis
placeholderstring'Sélectionnez...'Texte de la zone de drop
dragTextstring'Relâchez...'Texte pendant le drag
browseTextstring'Parcourir'Texte du bouton
variant'default' | 'dashed' | 'solid' | 'minimal''default'Style visuel
showProgressbooleanfalseAfficher la progression
allowPreviewbooleantrueAperçus d'images
showFileListbooleantrueListe des fichiers
loadingbooleanfalseÉtat de chargement

Props d'accessibilité (héritées de SuFormField)

Lorsque utilisé avec SuFormField, reçoit automatiquement :

PropTypeDescription
idstringID unique (fieldId)
aria-describedbystringID du message (messageId)
statestringÉtat de validation
disabledbooleanÉtat désactivé

Types

UploadedFile

typescript
interface UploadedFile {
  id: string
  file: File
  name: string
  size: number
  type: string
  status: 'pending' | 'uploading' | 'success' | 'error'
  progress?: number
  error?: string
  preview?: string
}

Events

EventTypeDescription
@update:modelValue(files: UploadedFile[]) => voidÉmis lors du changement (v-model)
@change(files: UploadedFile[]) => voidÉmis lors du changement
@upload(file: UploadedFile) => voidÉmis pour chaque fichier ajouté
@remove(file: UploadedFile) => voidÉmis lors de la suppression
@error(error: string, file?: File) => voidÉmis en cas d'erreur
@focus(event: FocusEvent) => voidÉmis lors du focus
@blur(event: FocusEvent) => voidÉmis lors de la perte de focus

Méthodes exposées

MéthodeTypeDescription
focus()() => voidDonne le focus
clear()() => voidSupprime tous les fichiers
fileInputRefRef<HTMLInputElement>Référence à l'input file
dropZoneRefRef<HTMLDivElement>Référence à la zone de drop

Validation des fichiers

Le composant effectue plusieurs validations automatiques :

🔍 Types de validation

  • Format : Vérification contre accept
  • Taille : Vérification contre maxSize
  • Nombre : Vérification contre maxFiles
  • Doublons : Prévention par nom

📝 Formats acceptés

vue
<!-- Extensions -->
<SuFileUpload accept=".jpg,.png,.pdf" />

<!-- MIME types -->
<SuFileUpload accept="image/*,application/pdf" />

<!-- Combinaison -->
<SuFileUpload accept="image/*,.pdf,.doc" />

Accessibilité

Conforme WCAG 2.1 AA et W3C :

✅ Fonctionnalités

  • Navigation clavier : Tab, Entrée, Espace
  • Drag & Drop accessible : Zone avec rôle button
  • ARIA : Labels, descriptions, états
  • Annonces vocales : aria-live
  • Focus visible : Indicateurs clairs
  • Messages d'état : Feedback visuel/vocal
  • Contraste : Ratios WCAG AA (4.5:1)
  • Support RTL : Langues droite-à-gauche

🎯 Bonnes pratiques

vue
<!-- ✅ BON : Avec SuFormField -->
<SuFormField 
  label="Documents"
  :required="true"
  message="PDF ou images, max 10MB"
>
  <template #default="slotProps">
    <SuFileUpload 
      accept=".pdf,image/*"
      :maxSize="10 * 1024 * 1024"
      v-bind="slotProps"
      v-model="docs"
    />
  </template>
</SuFormField>

<!-- ❌ MAUVAIS : Sans label ni contexte -->
<SuFileUpload v-model="files" />
ToucheAction
TabNaviguer vers/depuis
Entrée / EspaceOuvrir sélecteur
TabEntre fichiers listés
Entrée / EspaceSupprimer fichier

Composant SuFileUploadField

Pour plus de simplicité, utilisez SuFileUploadField qui combine SuFormField et SuFileUpload :

vue
<template>
  <!-- Au lieu de -->
  <SuFormField label="Documents" message="PDF uniquement">
    <template #default="slotProps">
      <SuFileUpload 
        accept=".pdf"
        v-bind="slotProps"
        v-model="docs"
      />
    </template>
  </SuFormField>
  
  <!-- Simplement écrire -->
  <SuFileUploadField 
    label="Documents"
    message="PDF uniquement"
    accept=".pdf"
    v-model="docs"
  />
</template>

Le composant SuFileUploadField accepte toutes les props de SuFormField et SuFileUpload combinées.

Voir la documentation complète de SuFileUploadField pour plus de détails.

Publié sous licence MIT.