npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@buillaume.biondo/fab-ui

v0.1.2

Published

> Bibliothèque de composants Vue 3 construite sur **Reka UI**, **Tailwind CSS v4** et **class-variance-authority**. Chaque composant respecte un système de design cohérent basé sur des CSS custom properties (`--color-*`, `--radius-*`, `--shadow-*`).

Downloads

291

Readme

FAB-UI

Bibliothèque de composants Vue 3 construite sur Reka UI, Tailwind CSS v4 et class-variance-authority. Chaque composant respecte un système de design cohérent basé sur des CSS custom properties (--color-*, --radius-*, --shadow-*).


npm require @buillaume.biondo/[email protected]

Sommaire

  1. Types globaux
  2. Inputs
  3. Boutons & Actions
  4. Badge & Avatar
  5. Feedback
  6. Contenu
  7. Formulaires étendus
  8. Overlays & Navigation
  9. Données

1. Types globaux

Ces types TypeScript sont définis dans /resources/js/types/ui-kit.ts et utilisés par la quasi-totalité des composants.

Severity

Couleur sémantique commune à Button, Badge, Alert, Card, ProgressBar, Toggle, Slider…

type Severity =
  | 'primary'    // couleur d'action principale du design system
  | 'secondary'  // ton neutre / gris
  | 'success'    // vert
  | 'danger'     // rouge
  | 'info'       // bleu
  | 'warning'    // orange
  | 'help'       // violet
  | 'contrast'   // contraste fort (noir/blanc selon le thème)

Size

Taille standardisée commune à Button, Badge, Avatar, Input…

type Size = 'tiny' | 'small' | 'default' | 'large' | 'extra'

ButtonShape

Forme visuelle du composant Button (et SplitButton).

type ButtonShape = 'plain' | 'outline' | 'ghost' | 'link'

BadgeShape

Forme visuelle du composant Badge.

type BadgeShape = 'filled' | 'outline' | 'ghost'

2. Inputs

Input

Champ de texte de base. Supporte v-model, validation (error), désactivation et l'attribut HTML type.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | modelValue | string \| number | — | Valeur liée via v-model | | defaultValue | string \| number | — | Valeur initiale non contrôlée | | type | string | 'text' | Type HTML de l'input (text, email, search, file…) | | placeholder | string | — | Texte indicatif | | disabled | boolean | false | Désactive le champ | | required | boolean | false | Marque le champ comme requis | | error | string | — | Si défini, applique le style d'erreur (aria-invalid) | | class | string | — | Classes Tailwind additionnelles |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | update:modelValue | string \| number | Émis à chaque frappe |

Exemples

<script setup lang="ts">
import { ref } from 'vue'
import { Input } from 'fab-ui'

const email = ref('')
</script>

<template>
  <Input v-model="email" type="email" placeholder="[email protected]" />
</template>
<!-- Avec erreur de validation -->
<Input
  v-model="email"
  type="email"
  placeholder="[email protected]"
  :error="errors.email"
/>
<p v-if="errors.email" class="text-sm text-red-500 mt-1">{{ errors.email }}</p>

InputPassword

Champ mot de passe avec bouton bascule pour afficher/masquer la valeur. Mêmes styles et comportements que Input.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | modelValue | string | — | Valeur liée via v-model | | defaultValue | string | — | Valeur initiale non contrôlée | | placeholder | string | — | Texte indicatif | | disabled | boolean | false | Désactive le champ | | required | boolean | false | Marque le champ comme requis | | error | string | — | Active le style d'erreur | | class | string | — | Classes additionnelles sur le wrapper |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | update:modelValue | string | Émis à chaque frappe |

Exemple

<script setup lang="ts">
import { ref } from 'vue'
import { InputPassword } from 'fab-ui'

const password = ref('')
</script>

<template>
  <InputPassword v-model="password" placeholder="Mot de passe" />
</template>

InputNumber

Champ numérique avec boutons et + intégrés. Gère min, max et step. Les boutons se désactivent automatiquement aux bornes.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | modelValue | number | — | Valeur liée via v-model | | defaultValue | number | — | Valeur initiale non contrôlée | | placeholder | string | — | Texte indicatif | | disabled | boolean | false | Désactive le champ et les boutons | | required | boolean | false | Marque le champ comme requis | | error | string | — | Active le style d'erreur | | min | number | — | Valeur minimale | | max | number | — | Valeur maximale | | step | number | 1 | Incrément des boutons +/− | | controls | boolean | true | Affiche ou masque les boutons +/− | | class | string | — | Classes additionnelles sur le wrapper |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | update:modelValue | number | Émis à chaque changement |

Exemple

<script setup lang="ts">
import { ref } from 'vue'
import { InputNumber } from 'fab-ui'

const quantity = ref(1)
</script>

<template>
  <!-- Quantité entre 1 et 99 -->
  <InputNumber v-model="quantity" :min="1" :max="99" />

  <!-- Sans boutons (champ seul) -->
  <InputNumber v-model="quantity" :controls="false" />
</template>

InputArea

Zone de texte multiligne (textarea) avec compteur de caractères optionnel.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | modelValue | string | — | Valeur liée via v-model | | defaultValue | string | — | Valeur initiale non contrôlée | | placeholder | string | — | Texte indicatif | | disabled | boolean | false | Désactive le champ | | required | boolean | false | Marque le champ comme requis | | error | string | — | Active le style d'erreur | | rows | number | 3 | Nombre de lignes visibles | | cols | number | — | Largeur fixe en colonnes (mode inline) | | maxlength | number | — | Limite de caractères — affiche un compteur | | resize | 'none' \| 'vertical' \| 'horizontal' \| 'both' | 'vertical' | Comportement du redimensionnement | | class | string | — | Classes additionnelles sur le wrapper |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | update:modelValue | string | Émis à chaque frappe |

Exemples

<script setup lang="ts">
import { ref } from 'vue'
import { InputArea } from 'fab-ui'

const bio = ref('')
</script>

<template>
  <InputArea v-model="bio" placeholder="Votre biographie…" :rows="5" />
</template>
<!-- Avec limite de caractères et resize désactivé -->
<InputArea
  v-model="bio"
  :maxlength="280"
  resize="none"
  placeholder="Bio (280 car. max)"
/>

Checkbox

Case à cocher accessible, construite sur CheckboxRoot de Reka UI. Compatible avec v-model (prop checked / événement update:checked).

Props

Hérite de CheckboxRootProps (Reka UI) :

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | checked / v-model | boolean \| 'indeterminate' | — | État de la case | | defaultChecked | boolean | — | État initial non contrôlé | | disabled | boolean | false | Désactive la case | | required | boolean | false | Marque comme requis dans un formulaire | | name | string | — | Nom pour la soumission de formulaire | | value | string | — | Valeur pour la soumission de formulaire | | class | string | — | Classes additionnelles |

Slots

| Slot | Description | |------|-------------| | default | Icône custom à la place de la coche par défaut |

Exemple

<script setup lang="ts">
import { ref } from 'vue'
import { Checkbox } from 'fab-ui'

const accepted = ref(false)
</script>

<template>
  <label class="flex items-center gap-2 cursor-pointer">
    <Checkbox v-model:checked="accepted" />
    <span>J'accepte les conditions d'utilisation</span>
  </label>
</template>

3. Boutons & Actions

Button

Bouton polyvalent avec 8 variantes de couleur, 4 formes, 5 tailles et des options d'icônes, de badge et de chargement. Basé sur Primitive de Reka UI pour pouvoir être rendu en <a>, <router-link>, etc.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | Severity | 'primary' | Couleur sémantique | | shape | ButtonShape | 'plain' | Forme visuelle (plain, outline, ghost, link) | | size | Size | 'default' | Taille (tiny, small, default, large, extra) | | fluid | boolean | false | Prend toute la largeur disponible (w-full) | | rounded | boolean | false | Coins complètement arrondis (rounded-full) | | icon | boolean | false | Mode icône seule — bouton carré (ou cercle si rounded) | | loading | boolean | false | Affiche un spinner et désactive le bouton | | disabled | boolean | false | Désactive le bouton | | badge | number \| null | null | Badge numérique en overlay coin supérieur droit (max 99+) | | iconLeft | Component | — | Composant icône affiché à gauche du texte | | iconRight | Component | — | Composant icône affiché à droite du texte | | as | string \| Component | 'button' | Élément ou composant HTML rendu | | asChild | boolean | false | Passe les props à l'enfant direct (pattern Reka UI) | | class | string | — | Classes additionnelles |

Slots

| Slot | Description | |------|-------------| | default | Contenu du bouton (texte, icône principale) |

Exemples

<script setup lang="ts">
import { Button } from 'fab-ui'
import { SaveIcon, TrashIcon } from 'lucide-vue-next'
</script>

<template>
  <!-- Variantes de forme -->
  <Button variant="primary" shape="plain">Enregistrer</Button>
  <Button variant="danger" shape="outline">Supprimer</Button>
  <Button variant="secondary" shape="ghost">Annuler</Button>
  <Button variant="primary" shape="link">En savoir plus</Button>
</template>
<!-- Tailles, icônes, loading, badge -->
<Button size="small" :icon-left="SaveIcon">Sauvegarder</Button>
<Button size="large" variant="danger" :icon-left="TrashIcon" shape="outline">Supprimer</Button>
<Button :loading="isSaving" variant="primary">Traitement…</Button>
<Button :badge="5" variant="primary">Notifications</Button>

<!-- Mode icône seule -->
<Button :icon="true" variant="ghost" rounded :aria-label="'Supprimer'">
  <TrashIcon />
</Button>

<!-- Rendu en lien -->
<Button as="a" href="/dashboard" variant="secondary" shape="ghost">
  Tableau de bord
</Button>

SplitButton

Bouton divisé : partie gauche déclenchable et partie droite ouvrant un menu déroulant. Partage les mêmes variantes que Button.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | Severity | 'primary' | Couleur sémantique | | shape | 'plain' \| 'outline' \| 'ghost' | 'plain' | Forme visuelle | | size | Size | 'default' | Taille | | fluid | boolean | false | Prend toute la largeur disponible | | disabled | boolean | false | Désactive les deux parties | | loading | boolean | false | Affiche un spinner dans la partie principale | | iconLeft | Component | — | Icône à gauche du label dans la partie principale | | class | string | — | Classes additionnelles sur le wrapper |

Slots

| Slot | Description | |------|-------------| | default | Contenu de la partie principale (texte) | | dropdown | Items du menu déroulant (utiliser DropdownMenuItem) |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | click | MouseEvent | Clic sur la partie principale du bouton |

Exemple

<script setup lang="ts">
import { SplitButton } from 'fab-ui'
import { DropdownMenuItem } from 'fab-ui'
import { SaveIcon } from 'lucide-vue-next'

function save() { /* ... */ }
function saveAsDraft() { /* ... */ }
function saveAndPublish() { /* ... */ }
</script>

<template>
  <SplitButton variant="primary" :icon-left="SaveIcon" @click="save">
    Enregistrer
    <template #dropdown>
      <DropdownMenuItem @click="saveAsDraft">Sauvegarder comme brouillon</DropdownMenuItem>
      <DropdownMenuItem @click="saveAndPublish">Sauvegarder et publier</DropdownMenuItem>
    </template>
  </SplitButton>
</template>

Toggle

Bouton à bascule (actif/inactif) basé sur Toggle de Reka UI. Peut afficher un texte différent selon l'état via onValue / offValue.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | Severity | 'primary' | Couleur quand l'état est on | | shape | 'ghost' \| 'outline' \| 'plain' | 'ghost' | Forme visuelle | | size | Size | 'default' | Taille | | pressed | boolean | — | État contrôlé | | defaultPressed | boolean | — | État initial non contrôlé | | disabled | boolean | false | Désactive le toggle | | onValue | string | — | Texte affiché quand pressed = true | | offValue | string | — | Texte affiché quand pressed = false | | class | string | — | Classes additionnelles |

Slots

| Slot | Description | |------|-------------| | default | Contenu du toggle (icône, texte). Ignoré si onValue/offValue sont définis |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | update:pressed | boolean | Changement d'état |

Exemples

<script setup lang="ts">
import { ref } from 'vue'
import { Toggle } from 'fab-ui'
import { BoldIcon } from 'lucide-vue-next'

const bold = ref(false)
</script>

<template>
  <!-- Toggle icône (éditeur de texte) -->
  <Toggle v-model:pressed="bold" variant="primary" shape="outline" aria-label="Gras">
    <BoldIcon />
  </Toggle>
</template>
<!-- Toggle texte contextuel -->
<Toggle
  v-model:pressed="isPublic"
  variant="success"
  on-value="Public"
  off-value="Privé"
  shape="plain"
/>

ToggleSwitch

Interrupteur de type switch (on/off) avec label intégré. Tailles limitées à small, default, large.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | Severity | 'primary' | Couleur quand le switch est actif | | size | 'small' \| 'default' \| 'large' | 'default' | Taille | | label | string | — | Libellé affiché à droite du switch | | checked | boolean | — | État contrôlé | | defaultChecked | boolean | — | État initial non contrôlé | | disabled | boolean | false | Désactive le switch | | required | boolean | false | Marque comme requis | | name | string | — | Nom pour la soumission de formulaire | | class | string | — | Classes additionnelles sur le wrapper |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | update:checked | boolean | Changement d'état |

Exemples

<script setup lang="ts">
import { ref } from 'vue'
import { ToggleSwitch } from 'fab-ui'

const notifications = ref(true)
</script>

<template>
  <ToggleSwitch v-model:checked="notifications" label="Notifications push" />
</template>
<!-- Variantes de couleur et taille -->
<ToggleSwitch v-model:checked="darkMode" variant="contrast" label="Mode sombre" size="large" />
<ToggleSwitch v-model:checked="analytics" variant="info" label="Analytics" size="small" />

4. Badge & Avatar

Badge

Pastille d'étiquette inline avec 8 couleurs, 3 formes et 5 tailles. Supporte un point coloré décoratif via dot.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | Severity | 'primary' | Couleur sémantique | | shape | BadgeShape | 'filled' | Forme (filled, outline, ghost) | | size | Size | 'default' | Taille | | fluid | boolean | false | Prend toute la largeur disponible | | dot | boolean | false | Affiche un petit point coloré à gauche du texte | | as | string \| Component | 'span' | Élément HTML rendu | | class | string | — | Classes additionnelles |

Slots

| Slot | Description | |------|-------------| | default | Contenu du badge (texte) |

Exemples

<script setup lang="ts">
import { Badge } from 'fab-ui'
</script>

<template>
  <!-- Formes -->
  <Badge variant="success" shape="filled">Actif</Badge>
  <Badge variant="warning" shape="outline">En attente</Badge>
  <Badge variant="danger" shape="ghost">Erreur</Badge>

  <!-- Avec point -->
  <Badge variant="success" dot>En ligne</Badge>

  <!-- Tailles -->
  <Badge size="tiny" variant="info">Tiny</Badge>
  <Badge size="extra" variant="primary">Extra</Badge>
</template>

OverlayBadge

Enveloppe positionnant un badge numérique (ou un point) en overlay par rapport à son enfant. Typiquement utilisé sur une icône ou un avatar.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | value | number \| null | — | Nombre affiché. 0, null ou absent = point. Max affiché : 99+ | | variant | Severity | 'danger' | Couleur du badge | | position | 'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' | 'top-right' | Position de l'overlay | | class | string | — | Classes additionnelles sur le wrapper |

Slots

| Slot | Description | |------|-------------| | default | Élément cible (icône, avatar…) |

Exemples

<script setup lang="ts">
import { OverlayBadge } from 'fab-ui'
import { BellIcon } from 'lucide-vue-next'
</script>

<template>
  <!-- Badge numérique sur une icône -->
  <OverlayBadge :value="12" variant="danger">
    <BellIcon class="size-6" />
  </OverlayBadge>

  <!-- Point indicateur (pas de valeur) -->
  <OverlayBadge variant="success">
    <img src="/avatar.jpg" class="size-8 rounded-full" alt="Avatar" />
  </OverlayBadge>

  <!-- Position personnalisée -->
  <OverlayBadge :value="3" position="bottom-right" variant="primary">
    <BellIcon class="size-6" />
  </OverlayBadge>
</template>

Avatar

Conteneur circulaire pour une image ou des initiales, basé sur AvatarRoot de Reka UI. Utiliser les sous-composants Reka UI AvatarImage et AvatarFallback à l'intérieur.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | size | Size | 'default' | Taille (tiny=20px, small=28px, default=36px, large=48px, extra=64px) | | class | string | — | Classes additionnelles |

Slots

| Slot | Description | |------|-------------| | default | AvatarImage + AvatarFallback de Reka UI |

Exemples

<script setup lang="ts">
import { Avatar } from 'fab-ui'
import { AvatarImage, AvatarFallback } from 'reka-ui'
</script>

<template>
  <!-- Avatar avec image et fallback initiales -->
  <Avatar size="large">
    <AvatarImage src="/users/alice.jpg" alt="Alice" />
    <AvatarFallback class="bg-blue-100 text-blue-700 font-semibold text-sm">
      AL
    </AvatarFallback>
  </Avatar>

  <!-- Différentes tailles -->
  <Avatar size="tiny"><AvatarFallback>A</AvatarFallback></Avatar>
  <Avatar size="small"><AvatarFallback>AB</AvatarFallback></Avatar>
  <Avatar size="extra"><AvatarFallback>AB</AvatarFallback></Avatar>
</template>

5. Feedback

Alert

Bannière d'alerte avec icône, titre, fermeture et disparition automatique. Se monte/démonte avec une animation de transition.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | Severity | 'info' | Couleur sémantique | | title | string | — | Titre en gras affiché avant le corps | | dismissible | boolean | false | Affiche un bouton de fermeture × | | icon | boolean | true | Affiche l'icône par défaut selon la variante | | iconComponent | Component | — | Remplace l'icône par défaut par un composant Vue | | delay | number | 0 | Fermeture automatique après N millisecondes (0 = désactivé) | | class | string | — | Classes additionnelles |

Slots

| Slot | Description | |------|-------------| | default | Corps du message | | icon | Icône personnalisée (remplace iconComponent) |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | close | — | Émis quand l'alerte se ferme (bouton ou delay) |

Exemples

<script setup lang="ts">
import { Alert } from 'fab-ui'
</script>

<template>
  <Alert variant="success" title="Enregistrement réussi">
    Vos modifications ont bien été sauvegardées.
  </Alert>

  <Alert variant="danger" title="Erreur" :dismissible="true">
    Une erreur est survenue lors de la connexion au serveur.
  </Alert>
</template>
<!-- Auto-fermeture après 5 secondes -->
<Alert variant="info" :delay="5000" @close="handleClose">
  Votre session expirera dans 5 minutes.
</Alert>

<!-- Icône personnalisée -->
<Alert variant="warning" :icon-component="ShieldIcon" title="Sécurité">
  Activez l'authentification à deux facteurs.
</Alert>

Toast — useToast()

Système de notifications toast global. Les toasts s'affichent via le composable useToast(). Maximum 3 toasts simultanés — le plus ancien est automatiquement retiré.

Durées par défaut

| Variante | Durée | Persistant | |----------|-------|-----------| | info | 4 000 ms | non | | success | 4 000 ms | non | | warning | 6 000 ms | non | | error | — | oui (doit être fermé manuellement) |

API du composable

const { toasts, success, warning, error, info, dismiss } = useToast()

| Méthode | Signature | Description | |---------|-----------|-------------| | success | (message: string, options?: ToastOptions) => string | Toast de succès | | warning | (message: string, options?: ToastOptions) => string | Toast d'avertissement | | error | (message: string, options?: ToastOptions) => string | Toast d'erreur persistant | | info | (message: string, options?: ToastOptions) => string | Toast informatif | | dismiss | (id: string) => void | Ferme un toast par son identifiant | | toasts | Toast[] réactif | Liste des toasts visibles |

Type ToastOptions

type ToastOptions = {
  duration?:   number;   // Durée d'affichage en ms (écrase la valeur par défaut)
  persistent?: boolean;  // true = ne se ferme pas automatiquement
}

Exemple

<script setup lang="ts">
import { useToast } from '@/composables/useToast'

const toast = useToast()

async function saveData() {
  try {
    await api.save()
    toast.success('Données enregistrées avec succès.')
  } catch (err) {
    toast.error('Impossible d'enregistrer. Vérifiez votre connexion.')
  }
}

function notify() {
  const id = toast.info('Traitement en cours…', { persistent: true })
  setTimeout(() => toast.dismiss(id), 3000)
}
</script>

<template>
  <button @click="saveData">Enregistrer</button>
  <button @click="toast.warning('Stock faible !', { duration: 8000 })">Avertir</button>
</template>

ProgressBar

Barre de progression déterminée ou indéterminée (animation) avec label de pourcentage optionnel.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | value | number | — | Valeur de 0 à 100. Si absent : mode indéterminé (animation de défilement) | | variant | Severity | 'primary' | Couleur de la barre | | size | 'tiny' \| 'small' \| 'default' \| 'large' | 'default' | Épaisseur de la barre | | label | boolean | false | Affiche le pourcentage en texte à droite | | class | string | — | Classes additionnelles |

Note : size n'inclut pas 'extra' pour ce composant.

Exemple

<script setup lang="ts">
import { ref } from 'vue'
import { ProgressBar } from 'fab-ui'

const progress = ref(65)
</script>

<template>
  <!-- Barre déterminée avec label -->
  <ProgressBar :value="progress" variant="primary" :label="true" />

  <!-- Barre indéterminée (chargement) -->
  <ProgressBar variant="info" size="tiny" />

  <!-- Variantes de couleur -->
  <ProgressBar :value="80" variant="success" size="small" />
  <ProgressBar :value="30" variant="danger" size="large" />
</template>

6. Contenu

Card + CardHeader

Conteneur de contenu avec bordure, ombre et slots structurels. CardHeader est un sous-composant optionnel qui organise le titre et les actions en grille.

Card — Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | 'default' \| Severity | 'default' | Couleur de la bordure | | padded | boolean | true | Ajoute un padding interne et un gap entre les slots | | bordered | boolean | true | Affiche la bordure | | hoverable | boolean | false | Effet de survol (ombre + curseur pointer) | | raised | boolean | false | Ombre plus prononcée | | class | string | — | Classes additionnelles |

Card — Slots

| Slot | Description | |------|-------------| | header | Zone d'en-tête (typiquement CardHeader) | | default | Corps principal | | footer | Pied de carte (actions, boutons…) |

CardHeader — Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | flush | boolean | false | Supprime le padding — utile pour une image collée aux bords | | class | string | — | Classes additionnelles |

Exemples

<script setup lang="ts">
import { Card, CardHeader } from 'fab-ui'
import { Button } from 'fab-ui'
</script>

<template>
  <Card>
    <template #header>
      <CardHeader>
        <h3 class="font-semibold text-lg">Titre de la carte</h3>
        <p class="text-sm text-muted-foreground">Description optionnelle</p>
      </CardHeader>
    </template>

    <p>Contenu principal de la carte.</p>

    <template #footer>
      <div class="flex justify-end gap-2 px-6">
        <Button variant="secondary" shape="ghost">Annuler</Button>
        <Button variant="primary">Confirmer</Button>
      </div>
    </template>
  </Card>
</template>
<!-- Carte cliquable avec variante de couleur -->
<Card variant="success" :hoverable="true" :raised="true" @click="handleClick">
  <template #header>
    <CardHeader>
      <h3 class="font-semibold">Paiement réussi</h3>
    </CardHeader>
  </template>
  <p class="px-6">Votre commande a été confirmée.</p>
</Card>

Code

Bloc de code avec coloration syntaxique (highlight.js, thème Atom One Dark), bouton de copie, numéros de ligne et hauteur maximale configurable.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | code | string | — | Code à afficher (alternatif : slot par défaut) | | language | string | — | Langage pour highlight.js (javascript, php, json, bash, yaml…) | | filename | string | — | Nom de fichier affiché dans l'en-tête | | copyable | boolean | true | Affiche le bouton "Copier" | | lineNumbers | boolean | false | Affiche les numéros de ligne | | maxHeight | string | — | CSS max-height du corps scrollable (ex : '400px') | | class | string | — | Classes additionnelles |

Slots

| Slot | Description | |------|-------------| | default | Alternative à code : texte brut du code (le composant extrait le texte) |

Exemples

<script setup lang="ts">
import { Code } from 'fab-ui'

const snippet = `const greet = (name: string) => \`Hello, \${name}!\``
</script>

<template>
  <!-- Via prop code -->
  <Code :code="snippet" language="typescript" filename="greet.ts" />

  <!-- Via slot -->
  <Code language="json" :line-numbers="true">{{ '{"key": "value"}' }}</Code>

  <!-- Sans bouton de copie, avec hauteur max -->
  <Code :code="longCode" language="php" :copyable="false" max-height="300px" />
</template>

Image

Composant image avec skeleton de chargement, fallback en cas d'erreur, ratio d'aspect et object-fit configurables.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | src | string | — | Requis. URL de l'image | | alt | string | '' | Texte alternatif | | ratio | '1/1' \| '4/3' \| '16/9' \| '3/2' \| '2/3' \| '9/16' | — | Ratio d'aspect fixe | | fit | 'cover' \| 'contain' \| 'fill' \| 'none' | 'cover' | CSS object-fit | | skeleton | boolean | true | Affiche un skeleton pendant le chargement | | fallback | string | — | URL de remplacement en cas d'erreur | | rounded | boolean \| 'full' | false | true = coins arrondis, 'full' = cercle | | class | string | — | Classes additionnelles sur le wrapper |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | load | Event | Image chargée avec succès | | error | Event | Erreur de chargement |

Exemples

<script setup lang="ts">
import { Image } from 'fab-ui'
</script>

<template>
  <!-- Image 16/9 avec skeleton -->
  <Image src="/banner.jpg" ratio="16/9" alt="Bannière" />

  <!-- Avatar circulaire -->
  <Image src="/avatar.jpg" ratio="1/1" rounded="full" class="w-16" />

  <!-- Avec fallback -->
  <Image
    src="/product.jpg"
    fallback="/placeholder.jpg"
    ratio="4/3"
    fit="contain"
    @error="onImageError"
  />
</template>

Carousel + CarouselSlide

Carrousel tactile avec navigation, indicateurs et lecture automatique.

Carousel — Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | arrows | boolean | true | Affiche les flèches de navigation | | dots | boolean | true | Affiche les points indicateurs | | autoplay | number | 0 | Intervalle en ms entre les slides (0 = désactivé) | | loop | boolean | false | Retourne au début après la dernière slide | | perView | number | 1 | Nombre de slides visibles simultanément | | gap | string | 'gap-4' | Classe Tailwind de gap entre les slides | | class | string | — | Classes additionnelles |

Carousel — Slots

| Slot | Description | |------|-------------| | default | Les CarouselSlide à afficher |

CarouselSlide — Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | perView | number | 1 | Doit correspondre au :perView du Carousel parent pour le calcul de largeur | | class | string | — | Classes additionnelles |

Exemple

<script setup lang="ts">
import { Carousel, CarouselSlide } from 'fab-ui'

const slides = [
  { title: 'Slide 1', img: '/img/1.jpg' },
  { title: 'Slide 2', img: '/img/2.jpg' },
  { title: 'Slide 3', img: '/img/3.jpg' },
]
</script>

<template>
  <!-- Carrousel simple -->
  <Carousel :autoplay="4000" loop>
    <CarouselSlide v-for="s in slides" :key="s.title">
      <img :src="s.img" :alt="s.title" class="w-full rounded-lg" />
    </CarouselSlide>
  </Carousel>

  <!-- Multi-slides (2 visibles) -->
  <Carousel :per-view="2" gap="gap-6" :dots="false">
    <CarouselSlide v-for="s in slides" :key="s.title" :per-view="2">
      <div class="rounded-lg bg-gray-100 p-6">{{ s.title }}</div>
    </CarouselSlide>
  </Carousel>
</template>

7. Formulaires étendus

Slider

Curseur de sélection de valeur numérique, basé sur SliderRoot de Reka UI. Supporte les plages (range) via un tableau de valeurs.

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | variant | Severity | 'primary' | Couleur de la range et du thumb | | size | 'small' \| 'default' \| 'large' | 'default' | Épaisseur de la piste et taille du thumb | | modelValue / v-model | number[] | — | Valeur(s) contrôlée(s) | | defaultValue | number[] | — | Valeur(s) initiale(s) non contrôlée(s) | | min | number | 0 | Valeur minimale | | max | number | 100 | Valeur maximale | | step | number | 1 | Incrément | | disabled | boolean | false | Désactive le slider | | orientation | 'horizontal' \| 'vertical' | 'horizontal' | Orientation | | class | string | — | Classes additionnelles |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | update:modelValue | number[] | Changement de valeur pendant le glissement | | valueCommit | number[] | Valeur finalisée au relâchement |

Exemples

<script setup lang="ts">
import { ref } from 'vue'
import { Slider } from 'fab-ui'

const volume = ref([50])
const priceRange = ref([20, 80])
</script>

<template>
  <!-- Curseur simple -->
  <Slider v-model="volume" :min="0" :max="100" variant="primary" />
  <p>Volume : {{ volume[0] }}%</p>

  <!-- Plage de prix -->
  <Slider v-model="priceRange" :min="0" :max="200" :step="5" variant="success" size="large" />
  <p>Prix : {{ priceRange[0] }}€ – {{ priceRange[1] }}€</p>
</template>

8. Overlays & Navigation

ContextMenu

Menu contextuel (clic droit) composé de plusieurs sous-composants. Basé sur Reka UI.

Composants exportés

| Composant | Rôle | |-----------|------| | ContextMenu | Racine — gère l'état ouvert/fermé | | ContextMenuTrigger | Zone déclenchant l'ouverture (clic droit) | | ContextMenuContent | Conteneur du menu (panneau flottant) | | ContextMenuItem | Élément cliquable | | ContextMenuLabel | Libellé non cliquable (groupe) | | ContextMenuSeparator | Séparateur horizontal | | ContextMenuShortcut | Raccourci clavier aligné à droite | | ContextMenuGroup | Groupe logique d'items | | ContextMenuSub | Racine du sous-menu | | ContextMenuSubTrigger | Déclencheur du sous-menu | | ContextMenuSubContent | Contenu du sous-menu | | ContextMenuCheckboxItem | Item avec case à cocher | | ContextMenuRadioGroup | Groupe radio | | ContextMenuRadioItem | Item radio |

ContextMenuContent — Props

Hérite de ContextMenuContentProps de Reka UI.

| Prop | Type | Description | |------|------|-------------| | class | string | Classes additionnelles |

ContextMenuItem — Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | inset | boolean | false | Décale le contenu vers la droite (alignement avec icône) | | variant | 'default' \| 'danger' | 'default' | 'danger' colore l'item en rouge | | disabled | boolean | false | Désactive l'item | | class | string | — | Classes additionnelles |

Exemple complet

<script setup lang="ts">
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuLabel,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuSub,
  ContextMenuSubTrigger,
  ContextMenuSubContent,
  ContextMenuCheckboxItem,
} from 'fab-ui'
import { ref } from 'vue'

const showPreview = ref(true)

function openFile()   { /* ... */ }
function deleteFile() { /* ... */ }
</script>

<template>
  <ContextMenu>
    <ContextMenuTrigger>
      <div class="w-64 h-32 rounded-lg border border-dashed flex items-center justify-center cursor-default select-none">
        Clic droit ici
      </div>
    </ContextMenuTrigger>

    <ContextMenuContent>
      <ContextMenuLabel>Actions</ContextMenuLabel>
      <ContextMenuSeparator />

      <ContextMenuItem @click="openFile">
        Ouvrir
        <ContextMenuShortcut>⌘O</ContextMenuShortcut>
      </ContextMenuItem>

      <ContextMenuSub>
        <ContextMenuSubTrigger>Partager</ContextMenuSubTrigger>
        <ContextMenuSubContent>
          <ContextMenuItem>Par e-mail</ContextMenuItem>
          <ContextMenuItem>Lien public</ContextMenuItem>
        </ContextMenuSubContent>
      </ContextMenuSub>

      <ContextMenuCheckboxItem v-model:checked="showPreview">
        Aperçu
      </ContextMenuCheckboxItem>

      <ContextMenuSeparator />

      <ContextMenuItem variant="danger" @click="deleteFile">
        Supprimer
        <ContextMenuShortcut>⌫</ContextMenuShortcut>
      </ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</template>

CollapsibleContent

Extension du CollapsibleContent de Reka UI avec un mode overlay : le panneau se déploie par-dessus les éléments suivants sans perturber le flux du document (idéal pour des menus déroulants custom).

Props

Hérite de CollapsibleContentProps de Reka UI.

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | overlay | boolean | false | Positionne le contenu en absolute par-dessus le reste | | class | string | — | Classes additionnelles |

Note : En mode overlay, le parent doit avoir position: relative.

Exemple

<script setup lang="ts">
import { CollapsibleRoot, CollapsibleTrigger } from 'reka-ui'
import { CollapsibleContent } from 'fab-ui'
import { Button } from 'fab-ui'
</script>

<template>
  <!-- Collapsible normal (dans le flux) -->
  <CollapsibleRoot>
    <CollapsibleTrigger as-child>
      <Button variant="secondary" shape="outline">Afficher les détails</Button>
    </CollapsibleTrigger>
    <CollapsibleContent class="mt-2 p-4 border rounded-lg">
      <p>Contenu révélé dans le flux normal.</p>
    </CollapsibleContent>
  </CollapsibleRoot>

  <!-- Mode overlay (dropdown custom) -->
  <div class="relative">
    <CollapsibleRoot>
      <CollapsibleTrigger as-child>
        <Button variant="secondary" shape="outline">Options</Button>
      </CollapsibleTrigger>
      <CollapsibleContent :overlay="true" class="p-2">
        <button class="block w-full text-left px-3 py-2 hover:bg-gray-100 rounded">Option 1</button>
        <button class="block w-full text-left px-3 py-2 hover:bg-gray-100 rounded">Option 2</button>
      </CollapsibleContent>
    </CollapsibleRoot>
  </div>
</template>

SpeedDial

Bouton d'action flottant (FAB) qui révèle un ensemble d'actions secondaires dans une direction configurable. Chaque action dispose d'un tooltip au survol.

Type SpeedDialItem

interface SpeedDialItem {
  label:     string;     // Texte du tooltip
  icon:      Component;  // Icône Lucide (ou tout composant Vue)
  onClick:   () => void; // Callback au clic
  disabled?: boolean;    // Désactive l'action
  variant?:  Severity;   // Couleur du bouton action (défaut : 'secondary')
}

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | items | SpeedDialItem[] | — | Requis. Liste des actions secondaires | | direction | 'up' \| 'down' \| 'left' \| 'right' | 'up' | Direction d'ouverture | | variant | Severity | 'primary' | Couleur du bouton principal | | size | 'small' \| 'default' \| 'large' | 'default' | Taille du bouton principal | | icon | Component | — | Icône custom du bouton principal (sinon + / ×) | | disabled | boolean | false | Désactive tout le composant | | class | string | — | Classes additionnelles |

Exemple

<script setup lang="ts">
import { SpeedDial, type SpeedDialItem } from 'fab-ui'
import { EditIcon, TrashIcon, ShareIcon, DownloadIcon } from 'lucide-vue-next'

const actions: SpeedDialItem[] = [
  { label: 'Modifier',     icon: EditIcon,     onClick: () => console.log('edit'),     variant: 'primary' },
  { label: 'Partager',     icon: ShareIcon,    onClick: () => console.log('share'),    variant: 'info' },
  { label: 'Télécharger',  icon: DownloadIcon, onClick: () => console.log('download'), variant: 'success' },
  { label: 'Supprimer',    icon: TrashIcon,    onClick: () => console.log('delete'),   variant: 'danger' },
]
</script>

<template>
  <!-- Fixé en bas à droite -->
  <div class="fixed bottom-6 right-6 z-50">
    <SpeedDial :items="actions" direction="up" variant="primary" size="default" />
  </div>

  <!-- Direction horizontale -->
  <SpeedDial :items="actions" direction="right" variant="secondary" size="small" />
</template>

9. Données

DataTable

Tableau de données complet avec tri multi-colonnes, recherche globale (debounced), sélection de lignes, pagination et slots custom par colonne.

Type DataTableColumn

interface DataTableColumn<Row = Record<string, unknown>> {
  key:       string;                                // Clé dans l'objet de données
  label:     string;                                // En-tête de la colonne
  sortable?: boolean;                               // Active le tri sur cette colonne
  width?:    string;                                // Largeur CSS ('120px', '20%'…)
  align?:    'left' | 'center' | 'right';           // Alignement du contenu (défaut : 'left')
  format?:   (value: unknown, row: Row) => string;  // Formateur de valeur (fallback si pas de slot)
}

Props

| Prop | Type | Défaut | Description | |------|------|--------|-------------| | columns | DataTableColumn[] | — | Requis. Définition des colonnes | | data | T[] | — | Requis. Données à afficher | | searchable | boolean | false | Affiche un champ de recherche globale (debounce 250 ms) | | selectable | boolean | false | Affiche une colonne de cases à cocher | | pageSize | number | 15 | Nombre de lignes par page (0 = pas de pagination) | | pageSizes | number[] | [10,25,50,100] | Options du sélecteur de taille de page | | loading | boolean | false | Affiche le skeleton de chargement | | emptyText | string | 'Aucun résultat.' | Texte affiché si aucune donnée | | striped | boolean | false | Lignes alternées (zèbre) | | hoverable | boolean | true | Mise en évidence au survol | | class | string | — | Classes additionnelles |

v-model

| Binding | Type | Description | |---------|------|-------------| | v-model:selected | T[] | Tableau des lignes sélectionnées (active implicitement la colonne checkbox) |

Événements

| Événement | Payload | Description | |-----------|---------|-------------| | @row-click | row: T | Clic sur une ligne entière | | @cell-click | key: string, row: T | Clic sur une cellule précise |

Slots

| Slot | Props du slot | Description | |------|---------------|-------------| | #column-{key} | { row: T, value: unknown } | Rendu custom pour la colonne key. Remplace le formateur format | | #empty | — | Contenu de l'état vide (remplace emptyText) |

Exemple complet

<script setup lang="ts">
import { ref } from 'vue'
import { DataTable, type DataTableColumn } from 'fab-ui'
import { Badge } from 'fab-ui'
import { Button } from 'fab-ui'

interface User {
  id:     number
  name:   string
  email:  string
  role:   string
  status: 'active' | 'inactive' | 'pending'
  joined: string
}

const columns: DataTableColumn<User>[] = [
  { key: 'name',   label: 'Nom',     sortable: true,  width: '200px' },
  { key: 'email',  label: 'E-mail',  sortable: true },
  { key: 'role',   label: 'Rôle',   align: 'center', width: '120px' },
  { key: 'status', label: 'Statut', align: 'center', width: '120px' },
  { key: 'joined', label: 'Inscrit',
    sortable: true,
    format: (v) => new Date(v as string).toLocaleDateString('fr-FR'),
  },
  { key: 'actions', label: '', width: '80px', align: 'right' },
]

const users = ref<User[]>([
  { id: 1, name: 'Alice Martin',   email: '[email protected]', role: 'Admin',    status: 'active',   joined: '2024-01-15' },
  { id: 2, name: 'Bob Dupont',     email: '[email protected]',   role: 'Éditeur',  status: 'inactive', joined: '2024-03-22' },
  { id: 3, name: 'Claire Bernard', email: '[email protected]',role: 'Lecteur',  status: 'pending',  joined: '2024-06-01' },
])

const selected = ref<User[]>([])
const loading  = ref(false)

function handleRowClick(user: User) {
  console.log('Ligne cliquée :', user.name)
}

function editUser(user: User) {
  console.log('Modifier :', user.id)
}
</script>

<template>
  <DataTable
    :columns="columns"
    :data="users"
    v-model:selected="selected"
    :searchable="true"
    :striped="true"
    :loading="loading"
    :page-size="10"
    empty-text="Aucun utilisateur trouvé."
    @row-click="handleRowClick"
    @cell-click="(key, row) => console.log(key, row)"
  >
    <!-- Slot custom pour la colonne status -->
    <template #column-status="{ value }">
      <Badge
        :variant="value === 'active' ? 'success' : value === 'pending' ? 'warning' : 'secondary'"
        shape="filled"
        size="small"
      >
        {{ value === 'active' ? 'Actif' : value === 'pending' ? 'En attente' : 'Inactif' }}
      </Badge>
    </template>

    <!-- Slot custom pour les actions -->
    <template #column-actions="{ row }">
      <Button
        size="small"
        variant="secondary"
        shape="ghost"
        @click.stop="editUser(row)"
      >
        Modifier
      </Button>
    </template>

    <!-- État vide personnalisé -->
    <template #empty>
      <div class="py-8 text-center text-gray-400">
        <p class="font-medium">Aucun résultat</p>
        <p class="text-sm mt-1">Modifiez vos critères de recherche.</p>
      </div>
    </template>
  </DataTable>

  <!-- Affichage des lignes sélectionnées -->
  <p v-if="selected.length" class="mt-4 text-sm text-gray-600">
    {{ selected.length }} utilisateur(s) sélectionné(s) :
    {{ selected.map(u => u.name).join(', ') }}
  </p>
</template>