feat: Nouveaux composants et styles Lions.dev

Ajout de nouveaux composants composites et styles SCSS Lions.dev pour enrichir l'extension Freya.

## Nouveaux composants
- confirmDialog: Dialogue de confirmation avec actions
- dialog: Dialogue générique personnalisable
- menu: Menu de navigation vertical
- menubar: Barre de menu horizontale
- messages: Composant d'affichage de messages multiples
- overlayPanel: Panneau overlay positionnable
- sidebar: Panneau latéral collapsible
- toast: Notifications toast

## Améliorations composants existants
- Amélioration support Ajax pour tous les champs (fieldInput, fieldSelect, etc.)
- Correction attributs pour compatibilité avec composite:clientBehavior
- Optimisation panel avec support toggleable et collapsed
- Amélioration button, commandButton, linkButton avec severities
- Amélioration dataTable et dataView avec pagination

## Styles SCSS Lions.dev
- Création architecture SCSS modulaire dans sass/lions/
- Variables globales Lions.dev (_variables.scss)
- Mixins réutilisables (_mixins.scss)
- Styles par catégorie de composants (12 fichiers)
- Point d'entrée lions.scss pour compilation

## Tests et démo
- Page buttons-showcase pour démonstration boutons
- Amélioration components-demo avec nouveaux composants
- Styles CSS demo pour tests visuels

## Infrastructure
- Mise à jour .gitignore (exclusion docs temporaires)
- Mise à jour versions dans pom.xml

Tous les composants respectent les patterns PrimeFaces et la charte graphique Lions.dev.
This commit is contained in:
dahoud
2026-01-03 14:04:05 +00:00
parent 12ef12c7e8
commit 751216b7d3
63 changed files with 16049 additions and 791 deletions

19
.gitignore vendored
View File

@@ -168,3 +168,22 @@ coverage/
docs/_build/
site/
# Documentation de développement/session (garder uniquement README.md)
*_HANDOFF_*.md
*_COMPLETE*.md
*_GUIDE*.md
*_REPORT*.md
*_SUMMARY*.md
*_AUDIT*.md
*_DEBUG*.md
*_FINAL*.md
*_MIGRATION*.md
*_OPTIMISATION*.md
*_SESSION*.md
AJAX_*.md
CHARTE_*.md
COMPOSANTS_*.md
GUIDE_*.md
PLAN_*.md
README_AMELIORATION_*.md

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.lions</groupId>
<artifactId>primefaces-freya-extension-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.0.0</version>
</parent>
<artifactId>primefaces-freya-extension-integration-tests</artifactId>
<name>PrimeFaces Freya Extension - Integration Tests</name>

View File

@@ -38,6 +38,7 @@ public class DemoBean implements Serializable {
// Données pour les nouveaux composants
private List<SampleUser> sampleUsers;
private SampleUser selectedUser; // Pour la sélection dans dataTable
private MenuModel breadcrumbModel;
private MenuModel stepsModel;
private MenuModel splitButtonModel;
@@ -264,6 +265,14 @@ public class DemoBean implements Serializable {
this.sampleUsers = sampleUsers;
}
public SampleUser getSelectedUser() {
return selectedUser;
}
public void setSelectedUser(SampleUser selectedUser) {
this.selectedUser = selectedUser;
}
public MenuModel getBreadcrumbModel() {
return breadcrumbModel;
}

View File

@@ -0,0 +1,595 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya">
<h:head>
<title>Lions.dev - Showcase Boutons</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Freya Official Resources -->
<h:outputStylesheet name="freya-layout/css/primeicons.css" />
<h:outputStylesheet name="freya-layout/css/primeflex.min.css" />
<h:outputStylesheet name="freya-layout/css/layout-light.css" />
<h:outputScript name="freya-layout/js/layout.js" />
<!-- Lions.dev SCSS (compiled) -->
<h:outputStylesheet library="sass" name="lions/lions.css" />
<style>
body {
background: linear-gradient(135deg, #E8F4FD 0%, #FFF9E6 100%);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #1F252B;
padding: 2rem;
}
.showcase-header {
background: linear-gradient(135deg, #2D9BEF 0%, #2275BC 100%);
color: white;
padding: 3rem 2rem;
border-radius: 12px;
margin-bottom: 2rem;
box-shadow: 0 20px 25px -5px rgba(45, 155, 239, 0.25);
}
.showcase-header h1 {
margin: 0 0 0.5rem 0;
font-size: 2.5rem;
font-weight: 700;
}
.showcase-header p {
margin: 0;
font-size: 1.125rem;
opacity: 0.95;
}
.section {
background: white;
border-radius: 12px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 1.75rem;
font-weight: 700;
margin: 0 0 1rem 0;
color: #2D9BEF;
border-bottom: 3px solid #2D9BEF;
padding-bottom: 0.5rem;
}
.subsection-title {
font-size: 1.25rem;
font-weight: 600;
margin: 1.5rem 0 1rem 0;
color: #4F5861;
}
.button-grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 1.5rem;
}
.color-swatch {
display: inline-block;
width: 1rem;
height: 1rem;
border-radius: 4px;
vertical-align: middle;
margin-right: 0.5rem;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.code-block {
background: #F4F6F8;
border: 1px solid #D2D9DF;
border-radius: 8px;
padding: 1rem;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.875rem;
color: #363E46;
overflow-x: auto;
margin: 1rem 0;
}
.info-box {
background: #E8F4FD;
border-left: 4px solid #2D9BEF;
padding: 1rem;
border-radius: 4px;
margin: 1rem 0;
}
.info-box p {
margin: 0;
color: #1C61A3;
}
.badge-demo {
position: relative;
display: inline-block;
}
</style>
</h:head>
<h:body>
<div class="showcase-header">
<h1>🦁 Lions.dev - Showcase Boutons</h1>
<p>Démonstration complète du système de boutons avec identité visuelle Lions.dev</p>
<p style="font-size: 0.875rem; margin-top: 0.5rem; opacity: 0.9;">
Version 1.0.0 | Priority #1 CRITIQUE | Gradients élégants • Animations hover • Focus accessible WCAG 2.1 AA
</p>
</div>
<h:form id="showcaseForm">
<p:messages id="messages" closable="true" />
<!-- ═══════════════════════════════════════════════════════════
SECTION 1: PALETTE DE COULEURS
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">🎨 Palette de Couleurs Lions.dev</h2>
<div class="grid">
<div class="col-12 md:col-6">
<h3 class="subsection-title">Couleurs Signature</h3>
<p><span class="color-swatch" style="background: #2D9BEF;"></span><strong>Lions Blue:</strong> #2D9BEF (Primary)</p>
<p><span class="color-swatch" style="background: #FFC700;"></span><strong>Lions Gold:</strong> #FFC700 (Premium/Help)</p>
</div>
<div class="col-12 md:col-6">
<h3 class="subsection-title">Couleurs Sémantiques</h3>
<p><span class="color-swatch" style="background: #22BB69;"></span><strong>Success:</strong> #22BB69</p>
<p><span class="color-swatch" style="background: #00BCD4;"></span><strong>Info:</strong> #00BCD4</p>
<p><span class="color-swatch" style="background: #FF9800;"></span><strong>Warning:</strong> #FF9800</p>
<p><span class="color-swatch" style="background: #F44336;"></span><strong>Danger:</strong> #F44336</p>
<p><span class="color-swatch" style="background: #8995A1;"></span><strong>Secondary:</strong> #8995A1</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 2: BOUTONS SOLID (Par défaut)
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">🎯 Boutons Solid - Toutes les Severities</h2>
<p class="text-secondary mb-3">Boutons avec gradients Lions.dev élégants et ombres colorées</p>
<div class="button-grid">
<fr:commandButton value="Primary (Blue)" icon="pi pi-check" severity="primary"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Secondary (Gray)" icon="pi pi-cog" severity="secondary"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Success (Green)" icon="pi pi-check-circle" severity="success"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Info (Cyan)" icon="pi pi-info-circle" severity="info"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Warning (Orange)" icon="pi pi-exclamation-triangle" severity="warning"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Help (Gold Premium)" icon="pi pi-star" severity="help"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Danger (Red)" icon="pi pi-times-circle" severity="danger"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Primary" severity="primary" /&gt;
&lt;fr:commandButton value="Success" severity="success" /&gt;
&lt;fr:commandButton value="Help (Gold)" severity="help" /&gt;
</div>
<div class="info-box">
<p><strong>💡 Astuce:</strong> Passez la souris sur les boutons pour voir les animations (translateY -2px + shadow elevation)</p>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 3: BOUTONS OUTLINED
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">📝 Boutons Outlined - Bordures Colorées</h2>
<p class="text-secondary mb-3">Boutons avec bordure colorée et background transparent</p>
<div class="button-grid">
<fr:commandButton value="Primary Outlined" icon="pi pi-check" severity="primary" outlined="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Secondary Outlined" icon="pi pi-cog" severity="secondary" outlined="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Success Outlined" icon="pi pi-check-circle" severity="success" outlined="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Info Outlined" icon="pi pi-info-circle" severity="info" outlined="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Warning Outlined" icon="pi pi-exclamation-triangle" severity="warning" outlined="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Help Outlined" icon="pi pi-star" severity="help" outlined="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Danger Outlined" icon="pi pi-times-circle" severity="danger" outlined="true"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Primary Outlined" severity="primary" outlined="true" /&gt;
&lt;fr:commandButton value="Danger Outlined" severity="danger" outlined="true" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 4: BOUTONS TEXT
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">🔗 Boutons Text - Minimalistes</h2>
<p class="text-secondary mb-3">Boutons sans bordure ni background (sauf au hover)</p>
<div class="button-grid">
<fr:commandButton value="Primary Text" icon="pi pi-check" severity="primary" text="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Success Text" icon="pi pi-check-circle" severity="success" text="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Info Text" icon="pi pi-info-circle" severity="info" text="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Danger Text" icon="pi pi-trash" severity="danger" text="true"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Primary Text" severity="primary" text="true" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 5: TAILLES (sm, base, lg)
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">📏 Tailles de Boutons</h2>
<p class="text-secondary mb-3">Support de 3 tailles : sm (32px), base/défaut (40px), lg (48px)</p>
<h3 class="subsection-title">Small (32px height)</h3>
<div class="button-grid">
<fr:commandButton value="Small Primary" icon="pi pi-check" severity="primary" size="sm"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Small Success" icon="pi pi-save" severity="success" size="sm"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Small Outlined" icon="pi pi-times" severity="danger" outlined="true" size="sm"
action="#{demoBean.save}" update="messages" />
</div>
<h3 class="subsection-title">Base / Défaut (40px height)</h3>
<div class="button-grid">
<fr:commandButton value="Base Primary" icon="pi pi-check" severity="primary"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Base Success" icon="pi pi-save" severity="success"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Base Outlined" icon="pi pi-times" severity="danger" outlined="true"
action="#{demoBean.save}" update="messages" />
</div>
<h3 class="subsection-title">Large (48px height)</h3>
<div class="button-grid">
<fr:commandButton value="Large Primary" icon="pi pi-check" severity="primary" size="lg"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Large Success" icon="pi pi-save" severity="success" size="lg"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Large Outlined" icon="pi pi-times" severity="danger" outlined="true" size="lg"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Small" size="sm" /&gt;
&lt;fr:commandButton value="Base" /&gt; &lt;!-- size="base" par défaut --&gt;
&lt;fr:commandButton value="Large" size="lg" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 6: BOUTONS ROUNDED (Pill shape)
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">🔵 Boutons Rounded - Forme Pill</h2>
<p class="text-secondary mb-3">Boutons avec border-radius: 9999px (forme pill/capsule)</p>
<div class="button-grid">
<fr:commandButton value="Rounded Primary" icon="pi pi-check" severity="primary" rounded="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Rounded Success" icon="pi pi-save" severity="success" rounded="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Rounded Outlined" icon="pi pi-heart" severity="danger" outlined="true" rounded="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Rounded Gold" icon="pi pi-star" severity="help" rounded="true"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Rounded Primary" severity="primary" rounded="true" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 7: BOUTONS RAISED (Elevated)
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">📐 Boutons Raised - Effet d'Élévation</h2>
<p class="text-secondary mb-3">Boutons avec shadow-lg pour effet surélevé (au hover: shadow-xl)</p>
<div class="button-grid">
<fr:commandButton value="Raised Primary" icon="pi pi-check" severity="primary" raised="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Raised Success" icon="pi pi-save" severity="success" raised="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Raised Gold Premium" icon="pi pi-star" severity="help" raised="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Raised Danger" icon="pi pi-trash" severity="danger" raised="true"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Raised" severity="primary" raised="true" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 8: COMBINAISONS DE VARIANTES
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">✨ Combinaisons de Variantes</h2>
<p class="text-secondary mb-3">Combinaisons de plusieurs attributs pour des boutons uniques</p>
<h3 class="subsection-title">Rounded + Raised</h3>
<div class="button-grid">
<fr:commandButton value="Rounded + Raised Primary" icon="pi pi-star" severity="primary"
rounded="true" raised="true" action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Rounded + Raised Gold" icon="pi pi-crown" severity="help"
rounded="true" raised="true" action="#{demoBean.save}" update="messages" />
</div>
<h3 class="subsection-title">Outlined + Rounded</h3>
<div class="button-grid">
<fr:commandButton value="Outlined + Rounded" icon="pi pi-heart" severity="danger"
outlined="true" rounded="true" action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Outlined + Rounded Info" icon="pi pi-info-circle" severity="info"
outlined="true" rounded="true" action="#{demoBean.save}" update="messages" />
</div>
<h3 class="subsection-title">Large + Rounded + Raised</h3>
<div class="button-grid">
<fr:commandButton value="Large + Rounded + Raised" icon="pi pi-bolt" severity="warning"
size="lg" rounded="true" raised="true" action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Combo" severity="primary"
rounded="true" raised="true" size="lg" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 9: ICON-ONLY BUTTONS
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">🎨 Icon-Only Buttons (Carrés et Ronds)</h2>
<p class="text-secondary mb-3">Boutons sans texte, uniquement avec icône (utilisez l'attribut title pour accessibilité)</p>
<h3 class="subsection-title">Icon-Only Square (défaut)</h3>
<div class="button-grid">
<fr:commandButton icon="pi pi-search" severity="primary" title="Rechercher"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-heart" severity="danger" title="Favoris"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-cog" severity="secondary" title="Paramètres"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-trash" severity="danger" outlined="true" title="Supprimer"
action="#{demoBean.save}" update="messages" />
</div>
<h3 class="subsection-title">Icon-Only Rounded (cercles)</h3>
<div class="button-grid">
<fr:commandButton icon="pi pi-plus" severity="success" rounded="true" title="Ajouter"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-pencil" severity="info" rounded="true" title="Éditer"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-trash" severity="danger" rounded="true" title="Supprimer"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-download" severity="primary" rounded="true" raised="true" title="Télécharger"
action="#{demoBean.save}" update="messages" />
</div>
<h3 class="subsection-title">Différentes Tailles</h3>
<div class="button-grid" style="align-items: center;">
<fr:commandButton icon="pi pi-star" severity="help" size="sm" title="Petit"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-star" severity="help" title="Moyen (défaut)"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-star" severity="help" size="lg" title="Grand"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton icon="pi pi-search" title="Rechercher" /&gt;
&lt;fr:commandButton icon="pi pi-plus" rounded="true" title="Ajouter" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 10: DISABLED STATE
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">❌ État Disabled</h2>
<p class="text-secondary mb-3">Boutons désactivés avec opacity: 0.5 et cursor: not-allowed</p>
<div class="button-grid">
<fr:commandButton value="Disabled Primary" icon="pi pi-check" severity="primary" disabled="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Disabled Success" icon="pi pi-save" severity="success" disabled="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Disabled Outlined" icon="pi pi-times" severity="danger" outlined="true" disabled="true"
action="#{demoBean.save}" update="messages" />
<fr:commandButton icon="pi pi-trash" severity="danger" rounded="true" disabled="true" title="Supprimer"
action="#{demoBean.save}" update="messages" />
</div>
<div class="code-block">
&lt;fr:commandButton value="Disabled" severity="primary" disabled="true" /&gt;
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 11: EXEMPLES PRATIQUES
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">💼 Exemples Pratiques - Cas d'Usage Réels</h2>
<h3 class="subsection-title">Formulaire d'Édition</h3>
<div class="button-grid">
<fr:commandButton value="Enregistrer" icon="pi pi-check" severity="success"
action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Annuler" icon="pi pi-times" severity="secondary" outlined="true"
action="#{demoBean.cancel}" update="messages" />
</div>
<h3 class="subsection-title">Actions Dangereuses avec Confirmation</h3>
<div class="button-grid">
<fr:commandButton value="Supprimer définitivement" icon="pi pi-trash" severity="danger" raised="true"
action="#{demoBean.delete}" update="messages">
<p:confirm header="Confirmation" message="Êtes-vous sûr de vouloir supprimer?" icon="pi pi-exclamation-triangle" />
</fr:commandButton>
<fr:commandButton value="Archiver" icon="pi pi-inbox" severity="warning" outlined="true"
action="#{demoBean.archive}" update="messages" />
</div>
<h3 class="subsection-title">Actions Premium / Upgrade</h3>
<div class="button-grid">
<fr:commandButton value="Passer à Premium" icon="pi pi-star" severity="help" size="lg" rounded="true" raised="true"
action="#{demoBean.upgrade}" update="messages" />
<fr:commandButton value="En savoir plus" icon="pi pi-info-circle" severity="help" outlined="true"
action="#{demoBean.learnMore}" update="messages" />
</div>
<h3 class="subsection-title">Toolbar d'Actions</h3>
<div class="button-grid">
<fr:commandButton icon="pi pi-plus" severity="success" rounded="true" title="Ajouter"
action="#{demoBean.add}" update="messages" />
<fr:commandButton icon="pi pi-pencil" severity="info" rounded="true" title="Éditer"
action="#{demoBean.edit}" update="messages" />
<fr:commandButton icon="pi pi-trash" severity="danger" rounded="true" title="Supprimer"
action="#{demoBean.delete}" update="messages" />
<fr:commandButton icon="pi pi-download" severity="primary" rounded="true" title="Exporter"
action="#{demoBean.export}" update="messages" />
<fr:commandButton icon="pi pi-refresh" severity="secondary" rounded="true" title="Rafraîchir"
action="#{demoBean.refresh}" update="messages" />
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
SECTION 12: ACCESSIBILITÉ
═══════════════════════════════════════════════════════════ -->
<div class="section">
<h2 class="section-title">♿ Accessibilité WCAG 2.1 AA</h2>
<div class="grid">
<div class="col-12 md:col-6">
<h3 class="subsection-title">✅ Fonctionnalités Accessibles</h3>
<ul>
<li><strong>Focus Ring:</strong> Anneau de focus visible (3px solid rgba blue) avec offset 2px</li>
<li><strong>Contrastes:</strong> Tous les boutons respectent WCAG AA (ratio ≥ 4.5:1)</li>
<li><strong>Navigation Clavier:</strong> Tous les boutons sont focusables et activables au clavier</li>
<li><strong>Tailles Touch-Friendly:</strong> Minimum 44px sur mobile (iOS guidelines)</li>
<li><strong>Reduced Motion:</strong> Support de prefers-reduced-motion (pas d'animations)</li>
<li><strong>High Contrast:</strong> Support de prefers-contrast: high (bordures 2px)</li>
</ul>
</div>
<div class="col-12 md:col-6">
<h3 class="subsection-title">🧪 Testez le Focus</h3>
<p class="text-secondary mb-3">Utilisez la touche TAB pour naviguer entre les boutons et observer le focus ring</p>
<div class="button-grid">
<fr:commandButton value="Focus 1" severity="primary" action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Focus 2" severity="success" action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Focus 3" severity="info" action="#{demoBean.save}" update="messages" />
<fr:commandButton value="Focus 4" severity="danger" action="#{demoBean.save}" update="messages" />
</div>
</div>
</div>
<div class="info-box" style="margin-top: 1.5rem;">
<p><strong>💡 Astuce Accessibilité:</strong> Pour les boutons icon-only, TOUJOURS utiliser l'attribut <code>title</code> pour fournir un label accessible aux lecteurs d'écran.</p>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FOOTER
═══════════════════════════════════════════════════════════ -->
<div class="section" style="background: #F4F6F8; border: 2px solid #2D9BEF;">
<h2 class="section-title">📊 Résumé Technique</h2>
<div class="grid">
<div class="col-12 md:col-4">
<h3 class="subsection-title">🎨 Design System</h3>
<ul style="font-size: 0.875rem;">
<li>Lions Blue #2D9BEF (Primary)</li>
<li>Lions Gold #FFC700 (Premium)</li>
<li>7 Severities disponibles</li>
<li>Gradients 135deg</li>
<li>Shadows colorées rgba(color, 0.25)</li>
<li>Border-radius: 8px (base)</li>
</ul>
</div>
<div class="col-12 md:col-4">
<h3 class="subsection-title">⚡ Animations</h3>
<ul style="font-size: 0.875rem;">
<li>Transition: 250ms cubic-bezier</li>
<li>Hover: translateY(-2px)</li>
<li>Hover: shadow elevation +10%</li>
<li>Active: translateY(0)</li>
<li>Support prefers-reduced-motion</li>
</ul>
</div>
<div class="col-12 md:col-4">
<h3 class="subsection-title">📏 Tailles</h3>
<ul style="font-size: 0.875rem;">
<li>Small: 32px height</li>
<li>Base: 40px height (défaut)</li>
<li>Large: 48px height</li>
<li>Touch minimum: 44px mobile</li>
<li>Icon spacing: 8px (base)</li>
</ul>
</div>
</div>
<p style="margin-top: 1.5rem; font-size: 0.875rem; color: #4F5861; text-align: center;">
<strong>© 2026 Lions Development Team</strong> | Version 1.0.0 | Priority #1 CRITIQUE ✅ COMPLÉTÉ
</p>
</div>
</h:form>
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade">
<p:commandButton value="Oui" type="button" styleClass="ui-confirmdialog-yes" />
<p:commandButton value="Non" type="button" styleClass="ui-confirmdialog-no" />
</p:confirmDialog>
</h:body>
</html>

View File

@@ -0,0 +1,122 @@
/* Common styles for Freya demo components */
.order-badge {
border-radius: 2px;
padding: .25em .5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: .3px;
}
.order-badge.order-delivered {
background: #ACEBB4;
color: #348861;
}
.order-badge.order-cancelled {
background: #FABD9A;
color: #AD342B;
}
.order-badge.order-pending {
background: #F8D895;
color: #A76927;
}
.order-badge.order-returned {
background: #EFB8E5;
color: #833F91;
}
.product-badge {
border-radius: 2px;
padding: .25em .5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: .3px;
text-align: center;
}
.product-badge.status-instock {
background: #ACEBB4;
color: #348861;
}
.product-badge.status-outofstock {
background: #FABD9A;
color: #AD342B;
}
.product-badge.status-lowstock {
background: #F8D895;
color: #A76927;
}
.customer-badge {
border-radius: 2px;
padding: .25em .5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: .3px;
}
.customer-badge.status-qualified {
background: #ACEBB4;
color: #348861;
}
.customer-badge.status-unqualified {
background: #FABD9A;
color: #AD342B;
}
.customer-badge.status-negotiation {
background: #F8D895;
color: #A76927;
}
.customer-badge.status-new {
background: #9BF2F7;
color: #2B7AA4;
}
.customer-badge.status-renewal {
background: #EFB8E5;
color: #833F91;
}
.customer-badge.status-proposal {
background: #FFD8B2;
color: #805B36;
}
.filter-container .ui-inputtext {
width: 400px;
}
.ui-selection-column {
width: 2rem;
}
@media (max-width: 640px) {
.filter-container {
width: 100%;
margin-top: .5rem;
}
.filter-container .ui-inputtext {
width: 100%;
}
.ui-selection-column {
width: auto;
text-align: center;
}
.ui-selection-column .ui-column-title {
display: none !important;
}
}

View File

@@ -0,0 +1,734 @@
.order-badge {
border-radius: 2px;
padding: 0.25em 0.5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: 0.3px;
}
.order-badge.order-delivered {
background: #ACEBB4;
color: #348861;
}
.order-badge.order-cancelled {
background: #FABD9A;
color: #AD342B;
}
.order-badge.order-pending {
background: #F8D895;
color: #A76927;
}
.order-badge.order-returned {
background: #EFB8E5;
color: #833F91;
}
.product-badge {
border-radius: 2px;
padding: 0.25em 0.5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: 0.3px;
text-align: center;
}
.product-badge.status-instock {
background: #ACEBB4;
color: #348861;
}
.product-badge.status-outofstock {
background: #FABD9A;
color: #AD342B;
}
.product-badge.status-lowstock {
background: #F8D895;
color: #A76927;
}
.customer-badge {
border-radius: 2px;
padding: 0.25em 0.5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: 0.3px;
}
.customer-badge.status-qualified {
background: #ACEBB4;
color: #348861;
}
.customer-badge.status-unqualified {
background: #FABD9A;
color: #AD342B;
}
.customer-badge.status-negotiation {
background: #F8D895;
color: #A76927;
}
.customer-badge.status-new {
background: #9BF2F7;
color: #2B7AA4;
}
.customer-badge.status-renewal {
background: #EFB8E5;
color: #833F91;
}
.customer-badge.status-proposal {
background: #FFD8B2;
color: #805B36;
}
.filter-container .ui-inputtext {
width: 400px;
}
.ui-selection-column {
width: 2rem;
}
@media (max-width: 640px) {
.filter-container {
width: 100%;
margin-top: 0.5rem;
}
.filter-container .ui-inputtext {
width: 100%;
}
.ui-selection-column {
width: auto;
text-align: center;
}
.ui-selection-column .ui-column-title {
display: none !important;
}
}
.crud-demo .ui-datatable {
margin-top: 1rem;
}
.crud-demo .product-image {
width: 100px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
}
.crud-demo .ui-dialog .product-image {
width: 250px;
margin: 0 auto 2rem auto;
display: block;
}
.crud-demo .ui-dialog-footer .ui-button {
min-width: 6rem;
}
.crud-demo .ui-datatable .ui-column-filter {
display: none;
}
.crud-demo .products-table-header {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.crud-demo .edit-button.ui-button {
margin-right: 0.5rem;
}
.crud-demo .orders-subtable {
padding: 1rem;
}
.crud-demo .products-table > .ui-datatable-tablewrapper > table > thead > tr > th:nth-child(2) {
width: 2rem;
}
.crud-demo .products-table .ui-rating {
display: inline-block;
}
@media (max-width: 640px) {
.products-table > .ui-datatable-tablewrapper > table > thead > tr > th:nth-child(2) .ui-column-title,
.products-table > .ui-datatable-tablewrapper > table > tbody > tr > td:nth-child(2) .ui-column-title {
display: none !important;
}
.products-buttonbar {
-ms-flex-direction: column;
flex-direction: column;
}
.products-buttonbar > div:last-child {
margin-top: 0.5rem;
}
}
.docs li {
line-height: 1.5;
}
.icons-demo .icons-list {
text-align: center;
color: rgba(41, 50, 65, 0.8);
}
.icons-demo .icons-list i {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.list-demo .product-name {
font-size: 1.5rem;
font-weight: 700;
}
.list-demo .product-description {
margin: 0 0 1rem 0;
}
.list-demo .product-category-icon {
vertical-align: middle;
margin-right: 0.5rem;
}
.list-demo .product-category {
font-weight: 600;
vertical-align: middle;
}
.list-demo .product-list-item {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
padding: 1rem;
}
.list-demo .product-list-item img {
width: 150px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
margin-right: 2rem;
}
.list-demo .product-list-item .product-list-detail {
flex: 1 1 0;
-ms-flex: 1 1 0px;
}
.list-demo .product-list-item .ui-rating {
margin: 0 0 0.5rem 0;
}
.list-demo .product-list-item .product-price {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.5rem;
align-self: flex-end;
}
.list-demo .product-list-item .product-list-action {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
}
.list-demo .product-list-item .ui-button {
margin-bottom: 0.5rem;
}
.list-demo .product-grid-item {
border: 1px solid #dee2e6;
box-shadow: none;
}
.list-demo .product-grid-item .product-grid-item-top,
.list-demo .product-grid-item .product-grid-item-bottom {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
}
.list-demo .product-grid-item img {
width: 75%;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
margin: 2rem 0;
}
.list-demo .product-grid-item .product-grid-item-content {
text-align: center;
}
.list-demo .product-grid-item .product-price {
font-size: 1.5rem;
font-weight: 600;
}
@media screen and (max-width: 576px) {
.list-demo .product-list-item {
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-align: center;
align-items: center;
}
.list-demo .product-list-item img {
width: 75%;
margin: 2rem 0;
}
.list-demo .product-list-item .product-list-detail {
text-align: center;
}
.list-demo .product-list-item .product-price {
align-self: center;
}
.list-demo .product-list-item .product-list-action {
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
}
.list-demo .product-list-item .product-list-action {
margin-top: 2rem;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-pack: justify;
justify-content: space-between;
-ms-flex-align: center;
align-items: center;
width: 100%;
}
}
.messages-demo .ui-button.ui-widget {
min-width: 6rem;
}
.messages-demo .field > label {
width: 125px;
}
.misc-demo .ui-button.ui-widget {
min-width: 6rem;
}
.misc-demo .badges .ui-badge,
.misc-demo .badges .ui-tag {
margin-right: 0.5rem;
}
.misc-demo .ui-chip.custom-chip {
background: var(--primary-color);
color: var(--primary-color-text);
}
.misc-demo .custom-scrolltop {
width: 2rem;
height: 2rem;
border-radius: 4px;
background-color: var(--primary-color);
}
.misc-demo .custom-scrolltop:hover {
background-color: var(--primary-color);
}
.misc-demo .custom-scrolltop .ui-scrolltop-icon {
font-size: 1rem;
color: var(--primary-color-text);
}
.misc-demo .custom-skeleton {
border: 1px solid var(--surface-d);
border-radius: 4px;
}
.misc-demo .custom-skeleton ul {
list-style: none;
}
.table-demo .ui-datatable .ui-column-filter {
display: none;
}
.table-demo .customers-table-header {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
@media (max-width: 640px) {
.table-demo .ui-progressbar {
margin-top: 0.5rem;
}
}
.custom-marker {
display: flex;
width: 2rem;
height: 2rem;
align-items: center;
justify-content: center;
color: #ffffff;
border-radius: 50%;
z-index: 1;
}
.ui-chronoline-event-content,
.ui-chronoline-event-opposite {
line-height: 1;
}
@media screen and (max-width: 960px) {
.customized-chronoline .ui-chronoline-event:nth-child(even) {
flex-direction: row !important;
}
.customized-chronoline .ui-chronoline-event:nth-child(even) .ui-chronoline-event-content {
text-align: left !important;
}
.customized-chronoline .ui-chronoline-event-opposite {
flex: 0;
}
.customized-chronoline .ui-card {
margin-top: 1rem;
}
}
.floatlabel-demo .field {
margin-top: 2rem;
}
/**
* prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics);
* @author Tim Shedor
*/
code[class*=language-],
pre[class*=language-] {
color: black;
background: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*=language-] {
position: relative;
margin: 0.5em 0;
overflow: visible;
padding: 0;
}
pre[class*=language-] > code {
position: relative;
border-left: 10px solid #358ccb;
box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;
background-color: #fdfdfd;
background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);
background-size: 3em 3em;
background-origin: content-box;
background-attachment: local;
}
code[class*=language] {
max-height: inherit;
padding: 0 1em;
display: block;
overflow: auto;
}
/* Margin bottom to accomodate shadow */
:not(pre) > code[class*=language-],
pre[class*=language-] {
background-color: #fdfdfd;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}
/* Inline code */
:not(pre) > code[class*=language-] {
position: relative;
padding: 0.2em;
border-radius: 0.3em;
color: #c92c2c;
border: 1px solid rgba(0, 0, 0, 0.1);
display: inline;
white-space: normal;
}
pre[class*=language-]:before,
pre[class*=language-]:after {
content: "";
z-index: -2;
display: block;
position: absolute;
bottom: 0.75em;
left: 0.18em;
width: 40%;
height: 20%;
max-height: 13em;
box-shadow: 0px 13px 8px #979797;
-webkit-transform: rotate(-2deg);
-moz-transform: rotate(-2deg);
-ms-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
transform: rotate(-2deg);
}
:not(pre) > code[class*=language-]:after,
pre[class*=language-]:after {
right: 0.75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #7D8B99;
}
.token.punctuation {
color: #5F6364;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.function-name,
.token.constant,
.token.symbol,
.token.deleted {
color: #c92c2c;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.function,
.token.builtin,
.token.inserted {
color: #2f9c0a;
}
.token.operator,
.token.entity,
.token.url,
.token.variable {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword,
.token.class-name {
color: #1990b8;
}
.token.regex,
.token.important {
color: #e90;
}
.language-css .token.string,
.style .token.string {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}
.token.important {
font-weight: normal;
}
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.namespace {
opacity: 0.7;
}
@media screen and (max-width: 767px) {
pre[class*=language-]:before,
pre[class*=language-]:after {
bottom: 14px;
box-shadow: none;
}
}
/* Plugin styles */
.token.tab:not(:empty):before,
.token.cr:before,
.token.lf:before {
color: #e0d7d1;
}
/* Plugin styles: Line Numbers */
pre[class*=language-].line-numbers {
padding-left: 0;
}
pre[class*=language-].line-numbers code {
padding-left: 3.8em;
}
pre[class*=language-].line-numbers .line-numbers-rows {
left: 0;
}
/* Plugin styles: Line Highlight */
pre[class*=language-][data-line] {
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
pre[data-line] code {
position: relative;
padding-left: 4em;
}
pre .line-highlight {
margin-top: 0;
}
/* PrimeFaces styles */
pre[class*=language-]:before, pre[class*=language-]:after {
display: none !important;
}
pre[class*=language-] code {
border-left: 6px solid var(--surface-border) !important;
box-shadow: none !important;
background: var(--surface-ground) !important;
margin: 1em 0;
color: var(--text-color);
}
.language-css .token.string,
.style .token.string {
background: transparent;
}
.block-section {
margin-bottom: 4rem;
}
.block-header {
padding: 1rem 2rem;
background-color: var(--surface-section);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: 1px solid var(--surface-d);
display: flex;
align-items: center;
justify-content: space-between;
}
.block-header .block-title {
font-size: 1.25rem;
font-weight: 600;
display: inline-flex;
align-items: center;
}
.block-header .block-title .badge-free {
border-radius: 4px;
padding: 0.25rem 0.5rem;
background-color: var(--orange-500);
color: white;
margin-left: 1rem;
font-weight: 600;
font-size: 0.875rem;
}
.block-header .block-title .badge-new {
border-radius: 4px;
padding: 0.25rem 0.5rem;
background-color: var(--green-500);
color: white;
margin-left: 1rem;
font-weight: 600;
font-size: 0.875rem;
}
.block-header .block-actions {
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.block-header .block-actions a {
display: flex;
align-items: center;
margin-right: 0.75rem;
padding: 0.5rem 1rem;
border-radius: 4px;
border: 1px solid transparent;
transition: background-color 0.2s;
cursor: pointer;
color: var(--text-color);
}
.block-header .block-actions a:last-child {
margin-right: 0;
}
.block-header .block-actions a:not(.block-action-disabled):hover {
background-color: var(--surface-c);
}
.block-header .block-actions a.block-action-active {
border-color: var(--primary-color);
color: var(--primary-color);
}
.block-header .block-actions a.block-action-copy i {
color: var(--primary-color);
font-size: 1.25rem;
}
.block-header .block-actions a.block-action-disabled {
opacity: 0.6;
pointer-events: none;
cursor: auto !important;
}
.block-header .block-actions a .pi-lock {
margin-right: 0.5rem;
}
.block-content {
padding: 0;
border: 1px solid var(--surface-d);
border-top: 0 none;
}
.block-content > div {
display: none;
}
.block-content > div.block-content-active {
display: block;
}
.block-section pre[class*=language-] {
margin: 0 !important;
}
.block-section pre[class*=language-]:before, .block-section pre[class*=language-]:after {
display: none !important;
}
.block-section pre[class*=language-] code {
border-left: 0 none !important;
box-shadow: none !important;
background: var(--surface-e) !important;
margin: 0;
color: var(--text-color);
font-size: 14px;
padding: 1.5rem 2rem !important;
}
@media screen and (max-width: 575px) {
.block-header {
flex-direction: column;
align-items: start;
}
.block-header .block-actions {
margin-top: 1rem;
}
}

View File

@@ -0,0 +1,122 @@
.order-badge {
border-radius: 2px;
padding: .25em .5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: .3px;
&.order-delivered {
background: #ACEBB4;
color: #348861;
}
&.order-cancelled {
background: #FABD9A;
color:#AD342B;
}
&.order-pending {
background: #F8D895;
color: #A76927;
}
&.order-returned {
background: #EFB8E5;
color: #833F91;
}
}
.product-badge {
border-radius: 2px;
padding: .25em .5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: .3px;
text-align: center;
&.status-instock {
background: #ACEBB4;
color: #348861;
}
&.status-outofstock {
background: #FABD9A;
color:#AD342B;
}
&.status-lowstock {
background: #F8D895;
color: #A76927;
}
}
.customer-badge {
border-radius: 2px;
padding: .25em .5rem;
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
letter-spacing: .3px;
&.status-qualified {
background: #ACEBB4;
color: #348861;
}
&.status-unqualified {
background: #FABD9A;
color:#AD342B;
}
&.status-negotiation {
background: #F8D895;
color: #A76927;
}
&.status-new {
background: #9BF2F7;
color: #2B7AA4;
}
&.status-renewal {
background: #EFB8E5;
color: #833F91;
}
&.status-proposal {
background: #FFD8B2;
color: #805B36;
}
}
.filter-container {
.ui-inputtext {
width: 400px;
}
}
.ui-selection-column {
width: 2rem;
}
@media (max-width: 640px) {
.filter-container {
width: 100%;
margin-top: .5rem;
}
.filter-container .ui-inputtext {
width: 100%;
}
.ui-selection-column {
width: auto;
text-align: center;
.ui-column-title {
display: none !important;
}
}
}

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>dev.lions</groupId>
<artifactId>primefaces-freya-extension-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>PrimeFaces Freya Extension - Parent</name>
<modules>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.lions</groupId>
<artifactId>primefaces-freya-extension-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.0.0</version>
</parent>
<artifactId>primefaces-freya-extension</artifactId>
<name>PrimeFaces Freya Extension - Runtime</name>

View File

@@ -4,13 +4,151 @@
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
Freya Avatar - Avatar utilisateur
═══════════════════════════════════════════════════════════
Lions.dev Avatar - Photo de profil ou initiales utilisateur
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: HAUTE #34
Avatar circulaire ou carré pour représenter un utilisateur.
Styles Lions.dev avec gradients, bordures et indicateurs de statut.
Usage:
<fr:avatar label="JD" size="large" shape="circle" />
<fr:avatar image="/images/user.png" size="xlarge" />
<fr:avatar label="JD" size="xlarge" shape="circle" />
Exemples:
1. Avatar avec initiales:
<fr:avatar label="JD"
shape="circle"
size="large" />
2. Avatar avec image:
<fr:avatar image="#{resource['images:user.jpg']}"
shape="circle"
size="xlarge" />
3. Avatar avec icône:
<fr:avatar icon="pi pi-user"
shape="circle"
size="normal"
severity="success" />
4. Avatar carré avec bordure:
<fr:avatar label="AB"
shape="square"
bordered="true" />
5. Avatar avec indicateur de statut:
<fr:avatar image="#{userBean.avatar}"
shape="circle"
status="online" />
6. Avatar arrondi (rounded):
<fr:avatar label="CD"
shape="rounded"
severity="warning" />
7. Groupe d'avatars:
<div class="ui-avatar-group">
<fr:avatar label="JD" />
<fr:avatar label="AB" severity="success" />
<fr:avatar label="CD" severity="danger" />
<fr:avatar label="+5" severity="secondary" />
</div>
Attributs disponibles:
- id: Identifiant du composant
- label: Initiales affichées (2 lettres max, ex: "JD")
- icon: Icône PrimeIcons (ex: "pi pi-user")
- image: URL de l'image de profil
- size: xs | sm | normal (défaut) | lg | xl - Taille de l'avatar
- shape: circle (défaut) | square | rounded - Forme de l'avatar
- severity: primary (défaut) | secondary | success | info | warning | danger
- bordered: true/false - Ajouter une bordure blanche
- status: online | offline | busy | away - Indicateur de statut
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
Sizes disponibles:
- xs: 24px (1.5rem) - Pour listes compactes
- sm: 32px (2rem) - Pour petits espaces
- normal: 40px (2.5rem) - Taille par défaut
- lg: 48px (3rem) - Pour profils importants
- xl: 64px (4rem) - Pour pages de profil
Shapes disponibles:
- circle: Avatar circulaire (par défaut)
- square: Avatar carré avec coins droits
- rounded: Avatar carré avec coins arrondis
Severities (couleurs de fond si pas d'image):
- primary: Gradient bleu Lions.dev (défaut)
- secondary: Gris neutre
- success: Gradient vert
- info: Gradient cyan
- warning: Gradient orange
- danger: Gradient rouge
Status indicators:
- online: Point vert en bas à droite
- offline: Point gris
- busy: Point rouge
- away: Point orange
Avatar Group:
Pour afficher plusieurs avatars chevauchés, utilisez:
<div class="ui-avatar-group">
<fr:avatar ... />
<fr:avatar ... />
</div>
Utilisation en code:
```java
@Named
@ViewScoped
public class UserBean {
private String userInitials = "JD";
private String avatarUrl = "/resources/images/avatar.jpg";
private boolean hasAvatar = true;
public String getUserInitials() {
String firstName = user.getFirstName();
String lastName = user.getLastName();
return (firstName.charAt(0) + "" + lastName.charAt(0)).toUpperCase();
}
public String getAvatarUrl() {
return hasAvatar ? avatarUrl : null;
}
public String getStatusIndicator() {
return user.isOnline() ? "online" : "offline";
}
}
```
Cas d'usage recommandés:
- Profil utilisateur: xl, circle, avec image
- Liste utilisateurs: sm, circle, initiales
- Commentaires: normal, circle, avec image
- Navigation: sm, circle, icône
- Équipe: lg, circle avec groupe
Notes de style:
- Avatar avec gradient bleu Lions.dev par défaut
- Initiales en majuscules automatiques
- Image en object-fit: cover pour remplissage
- Bordure blanche optionnelle avec ombre
- Indicateur de statut positionné en bas à droite
- Groupe: avatars chevauchés avec hover scale
- Responsive: xl devient lg sur mobile
Accessibilité:
- Attribut alt automatique sur les images
- Initiales lisibles par lecteurs d'écran
- Contraste WCAG AA respecté
- Indicateur de statut avec aria-label
-->
<composite:interface>
@@ -19,7 +157,10 @@
<composite:attribute name="icon" required="false" type="java.lang.String" />
<composite:attribute name="image" required="false" type="java.lang.String" />
<composite:attribute name="size" required="false" type="java.lang.String" default="normal" />
<composite:attribute name="shape" required="false" type="java.lang.String" default="square" />
<composite:attribute name="shape" required="false" type="java.lang.String" default="circle" />
<composite:attribute name="severity" required="false" type="java.lang.String" default="primary" />
<composite:attribute name="bordered" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="status" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
</composite:interface>
@@ -31,9 +172,14 @@
image="#{cc.attrs.image}"
size="#{cc.attrs.size}"
shape="#{cc.attrs.shape}"
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}" />
styleClass="ui-avatar-#{cc.attrs.severity} #{cc.attrs.bordered ? 'ui-avatar-bordered' : ''} #{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<span class="ui-avatar-status ui-avatar-status-#{cc.attrs.status}"
rendered="#{not empty cc.attrs.status}"
role="img"
aria-label="#{cc.attrs.status eq 'online' ? 'En ligne' : cc.attrs.status eq 'offline' ? 'Hors ligne' : cc.attrs.status eq 'busy' ? 'Occupé' : 'Absent'}" />
</p:avatar>
</composite:implementation>
</html>

View File

@@ -8,16 +8,25 @@
<!--
Freya Breadcrumb - Fil d'Ariane avec style Freya
Usage:
Usage with MenuModel:
<fr:breadcrumb model="#{bean.breadcrumbModel}" homeIcon="pi pi-home" />
Usage with inline items:
<fr:breadcrumb homeIcon="pi pi-home">
<p:menuitem value="Accueil" url="#" />
<p:menuitem value="Section" url="#" />
</fr:breadcrumb>
-->
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="model" required="true" />
<composite:attribute name="model" required="false" />
<composite:attribute name="homeIcon" required="false" type="java.lang.String" default="pi pi-home" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Support for inline p:menuitem children -->
<composite:insertChildren />
</composite:interface>
<composite:implementation>
@@ -26,6 +35,7 @@
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<p:menuitem value="" icon="#{cc.attrs.homeIcon}" outcome="/index" />
<composite:insertChildren />
</p:breadCrumb>
</composite:implementation>

View File

@@ -6,10 +6,59 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Button - Bouton simple avec style Freya
═══════════════════════════════════════════════════════════
Lions.dev Button - Bouton de navigation avec identité Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0
Priority: HAUTE #2
Description:
Bouton de navigation (p:button) avec styles Lions.dev.
Utilisé pour la navigation entre pages (outcome/href).
Pour les actions Ajax, utilisez fr:commandButton.
Fonctionnalités:
- Gradients Lions.dev élégants
- Animations hover (translateY + shadow elevation)
- Focus ring accessible (WCAG 2.1 AA)
- Support complet des variantes (solid, outlined, text, link, rounded, raised)
- Support des tailles (sm, base, lg)
- Badge support
Usage:
<fr:button value="Cliquer" icon="pi pi-check" severity="success" />
<fr:button value="Aller au Dashboard"
icon="pi pi-home"
outcome="/dashboard"
severity="primary"
size="lg" />
Exemples:
1. Navigation simple:
<fr:button value="Accueil" icon="pi pi-home" outcome="/index" />
2. Lien externe:
<fr:button value="Documentation" icon="pi pi-book" href="https://docs.lions.dev" target="_blank" />
3. Bouton outlined large:
<fr:button value="En savoir plus" severity="info" outlined="true" size="lg" outcome="/about" />
4. Bouton premium rounded:
<fr:button value="Upgrade Premium" severity="help" rounded="true" raised="true" outcome="/pricing" />
Severity disponibles:
- primary (Lions Blue #2D9BEF - défaut)
- secondary (Gray)
- success (Green #22BB69)
- info (Cyan #00BCD4)
- warning (Orange #FF9800)
- help (Lions Gold #FFC700 - premium)
- danger (Red #F44336)
Tailles disponibles:
- sm (32px height)
- base (40px height - défaut)
- lg (48px height)
-->
<composite:interface>
@@ -27,6 +76,10 @@
<!-- Severity: primary, secondary, success, info, warning, help, danger -->
<composite:attribute name="severity" required="false" type="java.lang.String" />
<!-- Size: sm, base, lg -->
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<!-- Variants: text, outlined, link -->
<composite:attribute name="text" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="outlined" required="false" type="java.lang.Boolean" default="false" />
@@ -46,7 +99,7 @@
outcome="#{cc.attrs.outcome}"
href="#{cc.attrs.href}"
target="#{cc.attrs.target}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{cc.attrs.text ? 'ui-button-text' : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.link ? 'ui-button-link' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'ui-button-'.concat(cc.attrs.size) : ''} #{cc.attrs.text ? 'ui-button-text' : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.link ? 'ui-button-link' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
style="#{cc.attrs.style}"
title="#{cc.attrs.title}"
badge="#{cc.attrs.badge}"

View File

@@ -7,12 +7,76 @@
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
Freya Card - Conteneur avec titre et contenu stylisé
═══════════════════════════════════════════════════════════
Lions.dev Card - Conteneur carte avec identité Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #9
Conteneur élégant pour regrouper du contenu avec header/footer optionnels.
Styles Lions.dev avec shadow, border-radius et hover effects.
Usage:
<fr:card title="Informations utilisateur" icon="pi pi-user">
<fr:card title="Informations" subtitle="Détails utilisateur" icon="pi pi-user">
<p>Contenu de la carte...</p>
<f:facet name="footer">
<fr:commandButton value="Enregistrer" severity="success" />
</f:facet>
</fr:card>
Exemples:
1. Card simple avec titre:
<fr:card title="Statistiques" icon="pi pi-chart-bar">
<p>45 utilisateurs actifs</p>
</fr:card>
2. Card avec header/footer personnalisés:
<fr:card>
<f:facet name="header">
<div class="flex justify-content-between">
<h5>Dashboard</h5>
<fr:commandButton icon="pi pi-refresh" text="true" />
</div>
</f:facet>
<p>Contenu...</p>
<f:facet name="footer">
<fr:button value="Voir plus" severity="primary" />
</f:facet>
</fr:card>
3. Card avec actions dans le header:
<fr:card title="Projet Alpha" icon="pi pi-folder">
<f:facet name="actions">
<fr:commandButton icon="pi pi-pencil" text="true" size="sm" />
<fr:commandButton icon="pi pi-trash" text="true" severity="danger" size="sm" />
</f:facet>
<p>Description du projet...</p>
</fr:card>
Attributs disponibles:
- title: Titre de la carte
- subtitle: Sous-titre (affiché sous le titre)
- icon: Icône PrimeIcons dans le header
- toggleable: true/false - Permet de plier/déplier (utiliser panel pour cela)
- collapsed: true/false - État initial plié
- styleClass: Classes CSS additionnelles (card-hover, card-clickable, card-outlined, etc.)
Facets disponibles:
- header: Contenu personnalisé du header
- footer: Contenu du footer
- actions: Actions dans le header (boutons, etc.)
Classes utilitaires:
- card-hover: Effet hover avec translateY
- card-clickable: Carte cliquable (curseur pointer)
- card-compact: Padding réduit
- card-large: Padding augmenté
- card-outlined: Sans background, bordure seulement
- card-elevated: Shadow XL
- card-flat: Pas de shadow
- card-border-top: Bordure colorée en haut
- card-border-left: Bordure colorée à gauche
- card-primary/success/info/warning/danger/help: Couleur de bordure
-->
<composite:interface>

View File

@@ -6,14 +6,63 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya CommandButton - Bouton d'action avec style Freya
═══════════════════════════════════════════════════════════
Lions.dev CommandButton - Bouton d'action avec identité Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0
Priority: CRITIQUE #1
Fonctionnalités:
- Gradients Lions.dev élégants
- Animations hover (translateY + shadow elevation)
- Focus ring accessible (WCAG 2.1 AA)
- Support complet des variantes (solid, outlined, text, link, rounded, raised)
- Support des tailles (sm, base, lg)
- States (loading, disabled)
- Badge support
Usage:
<fr:commandButton value="Sauvegarder"
icon="pi pi-save"
action="#{bean.save}"
update="@form"
severity="success" />
severity="success"
size="lg"
raised="true" />
Exemples de variantes:
1. Bouton solid primary (défaut):
<fr:commandButton value="Primary Action" severity="primary" />
2. Bouton outlined success:
<fr:commandButton value="Save" severity="success" outlined="true" />
3. Bouton text danger:
<fr:commandButton value="Delete" severity="danger" text="true" />
4. Bouton premium (gold):
<fr:commandButton value="Upgrade" severity="help" raised="true" />
5. Bouton rounded large:
<fr:commandButton value="Submit" size="lg" rounded="true" />
6. Icon-only button:
<fr:commandButton icon="pi pi-search" title="Rechercher" />
Severity disponibles:
- primary (Lions Blue #2D9BEF - défaut)
- secondary (Gray)
- success (Green #22BB69)
- info (Cyan #00BCD4)
- warning (Orange #FF9800)
- help (Lions Gold #FFC700 - premium)
- danger (Red #F44336)
Tailles disponibles:
- sm (32px height)
- base (40px height - défaut)
- lg (48px height)
-->
<composite:interface>
@@ -41,6 +90,10 @@
<!-- Severity: primary, secondary, success, info, warning, help, danger -->
<composite:attribute name="severity" required="false" type="java.lang.String" />
<!-- Size: sm, base, lg -->
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<!-- Variants -->
<composite:attribute name="text" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="outlined" required="false" type="java.lang.Boolean" default="false" />
@@ -52,6 +105,13 @@
<!-- Action source -->
<composite:actionSource name="button" targets="commandButton" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="action" event="action" targets="commandButton" default="true" />
<composite:clientBehavior name="click" event="click" targets="commandButton" />
<!-- Support for child content (HTML inside the button) -->
<composite:insertChildren />
</composite:interface>
<composite:implementation>
@@ -71,13 +131,15 @@
global="#{cc.attrs.global}"
validateClient="#{cc.attrs.validateClient}"
immediate="#{cc.attrs.immediate}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{cc.attrs.text ? 'ui-button-text' : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.link ? 'ui-button-link' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'ui-button-'.concat(cc.attrs.size) : ''} #{cc.attrs.text ? 'ui-button-text' : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.link ? 'ui-button-link' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
style="#{cc.attrs.style}"
title="#{cc.attrs.title}"
type="#{cc.attrs.type}"
widgetVar="#{cc.attrs.widgetVar}"
badge="#{cc.attrs.badge}"
badgeSeverity="#{cc.attrs.badgeSeverity}" />
badgeSeverity="#{cc.attrs.badgeSeverity}">
<composite:insertChildren />
</p:commandButton>
</composite:implementation>
</html>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="header" required="false" type="java.lang.String" default="Confirmation" />
<composite:attribute name="message" required="false" type="java.lang.String" />
<composite:attribute name="severity" required="false" type="java.lang.String" default="alert" />
<composite:attribute name="icon" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="false" type="java.lang.String" />
<composite:attribute name="closable" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="visible" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="appendTo" required="false" type="java.lang.String" default="@(body)" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="global" required="false" type="java.lang.Boolean" default="false" />
</composite:interface>
<composite:implementation>
<p:confirmDialog id="confirmDialog"
header="#{cc.attrs.header}"
message="#{cc.attrs.message}"
severity="#{cc.attrs.severity}"
icon="#{cc.attrs.icon}"
widgetVar="#{cc.attrs.widgetVar}"
closable="#{cc.attrs.closable}"
visible="#{cc.attrs.visible}"
appendTo="#{cc.attrs.appendTo}"
styleClass="#{cc.attrs.styleClass}"
global="#{cc.attrs.global}" />
</composite:implementation>
</html>

View File

@@ -7,18 +7,177 @@
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
Freya DataTable - DataTable avec style Freya et fonctionnalités courantes
═══════════════════════════════════════════════════════════
Lions.dev DataTable - Tableau de données avec tri/filtre/pagination
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #16
Tableau de données interactif avec tri, filtrage, pagination et sélection.
Styles Lions.dev avec header coloré, striped rows et interactions fluides.
Usage:
<fr:dataTable id="users" value="#{bean.users}" var="user" paginator="true" rows="10">
<p:column headerText="Nom" sortBy="#{user.nom}" filterBy="#{user.nom}">#{user.nom}</p:column>
<p:column headerText="Email">#{user.email}</p:column>
</fr:dataTable>
Exemples:
1. Tableau simple avec pagination:
<fr:dataTable id="users"
value="#{bean.users}"
var="user"
paginator="true"
rows="10"
globalFilter="true">
<p:column headerText="Nom">#{user.nom}</p:column>
rowsPerPageTemplate="5,10,25,50">
<p:column headerText="Nom" sortBy="#{user.nom}">#{user.nom}</p:column>
<p:column headerText="Email">#{user.email}</p:column>
<p:column headerText="Téléphone">#{user.telephone}</p:column>
</fr:dataTable>
2. Tableau avec filtre global:
<fr:dataTable id="products"
value="#{bean.products}"
var="product"
paginator="true"
rows="15"
globalFilter="true"
globalFilterPlaceholder="Rechercher un produit..."
widgetVar="productsTable">
<p:column headerText="Référence" sortBy="#{product.ref}">#{product.ref}</p:column>
<p:column headerText="Nom" sortBy="#{product.nom}" filterBy="#{product.nom}">#{product.nom}</p:column>
<p:column headerText="Prix" sortBy="#{product.prix}">#{product.prix} FCFA</p:column>
</fr:dataTable>
3. Tableau avec sélection multiple:
<fr:dataTable id="clients"
value="#{bean.clients}"
var="client"
selection="#{bean.selectedClients}"
selectionMode="multiple"
rowKey="#{client.id}"
paginator="true"
rows="20">
<p:column selectionMode="multiple" style="width:3rem" />
<p:column headerText="Code">#{client.code}</p:column>
<p:column headerText="Nom">#{client.nom}</p:column>
<p:column headerText="Ville">#{client.ville}</p:column>
</fr:dataTable>
4. Tableau avec colonnes triables et filtrables:
<fr:dataTable id="orders"
value="#{bean.orders}"
var="order"
paginator="true"
rows="10"
sortMode="multiple">
<p:column headerText="N° Commande" sortBy="#{order.numero}" filterBy="#{order.numero}">
#{order.numero}
</p:column>
<p:column headerText="Client" sortBy="#{order.client}" filterBy="#{order.client}">
#{order.client}
</p:column>
<p:column headerText="Date" sortBy="#{order.date}" filterBy="#{order.date}">
<h:outputText value="#{order.date}">
<f:convertDateTime pattern="dd/MM/yyyy" />
</h:outputText>
</p:column>
<p:column headerText="Montant" sortBy="#{order.montant}">
#{order.montant} FCFA
</p:column>
</fr:dataTable>
5. Tableau avec colonnes redimensionnables:
<fr:dataTable id="employees"
value="#{bean.employees}"
var="emp"
resizableColumns="true"
reorderableColumns="true"
paginator="true"
rows="25">
<p:column headerText="Matricule">#{emp.matricule}</p:column>
<p:column headerText="Nom complet">#{emp.nom} #{emp.prenom}</p:column>
<p:column headerText="Département">#{emp.departement}</p:column>
<p:column headerText="Salaire">#{emp.salaire} FCFA</p:column>
</fr:dataTable>
6. Tableau scrollable avec hauteur fixe:
<fr:dataTable id="logs"
value="#{bean.logs}"
var="log"
scrollable="true"
scrollHeight="400px">
<p:column headerText="Timestamp">#{log.timestamp}</p:column>
<p:column headerText="Niveau">#{log.niveau}</p:column>
<p:column headerText="Message">#{log.message}</p:column>
</fr:dataTable>
Attributs disponibles:
- value: Collection de données (List, LazyDataModel, etc.)
- var: Variable d'itération pour chaque ligne
- paginator: true/false - Activer la pagination (défaut: true)
- rows: Nombre de lignes par page (défaut: 10)
- rowsPerPageTemplate: Options pagination "5,10,25,50" (défaut)
- paginatorPosition: Position - "top", "bottom", "both" (défaut: bottom)
- selection: Variable pour stocker la sélection
- selectionMode: "single" ou "multiple" - Mode de sélection
- rowKey: Expression EL pour identifier les lignes (requis pour sélection)
- globalFilter: true/false - Afficher le champ de recherche global
- globalFilterPlaceholder: Texte du champ de recherche (défaut: "Rechercher...")
- sortMode: "single" ou "multiple" - Mode de tri (défaut: single)
- resizableColumns: true/false - Colonnes redimensionnables
- reorderableColumns: true/false - Colonnes réordonnables
- scrollable: true/false - Tableau scrollable
- scrollHeight: Hauteur scroll (ex: "400px")
- lazy: true/false - Chargement lazy (avec LazyDataModel)
- rowStyleClass: Expression EL pour classe CSS de ligne
- emptyMessage: Message si vide (défaut: "Aucun enregistrement trouvé")
- stripedRows: true/false - Lignes alternées (défaut: true)
- showGridlines: true/false - Afficher les bordures (défaut: false)
- size: "small", "normal", "large" - Taille des cellules
- widgetVar: Variable JS pour contrôle programmatique
- styleClass, style: Classes et styles CSS
Facets disponibles:
- header: Contenu personnalisé du header
Events AJAX disponibles:
- rowSelect, rowUnselect, rowDblselect: Sélection de lignes
- rowSelectCheckbox, rowUnselectCheckbox: Sélection par checkbox
- toggleSelect: Sélection "Tout/Rien"
- page: Changement de page
- sort: Tri
- filter: Filtrage
- rowEdit, rowEditInit, rowEditCancel: Édition en ligne
- colReorder, colResize: Réorganisation/redimensionnement colonnes
Colonnes p:column:
- headerText: Texte du header
- sortBy: Expression EL pour le tri
- filterBy: Expression EL pour le filtre
- filterMatchMode: "contains", "startsWith", "endsWith", "equals", etc.
- selectionMode: "multiple" ou "single" pour colonne de sélection
- style, styleClass: Styles de la colonne
Exemple AJAX:
<fr:dataTable id="users" value="#{bean.users}" var="user"
selection="#{bean.selectedUser}" selectionMode="single" rowKey="#{user.id}">
<p:ajax event="rowSelect" listener="#{bean.onRowSelect}" update="userDetail" />
<p:column headerText="Nom">#{user.nom}</p:column>
</fr:dataTable>
Contrôle JavaScript (avec widgetVar):
PF('usersTable').filter(); // Appliquer les filtres
PF('usersTable').clearFilters(); // Effacer les filtres
PF('usersTable').unselectAllRows(); // Désélectionner tout
Notes:
- Pagination automatique avec template français
- Tri multi-colonnes avec Shift+Click
- Filtrage en temps réel sur chaque colonne
- Sélection avec checkbox ou clic sur ligne
- Support du lazy loading pour grandes quantités de données
- Responsive avec scroll horizontal automatique sur mobile
-->
<composite:interface>
@@ -59,6 +218,22 @@
<!-- Facets -->
<composite:facet name="header" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="rowSelect" event="rowSelect" targets="dataTable" />
<composite:clientBehavior name="rowUnselect" event="rowUnselect" targets="dataTable" />
<composite:clientBehavior name="rowDblselect" event="rowDblselect" targets="dataTable" />
<composite:clientBehavior name="rowSelectCheckbox" event="rowSelectCheckbox" targets="dataTable" />
<composite:clientBehavior name="rowUnselectCheckbox" event="rowUnselectCheckbox" targets="dataTable" />
<composite:clientBehavior name="toggleSelect" event="toggleSelect" targets="dataTable" />
<composite:clientBehavior name="page" event="page" targets="dataTable" />
<composite:clientBehavior name="sort" event="sort" targets="dataTable" />
<composite:clientBehavior name="filter" event="filter" targets="dataTable" />
<composite:clientBehavior name="rowEdit" event="rowEdit" targets="dataTable" />
<composite:clientBehavior name="rowEditInit" event="rowEditInit" targets="dataTable" />
<composite:clientBehavior name="rowEditCancel" event="rowEditCancel" targets="dataTable" />
<composite:clientBehavior name="colReorder" event="colReorder" targets="dataTable" />
<composite:clientBehavior name="colResize" event="colResize" targets="dataTable" />
<!-- Content insertion for columns -->
<composite:insertChildren />
</composite:interface>

View File

@@ -7,21 +7,178 @@
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
Freya DataView - DataView avec style Freya pour affichage liste/grille
═══════════════════════════════════════════════════════════
Lions.dev DataView - Affichage de données en grille/liste
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #17
Affichage flexible de données avec mode liste et grille interchangeables.
Styles Lions.dev avec basculement layout, pagination et cartes élégantes.
Usage:
<fr:dataView id="products"
value="#{bean.products}"
var="product"
layout="grid"
paginator="true"
rows="12">
<fr:dataView id="products" value="#{bean.products}" var="product" layout="grid" paginator="true" rows="9">
<f:facet name="grid">
<div class="col-12 md:col-4">
<div class="card">#{product.name}</div>
</div>
</f:facet>
</fr:dataView>
Exemples:
1. DataView avec basculement liste/grille:
<fr:dataView id="products"
value="#{bean.products}"
var="product"
layout="grid"
paginator="true"
rows="12"
widgetVar="productsView">
<f:facet name="header">
<h5>Catalogue Produits (#{bean.products.size()} articles)</h5>
</f:facet>
<f:facet name="list">
<div class="flex align-items-center gap-4 p-4">
<img src="#{product.image}" alt="#{product.nom}" style="width:100px" />
<div class="flex-1">
<h6 class="mb-2">#{product.nom}</h6>
<p class="text-secondary">#{product.description}</p>
<strong>#{product.prix} FCFA</strong>
</div>
<fr:commandButton value="Ajouter" icon="pi pi-shopping-cart" severity="success" />
</div>
</f:facet>
<f:facet name="grid">
<div class="col-12 md:col-6 lg:col-4">
<fr:card title="#{product.nom}" icon="pi pi-tag">
<img src="#{product.image}" alt="#{product.nom}" style="width:100%" />
<p>#{product.description}</p>
<f:facet name="footer">
<strong>#{product.prix} FCFA</strong>
<fr:commandButton value="Ajouter" icon="pi pi-shopping-cart" severity="success" size="sm" />
</f:facet>
</fr:card>
</div>
</f:facet>
</fr:dataView>
2. DataView en mode grille uniquement (sans basculement):
<fr:dataView id="team"
value="#{bean.employees}"
var="emp"
layout="grid"
paginator="true"
rows="6"
rowsPerPageTemplate="3,6,9,12">
<f:facet name="grid">
<div class="col-12 md:col-6 lg:col-4">
<fr:card styleClass="card-hover text-center">
<img src="#{emp.avatar}" alt="#{emp.nom}" class="border-circle mb-3" style="width:120px;height:120px" />
<h5>#{emp.nom} #{emp.prenom}</h5>
<p class="text-secondary">#{emp.poste}</p>
<p><i class="pi pi-envelope mr-2"></i>#{emp.email}</p>
</fr:card>
</div>
</f:facet>
</fr:dataView>
3. DataView en mode liste avec pagination:
<fr:dataView id="orders"
value="#{bean.orders}"
var="order"
layout="list"
paginator="true"
rows="10">
<f:facet name="list">
<div class="flex justify-content-between align-items-center p-4 border-bottom-1 surface-border">
<div>
<h6 class="mb-1">Commande ##{order.numero}</h6>
<p class="text-secondary mb-0">
Client: #{order.client} • Date:
<h:outputText value="#{order.date}">
<f:convertDateTime pattern="dd/MM/yyyy" />
</h:outputText>
</p>
</div>
<div class="text-right">
<strong class="text-xl">#{order.montant} FCFA</strong>
<br/>
<span class="badge badge-#{order.statut == 'Livré' ? 'success' : 'warning'}">
#{order.statut}
</span>
</div>
</div>
</f:facet>
</fr:dataView>
4. DataView avec lazy loading:
<fr:dataView id="articles"
value="#{bean.lazyArticles}"
var="article"
layout="grid"
paginator="true"
rows="15"
lazy="true">
<f:facet name="grid">
<div class="col-12 md:col-4">
<fr:card title="#{article.titre}">
<p>#{article.resume}</p>
<small class="text-secondary">
Par #{article.auteur} • #{article.date}
</small>
<f:facet name="footer">
<fr:commandButton value="Lire plus" icon="pi pi-arrow-right" text="true" />
</f:facet>
</fr:card>
</div>
</f:facet>
</fr:dataView>
Attributs disponibles:
- value: Collection de données (List, LazyDataModel, etc.)
- var: Variable d'itération pour chaque élément
- layout: "list" ou "grid" - Mode d'affichage initial (défaut: list)
- gridIcon: Icône bouton grille (défaut: "pi pi-th-large")
- listIcon: Icône bouton liste (défaut: "pi pi-bars")
- paginator: true/false - Activer la pagination (défaut: true)
- rows: Nombre d'éléments par page (défaut: 9)
- rowsPerPageTemplate: Options pagination "3,6,9,12" (défaut)
- paginatorPosition: Position - "top", "bottom", "both" (défaut: bottom)
- lazy: true/false - Chargement lazy (avec LazyDataModel)
- emptyMessage: Message si vide (défaut: "Aucun enregistrement trouvé")
- widgetVar: Variable JS pour contrôle programmatique
- styleClass, style: Classes et styles CSS
Facets disponibles:
- header: Contenu personnalisé du header (titre, compteur, etc.)
- list: Template pour le mode liste
- grid: Template pour le mode grille
Structure des facets:
- Facet "list": Contient le template d'un élément en mode liste (largeur 100%)
- Facet "grid": Contient le template d'un élément en mode grille avec classes de colonnes
Utilisez les classes PrimeFlex: col-12 md:col-6 lg:col-4 (grille 3 colonnes sur desktop)
Exemples de layouts grille:
- 2 colonnes: col-12 md:col-6
- 3 colonnes: col-12 md:col-6 lg:col-4
- 4 colonnes: col-12 sm:col-6 md:col-4 lg:col-3
- 6 colonnes: col-12 sm:col-4 md:col-3 lg:col-2
Contrôle JavaScript (avec widgetVar):
PF('productsView').switchToListLayout(); // Passer en mode liste
PF('productsView').switchToGridLayout(); // Passer en mode grille
Notes:
- Basculement liste/grille avec boutons dans le header
- Mode grille: affichage en cartes flexibles
- Mode liste: affichage en lignes complètes
- Pagination automatique avec template français
- Responsive: grille s'adapte automatiquement sur mobile
- Utiliser fr:card dans les facets pour un style cohérent
- Support du lazy loading pour grandes quantités de données
-->
<composite:interface>

View File

@@ -0,0 +1,257 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
═══════════════════════════════════════════════════════════
Lions.dev Dialog - Boîtes de dialogue modales
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #21
Boîtes de dialogue modales avec header, content et footer personnalisables.
Styles Lions.dev avec backdrop blur, animations fluides et header coloré.
Usage:
<fr:dialog header="Titre" widgetVar="dlg">
<p:outputLabel value="Contenu..." />
<f:facet name="footer">
<fr:commandButton value="OK" onclick="PF('dlg').hide()" />
</f:facet>
</fr:dialog>
Exemples:
1. Dialog simple avec footer:
<fr:dialog header="Informations utilisateur" widgetVar="userDialog" modal="true" width="600">
<p:panelGrid columns="2">
<p:outputLabel value="Nom:" />
<p:outputLabel value="#{user.nom}" />
<p:outputLabel value="Email:" />
<p:outputLabel value="#{user.email}" />
</p:panelGrid>
<f:facet name="footer">
<fr:commandButton value="Fermer" onclick="PF('userDialog').hide()" severity="secondary" />
</f:facet>
</fr:dialog>
2. Dialog avec formulaire:
<fr:dialog header="Nouveau client" widgetVar="newClientDialog" modal="true" width="500">
<fr:fieldInput id="nom" label="Nom" value="#{bean.client.nom}" required="true" />
<fr:fieldInput id="email" label="Email" value="#{bean.client.email}" required="true" />
<fr:fieldInput id="tel" label="Téléphone" value="#{bean.client.telephone}" />
<f:facet name="footer">
<fr:commandButton value="Enregistrer" action="#{bean.saveClient}"
update="clientsTable" oncomplete="PF('newClientDialog').hide()"
severity="success" icon="pi pi-check" />
<fr:commandButton value="Annuler" onclick="PF('newClientDialog').hide()"
severity="secondary" outlined="true" />
</f:facet>
</fr:dialog>
3. Dialog redimensionnable et déplaçable:
<fr:dialog header="Paramètres" widgetVar="settingsDialog"
draggable="true" resizable="true" minWidth="400" minHeight="300"
width="700" height="500">
<p:tabView>
<p:tab title="Général">
<!-- Contenu onglet Général -->
</p:tab>
<p:tab title="Avancé">
<!-- Contenu onglet Avancé -->
</p:tab>
</p:tabView>
</fr:dialog>
4. Dialog maximisable:
<fr:dialog header="Détails commande" widgetVar="orderDialog"
modal="true" maximizable="true" width="800">
<fr:dataTable value="#{bean.orderItems}" var="item">
<p:column headerText="Produit">#{item.produit}</p:column>
<p:column headerText="Quantité">#{item.quantite}</p:column>
<p:column headerText="Prix">#{item.prix} FCFA</p:column>
</fr:dataTable>
<f:facet name="footer">
<fr:commandButton value="Imprimer" icon="pi pi-print" />
<fr:commandButton value="Fermer" onclick="PF('orderDialog').hide()" severity="secondary" />
</f:facet>
</fr:dialog>
5. Dialog avec position personnalisée:
<fr:dialog header="Notification" widgetVar="notifDialog"
position="top" modal="false" showEffect="fade" hideEffect="fade">
<p:outputLabel value="Une nouvelle mise à jour est disponible." />
<f:facet name="footer">
<fr:commandButton value="Télécharger" severity="primary" />
<fr:commandButton value="Plus tard" onclick="PF('notifDialog').hide()" text="true" />
</f:facet>
</fr:dialog>
Attributs disponibles:
- header: Titre du dialog
- widgetVar: Variable JS pour contrôle programmatique (REQUIS)
- modal: true/false - Dialog modal avec backdrop (défaut: false)
- visible: true/false - Visible au chargement (défaut: false)
- width: Largeur (px, %, auto) - défaut: auto
- height: Hauteur (px, %, auto) - défaut: auto
- minWidth: Largeur minimale en px (défaut: 150)
- minHeight: Hauteur minimale en px (défaut: 150)
- draggable: true/false - Déplaçable par le header (défaut: false)
- resizable: true/false - Redimensionnable (défaut: false)
- maximizable: true/false - Bouton maximiser (défaut: false)
- minimizable: true/false - Bouton minimiser (défaut: false)
- closable: true/false - Bouton fermer (défaut: true)
- closeOnEscape: true/false - Fermer avec Échap (défaut: false)
- position: Position - "center", "top", "bottom", "left", "right" (défaut: center)
- showEffect: Effet d'apparition - "fade", "slide", "explode", etc.
- hideEffect: Effet de disparition - "fade", "slide", "explode", etc.
- appendTo: Sélecteur de l'élément parent (défaut: @(body))
- responsive: true/false - Responsive sur mobile (défaut: true)
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
Facets disponibles:
- header: Header personnalisé (remplace l'attribut header)
- footer: Footer avec boutons d'action
Contrôle JavaScript (widgetVar):
```javascript
// Afficher le dialog
PF('dlg').show();
// Masquer le dialog
PF('dlg').hide();
// Basculer l'affichage
PF('dlg').toggle();
// Maximiser
PF('dlg').maximize();
// Minimiser
PF('dlg').minimize();
// Restaurer
PF('dlg').restore();
```
Events AJAX disponibles:
- open: Déclenché à l'ouverture
- close: Déclenché à la fermeture
- maximize: Déclenché à la maximisation
- minimize: Déclenché à la minimisation
- move: Déclenché au déplacement
- resize: Déclenché au redimensionnement
Exemple avec AJAX:
<fr:dialog header="Confirmation" widgetVar="confirmDialog">
<p:ajax event="close" listener="#{bean.onDialogClose}" />
<p:outputLabel value="Êtes-vous sûr ?" />
</fr:dialog>
Positions disponibles:
- center: Centre de l'écran (défaut)
- top: En haut centré
- bottom: En bas centré
- left: À gauche centré
- right: À droite centré
- top-left, top-right, bottom-left, bottom-right: Coins
Notes:
- widgetVar est REQUIS pour contrôler le dialog via JS
- Modal true bloque l'interaction avec la page (backdrop)
- Backdrop avec blur 3px pour effet moderne
- Animation fade-in/fade-out fluide
- Header avec gradient bleu Lions.dev
- Footer avec gradient gris
- Responsive: max 95vw/95vh sur mobile
- Support drag & drop du header si draggable="true"
- Redimensionnable par coin inférieur droit si resizable="true"
- Échap pour fermer si closeOnEscape="true"
- Support ARIA pour accessibilité
- Focus trap automatique en mode modal
-->
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="header" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="true" type="java.lang.String" />
<composite:attribute name="modal" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="visible" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="width" required="false" type="java.lang.String" default="auto" />
<composite:attribute name="height" required="false" type="java.lang.String" default="auto" />
<composite:attribute name="minWidth" required="false" type="java.lang.Integer" default="150" />
<composite:attribute name="minHeight" required="false" type="java.lang.Integer" default="150" />
<composite:attribute name="draggable" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="resizable" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="maximizable" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="minimizable" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="closable" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="closeOnEscape" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="position" required="false" type="java.lang.String" default="center" />
<composite:attribute name="showEffect" required="false" type="java.lang.String" />
<composite:attribute name="hideEffect" required="false" type="java.lang.String" />
<composite:attribute name="appendTo" required="false" type="java.lang.String" default="@(body)" />
<composite:attribute name="responsive" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Facets -->
<composite:facet name="header" />
<composite:facet name="footer" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="open" event="open" targets="dialog" />
<composite:clientBehavior name="close" event="close" targets="dialog" />
<composite:clientBehavior name="maximize" event="maximize" targets="dialog" />
<composite:clientBehavior name="minimize" event="minimize" targets="dialog" />
<composite:clientBehavior name="move" event="move" targets="dialog" />
<composite:clientBehavior name="resize" event="resize" targets="dialog" />
<!-- Content insertion -->
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<p:dialog id="dialog"
header="#{cc.attrs.header}"
widgetVar="#{cc.attrs.widgetVar}"
modal="#{cc.attrs.modal}"
visible="#{cc.attrs.visible}"
width="#{cc.attrs.width}"
height="#{cc.attrs.height}"
minWidth="#{cc.attrs.minWidth}"
minHeight="#{cc.attrs.minHeight}"
draggable="#{cc.attrs.draggable}"
resizable="#{cc.attrs.resizable}"
maximizable="#{cc.attrs.maximizable}"
minimizable="#{cc.attrs.minimizable}"
closable="#{cc.attrs.closable}"
closeOnEscape="#{cc.attrs.closeOnEscape}"
position="#{cc.attrs.position}"
showEffect="#{cc.attrs.showEffect}"
hideEffect="#{cc.attrs.hideEffect}"
appendTo="#{cc.attrs.appendTo}"
responsive="#{cc.attrs.responsive}"
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<!-- Custom header facet -->
<f:facet name="header" rendered="#{not empty cc.facets.header}">
<composite:renderFacet name="header" />
</f:facet>
<!-- Content -->
<composite:insertChildren />
<!-- Footer facet -->
<f:facet name="footer" rendered="#{not empty cc.facets.footer}">
<composite:renderFacet name="footer" />
</f:facet>
</p:dialog>
</composite:implementation>
</html>

View File

@@ -6,14 +6,96 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field AutoComplete - Pattern field + label + autoComplete + message
═══════════════════════════════════════════════════════════
Lions.dev Field AutoComplete - Champ avec suggestions
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #13
Champ texte avec auto-complétion dynamique et suggestions en temps réel.
Styles Lions.dev avec dropdown, sélection multiple et recherche serveur.
Usage:
<fr:fieldAutoComplete id="ville" label="Ville" value="#{bean.ville}" completeMethod="#{bean.completeVille}" />
Exemples:
1. AutoComplete simple avec dropdown:
<fr:fieldAutoComplete id="ville"
label="Ville"
value="#{bean.ville}"
completeMethod="#{bean.completeVille}"
dropdown="true" />
dropdown="true"
placeholder="Saisissez une ville..." />
2. Sélection multiple (tags):
<fr:fieldAutoComplete id="competences"
label="Compétences"
value="#{bean.competences}"
completeMethod="#{bean.completeCompetences}"
multiple="true"
placeholder="Ajoutez vos compétences..."
size="lg" />
3. AutoComplete avec objets complexes:
<fr:fieldAutoComplete id="utilisateur"
label="Utilisateur"
value="#{bean.selectedUser}"
completeMethod="#{bean.completeUsers}"
var="user"
itemLabel="#{user.nom}"
itemValue="#{user}"
converter="userConverter" />
4. Force selection (seulement valeurs de la liste):
<fr:fieldAutoComplete id="pays"
label="Pays"
value="#{bean.pays}"
completeMethod="#{bean.completePays}"
forceSelection="true"
dropdown="true"
size="sm" />
Attributs disponibles:
- label: Libellé du champ
- value: Valeur liée au bean
- completeMethod: Méthode backing bean (List complete(String query))
- var: Variable d'itération pour objets complexes
- itemLabel: Expression EL pour le libellé (si objets complexes)
- itemValue: Expression EL pour la valeur (si objets complexes)
- required: true/false - Champ obligatoire
- disabled: true/false - Champ désactivé
- readonly: true/false - Lecture seule
- placeholder: Texte d'aide
- dropdown: true/false - Bouton dropdown pour afficher toutes les suggestions
- multiple: true/false - Sélection multiple (mode tags)
- minQueryLength: Nombre min de caractères avant recherche (défaut: 1)
- queryDelay: Délai en ms avant recherche (défaut: 300)
- maxResults: Nombre max de résultats affichés (défaut: 10)
- forceSelection: true/false - N'accepter que les valeurs de la liste
- scrollHeight: Hauteur max du panel de suggestions en px (défaut: 200)
- converter: Converter JSF pour objets complexes
- size: sm | base (défaut) | lg - Taille du champ
- styleClass: Classes CSS additionnelles
Méthode completeMethod (backing bean):
```java
public List<String> completeVille(String query) {
return villeService.findByNom(query);
}
// Pour objets complexes:
public List<User> completeUsers(String query) {
return userService.searchByName(query);
}
```
Notes:
- Recherche déclenchée après minQueryLength caractères
- Délai queryDelay pour éviter requêtes excessives
- Support du clavier (flèches, Enter, Esc)
- Mode multiple: affiche les sélections en tags bleus
- forceSelection: empêche saisie libre
- dropdown: permet d'afficher toutes les suggestions
-->
<composite:interface>
@@ -40,6 +122,7 @@
<composite:attribute name="forceSelection" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="scrollHeight" required="false" type="java.lang.Integer" default="200" />
<composite:attribute name="converter" required="false" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<!-- Binding for input component -->
<composite:editableValueHolder name="input" targets="autoCompleteComponent" />
@@ -47,7 +130,7 @@
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="autoCompleteComponent"
value="#{cc.attrs.label}"

View File

@@ -6,14 +6,80 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Calendar - Pattern field + label + datePicker + message
═══════════════════════════════════════════════════════════
Lions.dev Field Calendar - Champ date avec calendrier popup
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #12
Sélecteur de date avec popup calendrier interactif.
Styles Lions.dev avec icône calendrier, min/max date et formats personnalisés.
Usage:
<fr:fieldCalendar id="birthdate" label="Date de naissance" value="#{bean.birthdate}" showIcon="true" />
Exemples:
1. Date de naissance avec icône:
<fr:fieldCalendar id="birthdate"
label="Date de naissance"
value="#{bean.birthdate}"
required="true"
showIcon="true"
yearRange="c-100:c+0"
required="true" />
2. Date avec format personnalisé:
<fr:fieldCalendar id="eventDate"
label="Date de l'événement"
value="#{bean.eventDate}"
pattern="dd/MM/yyyy"
showIcon="true"
size="lg" />
3. Date avec min/max (période):
<fr:fieldCalendar id="rendezvous"
label="Date du rendez-vous"
value="#{bean.rendezvous}"
mindate="#{bean.today}"
maxdate="#{bean.maxDate}"
showIcon="true" />
4. Saisie manuelle sans icône:
<fr:fieldCalendar id="dateManuelle"
label="Date"
value="#{bean.date}"
showIcon="false"
size="sm" />
Attributs disponibles:
- label: Libellé du champ
- value: Valeur liée au bean (java.util.Date ou LocalDate)
- required: true/false - Champ obligatoire
- disabled: true/false - Champ désactivé
- readonly: true/false - Lecture seule
- showIcon: true/false - Afficher icône calendrier (défaut: true)
- pattern: Format de la date (dd/MM/yyyy, MM/dd/yyyy, etc.)
- yearRange: Plage d'années ("2000:2030", "c-100:c+0" pour 100 ans avant aujourd'hui)
- mindate: Date minimale acceptée (java.util.Date)
- maxdate: Date maximale acceptée (java.util.Date)
- size: sm | base (défaut) | lg - Taille du champ
- styleClass: Classes CSS additionnelles
Events AJAX disponibles:
- dateSelect: Déclenché quand une date est sélectionnée dans le calendrier
- change: Déclenché au changement de valeur
- viewChange: Déclenché au changement de mois/année
- close: Déclenché à la fermeture du calendrier
Exemple avec AJAX:
<fr:fieldCalendar id="date" label="Date" value="#{bean.date}">
<p:ajax event="dateSelect" listener="#{bean.onDateSelect}" update="resultPanel" />
</fr:fieldCalendar>
Notes:
- Format par défaut selon la locale du navigateur
- Navigation mois/année avec flèches
- Support du clavier (flèches, Enter, Esc)
- Popup responsive sur mobile
-->
<composite:interface>
@@ -29,15 +95,24 @@
<composite:attribute name="yearRange" required="false" type="java.lang.String" />
<composite:attribute name="mindate" required="false" />
<composite:attribute name="maxdate" required="false" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Binding for calendar component -->
<composite:editableValueHolder name="calendar" targets="calendarComponent" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="dateSelect" event="dateSelect" targets="calendarComponent" />
<composite:clientBehavior name="change" event="change" targets="calendarComponent" />
<composite:clientBehavior name="blur" event="blur" targets="calendarComponent" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="calendarComponent" />
<composite:clientBehavior name="viewChange" event="viewChange" targets="calendarComponent" />
<composite:clientBehavior name="close" event="close" targets="calendarComponent" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="calendarComponent"
value="#{cc.attrs.label}"

View File

@@ -4,48 +4,168 @@
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Checkbox - Pattern field + checkbox + label
═══════════════════════════════════════════════════════════
Lions.dev Field Checkbox - Case à cocher avec label
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #28
Case à cocher (checkbox) avec label intégré.
Styles Lions.dev avec animations, gradient bleu et transitions fluides.
Usage:
<fr:fieldCheckbox id="acceptTerms"
label="J'accepte les conditions"
value="#{bean.acceptTerms}"
label="J'accepte les conditions générales"
value="#{userBean.acceptTerms}"
required="true" />
Exemples:
1. Checkbox simple:
<fr:fieldCheckbox id="newsletter"
label="S'abonner à la newsletter"
value="#{userBean.newsletter}" />
2. Checkbox requis:
<fr:fieldCheckbox id="terms"
label="J'accepte les conditions d'utilisation"
value="#{orderBean.termsAccepted}"
required="true" />
3. Checkbox désactivé:
<fr:fieldCheckbox id="premium"
label="Compte premium (non disponible)"
value="#{userBean.premium}"
disabled="true" />
4. Checkbox avec taille personnalisée:
<fr:fieldCheckbox id="remember"
label="Se souvenir de moi"
value="#{authBean.rememberMe}"
size="lg" />
5. Checkbox avec AJAX:
<fr:fieldCheckbox id="notifications"
label="Activer les notifications"
value="#{settingsBean.notificationsEnabled}">
<p:ajax event="change"
update="notifSettings"
listener="#{settingsBean.onNotificationToggle}" />
</fr:fieldCheckbox>
6. Checkbox avec texte d'aide:
<fr:fieldCheckbox id="twoFactor"
label="Authentification à deux facteurs"
value="#{securityBean.twoFactorEnabled}"
helpText="Ajoutez une couche de sécurité supplémentaire à votre compte" />
Attributs disponibles:
- id: Identifiant du composant
- label: Texte du label affiché à côté de la checkbox
- value: Valeur booléenne bindée (true/false)
- required: true/false - Champ obligatoire (défaut: false)
- disabled: true/false - Désactiver la checkbox (défaut: false)
- size: sm | base (défaut) | lg - Taille de la checkbox
- helpText: Texte d'aide affiché sous la checkbox
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
AJAX Events:
- change: Déclenché quand la valeur change
- valueChange: Déclenché lors du changement de valeur (legacy)
Exemples de backing bean:
```java
@Named
@ViewScoped
public class UserBean {
private boolean acceptTerms;
private boolean newsletter;
// Getters et setters
public boolean isAcceptTerms() {
return acceptTerms;
}
public void setAcceptTerms(boolean acceptTerms) {
this.acceptTerms = acceptTerms;
}
public boolean isNewsletter() {
return newsletter;
}
public void setNewsletter(boolean newsletter) {
this.newsletter = newsletter;
if (newsletter) {
System.out.println("User subscribed to newsletter");
}
}
}
```
Validation:
```java
// Checkbox requis
@NotNull(message = "Vous devez accepter les conditions")
private Boolean acceptTerms;
```
Notes de style:
- Checkbox avec gradient bleu Lions.dev quand coché
- Animation de check icon au clic
- Border bleu au hover
- Focus ring pour accessibilité
- Label cliquable (toggle la checkbox)
- Taille personnalisable (sm, base, lg)
- État disabled avec opacity réduite
- Support des messages d'erreur sous la checkbox
Accessibilité:
- Label associé correctement via for/id
- Support clavier (Space pour toggle)
- Focus ring visible
- Attribut required pour lecteurs d'écran
- Indicateur visuel obligatoire (*)
-->
<composite:interface>
<!-- Standard attributes -->
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="label" required="false" type="java.lang.String" />
<composite:attribute name="value" required="false" type="java.lang.Boolean" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="helpText" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Binding for checkbox component -->
<composite:editableValueHolder name="checkbox" targets="checkboxComponent" />
<composite:editableValueHolder name="checkbox" targets="checkbox" />
<composite:clientBehavior name="change" event="change" targets="checkbox" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="checkbox" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern for checkbox -->
<div class="field-checkbox #{cc.attrs.styleClass}">
<!-- Checkbox component -->
<p:selectBooleanCheckbox id="checkboxComponent"
<div class="field field-checkbox #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<p:selectBooleanCheckbox id="checkbox"
value="#{cc.attrs.value}"
required="#{cc.attrs.required}"
disabled="#{cc.attrs.disabled}" />
<!-- Label -->
<p:outputLabel for="@previous"
<p:outputLabel for="checkbox"
value="#{cc.attrs.label}"
rendered="#{not empty cc.attrs.label}">
<h:outputText value=" *" styleClass="p-error" rendered="#{cc.attrs.required}" />
</p:outputLabel>
<!-- Validation message -->
<p:message for="checkboxComponent" />
<small class="field-help" rendered="#{not empty cc.attrs.helpText}">
#{cc.attrs.helpText}
</small>
<p:message for="checkbox" styleClass="field-error" />
</div>
</composite:implementation>

View File

@@ -6,14 +6,121 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field FileUpload - Pattern field + label + fileUpload + message
═══════════════════════════════════════════════════════════
Lions.dev Field FileUpload - Upload de fichiers avec drag & drop
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #15
Champ d'upload de fichiers avec drag & drop, validation et aperçu.
Styles Lions.dev avec zone de dépôt stylisée, barre de progression et gestion multi-fichiers.
Usage:
<fr:fieldFileUpload id="document" label="Document" listener="#{bean.handleUpload}" allowTypes="/(\.|\/)(pdf|doc|docx)$/" />
Exemples:
1. Upload simple avec drag & drop:
<fr:fieldFileUpload id="document"
label="Document"
listener="#{bean.handleUpload}"
allowTypes="/(\.|\/)(pdf|doc|docx)$/"
fileLimit="3" />
label="Document PDF"
listener="#{bean.handleFileUpload}"
allowTypes="/(\.|\/)(pdf)$/"
sizeLimit="10485760"
auto="false"
dragDropSupport="true" />
2. Upload multiple de fichiers:
<fr:fieldFileUpload id="photos"
label="Photos (max 5)"
listener="#{bean.handlePhotos}"
multiple="true"
fileLimit="5"
allowTypes="/(\.|\/)(jpg|jpeg|png|gif)$/"
sizeLimit="5242880"
size="lg" />
3. Upload automatique (sans bouton):
<fr:fieldFileUpload id="avatar"
label="Photo de profil"
listener="#{bean.handleAvatar}"
auto="true"
allowTypes="/(\.|\/)(jpg|jpeg|png)$/"
sizeLimit="2097152"
update="avatarPreview"
size="sm" />
4. Upload avec validation stricte:
<fr:fieldFileUpload id="fichierConfig"
label="Fichier de configuration"
listener="#{bean.handleConfig}"
allowTypes="/(\.|\/)(xml|json|yml)$/"
fileLimit="1"
sizeLimit="1048576"
required="true"
dragDropSupport="false" />
Attributs disponibles:
- label: Libellé du champ
- listener: Méthode backing bean (void handleFileUpload(FileUploadEvent event))
- required: true/false - Champ obligatoire
- disabled: true/false - Champ désactivé
- mode: Mode d'upload - "advanced" (défaut avec drag & drop) ou "simple"
- multiple: true/false - Upload de plusieurs fichiers
- auto: true/false - Upload automatique à la sélection (sans bouton "Envoyer")
- allowTypes: Regex des types de fichiers acceptés ("/(\.|\/)(pdf|doc|docx)$/")
- sizeLimit: Taille max par fichier en octets (ex: 10485760 = 10 MB)
- fileLimit: Nombre max de fichiers (si multiple="true")
- dragDropSupport: true/false - Activer drag & drop (défaut: true)
- chooseLabel: Texte bouton "Choisir" (défaut: "Choisir")
- uploadLabel: Texte bouton "Envoyer" (défaut: "Envoyer")
- cancelLabel: Texte bouton "Annuler" (défaut: "Annuler")
- chooseIcon: Icône bouton choisir (défaut: "pi pi-plus")
- uploadIcon: Icône bouton envoyer (défaut: "pi pi-upload")
- cancelIcon: Icône bouton annuler (défaut: "pi pi-times")
- update: IDs des composants à mettre à jour après upload
- size: sm | base (défaut) | lg - Taille du champ
- styleClass: Classes CSS additionnelles
Méthode listener (backing bean):
```java
public void handleFileUpload(FileUploadEvent event) {
UploadedFile file = event.getFile();
String fileName = file.getFileName();
long fileSize = file.getSize();
String contentType = file.getContentType();
try (InputStream input = file.getInputStream()) {
// Traiter le fichier...
Files.copy(input, Paths.get("/uploads/" + fileName));
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage("Succès", fileName + " téléchargé avec succès."));
} catch (IOException e) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", "Échec du téléchargement."));
}
}
```
Tailles de fichiers courantes:
- 1 MB = 1048576 octets
- 5 MB = 5242880 octets
- 10 MB = 10485760 octets
- 50 MB = 52428800 octets
Types MIME courants:
- PDF: /(\.|\/)(pdf)$/
- Images: /(\.|\/)(jpg|jpeg|png|gif|webp)$/
- Documents: /(\.|\/)(pdf|doc|docx|xls|xlsx)$/
- Archives: /(\.|\/)(zip|rar|7z|tar|gz)$/
- Vidéos: /(\.|\/)(mp4|avi|mov|wmv)$/
Notes:
- Mode "advanced": Interface complète avec drag & drop et barre de progression
- Mode "simple": Bouton simple sans interface avancée
- Validation automatique des types et tailles avant upload
- Messages d'erreur automatiques si fichier invalide
- Barre de progression affichée pendant l'upload
- Support du drag & drop natif HTML5
-->
<composite:interface>
@@ -40,11 +147,12 @@
<composite:attribute name="chooseIcon" required="false" type="java.lang.String" default="pi pi-plus" />
<composite:attribute name="uploadIcon" required="false" type="java.lang.String" default="pi pi-upload" />
<composite:attribute name="cancelIcon" required="false" type="java.lang.String" default="pi pi-times" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="uploadComponent"
value="#{cc.attrs.label}"

View File

@@ -6,14 +6,73 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Input - Pattern field + label + input + message
═══════════════════════════════════════════════════════════
Lions.dev Field Input - Champ de saisie texte avec identité Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0
Priority: CRITIQUE #5
Description:
Champ de saisie texte (InputText) avec label, validation et messages d'erreur.
Suit le pattern Field de PrimeFaces avec styles Lions.dev.
Parfait pour nom, prénom, email, etc.
Fonctionnalités:
- Styles Lions.dev élégants avec bordures et focus ring
- Focus ring accessible (WCAG 2.1 AA)
- Support des tailles (sm, base, lg)
- États visuels (normal, focus, error, success, disabled)
- Messages de validation intégrés
- Label avec indicateur required (*)
Usage:
<fr:fieldInput id="nom"
label="Nom"
value="#{bean.nom}"
label="Nom complet"
value="#{bean.user.nom}"
required="true"
placeholder="Entrez votre nom" />
placeholder="Ex: Jean Dupont"
size="base" />
Exemples:
1. Champ simple requis:
<fr:fieldInput label="Email" value="#{bean.email}" required="true"
placeholder="email@example.com" />
2. Champ large avec max length:
<fr:fieldInput label="Titre" value="#{bean.title}" size="lg"
maxlength="100" placeholder="Titre du document" />
3. Champ petit en lecture seule:
<fr:fieldInput label="ID" value="#{bean.id}" size="sm" readonly="true" />
4. Champ désactivé:
<fr:fieldInput label="Statut" value="#{bean.status}" disabled="true" />
5. Avec validation Ajax:
<fr:fieldInput label="Nom d'utilisateur" value="#{bean.username}" required="true">
<p:ajax event="blur" update="@this" listener="#{bean.validateUsername}" />
</fr:fieldInput>
Tailles disponibles:
- sm (32px height) - Pour champs compacts
- base (44px height - défaut) - Taille standard
- lg (52px height) - Pour champs importants
Attributs disponibles:
- id: Identifiant du champ
- label: Label du champ (affiché au-dessus)
- value: Valeur bindée au bean
- required: true/false - Affiche * rouge si required
- placeholder: Texte d'aide dans le champ
- disabled: true/false - Champ désactivé
- readonly: true/false - Champ en lecture seule
- maxlength: Nombre max de caractères
- size: sm|base|lg - Taille du champ
- styleClass: Classes CSS additionnelles
Events Ajax supportés:
- keyup, keydown, blur, focus, change, valueChange
-->
<composite:interface>
@@ -26,15 +85,24 @@
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="readonly" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="maxlength" required="false" type="java.lang.Integer" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Binding for input component -->
<composite:editableValueHolder name="input" targets="inputComponent" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="keyup" event="keyup" targets="inputComponent" />
<composite:clientBehavior name="keydown" event="keydown" targets="inputComponent" />
<composite:clientBehavior name="blur" event="blur" targets="inputComponent" />
<composite:clientBehavior name="focus" event="focus" targets="inputComponent" />
<composite:clientBehavior name="change" event="change" targets="inputComponent" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="inputComponent" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<!-- Lions.dev field pattern -->
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="inputComponent"
value="#{cc.attrs.label}"

View File

@@ -7,15 +7,98 @@
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
Freya Field MultiSelect - Pattern field + label + selectCheckboxMenu + message
═══════════════════════════════════════════════════════════
Lions.dev Field MultiSelect - Sélection multiple avec checkboxes
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #14
Champ de sélection multiple avec checkboxes dans un menu déroulant.
Styles Lions.dev avec filtre de recherche, "Tout sélectionner" et compteur.
Usage:
<fr:fieldMultiSelect id="roles"
label="Rôles"
value="#{bean.selectedRoles}"
filter="true">
<fr:fieldMultiSelect id="roles" label="Rôles" value="#{bean.selectedRoles}" filter="true">
<f:selectItems value="#{bean.availableRoles}" />
</fr:fieldMultiSelect>
Exemples:
1. MultiSelect simple avec filtre:
<fr:fieldMultiSelect id="roles"
label="Rôles utilisateur"
value="#{bean.selectedRoles}"
filter="true"
required="true">
<f:selectItems value="#{bean.availableRoles}" var="role"
itemLabel="#{role.nom}" itemValue="#{role.id}" />
</fr:fieldMultiSelect>
2. MultiSelect avec objets complexes:
<fr:fieldMultiSelect id="departements"
label="Départements"
value="#{bean.selectedDepts}"
filter="true"
filterMatchMode="contains">
<f:selectItems value="#{bean.allDepartements}" var="dept"
itemLabel="#{dept.code} - #{dept.nom}" itemValue="#{dept}" />
</fr:fieldMultiSelect>
3. MultiSelect compact:
<fr:fieldMultiSelect id="tags"
label="Tags"
value="#{bean.selectedTags}"
filter="true"
size="sm">
<f:selectItem itemLabel="Java" itemValue="java" />
<f:selectItem itemLabel="JavaScript" itemValue="js" />
<f:selectItem itemLabel="Python" itemValue="python" />
<f:selectItem itemLabel="C#" itemValue="csharp" />
</fr:fieldMultiSelect>
4. MultiSelect large avec groupes:
<fr:fieldMultiSelect id="permissions"
label="Permissions"
value="#{bean.selectedPermissions}"
filter="true"
size="lg">
<f:selectItems value="#{bean.allPermissions}" var="perm"
itemLabel="#{perm.libelle}" itemValue="#{perm.code}" />
</fr:fieldMultiSelect>
Attributs disponibles:
- label: Libellé du champ
- value: Collection des valeurs sélectionnées (List, Set, etc.)
- required: true/false - Champ obligatoire
- disabled: true/false - Champ désactivé
- filter: true/false - Activer la recherche (défaut: false)
- filterMatchMode: Mode de recherche - "contains", "startsWith", "endsWith" (défaut: contains)
- multiple: true/false - Toujours true pour ce composant (défaut: true)
- size: sm | base (défaut) | lg - Taille du champ
- styleClass: Classes CSS additionnelles
Contenu (f:selectItems ou f:selectItem):
- Utiliser <f:selectItems> pour une collection dynamique
- Utiliser <f:selectItem> pour des valeurs fixes
Exemples de f:selectItems:
```xhtml
<!-- Liste simple de strings -->
<f:selectItems value="#{bean.options}" />
<!-- Liste d'objets avec var -->
<f:selectItems value="#{bean.users}" var="user"
itemLabel="#{user.nom}" itemValue="#{user.id}" />
<!-- Map -->
<f:selectItems value="#{bean.optionsMap}" />
```
Notes:
- Affiche un compteur "X sur Y sélectionnés"
- Case "Tout sélectionner / Tout désélectionner" en header
- Filtre de recherche en temps réel (si filter="true")
- Scroll automatique si plus de 10 éléments
- Support du clavier (flèches, Espace, Enter, Esc)
- Affichage responsive sur mobile
-->
<composite:interface>
@@ -28,6 +111,7 @@
<composite:attribute name="filter" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="filterMatchMode" required="false" type="java.lang.String" default="contains" />
<composite:attribute name="multiple" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Binding for select component -->
@@ -39,7 +123,7 @@
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="selectComponent"
value="#{cc.attrs.label}"

View File

@@ -6,22 +6,79 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Number - Pattern field + label + inputNumber + message
═══════════════════════════════════════════════════════════
Lions.dev Field Number - Champ numérique avec spinners
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #11
Champ numérique avec boutons +/- (spinners), formatage et validation.
Styles Lions.dev avec symboles monétaires, min/max et précision décimale.
Usage:
<fr:fieldNumber id="prix" label="Prix" value="#{bean.prix}" symbol=" FCFA" symbolPosition="s" decimalPlaces="0" />
Exemples:
1. Champ prix avec symbole monétaire:
<fr:fieldNumber id="prix"
label="Prix"
label="Prix unitaire"
value="#{bean.prix}"
symbol=" FCFA"
symbolPosition="s"
decimalPlaces="0" />
decimalPlaces="0"
minValue="0" />
With min/max:
2. Pourcentage avec min/max:
<fr:fieldNumber id="pourcentage"
label="Pourcentage"
label="Pourcentage de remise"
value="#{bean.pourcentage}"
minValue="0"
maxValue="100" />
maxValue="100"
symbol="%"
symbolPosition="s" />
3. Champ décimal (2 chiffres):
<fr:fieldNumber id="montant"
label="Montant"
value="#{bean.montant}"
decimalPlaces="2"
symbol=" €"
symbolPosition="s"
size="lg" />
4. Quantité simple:
<fr:fieldNumber id="quantite"
label="Quantité"
value="#{bean.quantite}"
decimalPlaces="0"
minValue="1"
required="true"
size="sm" />
Attributs disponibles:
- label: Libellé du champ
- value: Valeur liée au bean
- required: true/false - Champ obligatoire
- disabled: true/false - Champ désactivé
- readonly: true/false - Lecture seule
- placeholder: Texte d'aide
- symbol: Symbole monétaire/unité (€, $, FCFA, %, etc.)
- symbolPosition: Position du symbole - "p" (prefix) ou "s" (suffix)
- decimalPlaces: Nombre de décimales (0, 2, 3, etc.)
- minValue: Valeur minimale acceptée
- maxValue: Valeur maximale acceptée
- size: sm | base (défaut) | lg - Taille du champ
- styleClass: Classes CSS additionnelles
Position du symbole:
- "p" (prefix): Avant le nombre (€ 1000)
- "s" (suffix): Après le nombre (1000 FCFA)
Notes:
- Les spinners (+/-) apparaissent automatiquement
- Validation automatique min/max avec message d'erreur
- Support du clavier (flèches haut/bas)
- Formatage automatique selon locale
-->
<composite:interface>
@@ -46,6 +103,7 @@
-->
<composite:attribute name="minValue" required="false" type="java.lang.String" default="-999999999999" />
<composite:attribute name="maxValue" required="false" type="java.lang.String" default="999999999999" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<!-- Binding for input component -->
<composite:editableValueHolder name="input" targets="inputComponent" />
@@ -53,7 +111,7 @@
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="inputComponent"
value="#{cc.attrs.label}"

View File

@@ -6,14 +6,19 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Password - Pattern field + label + password + message
═══════════════════════════════════════════════════════════
Lions.dev Field Password - Champ mot de passe Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #8
Champ mot de passe avec indicateur de force, toggle mask et validation.
Styles Lions.dev avec barre de force colorée (weak/medium/strong).
Usage:
<fr:fieldPassword id="password"
label="Mot de passe"
value="#{bean.password}"
required="true"
feedback="true" />
<fr:fieldPassword id="pwd" label="Mot de passe" value="#{bean.pwd}" feedback="true" toggleMask="true" />
Tailles: sm | base (défaut) | lg
Attributs: feedback (true/false), toggleMask (true/false), maxlength, disabled, size
-->
<composite:interface>
@@ -28,15 +33,24 @@
<composite:attribute name="feedback" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="toggleMask" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="maxlength" required="false" type="java.lang.Integer" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Binding for password component -->
<composite:editableValueHolder name="password" targets="passwordComponent" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="keyup" event="keyup" targets="passwordComponent" />
<composite:clientBehavior name="keydown" event="keydown" targets="passwordComponent" />
<composite:clientBehavior name="blur" event="blur" targets="passwordComponent" />
<composite:clientBehavior name="focus" event="focus" targets="passwordComponent" />
<composite:clientBehavior name="change" event="change" targets="passwordComponent" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="passwordComponent" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="passwordComponent"
value="#{cc.attrs.label}"

View File

@@ -5,58 +5,215 @@
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
Freya Field Radio - Pattern field + label + selectOneRadio + message
═══════════════════════════════════════════════════════════
Lions.dev Field Radio - Boutons radio avec label
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #29
Groupe de boutons radio (choix unique) avec label intégré.
Styles Lions.dev avec animations, gradient bleu et transitions fluides.
Usage:
<fr:fieldRadio id="gender"
label="Genre"
value="#{userBean.gender}">
<f:selectItem itemLabel="Homme" itemValue="M" />
<f:selectItem itemLabel="Femme" itemValue="F" />
</fr:fieldRadio>
Exemples:
1. Radio basique:
<fr:fieldRadio id="civilite"
label="Civilité"
value="#{bean.civilite}">
value="#{userBean.civilite}">
<f:selectItem itemLabel="M." itemValue="M" />
<f:selectItem itemLabel="Mme" itemValue="MME" />
<f:selectItem itemLabel="Mlle" itemValue="MLLE" />
</fr:fieldRadio>
2. Radio avec layout horizontal (par défaut):
<fr:fieldRadio id="satisfaction"
label="Niveau de satisfaction"
value="#{surveyBean.satisfaction}"
layout="lineDirection">
<f:selectItem itemLabel="Très satisfait" itemValue="5" />
<f:selectItem itemLabel="Satisfait" itemValue="4" />
<f:selectItem itemLabel="Neutre" itemValue="3" />
<f:selectItem itemLabel="Insatisfait" itemValue="2" />
<f:selectItem itemLabel="Très insatisfait" itemValue="1" />
</fr:fieldRadio>
3. Radio avec layout vertical:
<fr:fieldRadio id="plan"
label="Choisissez votre plan"
value="#{subscriptionBean.plan}"
layout="pageDirection">
<f:selectItem itemLabel="Gratuit" itemValue="FREE" />
<f:selectItem itemLabel="Standard (9.99€/mois)" itemValue="STANDARD" />
<f:selectItem itemLabel="Premium (19.99€/mois)" itemValue="PREMIUM" />
<f:selectItem itemLabel="Entreprise (contactez-nous)" itemValue="ENTERPRISE" />
</fr:fieldRadio>
4. Radio requis:
<fr:fieldRadio id="acceptPolicy"
label="Acceptez-vous notre politique de confidentialité ?"
value="#{userBean.policyAccepted}"
required="true">
<f:selectItem itemLabel="Oui, j'accepte" itemValue="true" />
<f:selectItem itemLabel="Non, je refuse" itemValue="false" />
</fr:fieldRadio>
5. Radio avec AJAX:
<fr:fieldRadio id="paymentMethod"
label="Mode de paiement"
value="#{orderBean.paymentMethod}">
<f:selectItem itemLabel="Carte bancaire" itemValue="CARD" />
<f:selectItem itemLabel="PayPal" itemValue="PAYPAL" />
<f:selectItem itemLabel="Virement" itemValue="TRANSFER" />
<p:ajax event="change"
update="paymentDetails"
listener="#{orderBean.onPaymentMethodChange}" />
</fr:fieldRadio>
6. Radio avec texte d'aide:
<fr:fieldRadio id="deliveryMode"
label="Mode de livraison"
value="#{orderBean.deliveryMode}"
helpText="Choisissez le mode de livraison qui vous convient">
<f:selectItem itemLabel="Standard (3-5 jours)" itemValue="STANDARD" />
<f:selectItem itemLabel="Express (24h)" itemValue="EXPRESS" />
<f:selectItem itemLabel="Retrait en magasin" itemValue="PICKUP" />
</fr:fieldRadio>
Attributs disponibles:
- id: Identifiant du composant
- label: Label affiché au-dessus du groupe de radio
- value: Valeur bindée (objet sélectionné)
- required: true/false - Champ obligatoire (défaut: false)
- disabled: true/false - Désactiver tous les radios (défaut: false)
- layout: "lineDirection" (horizontal) | "pageDirection" (vertical) - Défaut: lineDirection
- size: sm | base (défaut) | lg - Taille des boutons radio
- helpText: Texte d'aide affiché sous le groupe
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
Insertion de choix:
Utilisez <f:selectItem> ou <f:selectItems> comme enfants:
- <f:selectItem itemLabel="Label" itemValue="value" />
- <f:selectItems value="#{bean.options}" />
AJAX Events:
- change: Déclenché quand la sélection change
- valueChange: Déclenché lors du changement de valeur
Exemples de backing bean:
```java
@Named
@ViewScoped
public class UserBean {
private String civilite;
private String gender;
private List<SelectItem> civiliteOptions;
@PostConstruct
public void init() {
civiliteOptions = new ArrayList<>();
civiliteOptions.add(new SelectItem("M", "M."));
civiliteOptions.add(new SelectItem("MME", "Mme"));
civiliteOptions.add(new SelectItem("MLLE", "Mlle"));
}
// Getters et setters
public String getCivilite() {
return civilite;
}
public void setCivilite(String civilite) {
this.civilite = civilite;
}
public List<SelectItem> getCiviliteOptions() {
return civiliteOptions;
}
}
```
Validation:
```java
@NotNull(message = "Veuillez sélectionner une option")
private String civilite;
```
Layout options:
- lineDirection: Boutons disposés horizontalement (par défaut)
- pageDirection: Boutons disposés verticalement
- grid: Grille responsive (2 colonnes sur mobile, 3+ sur desktop)
- custom: Layout personnalisé avec CSS
Notes de style:
- Radio avec gradient bleu Lions.dev quand sélectionné
- Animation du point central au clic
- Border bleu au hover
- Focus ring pour accessibilité
- Label cliquable
- Taille personnalisable (sm, base, lg)
- État disabled avec opacity réduite
- Support des messages d'erreur
Accessibilité:
- Labels associés correctement
- Support clavier (flèches pour naviguer, Space/Enter pour sélectionner)
- Focus ring visible
- Attribut required pour lecteurs d'écran
- Indicateur visuel obligatoire (*)
- ARIA roles automatiques
-->
<composite:interface>
<!-- Standard attributes -->
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="label" required="false" type="java.lang.String" />
<composite:attribute name="value" required="false" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="layout" required="false" type="java.lang.String" default="lineDirection" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="helpText" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Binding for select component -->
<composite:editableValueHolder name="select" targets="selectComponent" />
<composite:editableValueHolder name="select" targets="select" />
<composite:clientBehavior name="change" event="change" targets="select" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="select" />
<!-- Insertion point for select items -->
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="selectComponent"
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<p:outputLabel for="select"
value="#{cc.attrs.label}"
rendered="#{not empty cc.attrs.label}">
<h:outputText value=" *" styleClass="p-error" rendered="#{cc.attrs.required}" />
</p:outputLabel>
<!-- SelectOneRadio component -->
<p:selectOneRadio id="selectComponent"
<p:selectOneRadio id="select"
value="#{cc.attrs.value}"
required="#{cc.attrs.required}"
disabled="#{cc.attrs.disabled}"
layout="#{cc.attrs.layout}">
<!-- Insert child f:selectItem(s) -->
<composite:insertChildren />
</p:selectOneRadio>
<!-- Validation message -->
<p:message for="selectComponent" />
<small class="field-help" rendered="#{not empty cc.attrs.helpText}">
#{cc.attrs.helpText}
</small>
<p:message for="select" styleClass="field-error" />
</div>
</composite:implementation>

View File

@@ -4,53 +4,224 @@
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Rating - Pattern field + rating + label
═══════════════════════════════════════════════════════════
Lions.dev Field Rating - Notation par étoiles
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #33
Système de notation par étoiles (ou icônes personnalisées) avec label.
Styles Lions.dev avec étoiles gold, animations hover et transitions élégantes.
Usage:
<fr:fieldRating id="note"
label="Note"
value="#{bean.note}" />
label="Votre note"
value="#{reviewBean.rating}"
stars="5" />
Exemples:
1. Rating basique (5 étoiles):
<fr:fieldRating id="productRating"
label="Notez ce produit"
value="#{productBean.rating}"
stars="5" />
2. Rating 10 étoiles:
<fr:fieldRating id="movieRating"
label="Note du film"
value="#{movieBean.rating}"
stars="10" />
3. Rating sans bouton cancel:
<fr:fieldRating id="satisfaction"
label="Niveau de satisfaction"
value="#{surveyBean.satisfaction}"
cancel="false" />
4. Rating en lecture seule:
<fr:fieldRating id="avgRating"
label="Note moyenne"
value="#{productBean.avgRating}"
readonly="true"
stars="5" />
5. Rating avec AJAX:
<fr:fieldRating id="userRating"
label="Votre évaluation"
value="#{reviewBean.userRating}">
<p:ajax event="rate"
update="ratingPanel"
listener="#{reviewBean.onRate}" />
</fr:fieldRating>
6. Rating avec taille personnalisée:
<fr:fieldRating id="qualityRating"
label="Qualité du service"
value="#{feedbackBean.quality}"
size="lg"
helpText="Cliquez sur les étoiles pour noter" />
7. Rating requis:
<fr:fieldRating id="mandatoryRating"
label="Évaluation obligatoire"
value="#{formBean.rating}"
required="true"
cancel="false" />
8. Rating avec hearts (coeurs):
<fr:fieldRating id="favoriteLevel"
label="Niveau de favori"
value="#{userBean.favoriteLevel}"
variant="hearts"
stars="5" />
Attributs disponibles:
- id: Identifiant du composant
- label: Label affiché au-dessus du rating
- value: Valeur numérique bindée (Integer, 0-stars)
- stars: Nombre d'étoiles affichées (défaut: 5)
- cancel: true/false - Afficher bouton annuler (défaut: true)
- readonly: true/false - Mode lecture seule (défaut: false)
- disabled: true/false - Désactiver le rating (défaut: false)
- size: sm | base (défaut) | lg - Taille des étoiles
- variant: stars (défaut) | hearts - Type d'icône
- required: true/false - Champ obligatoire (défaut: false)
- helpText: Texte d'aide affiché sous le rating
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
AJAX Events:
- rate: Déclenché quand l'utilisateur note
- cancel: Déclenché quand l'utilisateur annule sa note
Exemples de backing bean:
```java
@Named
@ViewScoped
public class ReviewBean {
private Integer rating;
private Integer productRating = 0;
public void onRate() {
System.out.println("User rated: " + rating + " stars");
// Sauvegarder la note en base de données
}
public void onCancelRating() {
System.out.println("User cancelled rating");
rating = null;
}
// Getters et setters
public Integer getRating() {
return rating;
}
public void setRating(Integer rating) {
this.rating = rating;
}
public Integer getProductRating() {
return productRating;
}
public void setProductRating(Integer productRating) {
this.productRating = productRating;
}
}
```
Validation:
```java
@NotNull(message = "Veuillez noter le produit")
@Min(value = 1, message = "Veuillez donner au moins 1 étoile")
@Max(value = 5, message = "Maximum 5 étoiles")
private Integer rating;
```
Variants disponibles:
- stars: Étoiles gold traditionnelles (défaut)
- hearts: Coeurs rouges pour favoris/amour
Cas d'usage recommandés:
- Notes de produits: 5 étoiles avec cancel
- Satisfaction client: 5 étoiles sans cancel
- Notes de films/séries: 10 étoiles
- Affichage moyenne: readonly=true
- Niveau de favori: hearts variant
Bouton Cancel:
- Permet de remettre à zéro la notation
- Icône croix rouge à gauche des étoiles
- Utile pour permettre à l'utilisateur de revenir sur sa note
- Désactiver avec cancel="false" si la note est obligatoire
Notes de style:
- Étoiles gold Lions.dev (#FFC700) quand actives
- Étoiles grises quand inactives
- Hover: Étoiles jaunes avec scale 1.15
- Active: Étoiles avec text-shadow gold
- Bouton cancel rouge avec hover effect
- Tailles étoiles: sm (18px), base (24px), lg (32px)
- État readonly: pas de hover, curseur default
- État disabled: opacity réduite, curseur not-allowed
Accessibilité:
- Label associé correctement
- Support clavier (flèches pour naviguer, Enter pour sélectionner)
- Focus ring visible
- Attribut role="radiogroup"
- Attributs aria-label sur chaque étoile
- État readonly clairement indiqué
- Valeur annoncée aux lecteurs d'écran
-->
<composite:interface>
<!-- Standard attributes -->
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="label" required="false" type="java.lang.String" />
<composite:attribute name="value" required="false" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="readonly" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="stars" required="false" type="java.lang.Integer" default="5" />
<composite:attribute name="cancel" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="readonly" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="variant" required="false" type="java.lang.String" default="stars" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="helpText" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Binding for rating component -->
<composite:editableValueHolder name="rating" targets="ratingComponent" />
<composite:editableValueHolder name="rating" targets="rating" />
<composite:clientBehavior name="rate" event="rate" targets="rating" />
<composite:clientBehavior name="cancel" event="cancel" targets="rating" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="ratingComponent"
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<p:outputLabel for="rating"
value="#{cc.attrs.label}"
rendered="#{not empty cc.attrs.label}">
<h:outputText value=" *" styleClass="p-error" rendered="#{cc.attrs.required}" />
</p:outputLabel>
<!-- Rating component -->
<p:rating id="ratingComponent"
<p:rating id="rating"
value="#{cc.attrs.value}"
required="#{cc.attrs.required}"
disabled="#{cc.attrs.disabled}"
readonly="#{cc.attrs.readonly}"
stars="#{cc.attrs.stars}"
cancel="#{cc.attrs.cancel}" />
cancel="#{cc.attrs.cancel}"
readonly="#{cc.attrs.readonly}"
disabled="#{cc.attrs.disabled}"
required="#{cc.attrs.required}"
styleClass="#{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'ui-rating-'.concat(cc.attrs.size) : ''} #{cc.attrs.variant eq 'hearts' ? 'ui-rating-hearts' : ''}" />
<!-- Validation message -->
<p:message for="ratingComponent" />
<small class="field-help" rendered="#{not empty cc.attrs.helpText}">
#{cc.attrs.helpText}
</small>
<p:message for="rating" styleClass="field-error" />
</div>
</composite:implementation>

View File

@@ -7,17 +7,22 @@
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
Freya Field Select - Pattern field + label + selectOneMenu + message
═══════════════════════════════════════════════════════════
Lions.dev Field Select - Liste déroulante avec identité Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #6
Champ de sélection (dropdown) avec filtrage optionnel, label et validation.
Styles Lions.dev avec focus ring accessible et états visuels clairs.
Usage:
<fr:fieldSelect id="category"
label="Catégorie"
value="#{bean.category}"
required="true"
filter="true">
<f:selectItem itemLabel="Sélectionnez..." itemValue="" />
<f:selectItems value="#{bean.categories}" />
<fr:fieldSelect id="pays" label="Pays" value="#{bean.pays}" required="true" filter="true">
<f:selectItem itemLabel="Choisir..." itemValue="" />
<f:selectItems value="#{bean.listePays}" />
</fr:fieldSelect>
Tailles: sm (32px) | base (44px - défaut) | lg (52px)
Attributs: filter, filterMatchMode, disabled, required, size
-->
<composite:interface>
@@ -29,18 +34,26 @@
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="filter" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="filterMatchMode" required="false" type="java.lang.String" default="contains" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Binding for select component -->
<composite:editableValueHolder name="select" targets="selectComponent" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="change" event="change" targets="selectComponent" />
<composite:clientBehavior name="blur" event="blur" targets="selectComponent" />
<composite:clientBehavior name="focus" event="focus" targets="selectComponent" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="selectComponent" />
<composite:clientBehavior name="itemSelect" event="itemSelect" targets="selectComponent" />
<!-- Insertion point for select items -->
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="selectComponent"
value="#{cc.attrs.label}"

View File

@@ -4,28 +4,192 @@
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Slider - Pattern field + label + slider + message
═══════════════════════════════════════════════════════════
Lions.dev Field Slider - Curseur de sélection numérique
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #32
Curseur (slider) pour sélection de valeur numérique avec handle personnalisé.
Styles Lions.dev avec gradient bleu, animations fluides et transitions élégantes.
Usage:
<fr:fieldSlider id="volume"
label="Volume"
value="#{bean.volume}"
value="#{settingsBean.volume}"
minValue="0"
maxValue="100" />
Exemples:
1. Slider basique (0-100):
<fr:fieldSlider id="brightness"
label="Luminosité"
value="#{settingsBean.brightness}"
minValue="0"
maxValue="100" />
2. Slider avec step personnalisé:
<fr:fieldSlider id="price"
label="Prix maximum"
value="#{filterBean.maxPrice}"
minValue="0"
maxValue="1000"
step="50"
displayTemplate="{value}€" />
3. Slider vertical:
<fr:fieldSlider id="temperature"
label="Température"
value="#{climateBean.temperature}"
minValue="15"
maxValue="30"
orientation="vertical"
displayTemplate="{value}°C" />
4. Slider avec AJAX:
<fr:fieldSlider id="zoom"
label="Zoom"
value="#{editorBean.zoomLevel}"
minValue="50"
maxValue="200"
step="10">
<p:ajax event="slideEnd"
update="preview"
listener="#{editorBean.onZoomChange}" />
</fr:fieldSlider>
5. Range slider (2 valeurs):
<fr:fieldSlider id="ageRange"
label="Tranche d'âge"
value="#{filterBean.ageRange}"
minValue="18"
maxValue="65"
range="true" />
6. Slider avec variant success:
<fr:fieldSlider id="progress"
label="Progression"
value="#{taskBean.progress}"
minValue="0"
maxValue="100"
variant="success"
displayTemplate="{value}%" />
7. Slider avec taille personnalisée:
<fr:fieldSlider id="quality"
label="Qualité"
value="#{exportBean.quality}"
minValue="1"
maxValue="10"
size="lg"
helpText="1 = Basse qualité, 10 = Haute qualité" />
Attributs disponibles:
- id: Identifiant du composant
- label: Label affiché au-dessus du slider
- value: Valeur numérique bindée (Integer)
- minValue: Valeur minimale (défaut: 0)
- maxValue: Valeur maximale (défaut: 100)
- step: Incrément de valeur (défaut: 1)
- animate: true/false - Animation fluide (défaut: true)
- displayTemplate: Format d'affichage (défaut: "{value}")
- range: true/false - Slider avec 2 handles (défaut: false)
- orientation: "horizontal" | "vertical" - Défaut: horizontal
- variant: primary (défaut) | success | danger | warning - Couleur du track
- size: sm | base (défaut) | lg - Taille du slider
- disabled: true/false - Désactiver le slider (défaut: false)
- helpText: Texte d'aide affiché sous le slider
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
AJAX Events:
- slide: Déclenché pendant le déplacement
- slideEnd: Déclenché à la fin du déplacement (recommandé)
Exemples de backing bean:
```java
@Named
@ViewScoped
public class SettingsBean {
private int volume = 50;
private int brightness = 80;
private int[] ageRange = {25, 45}; // Pour range slider
public void onVolumeChange() {
System.out.println("Volume changed to: " + volume);
}
// Getters et setters
public int getVolume() {
return volume;
}
public void setVolume(int volume) {
this.volume = volume;
}
public int getBrightness() {
return brightness;
}
public void setBrightness(int brightness) {
this.brightness = brightness;
}
public int[] getAgeRange() {
return ageRange;
}
public void setAgeRange(int[] ageRange) {
this.ageRange = ageRange;
}
}
```
Display Templates:
- {value}: Affiche la valeur brute (ex: "50")
- {value}%: Affiche en pourcentage (ex: "50%")
- {value}€: Affiche avec symbole euro (ex: "50€")
- {value}°C: Affiche en degrés (ex: "50°C")
- Prix: {value}€: Texte avec valeur (ex: "Prix: 50€")
Variants disponibles:
- primary: Gradient bleu Lions.dev (défaut)
- success: Gradient vert pour progression positive
- danger: Gradient rouge pour valeurs critiques
- warning: Gradient orange pour avertissements
Cas d'usage recommandés:
- Volume, luminosité, zoom: 0-100
- Prix, budget: Avec step et displayTemplate
- Température: Avec orientation vertical et symbole °C
- Progression: Variant success avec %
- Filtres de recherche: Range slider pour intervalles
Notes de style:
- Track avec gradient bleu Lions.dev
- Handle circulaire avec border bleu
- Hover: Handle agrandi avec shadow
- Active: Handle encore plus grand (effet grab)
- Background track gris clair
- Tailles handle: sm (16px), base (20px), lg (24px)
- État disabled avec opacity réduite
- Transition fluide des valeurs
Accessibilité:
- Label associé correctement
- Support clavier (flèches pour ajuster)
- Focus ring visible sur handle
- Attribut role="slider"
- Attributs aria-valuemin, aria-valuemax, aria-valuenow
- Template d'affichage pour clarté
-->
<composite:interface>
<!-- Standard attributes -->
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="label" required="false" type="java.lang.String" />
<composite:attribute name="value" required="false" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Slider specific attributes -->
<composite:attribute name="minValue" required="false" type="java.lang.Integer" default="0" />
<composite:attribute name="maxValue" required="false" type="java.lang.Integer" default="100" />
<composite:attribute name="step" required="false" type="java.lang.Integer" default="1" />
@@ -33,23 +197,28 @@
<composite:attribute name="displayTemplate" required="false" type="java.lang.String" default="{value}" />
<composite:attribute name="range" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="orientation" required="false" type="java.lang.String" default="horizontal" />
<composite:attribute name="variant" required="false" type="java.lang.String" default="primary" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="helpText" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Binding for input component -->
<composite:editableValueHolder name="input" targets="sliderComponent" />
<composite:editableValueHolder name="input" targets="hiddenInput" />
<composite:clientBehavior name="slide" event="slide" targets="slider" />
<composite:clientBehavior name="slideEnd" event="slideEnd" targets="slider" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="sliderComponent"
value="#{cc.attrs.label}"
rendered="#{not empty cc.attrs.label}">
<h:outputText value=" *" styleClass="p-error" rendered="#{cc.attrs.required}" />
</p:outputLabel>
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<!-- Slider component -->
<p:slider id="sliderComponent"
<p:outputLabel for="slider"
value="#{cc.attrs.label}"
rendered="#{not empty cc.attrs.label}" />
<p:slider id="slider"
for="hiddenInput"
minValue="#{cc.attrs.minValue}"
maxValue="#{cc.attrs.maxValue}"
@@ -58,15 +227,17 @@
displayTemplate="#{cc.attrs.displayTemplate}"
range="#{cc.attrs.range}"
orientation="#{cc.attrs.orientation}"
disabled="#{cc.attrs.disabled}" />
disabled="#{cc.attrs.disabled}"
styleClass="ui-slider-#{cc.attrs.variant} #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'ui-slider-'.concat(cc.attrs.size) : ''}" />
<!-- Hidden input for value binding -->
<h:inputHidden id="hiddenInput" value="#{cc.attrs.value}" />
<!-- Validation message -->
<p:message for="hiddenInput" />
<small class="field-help" rendered="#{not empty cc.attrs.helpText}">
#{cc.attrs.helpText}
</small>
<p:message for="hiddenInput" styleClass="field-error" />
</div>
</composite:implementation>
</html>

View File

@@ -4,46 +4,208 @@
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Switch - Pattern field + switch + label
═══════════════════════════════════════════════════════════
Lions.dev Field Switch - Interrupteur toggle avec label
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #30
Interrupteur toggle (switch) avec label intégré et animation fluide.
Styles Lions.dev avec gradients, animations slide et transitions élégantes.
Usage:
<fr:fieldSwitch id="active"
label="Activé"
value="#{bean.active}" />
<fr:fieldSwitch id="notifications"
label="Activer les notifications"
value="#{settingsBean.notificationsEnabled}" />
Exemples:
1. Switch basique:
<fr:fieldSwitch id="darkMode"
label="Mode sombre"
value="#{settingsBean.darkMode}" />
2. Switch avec taille personnalisée:
<fr:fieldSwitch id="autoSave"
label="Sauvegarde automatique"
value="#{editorBean.autoSave}"
size="lg" />
3. Switch avec variant success:
<fr:fieldSwitch id="accountActive"
label="Compte actif"
value="#{userBean.isActive}"
variant="success" />
4. Switch avec AJAX:
<fr:fieldSwitch id="emailNotif"
label="Notifications par email"
value="#{settingsBean.emailNotifications}">
<p:ajax event="change"
update="emailSettings"
listener="#{settingsBean.onEmailNotifToggle}" />
</fr:fieldSwitch>
5. Switch désactivé:
<fr:fieldSwitch id="premiumFeature"
label="Fonctionnalité premium (nécessite abonnement)"
value="#{userBean.premiumEnabled}"
disabled="true" />
6. Switch avec texte d'aide:
<fr:fieldSwitch id="twoFactor"
label="Authentification à deux facteurs"
value="#{securityBean.twoFactorAuth}"
helpText="Ajoutez une couche de sécurité supplémentaire à votre compte" />
7. Switch avec on/off labels:
<fr:fieldSwitch id="publicProfile"
label="Profil public"
value="#{userBean.profilePublic}"
onLabel="Public"
offLabel="Privé" />
Attributs disponibles:
- id: Identifiant du composant
- label: Texte du label affiché à côté du switch
- value: Valeur booléenne bindée (true/false)
- required: true/false - Champ obligatoire (défaut: false)
- disabled: true/false - Désactiver le switch (défaut: false)
- size: sm | base (défaut) | lg - Taille du switch
- variant: primary (défaut) | success | danger - Couleur quand activé
- onLabel: Texte affiché quand ON
- offLabel: Texte affiché quand OFF
- helpText: Texte d'aide affiché sous le switch
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
AJAX Events:
- change: Déclenché quand l'état change
- valueChange: Déclenché lors du changement d'état
Exemples de backing bean:
```java
@Named
@ViewScoped
public class SettingsBean {
private boolean notificationsEnabled = true;
private boolean darkMode = false;
private boolean emailNotifications = true;
public void onEmailNotifToggle() {
if (emailNotifications) {
System.out.println("Email notifications enabled");
// Initialiser les préférences d'email
} else {
System.out.println("Email notifications disabled");
}
}
// Getters et setters
public boolean isNotificationsEnabled() {
return notificationsEnabled;
}
public void setNotificationsEnabled(boolean notificationsEnabled) {
this.notificationsEnabled = notificationsEnabled;
}
public boolean isDarkMode() {
return darkMode;
}
public void setDarkMode(boolean darkMode) {
this.darkMode = darkMode;
}
public boolean isEmailNotifications() {
return emailNotifications;
}
public void setEmailNotifications(boolean emailNotifications) {
this.emailNotifications = emailNotifications;
}
}
```
Validation:
```java
@AssertTrue(message = "Vous devez accepter les conditions")
private boolean termsAccepted;
```
Variants disponibles:
- primary: Gradient bleu Lions.dev (défaut)
- success: Gradient vert pour états actifs/validés
- danger: Gradient rouge pour activation/désactivation critique
Notes de style:
- Switch avec gradient bleu Lions.dev quand activé
- Animation slide fluide du bouton
- Background gris quand désactivé
- Hover effect sur le background
- Focus ring pour accessibilité
- Label cliquable (toggle le switch)
- Tailles: sm (36px), base (44px), lg (52px)
- État disabled avec opacity réduite
- Support des messages d'erreur
Différences avec Checkbox:
- Switch: Pour des actions ON/OFF avec effet immédiat
- Checkbox: Pour des choix multiples ou validation de formulaire
Accessibilité:
- Label associé correctement
- Support clavier (Space pour toggle)
- Focus ring visible
- Attribut role="switch"
- Attribut aria-checked dynamique
- Indicateur visuel d'état
-->
<composite:interface>
<!-- Standard attributes -->
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="label" required="false" type="java.lang.String" />
<composite:attribute name="value" required="false" />
<composite:attribute name="value" required="false" type="java.lang.Boolean" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="variant" required="false" type="java.lang.String" default="primary" />
<composite:attribute name="onLabel" required="false" type="java.lang.String" />
<composite:attribute name="offLabel" required="false" type="java.lang.String" />
<composite:attribute name="helpText" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Binding for switch component -->
<composite:editableValueHolder name="switch" targets="switchComponent" />
<composite:editableValueHolder name="switch" targets="switch" />
<composite:clientBehavior name="change" event="change" targets="switch" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="switch" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern for switches -->
<div class="field-checkbox #{cc.attrs.styleClass}">
<p:toggleSwitch id="switchComponent"
<div class="field field-checkbox #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<p:inputSwitch id="switch"
value="#{cc.attrs.value}"
required="#{cc.attrs.required}"
disabled="#{cc.attrs.disabled}" />
disabled="#{cc.attrs.disabled}"
onLabel="#{cc.attrs.onLabel}"
offLabel="#{cc.attrs.offLabel}"
styleClass="ui-inputswitch-#{cc.attrs.variant}" />
<!-- Label -->
<p:outputLabel for="switchComponent"
<p:outputLabel for="switch"
value="#{cc.attrs.label}"
rendered="#{not empty cc.attrs.label}">
<h:outputText value=" *" styleClass="p-error" rendered="#{cc.attrs.required}" />
</p:outputLabel>
<!-- Validation message -->
<p:message for="switchComponent" />
<small class="field-help" rendered="#{not empty cc.attrs.helpText}">
#{cc.attrs.helpText}
</small>
<p:message for="switch" styleClass="field-error" />
</div>
</composite:implementation>

View File

@@ -6,14 +6,19 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Textarea - Pattern field + label + textarea + message
═══════════════════════════════════════════════════════════
Lions.dev Field Textarea - Zone de texte multiligne Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #7
Champ de saisie multiligne (Textarea) avec label, validation et auto-resize.
Styles Lions.dev avec bordures élégantes et focus ring accessible.
Usage:
<fr:fieldTextarea id="description"
label="Description"
value="#{bean.description}"
rows="5"
required="true" />
<fr:fieldTextarea id="bio" label="Biographie" value="#{bean.bio}" rows="4" required="true" />
Tailles: sm | base (défaut) | lg
Attributs: rows, cols, autoResize, maxlength, disabled, readonly, size
-->
<composite:interface>
@@ -29,15 +34,24 @@
<composite:attribute name="cols" required="false" type="java.lang.Integer" />
<composite:attribute name="autoResize" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="maxlength" required="false" type="java.lang.Integer" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Binding for textarea component -->
<composite:editableValueHolder name="input" targets="textareaComponent" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="keyup" event="keyup" targets="textareaComponent" />
<composite:clientBehavior name="keydown" event="keydown" targets="textareaComponent" />
<composite:clientBehavior name="blur" event="blur" targets="textareaComponent" />
<composite:clientBehavior name="focus" event="focus" targets="textareaComponent" />
<composite:clientBehavior name="change" event="change" targets="textareaComponent" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="textareaComponent" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="textareaComponent"
value="#{cc.attrs.label}"

View File

@@ -4,61 +4,226 @@
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
Freya Field Toggle - Pattern field + selectBooleanButton + label
═══════════════════════════════════════════════════════════
Lions.dev Field Toggle - Bouton toggle ON/OFF avec label
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #31
Bouton toggle (selectBooleanButton) avec état ON/OFF, labels et icônes personnalisables.
Styles Lions.dev avec gradient bleu, animations et transitions élégantes.
Usage:
<fr:fieldToggle id="notif"
<fr:fieldToggle id="active"
label="Compte actif"
value="#{userBean.isActive}"
onLabel="Actif"
offLabel="Inactif" />
Exemples:
1. Toggle basique:
<fr:fieldToggle id="published"
label="Publié"
value="#{articleBean.published}"
onLabel="Publié"
offLabel="Brouillon" />
2. Toggle avec icônes:
<fr:fieldToggle id="notifications"
label="Notifications"
value="#{bean.notif}"
onLabel="Oui"
offLabel="Non"
value="#{settingsBean.notifications}"
onLabel="ON"
offLabel="OFF"
onIcon="pi pi-check"
offIcon="pi pi-times" />
3. Toggle avec AJAX:
<fr:fieldToggle id="status"
label="Statut"
value="#{userBean.accountStatus}">
<p:ajax event="change"
update="statusPanel"
listener="#{userBean.onStatusChange}" />
</fr:fieldToggle>
4. Toggle désactivé:
<fr:fieldToggle id="premium"
label="Compte premium"
value="#{userBean.premiumAccount}"
disabled="true"
onLabel="Premium"
offLabel="Standard" />
5. Toggle avec taille personnalisée:
<fr:fieldToggle id="featured"
label="Article en vedette"
value="#{articleBean.featured}"
size="lg"
onLabel="Oui"
offLabel="Non" />
6. Toggle avec texte d'aide:
<fr:fieldToggle id="public"
label="Profil public"
value="#{userBean.publicProfile}"
helpText="Rendre votre profil visible par tous les utilisateurs"
onLabel="Public"
offLabel="Privé" />
7. Toggle Yes/No avec icônes:
<fr:fieldToggle id="accept"
label="Accepter les conditions"
value="#{orderBean.termsAccepted}"
onLabel="Oui"
offLabel="Non"
onIcon="pi pi-check-circle"
offIcon="pi pi-times-circle"
required="true" />
Attributs disponibles:
- id: Identifiant du composant
- label: Label affiché au-dessus du toggle
- value: Valeur booléenne bindée (true/false)
- onLabel: Texte affiché quand ON (défaut: "Oui")
- offLabel: Texte affiché quand OFF (défaut: "Non")
- onIcon: Icône PrimeIcons affichée quand ON
- offIcon: Icône PrimeIcons affichée quand OFF
- required: true/false - Champ obligatoire (défaut: false)
- disabled: true/false - Désactiver le toggle (défaut: false)
- size: sm | base (défaut) | lg - Taille du bouton
- helpText: Texte d'aide affiché sous le toggle
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
AJAX Events:
- change: Déclenché quand l'état change
- valueChange: Déclenché lors du changement de valeur
Exemples de backing bean:
```java
@Named
@ViewScoped
public class ArticleBean {
private boolean published = false;
private boolean featured = false;
public void onPublishedToggle() {
if (published) {
System.out.println("Article published");
// Envoyer notifications, indexer pour recherche, etc.
} else {
System.out.println("Article unpublished");
}
}
// Getters et setters
public boolean isPublished() {
return published;
}
public void setPublished(boolean published) {
this.published = published;
}
public boolean isFeatured() {
return featured;
}
public void setFeatured(boolean featured) {
this.featured = featured;
}
}
```
Validation:
```java
@AssertTrue(message = "Vous devez accepter les conditions")
private boolean termsAccepted;
```
Différences avec Switch:
- Toggle Button: Bouton clickable avec labels ON/OFF visibles
- Switch: Toggle slider minimaliste avec animation slide
Quand utiliser Toggle Button:
- Quand les labels ON/OFF apportent de la clarté
- Pour des actions business (Actif/Inactif, Publié/Brouillon)
- Quand l'état doit être explicite pour l'utilisateur
- Pour des formulaires où le contexte nécessite des labels
Icônes recommandées:
- Check/Times: pi-check / pi-times
- Check Circle/Times Circle: pi-check-circle / pi-times-circle
- Thumbs Up/Down: pi-thumbs-up / pi-thumbs-down
- Power: pi-power (ON/OFF général)
- Lock/Unlock: pi-lock / pi-unlock
Notes de style:
- Bouton avec gradient bleu Lions.dev quand ON (actif)
- Bouton gris avec border quand OFF (inactif)
- Hover effect sur border et background
- Labels et icônes centrés dans le bouton
- Focus ring pour accessibilité
- Tailles: sm, base, lg
- État disabled avec opacity réduite
- Transition fluide entre états
Accessibilité:
- Label associé correctement
- Support clavier (Space/Enter pour toggle)
- Focus ring visible
- Attribut role="button"
- Attribut aria-pressed dynamique
- Labels ON/OFF pour clarté
-->
<composite:interface>
<!-- Standard attributes -->
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="label" required="false" type="java.lang.String" />
<composite:attribute name="value" required="false" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<!-- Toggle specific attributes -->
<composite:attribute name="value" required="false" type="java.lang.Boolean" />
<composite:attribute name="onLabel" required="false" type="java.lang.String" default="Oui" />
<composite:attribute name="offLabel" required="false" type="java.lang.String" default="Non" />
<composite:attribute name="onIcon" required="false" type="java.lang.String" />
<composite:attribute name="offIcon" required="false" type="java.lang.String" />
<composite:attribute name="required" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="disabled" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<composite:attribute name="helpText" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<!-- Binding for toggle component -->
<composite:editableValueHolder name="toggle" targets="toggleComponent" />
<composite:editableValueHolder name="toggle" targets="toggle" />
<composite:clientBehavior name="change" event="change" targets="toggle" />
<composite:clientBehavior name="valueChange" event="valueChange" targets="toggle" />
</composite:interface>
<composite:implementation>
<!-- Freya field pattern -->
<div class="field #{cc.attrs.styleClass}">
<!-- Label with required indicator -->
<p:outputLabel for="toggleComponent"
<div class="field #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'field-'.concat(cc.attrs.size) : ''} #{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<p:outputLabel for="toggle"
value="#{cc.attrs.label}"
rendered="#{not empty cc.attrs.label}">
<h:outputText value=" *" styleClass="p-error" rendered="#{cc.attrs.required}" />
</p:outputLabel>
<!-- SelectBooleanButton component -->
<p:selectBooleanButton id="toggleComponent"
<p:selectBooleanButton id="toggle"
value="#{cc.attrs.value}"
required="#{cc.attrs.required}"
disabled="#{cc.attrs.disabled}"
onLabel="#{cc.attrs.onLabel}"
offLabel="#{cc.attrs.offLabel}"
onIcon="#{cc.attrs.onIcon}"
offIcon="#{cc.attrs.offIcon}" />
offIcon="#{cc.attrs.offIcon}"
styleClass="#{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'ui-togglebutton-'.concat(cc.attrs.size) : ''}" />
<!-- Validation message -->
<p:message for="toggleComponent" />
<small class="field-help" rendered="#{not empty cc.attrs.helpText}">
#{cc.attrs.helpText}
</small>
<p:message for="toggle" styleClass="field-error" />
</div>
</composite:implementation>

View File

@@ -6,10 +6,138 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya Growl - Notifications toast avec style Freya
═══════════════════════════════════════════════════════════
Lions.dev Growl - Notifications growl empilables (legacy)
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #20
Notifications growl empilables en coin d'écran (composant legacy).
Styles Lions.dev avec gradients, ombres XL et animations fluides.
⚠️ Note: p:growl est déprécié dans PrimeFaces au profit de p:toast.
Il est recommandé d'utiliser <fr:toast> pour les nouveaux projets.
Ce composant est maintenu pour compatibilité avec le code existant.
Usage:
<fr:growl id="growl" life="3000" sticky="false" />
<fr:growl id="growl" />
Exemples:
1. Growl basique (coin supérieur droit):
<fr:growl id="growl" />
2. Growl coin inférieur gauche avec durée personnalisée:
<fr:growl id="growl" position="bottom-left" life="5000" />
3. Growl sticky (ne se ferme pas automatiquement):
<fr:growl id="growl" sticky="true" />
4. Growl avec widgetVar pour contrôle JS:
<fr:growl id="growl" widgetVar="growlWidget" position="top-center" />
5. Growl pour un composant spécifique:
<fr:growl id="formGrowl" forId="userForm" position="bottom-right" />
Attributs disponibles:
- id: Identifiant du composant
- life: Durée d'affichage en ms (défaut: 3000ms = 3s)
- sticky: true/false - Ne pas fermer automatiquement (défaut: false)
- position: Position à l'écran (défaut: "top-right")
- "top-right" (défaut)
- "top-left"
- "bottom-right"
- "bottom-left"
- "top-center"
- "bottom-center"
- "center"
- showDetail: true/false - Afficher les détails (défaut: true)
- showSummary: true/false - Afficher le résumé (défaut: true)
- escape: true/false - Échapper le HTML (défaut: true)
- forId: ID du composant ciblé
- globalOnly: true/false - Messages globaux seulement (défaut: false)
- widgetVar: Variable JS pour contrôle programmatique
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
Ajout de growl depuis le backing bean:
```java
// Growl info
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Information",
"Le traitement est en cours..."));
// Growl success
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Succès",
"Votre commande a été enregistrée avec succès."));
// Growl warning
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_WARN,
"Attention",
"Votre session expirera dans 5 minutes."));
// Growl error
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Erreur",
"Impossible de se connecter au serveur."));
```
Contrôle JavaScript (avec widgetVar):
```javascript
// Afficher un growl programmatiquement
PF('growlWidget').show({
severity: 'info',
summary: 'Notification',
detail: 'Message personnalisé',
life: 3000
});
// Effacer tous les growl
PF('growlWidget').clear();
```
Types de severity:
- info: Notification d'information (bleu cyan)
- success: Opération réussie (vert)
- warn: Avertissement (orange)
- error: Erreur (rouge)
Positions disponibles:
- top-right: Coin supérieur droit (défaut)
- top-left: Coin supérieur gauche
- bottom-right: Coin inférieur droit
- bottom-left: Coin inférieur gauche
- top-center: Centre en haut
- bottom-center: Centre en bas
- center: Centre de l'écran
Migration vers Toast:
Pour migrer de <fr:growl> vers <fr:toast>, remplacez simplement:
Ancien code avec growl:
<fr:growl id="growl" position="top-right" life="3000" />
Nouveau code avec toast:
<fr:toast id="toast" position="top-right" life="3000" />
Les attributs et le code Java backend restent identiques.
Notes:
- Empilage automatique de plusieurs notifications
- Animation fade-in/fade-out fluide
- Effet hover avec translateY
- Bordure gauche colorée selon severity
- Gradients subtils selon le type
- Ombres XL pour effet de profondeur
- Icônes automatiques selon severity
- Bouton fermer avec rotation au hover
- Responsive: pleine largeur sur mobile
- Support ARIA pour accessibilité
- Même API que p:toast pour migration facile
-->
<composite:interface>

View File

@@ -6,13 +6,65 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya LinkButton - Lien stylisé comme bouton
═══════════════════════════════════════════════════════════
Lions.dev LinkButton - Lien stylisé en bouton avec identité Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0
Priority: HAUTE #3
Description:
Lien de navigation (h:link/h:button) stylisé comme un bouton Lions.dev.
Utilisé pour la navigation avec support des f:param et includeViewParams.
Combine les avantages de h:link (SEO, bookmarkable) avec le style des boutons.
Fonctionnalités:
- Gradients Lions.dev élégants
- Animations hover (translateY + shadow elevation)
- Focus ring accessible (WCAG 2.1 AA)
- Support complet des variantes (solid, outlined, text, link, rounded, raised)
- Support des tailles (sm, base, lg)
- Support f:param pour paramètres URL
Usage:
<fr:linkButton value="Voir détails"
outcome="/details"
<fr:linkButton value="Voir les détails"
icon="pi pi-eye"
severity="info" />
outcome="/product-details"
severity="info"
size="lg">
<f:param name="id" value="#{product.id}" />
</fr:linkButton>
Exemples:
1. Navigation avec paramètres:
<fr:linkButton value="Éditer" icon="pi pi-pencil" outcome="/edit" severity="primary">
<f:param name="id" value="#{item.id}" />
</fr:linkButton>
2. Lien externe:
<fr:linkButton value="Documentation" icon="pi pi-book" href="https://docs.lions.dev"
target="_blank" severity="info" outlined="true" />
3. Navigation avec fragment (anchor):
<fr:linkButton value="Aller à la section" outcome="/page" fragment="section2" />
4. Bouton premium avec params:
<fr:linkButton value="Upgrade Now" severity="help" rounded="true" raised="true"
outcome="/pricing" includeViewParams="true" />
Severity disponibles:
- primary (Lions Blue #2D9BEF - défaut)
- secondary (Gray)
- success (Green #22BB69)
- info (Cyan #00BCD4)
- warning (Orange #FF9800)
- help (Lions Gold #FFC700 - premium)
- danger (Red #F44336)
Tailles disponibles:
- sm (32px height)
- base (40px height - défaut)
- lg (48px height)
-->
<composite:interface>
@@ -32,6 +84,10 @@
<!-- Severity: primary, secondary, success, info, warning, help, danger -->
<composite:attribute name="severity" required="false" type="java.lang.String" />
<!-- Size: sm, base, lg -->
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<!-- Variants -->
<composite:attribute name="text" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="outlined" required="false" type="java.lang.Boolean" default="false" />
@@ -54,7 +110,7 @@
target="#{cc.attrs.target}"
includeViewParams="#{cc.attrs.includeViewParams}"
fragment="#{cc.attrs.fragment}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{cc.attrs.text ? 'ui-button-text' : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.link ? 'ui-button-link' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'ui-button-'.concat(cc.attrs.size) : ''} #{cc.attrs.text ? 'ui-button-text' : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.link ? 'ui-button-link' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
style="#{cc.attrs.style}"
title="#{cc.attrs.title}">
<composite:insertChildren />

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
═══════════════════════════════════════════════════════════
Lions.dev Menu - Menu vertical de navigation
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #25
Menu vertical avec sous-menus, icônes et styles Lions.dev.
-->
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="model" required="true" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="false" type="java.lang.String" />
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<p:menu id="menu"
model="#{cc.attrs.model}"
style="#{cc.attrs.style}"
styleClass="#{cc.attrs.styleClass}"
widgetVar="#{cc.attrs.widgetVar}">
<composite:insertChildren />
</p:menu>
</composite:implementation>
</html>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
═══════════════════════════════════════════════════════════
Lions.dev Menubar - Barre de menu horizontale
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #26
Menubar horizontal avec dropdowns, icônes et styles Lions.dev.
-->
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="model" required="true" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="false" type="java.lang.String" />
<composite:facet name="options" />
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<p:menubar id="menubar"
model="#{cc.attrs.model}"
style="#{cc.attrs.style}"
styleClass="#{cc.attrs.styleClass}"
widgetVar="#{cc.attrs.widgetVar}">
<f:facet name="options" rendered="#{not empty cc.facets.options}">
<composite:renderFacet name="options" />
</f:facet>
<composite:insertChildren />
</p:menubar>
</composite:implementation>
</html>

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
═══════════════════════════════════════════════════════════
Lions.dev Messages - Affichage de messages FacesMessage
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #18
Affichage de messages info/succès/warning/erreur avec styles Lions.dev.
Bordure colorée à gauche, icônes et bouton fermer.
Usage:
<fr:messages id="msgs" />
Exemples:
1. Messages globaux (tous les messages):
<fr:messages id="globalMessages" />
2. Messages pour un composant spécifique:
<fr:messages id="formMessages" forId="userForm" />
3. Messages avec détails seulement:
<fr:messages id="detailMessages" showSummary="false" showDetail="true" />
4. Messages non fermables (sticky):
<fr:messages id="stickyMessages" closable="false" />
5. Messages compacts:
<fr:messages id="compactMessages" styleClass="ui-messages-compact" />
Attributs disponibles:
- id: Identifiant du composant
- forId: ID du composant ciblé (affiche seulement les messages de ce composant)
- showSummary: true/false - Afficher le résumé (défaut: true)
- showDetail: true/false - Afficher les détails (défaut: true)
- closable: true/false - Afficher le bouton fermer (défaut: true)
- globalOnly: true/false - Afficher seulement les messages globaux (défaut: false)
- redisplay: true/false - Réafficher les messages déjà affichés (défaut: true)
- escape: true/false - Échapper le HTML (défaut: true)
- styleClass: Classes CSS additionnelles (ui-messages-compact, ui-messages-large, ui-messages-inline)
- style: Styles CSS inline
Ajout de messages depuis le backing bean:
```java
// Message global info
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", "Opération réussie."));
// Message global success
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", "Enregistrement effectué."));
// Message global warning
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", "Vérifiez les données."));
// Message global error
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", "Échec de l'opération."));
// Message pour un composant spécifique
FacesContext.getCurrentInstance().addMessage("userForm:email",
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", "Email invalide."));
```
Types de messages (severity):
- SEVERITY_INFO: Messages d'information (bleu cyan)
- SEVERITY_WARN: Avertissements (orange)
- SEVERITY_ERROR: Erreurs (rouge)
- FacesMessage sans severity: Traité comme INFO
Classes utilitaires:
- ui-messages-compact: Messages compacts (padding réduit)
- ui-messages-large: Messages larges (padding augmenté)
- ui-messages-inline: Messages inline (pas de margin)
Notes:
- Affiche automatiquement les messages FacesMessage de la requête
- Un message par severité (info, success, warning, error)
- Bouton fermer avec animation de rotation au hover
- Bordure colorée à gauche selon la severity
- Icônes automatiques selon le type
- Support ARIA pour accessibilité
- Animations fade-in automatiques
-->
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="forId" required="false" type="java.lang.String" />
<composite:attribute name="showSummary" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="showDetail" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="closable" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="globalOnly" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="redisplay" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="escape" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
</composite:interface>
<composite:implementation>
<p:messages id="messages"
for="#{cc.attrs.forId}"
showSummary="#{cc.attrs.showSummary}"
showDetail="#{cc.attrs.showDetail}"
closable="#{cc.attrs.closable}"
globalOnly="#{cc.attrs.globalOnly}"
redisplay="#{cc.attrs.redisplay}"
escape="#{cc.attrs.escape}"
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}" />
</composite:implementation>
</html>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="false" type="java.lang.String" />
<composite:attribute name="showEvent" required="false" type="java.lang.String" default="click" />
<composite:attribute name="hideEvent" required="false" type="java.lang.String" default="click" />
<composite:attribute name="dismissable" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="showCloseIcon" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="dynamic" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="appendTo" required="false" type="java.lang.String" default="@(body)" />
<composite:attribute name="my" required="false" type="java.lang.String" default="left top" />
<composite:attribute name="at" required="false" type="java.lang.String" default="left bottom" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<composite:attribute name="forComponent" required="false" type="java.lang.String" />
<composite:facet name="header" />
<composite:facet name="footer" />
<composite:clientBehavior name="toggle" event="toggle" targets="overlayPanel" />
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<p:overlayPanel id="overlayPanel"
widgetVar="#{cc.attrs.widgetVar}"
for="#{cc.attrs.forComponent}"
showEvent="#{cc.attrs.showEvent}"
hideEvent="#{cc.attrs.hideEvent}"
dismissable="#{cc.attrs.dismissable}"
showCloseIcon="#{cc.attrs.showCloseIcon}"
dynamic="#{cc.attrs.dynamic}"
appendTo="#{cc.attrs.appendTo}"
my="#{cc.attrs.my}"
at="#{cc.attrs.at}"
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<f:facet name="header" rendered="#{not empty cc.facets.header}">
<composite:renderFacet name="header" />
</f:facet>
<composite:insertChildren />
<f:facet name="footer" rendered="#{not empty cc.facets.footer}">
<composite:renderFacet name="footer" />
</f:facet>
</p:overlayPanel>
</composite:implementation>
</html>

View File

@@ -7,12 +7,82 @@
xmlns:f="http://xmlns.jcp.org/jsf/core">
<!--
Freya Panel - Panneau PrimeFaces avec style Freya
═══════════════════════════════════════════════════════════
Lions.dev Panel - Panneau pliable avec identité Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #10
Panneau pliable/fermable pour organiser le contenu en sections.
Styles Lions.dev avec header coloré selon severity et animations smooth.
Usage:
<fr:panel header="Configuration" toggleable="true">
<fr:panel header="Configuration" icon="pi pi-cog" toggleable="true" severity="primary">
<p>Contenu du panneau...</p>
</fr:panel>
Exemples:
1. Panel simple pliable:
<fr:panel header="Détails" toggleable="true" collapsed="false">
<p>Contenu visible par défaut...</p>
</fr:panel>
2. Panel avec severity et icône:
<fr:panel header="Alerte" icon="pi pi-exclamation-triangle" severity="warning" toggleable="true">
<p>Vérifiez vos paramètres...</p>
</fr:panel>
3. Panel avec actions dans le header:
<fr:panel header="Projet" icon="pi pi-folder">
<f:facet name="actions">
<fr:commandButton icon="pi pi-refresh" text="true" size="sm" />
<fr:commandButton icon="pi pi-download" text="true" size="sm" />
</f:facet>
<p>Contenu du projet...</p>
</fr:panel>
4. Panel fermable:
<fr:panel header="Notification" closable="true" severity="info">
<p>Cette section peut être fermée définitivement.</p>
</fr:panel>
5. Panel avec footer:
<fr:panel header="Formulaire" severity="success">
<fr:fieldInput label="Nom" value="#{bean.nom}" />
<fr:fieldInput label="Email" value="#{bean.email}" />
<f:facet name="footer">
<fr:commandButton value="Enregistrer" severity="success" />
<fr:commandButton value="Annuler" severity="secondary" outlined="true" />
</f:facet>
</fr:panel>
Attributs disponibles:
- header: Titre du panneau
- icon: Icône PrimeIcons dans le header
- severity: primary|success|info|warning|danger|help - Couleur du header
- toggleable: true/false - Permet de plier/déplier
- collapsed: true/false - État initial (plié si true)
- closable: true/false - Bouton fermer (X)
- widgetVar: Variable JavaScript pour contrôle programmatique
- styleClass: Classes CSS additionnelles
Facets disponibles:
- header: Contenu personnalisé du header (remplace header text)
- actions: Boutons/actions dans le header
- footer: Contenu du footer
Severity variants:
- primary: Header bleu Lions (#2D9BEF)
- success: Header vert (#22BB69)
- info: Header cyan (#00BCD4)
- warning: Header orange (#FF9800)
- danger: Header rouge (#F44336)
- help: Header or Lions (#FFC700 - premium)
Contrôle JavaScript (avec widgetVar):
PF('monPanel').toggle(); // Plier/déplier
PF('monPanel').collapse(); // Plier
PF('monPanel').expand(); // Déplier
-->
<composite:interface>
@@ -24,6 +94,8 @@
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="false" type="java.lang.String" />
<composite:attribute name="icon" required="false" type="java.lang.String" />
<composite:attribute name="severity" required="false" type="java.lang.String" />
<!-- Facets -->
<composite:facet name="header" />
@@ -40,13 +112,16 @@
toggleable="#{cc.attrs.toggleable}"
collapsed="#{cc.attrs.collapsed}"
closable="#{cc.attrs.closable}"
styleClass="#{cc.attrs.styleClass}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-panel-'.concat(cc.attrs.severity) : ''}"
style="#{cc.attrs.style}"
widgetVar="#{cc.attrs.widgetVar}">
<!-- Custom header facet -->
<f:facet name="header" rendered="#{not empty cc.facets.header}">
<!-- Custom header facet with icon support -->
<f:facet name="header" rendered="#{not empty cc.facets.header or not empty cc.attrs.icon}">
<div class="flex align-items-center gap-2">
<i class="#{cc.attrs.icon}" rendered="#{not empty cc.attrs.icon}" />
<composite:renderFacet name="header" />
</div>
</f:facet>
<!-- Actions in header -->

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="true" type="java.lang.String" />
<composite:attribute name="position" required="false" type="java.lang.String" default="left" />
<composite:attribute name="visible" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="fullScreen" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="modal" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="showCloseIcon" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="baseZIndex" required="false" type="java.lang.Integer" default="1000" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<composite:facet name="header" />
<composite:facet name="footer" />
<composite:clientBehavior name="show" event="show" targets="sidebar" />
<composite:clientBehavior name="hide" event="hide" targets="sidebar" />
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<p:sidebar id="sidebar"
widgetVar="#{cc.attrs.widgetVar}"
position="#{cc.attrs.position}"
visible="#{cc.attrs.visible}"
fullScreen="#{cc.attrs.fullScreen}"
modal="#{cc.attrs.modal}"
showCloseIcon="#{cc.attrs.showCloseIcon}"
baseZIndex="#{cc.attrs.baseZIndex}"
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}">
<f:facet name="header" rendered="#{not empty cc.facets.header}">
<composite:renderFacet name="header" />
</f:facet>
<composite:insertChildren />
<f:facet name="footer" rendered="#{not empty cc.facets.footer}">
<composite:renderFacet name="footer" />
</f:facet>
</p:sidebar>
</composite:implementation>
</html>

View File

@@ -6,14 +6,77 @@
xmlns:p="http://primefaces.org/ui">
<!--
Freya SplitButton - Bouton avec menu déroulant
═══════════════════════════════════════════════════════════
Lions.dev SplitButton - Bouton avec menu déroulant Lions.dev
═══════════════════════════════════════════════════════════
Version: 1.0.0
Priority: HAUTE #4
Description:
Bouton divisé combinant une action principale et un menu déroulant d'actions secondaires.
Parfait pour "Sauvegarder + Options", "Exporter + Formats", etc.
Utilise MenuModel pour les options du menu.
Fonctionnalités:
- Gradients Lions.dev élégants sur bouton principal
- Animations hover (translateY + shadow elevation)
- Focus ring accessible (WCAG 2.1 AA)
- Support des variantes (solid, outlined, rounded, raised)
- Support des tailles (sm, base, lg)
- Menu déroulant avec items personnalisables
Usage:
<fr:splitButton value="Sauvegarder"
icon="pi pi-save"
action="#{bean.save}"
model="#{bean.saveMenuModel}"
severity="success" />
model="#{bean.saveOptionsMenu}"
severity="success"
size="lg" />
Exemples:
1. Actions de sauvegarde multiples:
<fr:splitButton value="Sauvegarder" icon="pi pi-save" severity="success"
action="#{bean.save}" model="#{bean.saveMenu}" update="@form" />
Bean:
private MenuModel saveMenu;
@PostConstruct
public void init() {
saveMenu = new DefaultMenuModel();
saveMenu.getElements().add(new DefaultMenuItem("Sauvegarder et continuer"));
saveMenu.getElements().add(new DefaultMenuItem("Sauvegarder et fermer"));
saveMenu.getElements().add(new DefaultMenuItem("Sauvegarder comme brouillon"));
}
2. Export avec formats multiples:
<fr:splitButton value="Exporter PDF" icon="pi pi-download" severity="primary"
action="#{bean.exportPDF}" model="#{bean.exportFormatsMenu}" />
3. Actions dangereuses avec confirmation:
<fr:splitButton value="Supprimer" icon="pi pi-trash" severity="danger"
action="#{bean.delete}" model="#{bean.deleteOptionsMenu}"
outlined="true" raised="true" />
4. Bouton premium avec options:
<fr:splitButton value="Publier" severity="help" rounded="true"
action="#{bean.publish}" model="#{bean.publishMenu}" />
Severity disponibles:
- primary (Lions Blue #2D9BEF - défaut)
- secondary (Gray)
- success (Green #22BB69)
- info (Cyan #00BCD4)
- warning (Orange #FF9800)
- help (Lions Gold #FFC700 - premium)
- danger (Red #F44336)
Tailles disponibles:
- sm (32px height)
- base (40px height - défaut)
- lg (48px height)
Note: Le menu déroulant hérite automatiquement du style du bouton principal.
-->
<composite:interface>
@@ -38,6 +101,10 @@
<!-- Severity: primary, secondary, success, info, warning, help, danger -->
<composite:attribute name="severity" required="false" type="java.lang.String" />
<!-- Size: sm, base, lg -->
<composite:attribute name="size" required="false" type="java.lang.String" default="base" />
<!-- Variants -->
<composite:attribute name="outlined" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="rounded" required="false" type="java.lang.Boolean" default="false" />
@@ -46,6 +113,10 @@
<!-- Action source -->
<composite:actionSource name="button" targets="splitButton" />
<!-- AJAX Behaviors Support -->
<composite:clientBehavior name="action" event="action" targets="splitButton" default="true" />
<composite:clientBehavior name="click" event="click" targets="splitButton" />
<!-- Content insertion for p:menuitem -->
<composite:insertChildren />
</composite:interface>
@@ -65,7 +136,7 @@
global="#{cc.attrs.global}"
model="#{cc.attrs.model}"
menuStyleClass="#{cc.attrs.menuStyleClass}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
styleClass="#{cc.attrs.styleClass} #{not empty cc.attrs.severity ? 'ui-button-'.concat(cc.attrs.severity) : ''} #{not empty cc.attrs.size and cc.attrs.size ne 'base' ? 'ui-button-'.concat(cc.attrs.size) : ''} #{cc.attrs.outlined ? 'ui-button-outlined' : ''} #{cc.attrs.rounded ? 'ui-button-rounded' : ''} #{cc.attrs.raised ? 'ui-button-raised' : ''}"
style="#{cc.attrs.style}"
title="#{cc.attrs.title}"
widgetVar="#{cc.attrs.widgetVar}">

View File

@@ -3,53 +3,39 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
xmlns:p="http://primefaces.org/ui">
<!--
Freya TabView - Onglets avec style Freya
Usage:
<fr:tabView id="tabs" activeIndex="0">
<p:tab title="Général">
<p>Contenu onglet 1</p>
</p:tab>
<p:tab title="Détails">
<p>Contenu onglet 2</p>
</p:tab>
</fr:tabView>
═══════════════════════════════════════════════════════════
Lions.dev TabView - Onglets de navigation
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #27
Onglets de navigation avec icônes, barre active et styles Lions.dev.
-->
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="activeIndex" required="false" type="java.lang.Integer" default="0" />
<composite:attribute name="orientation" required="false" type="java.lang.String" default="top" />
<composite:attribute name="dynamic" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="cache" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="scrollable" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="orientation" required="false" type="java.lang.String" default="top" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="widgetVar" required="false" type="java.lang.String" />
<!-- Content insertion for tabs -->
<composite:clientBehavior name="tabChange" event="tabChange" targets="tabView" />
<composite:clientBehavior name="tabClose" event="tabClose" targets="tabView" />
<composite:insertChildren />
</composite:interface>
<composite:implementation>
<p:tabView id="tabView"
activeIndex="#{cc.attrs.activeIndex}"
orientation="#{cc.attrs.orientation}"
dynamic="#{cc.attrs.dynamic}"
cache="#{cc.attrs.cache}"
scrollable="#{cc.attrs.scrollable}"
orientation="#{cc.attrs.orientation}"
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}"
styleClass="#{cc.attrs.styleClass}"
widgetVar="#{cc.attrs.widgetVar}">
<!-- Tabs -->
<composite:insertChildren />
</p:tabView>
</composite:implementation>
</html>

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui">
<!--
═══════════════════════════════════════════════════════════
Lions.dev Toast - Notifications toast temporaires
═══════════════════════════════════════════════════════════
Version: 1.0.0 | Priority: CRITIQUE #19
Notifications toast modernes en coin d'écran avec fermeture automatique.
Styles Lions.dev avec gradients, ombres XL et animations fluides.
Usage:
<fr:toast id="toast" />
Exemples:
1. Toast basique (coin supérieur droit):
<fr:toast id="toast" />
2. Toast coin inférieur gauche avec durée personnalisée:
<fr:toast id="toast" position="bottom-left" life="5000" />
3. Toast sticky (ne se ferme pas automatiquement):
<fr:toast id="toast" sticky="true" />
4. Toast avec widgetVar pour contrôle JS:
<fr:toast id="toast" widgetVar="toastWidget" position="top-center" />
5. Toast pour un composant spécifique:
<fr:toast id="formToast" forId="userForm" position="bottom-right" />
Attributs disponibles:
- id: Identifiant du composant
- life: Durée d'affichage en ms (défaut: 3000ms = 3s)
- sticky: true/false - Ne pas fermer automatiquement (défaut: false)
- position: Position à l'écran (défaut: "top-right")
- "top-right" (défaut)
- "top-left"
- "bottom-right"
- "bottom-left"
- "top-center"
- "bottom-center"
- "center"
- showDetail: true/false - Afficher les détails (défaut: true)
- showSummary: true/false - Afficher le résumé (défaut: true)
- escape: true/false - Échapper le HTML (défaut: true)
- forId: ID du composant ciblé
- globalOnly: true/false - Messages globaux seulement (défaut: false)
- widgetVar: Variable JS pour contrôle programmatique
- styleClass: Classes CSS additionnelles
- style: Styles CSS inline
Ajout de toasts depuis le backing bean:
```java
// Toast info
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Information",
"Le traitement est en cours..."));
// Toast success
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Succès",
"Votre commande a été enregistrée avec succès."));
// Toast warning
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_WARN,
"Attention",
"Votre session expirera dans 5 minutes."));
// Toast error
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Erreur",
"Impossible de se connecter au serveur."));
```
Contrôle JavaScript (avec widgetVar):
```javascript
// Afficher un toast programmatiquement
PF('toastWidget').show({
severity: 'info',
summary: 'Notification',
detail: 'Message personnalisé',
life: 3000
});
// Effacer tous les toasts
PF('toastWidget').clear();
```
Types de severity:
- info: Notification d'information (bleu cyan)
- success: Opération réussie (vert)
- warn: Avertissement (orange)
- error: Erreur (rouge)
Positions disponibles:
- top-right: Coin supérieur droit (défaut) - Recommandé pour notifications
- top-left: Coin supérieur gauche
- bottom-right: Coin inférieur droit - Recommandé pour confirmations
- bottom-left: Coin inférieur gauche
- top-center: Centre en haut
- bottom-center: Centre en bas
- center: Centre de l'écran
Durées recommandées:
- Info courte: 2000-3000ms (2-3 secondes)
- Succès: 3000-4000ms (3-4 secondes)
- Warning: 5000-7000ms (5-7 secondes)
- Error: 7000-10000ms (7-10 secondes) ou sticky
- Sticky: Pour les messages critiques nécessitant une action
Notes:
- Empilage automatique de plusieurs toasts
- Animation fade-in/fade-out fluide
- Effet hover avec translateY
- Bordure gauche colorée selon severity
- Gradients subtils selon le type
- Ombres XL pour effet de profondeur
- Icônes automatiques selon severity
- Bouton fermer avec rotation au hover
- Responsive: pleine largeur sur mobile
- Support ARIA pour accessibilité
-->
<composite:interface>
<composite:attribute name="id" required="false" type="java.lang.String" />
<composite:attribute name="life" required="false" type="java.lang.Integer" default="3000" />
<composite:attribute name="sticky" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="position" required="false" type="java.lang.String" default="top-right" />
<composite:attribute name="showDetail" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="showSummary" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="escape" required="false" type="java.lang.Boolean" default="true" />
<composite:attribute name="forId" required="false" type="java.lang.String" />
<composite:attribute name="globalOnly" required="false" type="java.lang.Boolean" default="false" />
<composite:attribute name="widgetVar" required="false" type="java.lang.String" />
<composite:attribute name="styleClass" required="false" type="java.lang.String" />
<composite:attribute name="style" required="false" type="java.lang.String" />
</composite:interface>
<composite:implementation>
<p:toast id="toast"
life="#{cc.attrs.life}"
sticky="#{cc.attrs.sticky}"
position="#{cc.attrs.position}"
showDetail="#{cc.attrs.showDetail}"
showSummary="#{cc.attrs.showSummary}"
escape="#{cc.attrs.escape}"
for="#{cc.attrs.forId}"
globalOnly="#{cc.attrs.globalOnly}"
widgetVar="#{cc.attrs.widgetVar}"
styleClass="#{cc.attrs.styleClass}"
style="#{cc.attrs.style}" />
</composite:implementation>
</html>

View File

@@ -0,0 +1,570 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Mixins SCSS
* ═══════════════════════════════════════════════════════════
* Mixins réutilisables pour composants Lions.dev
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* ═══════════════════════════════════════════════════════════
*/
@import 'variables';
// ============================================
// 📱 RESPONSIVE BREAKPOINTS
// ============================================
@mixin lions-responsive-sm {
@media (min-width: $lions-breakpoint-sm) {
@content;
}
}
@mixin lions-responsive-md {
@media (min-width: $lions-breakpoint-md) {
@content;
}
}
@mixin lions-responsive-lg {
@media (min-width: $lions-breakpoint-lg) {
@content;
}
}
@mixin lions-responsive-xl {
@media (min-width: $lions-breakpoint-xl) {
@content;
}
}
@mixin lions-responsive-2xl {
@media (min-width: $lions-breakpoint-2xl) {
@content;
}
}
// Responsive custom breakpoint
@mixin lions-responsive-custom($breakpoint) {
@media (min-width: $breakpoint) {
@content;
}
}
// ============================================
// 🎨 GRADIENTS
// ============================================
@mixin lions-gradient-blue {
background: linear-gradient(135deg, $lions-blue-500 0%, $lions-blue-700 100%);
}
@mixin lions-gradient-gold {
background: linear-gradient(135deg, $lions-gold-500 0%, $lions-gold-700 100%);
}
@mixin lions-gradient-success {
background: linear-gradient(135deg, $lions-success-500 0%, $lions-success-700 100%);
}
@mixin lions-gradient-warning {
background: linear-gradient(135deg, $lions-warning-500 0%, $lions-warning-700 100%);
}
@mixin lions-gradient-danger {
background: linear-gradient(135deg, $lions-danger-500 0%, $lions-danger-700 100%);
}
@mixin lions-gradient-info {
background: linear-gradient(135deg, $lions-info-500 0%, $lions-info-700 100%);
}
// Gradient custom
@mixin lions-gradient-custom($color-start, $color-end, $angle: 135deg) {
background: linear-gradient($angle, $color-start 0%, $color-end 100%);
}
// ============================================
// 🎯 OMBRES
// ============================================
@mixin lions-shadow($level: 'base') {
@if $level == 'xs' {
box-shadow: $lions-shadow-xs;
} @else if $level == 'sm' {
box-shadow: $lions-shadow-sm;
} @else if $level == 'base' {
box-shadow: $lions-shadow-base;
} @else if $level == 'md' {
box-shadow: $lions-shadow-md;
} @else if $level == 'lg' {
box-shadow: $lions-shadow-lg;
} @else if $level == 'xl' {
box-shadow: $lions-shadow-xl;
} @else if $level == '2xl' {
box-shadow: $lions-shadow-2xl;
}
}
// Shadow colorée
@mixin lions-shadow-color($color, $opacity: 0.25) {
box-shadow: 0 4px 16px rgba($color, $opacity);
}
// ============================================
// 🔲 BORDER RADIUS
// ============================================
@mixin lions-rounded($size: 'base') {
@if $size == 'none' {
border-radius: $lions-radius-none;
} @else if $size == 'sm' {
border-radius: $lions-radius-sm;
} @else if $size == 'base' {
border-radius: $lions-radius-base;
} @else if $size == 'md' {
border-radius: $lions-radius-md;
} @else if $size == 'lg' {
border-radius: $lions-radius-lg;
} @else if $size == 'xl' {
border-radius: $lions-radius-xl;
} @else if $size == '2xl' {
border-radius: $lions-radius-2xl;
} @else if $size == 'full' {
border-radius: $lions-radius-full;
}
}
// ============================================
// 🌀 TRANSITIONS
// ============================================
@mixin lions-transition($properties: all, $duration: $lions-transition-base, $easing: $lions-ease-in-out) {
transition: $properties $duration $easing;
}
// ============================================
// ♿ FOCUS RING
// ============================================
@mixin lions-focus-ring($color: $lions-blue-500, $opacity: 0.4) {
outline: 3px solid rgba($color, $opacity);
outline-offset: 2px;
}
@mixin lions-focus-visible {
&:focus-visible {
@include lions-focus-ring();
}
}
// ============================================
// 📐 SIZING
// ============================================
@mixin lions-size($width, $height: $width) {
width: $width;
height: $height;
}
// Square
@mixin lions-square($size) {
@include lions-size($size, $size);
}
// Circle
@mixin lions-circle($size) {
@include lions-square($size);
border-radius: $lions-radius-full;
}
// ============================================
// 📏 SPACING
// ============================================
@mixin lions-padding($value) {
padding: $value;
}
@mixin lions-padding-x($value) {
padding-left: $value;
padding-right: $value;
}
@mixin lions-padding-y($value) {
padding-top: $value;
padding-bottom: $value;
}
@mixin lions-margin($value) {
margin: $value;
}
@mixin lions-margin-x($value) {
margin-left: $value;
margin-right: $value;
}
@mixin lions-margin-y($value) {
margin-top: $value;
margin-bottom: $value;
}
// ============================================
// 🔤 TYPOGRAPHY
// ============================================
@mixin lions-font-size($size: 'base') {
@if $size == 'xs' {
font-size: $lions-font-xs;
} @else if $size == 'sm' {
font-size: $lions-font-sm;
} @else if $size == 'base' {
font-size: $lions-font-base;
} @else if $size == 'lg' {
font-size: $lions-font-lg;
} @else if $size == 'xl' {
font-size: $lions-font-xl;
} @else if $size == '2xl' {
font-size: $lions-font-2xl;
} @else if $size == '3xl' {
font-size: $lions-font-3xl;
} @else if $size == '4xl' {
font-size: $lions-font-4xl;
} @else if $size == '5xl' {
font-size: $lions-font-5xl;
}
}
@mixin lions-font-weight($weight: 'regular') {
@if $weight == 'light' {
font-weight: $lions-font-light;
} @else if $weight == 'regular' {
font-weight: $lions-font-regular;
} @else if $weight == 'medium' {
font-weight: $lions-font-medium;
} @else if $weight == 'semibold' {
font-weight: $lions-font-semibold;
} @else if $weight == 'bold' {
font-weight: $lions-font-bold;
} @else if $weight == 'extrabold' {
font-weight: $lions-font-extrabold;
} @else if $weight == 'black' {
font-weight: $lions-font-black;
}
}
// Truncate text with ellipsis
@mixin lions-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Line clamp (multiline truncate)
@mixin lions-line-clamp($lines: 2) {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
// ============================================
// 🎨 BUTTON VARIANTS
// ============================================
@mixin lions-button-base {
display: inline-flex;
align-items: center;
justify-content: center;
gap: $lions-spacing-2;
font-family: $lions-font-family-primary;
@include lions-font-weight('semibold');
text-align: center;
text-decoration: none;
cursor: pointer;
user-select: none;
border: $lions-button-border-width solid transparent;
@include lions-rounded('base');
@include lions-transition();
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
}
@mixin lions-button-size($size: 'base') {
@if $size == 'sm' {
height: $lions-button-height-sm;
padding: 0 $lions-button-padding-x-sm;
@include lions-font-size('sm');
} @else if $size == 'base' {
height: $lions-button-height-base;
padding: 0 $lions-button-padding-x-base;
@include lions-font-size('base');
} @else if $size == 'lg' {
height: $lions-button-height-lg;
padding: 0 $lions-button-padding-x-lg;
@include lions-font-size('lg');
}
}
@mixin lions-button-solid($color: $lions-blue-500, $color-dark: $lions-blue-700) {
@include lions-gradient-custom($color, $color-dark);
color: $lions-white;
@include lions-shadow-color($color, 0.25);
&:hover {
transform: translateY(-2px);
@include lions-shadow-color($color, 0.35);
@include lions-gradient-custom(darken($color, 5%), darken($color-dark, 5%));
}
&:active {
transform: translateY(0);
@include lions-shadow-color($color, 0.2);
}
&:focus-visible {
@include lions-focus-ring($color);
}
}
@mixin lions-button-outlined($color: $lions-blue-500) {
background: transparent;
border-color: $color;
color: darken($color, 10%);
box-shadow: none;
&:hover {
background: rgba($color, 0.1);
border-color: darken($color, 10%);
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
background: rgba($color, 0.15);
}
&:focus-visible {
@include lions-focus-ring($color);
}
}
@mixin lions-button-text($color: $lions-blue-500) {
background: transparent;
border: none;
color: darken($color, 10%);
box-shadow: none;
&:hover {
background: rgba($color, 0.1);
}
&:active {
background: rgba($color, 0.15);
}
&:focus-visible {
@include lions-focus-ring($color);
}
}
// ============================================
// 📝 FORM FIELD VARIANTS
// ============================================
@mixin lions-input-base {
width: 100%;
font-family: $lions-font-family-primary;
@include lions-font-size('base');
color: $lions-text-primary;
background: $lions-white;
border: $lions-input-border-width solid $lions-gray-300;
@include lions-rounded('base');
@include lions-transition();
&::placeholder {
color: $lions-gray-400;
}
&:hover {
border-color: $lions-gray-400;
}
&:focus {
outline: none;
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
&:disabled {
background: $lions-gray-100;
cursor: not-allowed;
opacity: 0.6;
}
}
@mixin lions-input-size($size: 'base') {
@if $size == 'sm' {
height: $lions-input-height-sm;
padding: 0 $lions-spacing-3;
@include lions-font-size('sm');
} @else if $size == 'base' {
height: $lions-input-height-base;
padding: $lions-input-padding-y $lions-input-padding-x;
} @else if $size == 'lg' {
height: $lions-input-height-lg;
padding: $lions-spacing-4 $lions-spacing-5;
@include lions-font-size('lg');
}
}
@mixin lions-input-state-error {
border-color: $lions-danger-500;
box-shadow: 0 0 0 3px rgba($lions-danger-500, 0.15);
&:focus {
border-color: $lions-danger-500;
box-shadow: 0 0 0 3px rgba($lions-danger-500, 0.15);
}
}
@mixin lions-input-state-success {
border-color: $lions-success-500;
box-shadow: 0 0 0 3px rgba($lions-success-500, 0.15);
&:focus {
border-color: $lions-success-500;
box-shadow: 0 0 0 3px rgba($lions-success-500, 0.15);
}
}
// ============================================
// 🎴 CARD & PANEL
// ============================================
@mixin lions-card {
background: $lions-white;
border: 1px solid $lions-gray-200;
@include lions-rounded('md');
@include lions-shadow('base');
@include lions-padding($lions-card-padding);
@include lions-transition();
&:hover {
@include lions-shadow('md');
}
}
// ============================================
// 🔄 LOADING SPINNER
// ============================================
@mixin lions-spinner($size: 1rem, $color: $lions-blue-500) {
display: inline-block;
@include lions-size($size);
border: 2px solid rgba($color, 0.25);
border-top-color: $color;
border-radius: $lions-radius-full;
animation: lions-spin 0.6s linear infinite;
}
@keyframes lions-spin {
to {
transform: rotate(360deg);
}
}
// ============================================
// 📊 TABLE
// ============================================
@mixin lions-table-base {
width: 100%;
border-collapse: separate;
border-spacing: 0;
border: 1px solid $lions-gray-200;
@include lions-rounded('md');
overflow: hidden;
}
@mixin lions-table-header {
background: $lions-gray-100;
@include lions-font-weight('semibold');
color: $lions-text-primary;
padding: $lions-spacing-4;
border-bottom: 2px solid $lions-gray-300;
text-align: left;
}
@mixin lions-table-cell {
padding: $lions-spacing-4;
border-bottom: 1px solid $lions-gray-200;
color: $lions-text-primary;
}
@mixin lions-table-row-hover {
&:hover {
background: rgba($lions-blue-50, 0.5);
}
}
// ============================================
// 🎭 UTILITIES
// ============================================
// Visually hidden (screen reader only)
@mixin lions-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
// Reset list styles
@mixin lions-list-reset {
list-style: none;
padding: 0;
margin: 0;
}
// Clearfix
@mixin lions-clearfix {
&::after {
content: '';
display: table;
clear: both;
}
}
// Center element absolutely
@mixin lions-absolute-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// Flexbox center
@mixin lions-flex-center {
display: flex;
align-items: center;
justify-content: center;
}
// Grid center
@mixin lions-grid-center {
display: grid;
place-items: center;
}

View File

@@ -0,0 +1,273 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Variables SCSS
* ═══════════════════════════════════════════════════════════
* Toutes les variables de design système Lions.dev
* Basé sur la Charte Graphique Lions.dev v1.0.0
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* ═══════════════════════════════════════════════════════════
*/
// ============================================
// 🎨 PALETTE DE COULEURS LIONS.DEV
// ============================================
// 🔵 Lions Blue (Couleur signature)
$lions-blue-50: #E8F4FD !default;
$lions-blue-100: #C3E2FA !default;
$lions-blue-200: #9BCEF7 !default;
$lions-blue-300: #72BAF4 !default;
$lions-blue-400: #50AAF1 !default;
$lions-blue-500: #2D9BEF !default; // Base - Lions Blue
$lions-blue-600: #2889D6 !default;
$lions-blue-700: #2275BC !default;
$lions-blue-800: #1C61A3 !default;
$lions-blue-900: #164D89 !default;
// 🟠 Lions Gold (Couleur d'accentuation)
$lions-gold-50: #FFF9E6 !default;
$lions-gold-100: #FFEFB3 !default;
$lions-gold-200: #FFE580 !default;
$lions-gold-300: #FFDB4D !default;
$lions-gold-400: #FFD11A !default;
$lions-gold-500: #FFC700 !default; // Base - Lions Gold
$lions-gold-600: #E6B300 !default;
$lions-gold-700: #CC9F00 !default;
$lions-gold-800: #B38B00 !default;
$lions-gold-900: #997700 !default;
// ✅ Success (Vert Lions)
$lions-success-50: #E8F8F0 !default;
$lions-success-100: #C3ECD5 !default;
$lions-success-200: #9CE0BA !default;
$lions-success-300: #74D39F !default;
$lions-success-400: #4DC784 !default;
$lions-success-500: #22BB69 !default; // Base
$lions-success-600: #1FA85F !default;
$lions-success-700: #1B9555 !default;
$lions-success-800: #18824B !default;
$lions-success-900: #146F41 !default;
// ⚠️ Warning (Orange Lions)
$lions-warning-50: #FFF3E0 !default;
$lions-warning-100: #FFE0B3 !default;
$lions-warning-200: #FFCC80 !default;
$lions-warning-300: #FFB84D !default;
$lions-warning-400: #FFA826 !default;
$lions-warning-500: #FF9800 !default; // Base
$lions-warning-600: #F08A00 !default;
$lions-warning-700: #E07B00 !default;
$lions-warning-800: #D16D00 !default;
$lions-warning-900: #C15E00 !default;
// ❌ Danger (Rouge Lions)
$lions-danger-50: #FEEBEE !default;
$lions-danger-100: #FCCDD2 !default;
$lions-danger-200: #FAA6AF !default;
$lions-danger-300: #F87F8C !default;
$lions-danger-400: #F65870 !default;
$lions-danger-500: #F44336 !default; // Base
$lions-danger-600: #E93D31 !default;
$lions-danger-700: #DD342C !default;
$lions-danger-800: #D22C27 !default;
$lions-danger-900: #C41F1E !default;
// Info (Cyan Lions)
$lions-info-50: #E0F7FA !default;
$lions-info-100: #B3EBF3 !default;
$lions-info-200: #80DEEB !default;
$lions-info-300: #4DD1E3 !default;
$lions-info-400: #26C6DC !default;
$lions-info-500: #00BCD4 !default; // Base
$lions-info-600: #00AAC1 !default;
$lions-info-700: #0098AE !default;
$lions-info-800: #00869B !default;
$lions-info-900: #007488 !default;
// 🎨 Surface (Neutral)
$lions-white: #FFFFFF !default;
$lions-gray-50: #FAFBFC !default;
$lions-gray-100: #F4F6F8 !default;
$lions-gray-200: #E8ECEF !default;
$lions-gray-300: #D2D9DF !default;
$lions-gray-400: #B0B9C2 !default;
$lions-gray-500: #8995A1 !default; // Base
$lions-gray-600: #68737F !default;
$lions-gray-700: #4F5861 !default;
$lions-gray-800: #363E46 !default;
$lions-gray-900: #1F252B !default;
$lions-black: #0A0D0F !default;
// Text colors
$lions-text-primary: $lions-gray-900 !default;
$lions-text-secondary: $lions-gray-600 !default;
$lions-text-disabled: $lions-gray-400 !default;
$lions-text-inverse: $lions-white !default;
// ============================================
// 🔤 TYPOGRAPHIE
// ============================================
// Font families
$lions-font-family-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif !default;
$lions-font-family-headings: 'Inter', system-ui, -apple-system, sans-serif !default;
$lions-font-family-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace !default;
// Font sizes (Ratio 1.250 - Major Third)
$lions-font-xs: 0.750rem !default; // 12px
$lions-font-sm: 0.875rem !default; // 14px
$lions-font-base: 1.000rem !default; // 16px (base)
$lions-font-lg: 1.125rem !default; // 18px
$lions-font-xl: 1.250rem !default; // 20px
$lions-font-2xl: 1.563rem !default; // 25px
$lions-font-3xl: 1.953rem !default; // 31px
$lions-font-4xl: 2.441rem !default; // 39px
$lions-font-5xl: 3.052rem !default; // 49px
// Font weights
$lions-font-light: 300 !default;
$lions-font-regular: 400 !default;
$lions-font-medium: 500 !default;
$lions-font-semibold: 600 !default;
$lions-font-bold: 700 !default;
$lions-font-extrabold: 800 !default;
$lions-font-black: 900 !default;
// Line heights
$lions-line-height-tight: 1.25 !default;
$lions-line-height-normal: 1.5 !default;
$lions-line-height-relaxed: 1.75 !default;
$lions-line-height-loose: 2.0 !default;
// ============================================
// 📏 ESPACEMENTS (Système 4px)
// ============================================
$lions-spacing-0: 0 !default;
$lions-spacing-1: 0.25rem !default; // 4px
$lions-spacing-2: 0.5rem !default; // 8px
$lions-spacing-3: 0.75rem !default; // 12px
$lions-spacing-4: 1rem !default; // 16px (base)
$lions-spacing-5: 1.25rem !default; // 20px
$lions-spacing-6: 1.5rem !default; // 24px
$lions-spacing-8: 2rem !default; // 32px
$lions-spacing-10: 2.5rem !default; // 40px
$lions-spacing-12: 3rem !default; // 48px
$lions-spacing-16: 4rem !default; // 64px
$lions-spacing-20: 5rem !default; // 80px
$lions-spacing-24: 6rem !default; // 96px
// ============================================
// 🔲 COMPOSANTS - DIMENSIONS
// ============================================
// Boutons
$lions-button-height-sm: 2rem !default; // 32px
$lions-button-height-base: 2.5rem !default; // 40px
$lions-button-height-lg: 3rem !default; // 48px
$lions-button-padding-x-sm: 0.75rem !default; // 12px
$lions-button-padding-x-base: 1rem !default; // 16px
$lions-button-padding-x-lg: 1.5rem !default; // 24px
$lions-button-border-radius: 0.5rem !default; // 8px
$lions-button-border-width: 2px !default;
// Champs de formulaire
$lions-input-height-sm: 2rem !default; // 32px
$lions-input-height-base: 2.75rem !default; // 44px
$lions-input-height-lg: 3.25rem !default; // 52px
$lions-input-padding-x: 1rem !default; // 16px
$lions-input-padding-y: 0.75rem !default; // 12px
$lions-input-border-radius: 0.5rem !default; // 8px
$lions-input-border-width: 1px !default;
// Cards & Panels
$lions-card-padding: $lions-spacing-6 !default; // 24px
$lions-card-border-radius: 0.75rem !default; // 12px
$lions-card-border-width: 1px !default;
// Tables
$lions-table-row-padding: $lions-spacing-4 !default; // 16px
$lions-table-header-height: 3rem !default; // 48px
$lions-table-row-height: 2.75rem !default; // 44px
// ============================================
// 🌈 ANIMATIONS & TRANSITIONS
// ============================================
// Durées
$lions-transition-fast: 150ms !default;
$lions-transition-base: 250ms !default;
$lions-transition-slow: 350ms !default;
// Easing functions
$lions-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1) !default;
$lions-ease-out: cubic-bezier(0.0, 0, 0.2, 1) !default;
$lions-ease-in: cubic-bezier(0.4, 0, 1, 1) !default;
$lions-ease-smooth: cubic-bezier(0.25, 0.1, 0.25, 1) !default;
// ============================================
// 🎯 OMBRES (Elevation System)
// ============================================
$lions-shadow-xs: 0 1px 2px 0 rgba($lions-black, 0.05) !default;
$lions-shadow-sm: 0 1px 3px 0 rgba($lions-black, 0.1), 0 1px 2px 0 rgba($lions-black, 0.06) !default;
$lions-shadow-base: 0 4px 6px -1px rgba($lions-black, 0.1), 0 2px 4px -1px rgba($lions-black, 0.06) !default;
$lions-shadow-md: 0 10px 15px -3px rgba($lions-black, 0.1), 0 4px 6px -2px rgba($lions-black, 0.05) !default;
$lions-shadow-lg: 0 20px 25px -5px rgba($lions-black, 0.1), 0 10px 10px -5px rgba($lions-black, 0.04) !default;
$lions-shadow-xl: 0 25px 50px -12px rgba($lions-black, 0.25) !default;
$lions-shadow-2xl: 0 30px 60px -15px rgba($lions-black, 0.3) !default;
// Shadows colorées
$lions-shadow-blue: 0 4px 16px rgba($lions-blue-500, 0.25) !default;
$lions-shadow-gold: 0 4px 16px rgba($lions-gold-500, 0.25) !default;
// ============================================
// 🔄 BREAKPOINTS (Mobile-First)
// ============================================
$lions-breakpoint-xs: 0 !default;
$lions-breakpoint-sm: 576px !default;
$lions-breakpoint-md: 768px !default;
$lions-breakpoint-lg: 1024px !default;
$lions-breakpoint-xl: 1280px !default;
$lions-breakpoint-2xl: 1536px !default;
// ============================================
// 🎨 Z-INDEX LAYERS
// ============================================
$lions-z-dropdown: 1000 !default;
$lions-z-sticky: 1020 !default;
$lions-z-fixed: 1030 !default;
$lions-z-modal-backdrop: 1040 !default;
$lions-z-modal: 1050 !default;
$lions-z-popover: 1060 !default;
$lions-z-tooltip: 1070 !default;
// ============================================
// 🔲 BORDER RADIUS
// ============================================
$lions-radius-none: 0 !default;
$lions-radius-sm: 0.25rem !default; // 4px
$lions-radius-base: 0.5rem !default; // 8px
$lions-radius-md: 0.75rem !default; // 12px
$lions-radius-lg: 1rem !default; // 16px
$lions-radius-xl: 1.5rem !default; // 24px
$lions-radius-2xl: 2rem !default; // 32px
$lions-radius-full: 9999px !default; // Pill shape
// ============================================
// 📐 GRID SYSTEM
// ============================================
$lions-grid-columns: 12 !default;
$lions-grid-gutter-width: $lions-spacing-6 !default; // 24px
$lions-container-max-width-sm: 540px !default;
$lions-container-max-width-md: 720px !default;
$lions-container-max-width-lg: 960px !default;
$lions-container-max-width-xl: 1140px !default;
$lions-container-max-width-2xl: 1320px !default;

View File

@@ -0,0 +1,451 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Breadcrumb & Steps Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Breadcrumb et Steps (fil d'Ariane et étapes)
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: MOYENNE #37-38
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 🍞 BREADCRUMB STYLES
// ============================================
.ui-breadcrumb {
background: rgba($lions-gray-100, 0.5);
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
padding: $lions-spacing-3 $lions-spacing-5;
@include lions-shadow('sm');
.ui-breadcrumb-list {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: $lions-spacing-2;
list-style: none;
margin: 0;
padding: 0;
}
.ui-breadcrumb-item {
display: flex;
align-items: center;
gap: $lions-spacing-2;
.ui-menuitem-link {
display: inline-flex;
align-items: center;
gap: $lions-spacing-2;
padding: $lions-spacing-1 $lions-spacing-2;
@include lions-rounded('sm');
color: $lions-gray-600;
text-decoration: none;
@include lions-font-size('sm');
@include lions-font-weight('medium');
@include lions-transition();
.ui-menuitem-icon {
font-size: 1rem;
color: $lions-gray-500;
}
&:hover {
background: rgba($lions-blue-50, 0.6);
color: $lions-blue-700;
.ui-menuitem-icon {
color: $lions-blue-600;
}
}
&:focus-visible {
@include lions-focus-ring();
}
}
// Separator
.ui-breadcrumb-chevron {
color: $lions-gray-400;
font-size: 0.875rem;
@include lions-transition();
}
// Last item (current page)
&:last-child {
.ui-menuitem-link {
color: $lions-text-primary;
@include lions-font-weight('semibold');
pointer-events: none;
.ui-menuitem-icon {
color: $lions-blue-600;
}
}
}
// Home icon
&:first-child {
.ui-menuitem-link {
.ui-menuitem-icon {
font-size: 1.125rem;
}
}
}
}
}
// Compact breadcrumb
.ui-breadcrumb-compact {
padding: $lions-spacing-2 $lions-spacing-4;
.ui-menuitem-link {
padding: $lions-spacing-1;
@include lions-font-size('xs');
}
.ui-breadcrumb-chevron {
font-size: 0.75rem;
}
}
// ============================================
// 📊 STEPS STYLES
// ============================================
.ui-steps {
position: relative;
padding: $lions-spacing-4 0;
.ui-steps-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
position: relative;
}
.ui-steps-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
text-align: center;
// Connector line
&::before {
content: '';
position: absolute;
top: 1.25rem;
left: 50%;
right: -50%;
height: 2px;
background: $lions-gray-300;
z-index: 0;
@include lions-transition();
}
&:last-child::before {
display: none;
}
.ui-menuitem-link {
display: flex;
flex-direction: column;
align-items: center;
gap: $lions-spacing-3;
text-decoration: none;
position: relative;
z-index: 1;
@include lions-transition();
.ui-steps-number {
width: 2.5rem;
height: 2.5rem;
@include lions-rounded('full');
@include lions-flex-center;
background: $lions-white;
border: 2px solid $lions-gray-300;
color: $lions-gray-600;
@include lions-font-weight('semibold');
@include lions-font-size('base');
@include lions-shadow('sm');
@include lions-transition();
}
.ui-steps-title {
@include lions-font-size('sm');
@include lions-font-weight('medium');
color: $lions-gray-600;
@include lions-transition();
max-width: 150px;
}
&:hover {
.ui-steps-number {
border-color: $lions-blue-400;
transform: scale(1.05);
}
.ui-steps-title {
color: $lions-blue-700;
}
}
&:focus-visible {
.ui-steps-number {
@include lions-focus-ring();
}
}
}
// Completed state
&.ui-steps-completed {
&::before {
background: $lions-success-500;
}
.ui-menuitem-link {
.ui-steps-number {
@include lions-gradient-success;
border-color: $lions-success-600;
color: $lions-white;
.pi {
font-size: 1rem;
}
}
.ui-steps-title {
color: $lions-success-700;
@include lions-font-weight('semibold');
}
}
}
// Active/Current state
&.ui-steps-current {
.ui-menuitem-link {
.ui-steps-number {
@include lions-gradient-blue;
border-color: $lions-blue-600;
color: $lions-white;
@include lions-shadow('md');
box-shadow: 0 0 0 4px rgba($lions-blue-500, 0.2), $lions-shadow-md;
}
.ui-steps-title {
color: $lions-blue-700;
@include lions-font-weight('bold');
}
}
}
// Disabled/Future state
&.ui-steps-disabled {
.ui-menuitem-link {
pointer-events: none;
.ui-steps-number {
background: $lions-gray-100;
border-color: $lions-gray-300;
color: $lions-gray-400;
}
.ui-steps-title {
color: $lions-gray-400;
}
}
}
}
// Vertical steps
&.ui-steps-vertical {
.ui-steps-list {
flex-direction: column;
align-items: flex-start;
}
.ui-steps-item {
flex-direction: row;
align-items: flex-start;
text-align: left;
width: 100%;
padding: $lions-spacing-4 0;
&::before {
top: 3.5rem;
bottom: -$lions-spacing-4;
left: 1.25rem;
right: auto;
width: 2px;
height: auto;
}
&:last-child {
padding-bottom: 0;
&::before {
display: none;
}
}
.ui-menuitem-link {
flex-direction: row;
align-items: center;
gap: $lions-spacing-4;
}
.ui-steps-title {
text-align: left;
max-width: none;
}
}
}
// Readonly steps
&.ui-steps-readonly {
.ui-steps-item .ui-menuitem-link {
pointer-events: none;
cursor: default;
}
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-breadcrumb {
.ui-breadcrumb-list {
gap: $lions-spacing-1;
}
.ui-menuitem-link {
padding: $lions-spacing-1;
@include lions-font-size('xs');
}
.ui-breadcrumb-chevron {
font-size: 0.75rem;
}
}
.ui-steps {
// Switch to vertical on mobile
.ui-steps-list {
flex-direction: column;
align-items: flex-start;
}
.ui-steps-item {
flex-direction: row;
align-items: flex-start;
text-align: left;
width: 100%;
padding: $lions-spacing-3 0;
&::before {
top: 3rem;
bottom: 0;
left: 1.25rem;
right: auto;
width: 2px;
height: calc(100% - 2.5rem);
}
&:last-child::before {
display: none;
}
.ui-menuitem-link {
flex-direction: row;
align-items: center;
gap: $lions-spacing-3;
}
.ui-steps-number {
width: 2rem;
height: 2rem;
@include lions-font-size('sm');
}
.ui-steps-title {
text-align: left;
max-width: none;
@include lions-font-size('xs');
}
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-breadcrumb,
.ui-steps {
// Focus visible
.ui-menuitem-link:focus-visible {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
.ui-menuitem-link,
.ui-steps-number {
border-width: 2px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
}
// ============================================
// 🎨 UTILITY CLASSES
// ============================================
// Large steps
.ui-steps-lg {
.ui-steps-number {
width: 3rem !important;
height: 3rem !important;
@include lions-font-size('lg');
}
.ui-steps-title {
@include lions-font-size('base');
}
}
// Compact steps
.ui-steps-sm {
padding: $lions-spacing-2 0;
.ui-steps-number {
width: 2rem !important;
height: 2rem !important;
@include lions-font-size('sm');
}
.ui-steps-title {
@include lions-font-size('xs');
}
}

View File

@@ -0,0 +1,442 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Button Component Styles
* ═══════════════════════════════════════════════════════════
* Amélioration visuelle complète des boutons avec identité Lions.dev
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #1
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 🎯 BASE BUTTON STYLES
// ============================================
.ui-button {
@include lions-button-base;
@include lions-button-size('base');
@include lions-button-solid($lions-blue-500, $lions-blue-700);
// Override PrimeFaces defaults
font-family: $lions-font-family-primary !important;
// Icon spacing
.ui-button-icon-left {
margin-right: $lions-spacing-2;
}
.ui-button-icon-right {
margin-left: $lions-spacing-2;
}
}
// ============================================
// 🎨 SEVERITY VARIANTS (Solid buttons)
// ============================================
// Primary (Lions Blue) - Default
.ui-button-primary,
.ui-button:not([class*="ui-button-"]) {
@include lions-button-solid($lions-blue-500, $lions-blue-700);
}
// Secondary (Gray)
.ui-button-secondary {
@include lions-button-solid($lions-gray-500, $lions-gray-700);
}
// Success (Green)
.ui-button-success {
@include lions-button-solid($lions-success-500, $lions-success-700);
}
// Info (Cyan)
.ui-button-info {
@include lions-button-solid($lions-info-500, $lions-info-700);
}
// Warning (Orange)
.ui-button-warning {
@include lions-button-solid($lions-warning-500, $lions-warning-700);
}
// Help (Lions Gold - Premium)
.ui-button-help {
@include lions-button-solid($lions-gold-500, $lions-gold-700);
// Extra visual impact for premium actions
font-weight: $lions-font-bold;
letter-spacing: 0.025em;
}
// Danger (Red)
.ui-button-danger {
@include lions-button-solid($lions-danger-500, $lions-danger-700);
}
// ============================================
// 📝 OUTLINED VARIANT
// ============================================
.ui-button.ui-button-outlined {
&.ui-button-primary,
&:not([class*="ui-button-"]) {
@include lions-button-outlined($lions-blue-500);
}
&.ui-button-secondary {
@include lions-button-outlined($lions-gray-600);
}
&.ui-button-success {
@include lions-button-outlined($lions-success-500);
}
&.ui-button-info {
@include lions-button-outlined($lions-info-500);
}
&.ui-button-warning {
@include lions-button-outlined($lions-warning-500);
}
&.ui-button-help {
@include lions-button-outlined($lions-gold-600);
}
&.ui-button-danger {
@include lions-button-outlined($lions-danger-500);
}
}
// ============================================
// 🔗 TEXT VARIANT
// ============================================
.ui-button.ui-button-text {
&.ui-button-primary,
&:not([class*="ui-button-"]) {
@include lions-button-text($lions-blue-500);
}
&.ui-button-secondary {
@include lions-button-text($lions-gray-600);
}
&.ui-button-success {
@include lions-button-text($lions-success-500);
}
&.ui-button-info {
@include lions-button-text($lions-info-500);
}
&.ui-button-warning {
@include lions-button-text($lions-warning-500);
}
&.ui-button-help {
@include lions-button-text($lions-gold-600);
}
&.ui-button-danger {
@include lions-button-text($lions-danger-500);
}
}
// ============================================
// 🔗 LINK VARIANT (Text sans background hover)
// ============================================
.ui-button.ui-button-link {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
height: auto !important;
color: $lions-blue-500;
text-decoration: underline;
text-underline-offset: 2px;
&:hover {
color: $lions-blue-700;
transform: none;
text-decoration: underline;
}
&:active {
color: $lions-blue-800;
transform: none;
}
&:focus-visible {
@include lions-focus-ring($lions-blue-500);
}
// Severity variants for link
&.ui-button-secondary {
color: $lions-gray-700;
&:hover { color: $lions-gray-900; }
}
&.ui-button-success {
color: $lions-success-500;
&:hover { color: $lions-success-700; }
}
&.ui-button-danger {
color: $lions-danger-500;
&:hover { color: $lions-danger-700; }
}
}
// ============================================
// 🔵 ROUNDED VARIANT (Pill shape)
// ============================================
.ui-button.ui-button-rounded {
border-radius: $lions-radius-full !important;
padding-left: $lions-spacing-6 !important;
padding-right: $lions-spacing-6 !important;
}
// ============================================
// 📐 RAISED VARIANT (Elevated)
// ============================================
.ui-button.ui-button-raised {
@include lions-shadow('lg');
&:hover {
@include lions-shadow('xl');
transform: translateY(-3px);
}
&:active {
@include lions-shadow('md');
transform: translateY(-1px);
}
}
// ============================================
// 📏 SIZE VARIANTS
// ============================================
.ui-button {
// Small buttons
&.ui-button-sm,
&[data-size="sm"] {
@include lions-button-size('sm');
.ui-button-icon-left {
margin-right: $lions-spacing-1;
}
.ui-button-icon-right {
margin-left: $lions-spacing-1;
}
}
// Large buttons
&.ui-button-lg,
&[data-size="lg"] {
@include lions-button-size('lg');
.ui-button-icon-left {
margin-right: $lions-spacing-3;
}
.ui-button-icon-right {
margin-left: $lions-spacing-3;
}
}
}
// ============================================
// 🔄 LOADING STATE
// ============================================
.ui-button {
&.ui-state-loading,
&[aria-busy="true"] {
position: relative;
pointer-events: none;
color: transparent !important;
&::after {
content: '';
@include lions-spinner(1rem, $lions-white);
@include lions-absolute-center;
}
}
// Loading state for outlined/text buttons
&.ui-button-outlined,
&.ui-button-text {
&.ui-state-loading,
&[aria-busy="true"] {
&::after {
border-top-color: currentColor;
border-color: rgba(currentColor, 0.25);
}
}
}
}
// ============================================
// ❌ DISABLED STATE
// ============================================
.ui-button {
&:disabled,
&.ui-state-disabled {
opacity: 0.5 !important;
cursor: not-allowed !important;
pointer-events: none !important;
transform: none !important;
box-shadow: none !important;
}
}
// ============================================
// 🎖️ BADGE SUPPORT
// ============================================
.ui-button {
.ui-badge {
position: absolute;
top: -$lions-spacing-2;
right: -$lions-spacing-2;
min-width: $lions-spacing-5;
height: $lions-spacing-5;
line-height: $lions-spacing-5;
border-radius: $lions-radius-full;
background: $lions-danger-500;
color: $lions-white;
font-size: $lions-font-xs;
font-weight: $lions-font-bold;
padding: 0 $lions-spacing-1;
&.ui-badge-success {
background: $lions-success-500;
}
&.ui-badge-warning {
background: $lions-warning-500;
}
&.ui-badge-info {
background: $lions-info-500;
}
}
}
// ============================================
// 🎨 ICON-ONLY BUTTONS (Square)
// ============================================
.ui-button {
&.ui-button-icon-only {
width: $lions-button-height-base;
padding: 0 !important;
@include lions-flex-center;
&.ui-button-sm,
&[data-size="sm"] {
width: $lions-button-height-sm;
}
&.ui-button-lg,
&[data-size="lg"] {
width: $lions-button-height-lg;
}
// Rounded icon-only buttons become circles
&.ui-button-rounded {
border-radius: $lions-radius-full !important;
}
}
}
// ============================================
// 🎯 BUTTON GROUP SUPPORT
// ============================================
.ui-buttonset {
display: inline-flex;
gap: 0;
.ui-button {
border-radius: 0;
margin-left: -1px;
&:first-child {
margin-left: 0;
border-top-left-radius: $lions-radius-base;
border-bottom-left-radius: $lions-radius-base;
}
&:last-child {
border-top-right-radius: $lions-radius-base;
border-bottom-right-radius: $lions-radius-base;
}
&:hover,
&:focus {
z-index: 1;
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-button {
// Ensure focus ring is always visible
&:focus-visible {
@include lions-focus-ring();
}
// High contrast mode support
@media (prefers-contrast: high) {
border-width: 2px !important;
}
// Reduced motion support
@media (prefers-reduced-motion: reduce) {
transition: none !important;
&:hover {
transform: none !important;
}
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
// Touch-friendly on mobile
@media (hover: none) and (pointer: coarse) {
.ui-button {
min-height: 44px; // iOS touch target minimum
&.ui-button-sm {
min-height: 40px;
}
}
}
// Smaller spacing on mobile
@include lions-responsive-custom($lions-breakpoint-md) {
.ui-buttonset {
gap: $lions-spacing-1;
}
}

View File

@@ -0,0 +1,509 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Card & Panel Component Styles
* ═══════════════════════════════════════════════════════════
* Amélioration visuelle complète des cartes et panneaux avec identité Lions.dev
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #9-10
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 🎴 CARD BASE STYLES
// ============================================
.card {
@include lions-card;
@include lions-transition();
// Card with border accent
&.card-border-top {
border-top: 4px solid $lions-blue-500;
}
&.card-border-left {
border-left: 4px solid $lions-blue-500;
}
// Severity colors for border
&.card-primary {
border-top-color: $lions-blue-500;
border-left-color: $lions-blue-500;
}
&.card-success {
border-top-color: $lions-success-500;
border-left-color: $lions-success-500;
}
&.card-info {
border-top-color: $lions-info-500;
border-left-color: $lions-info-500;
}
&.card-warning {
border-top-color: $lions-warning-500;
border-left-color: $lions-warning-500;
}
&.card-danger {
border-top-color: $lions-danger-500;
border-left-color: $lions-danger-500;
}
&.card-help {
border-top-color: $lions-gold-500;
border-left-color: $lions-gold-500;
}
// Hover effect
&.card-hover:hover {
@include lions-shadow('lg');
transform: translateY(-2px);
cursor: pointer;
}
// Clickable card
&.card-clickable {
cursor: pointer;
@include lions-transition();
&:hover {
@include lions-shadow('lg');
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
}
}
// ============================================
// 📋 CARD HEADER
// ============================================
.card-header {
padding: $lions-spacing-4 $lions-spacing-6;
border-bottom: 1px solid $lions-gray-200;
background: $lions-gray-50;
@include lions-rounded('md');
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
h5, h6 {
margin: 0;
@include lions-font-weight('bold');
color: $lions-text-primary;
}
.card-title {
@include lions-font-size('lg');
@include lions-font-weight('bold');
color: $lions-text-primary;
margin: 0;
}
.card-subtitle {
@include lions-font-size('sm');
color: $lions-text-secondary;
margin: $lions-spacing-1 0 0 0;
}
// Icon in header
.card-icon {
font-size: 1.5rem;
color: $lions-blue-500;
margin-right: $lions-spacing-3;
}
// Actions in header (buttons, etc.)
.card-actions {
display: flex;
gap: $lions-spacing-2;
align-items: center;
}
}
// ============================================
// 📄 CARD CONTENT
// ============================================
.card-content {
padding: $lions-spacing-6;
color: $lions-text-primary;
line-height: $lions-line-height-normal;
p:last-child {
margin-bottom: 0;
}
}
// Compact card content
.card-compact .card-content {
padding: $lions-spacing-4;
}
// Large card content
.card-large .card-content {
padding: $lions-spacing-8;
}
// ============================================
// 🦶 CARD FOOTER
// ============================================
.card-footer {
padding: $lions-spacing-4 $lions-spacing-6;
border-top: 1px solid $lions-gray-200;
background: $lions-gray-50;
@include lions-rounded('md');
border-top-left-radius: 0;
border-top-right-radius: 0;
// Actions in footer
display: flex;
justify-content: space-between;
align-items: center;
gap: $lions-spacing-2;
}
// ============================================
// 📦 PANEL STYLES (Collapsible Card)
// ============================================
.ui-panel {
@include lions-card;
@include lions-transition();
.ui-panel-titlebar {
background: $lions-gray-50;
border-bottom: 1px solid $lions-gray-200;
padding: $lions-spacing-4 $lions-spacing-6;
@include lions-rounded('md');
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
display: flex;
justify-content: space-between;
align-items: center;
.ui-panel-title {
@include lions-font-size('lg');
@include lions-font-weight('bold');
color: $lions-text-primary;
margin: 0;
display: flex;
align-items: center;
gap: $lions-spacing-2;
// Icon support
i {
font-size: 1.25rem;
color: $lions-blue-500;
}
}
.ui-panel-titlebar-icon {
@include lions-button-base;
width: 2rem;
height: 2rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
color: $lions-gray-600;
@include lions-transition();
&:hover {
background: $lions-gray-200;
color: $lions-blue-500;
}
&:focus-visible {
@include lions-focus-ring();
}
}
}
.ui-panel-content {
padding: $lions-spacing-6;
background: $lions-white;
@include lions-rounded('md');
border-top-left-radius: 0;
border-top-right-radius: 0;
}
// Panel states
&.ui-panel-collapsed {
.ui-panel-content {
display: none;
}
.ui-panel-titlebar {
@include lions-rounded('md');
}
}
}
// ============================================
// 🎨 PANEL SEVERITY VARIANTS
// ============================================
.ui-panel-primary {
.ui-panel-titlebar {
background: linear-gradient(135deg, rgba($lions-blue-500, 0.1) 0%, rgba($lions-blue-700, 0.05) 100%);
border-bottom-color: $lions-blue-200;
.ui-panel-title i {
color: $lions-blue-500;
}
}
}
.ui-panel-success {
.ui-panel-titlebar {
background: linear-gradient(135deg, rgba($lions-success-500, 0.1) 0%, rgba($lions-success-700, 0.05) 100%);
border-bottom-color: $lions-success-200;
.ui-panel-title i {
color: $lions-success-500;
}
}
}
.ui-panel-info {
.ui-panel-titlebar {
background: linear-gradient(135deg, rgba($lions-info-500, 0.1) 0%, rgba($lions-info-700, 0.05) 100%);
border-bottom-color: $lions-info-200;
.ui-panel-title i {
color: $lions-info-500;
}
}
}
.ui-panel-warning {
.ui-panel-titlebar {
background: linear-gradient(135deg, rgba($lions-warning-500, 0.1) 0%, rgba($lions-warning-700, 0.05) 100%);
border-bottom-color: $lions-warning-200;
.ui-panel-title i {
color: $lions-warning-500;
}
}
}
.ui-panel-danger {
.ui-panel-titlebar {
background: linear-gradient(135deg, rgba($lions-danger-500, 0.1) 0%, rgba($lions-danger-700, 0.05) 100%);
border-bottom-color: $lions-danger-200;
.ui-panel-title i {
color: $lions-danger-500;
}
}
}
.ui-panel-help {
.ui-panel-titlebar {
background: linear-gradient(135deg, rgba($lions-gold-500, 0.15) 0%, rgba($lions-gold-700, 0.08) 100%);
border-bottom-color: $lions-gold-200;
.ui-panel-title i {
color: $lions-gold-600;
}
}
}
// ============================================
// 📐 CARD SIZES
// ============================================
.card-compact {
padding: $lions-spacing-4;
.card-header {
padding: $lions-spacing-3 $lions-spacing-4;
}
.card-content {
padding: $lions-spacing-4;
}
.card-footer {
padding: $lions-spacing-3 $lions-spacing-4;
}
}
.card-large {
padding: $lions-spacing-8;
.card-header {
padding: $lions-spacing-6 $lions-spacing-8;
}
.card-content {
padding: $lions-spacing-8;
}
.card-footer {
padding: $lions-spacing-6 $lions-spacing-8;
}
}
// ============================================
// 🎯 SPECIAL CARD VARIANTS
// ============================================
// Outlined card (no fill)
.card-outlined {
background: transparent;
border: 2px solid $lions-gray-300;
box-shadow: none;
&:hover {
border-color: $lions-blue-500;
@include lions-shadow('sm');
}
}
// Elevated card (more shadow)
.card-elevated {
@include lions-shadow('xl');
&:hover {
@include lions-shadow('2xl');
}
}
// Flat card (no shadow)
.card-flat {
box-shadow: none;
border: 1px solid $lions-gray-200;
}
// Gradient card backgrounds
.card-gradient-blue {
background: linear-gradient(135deg, rgba($lions-blue-500, 0.05) 0%, rgba($lions-blue-700, 0.02) 100%);
}
.card-gradient-gold {
background: linear-gradient(135deg, rgba($lions-gold-500, 0.08) 0%, rgba($lions-gold-700, 0.03) 100%);
}
.card-gradient-success {
background: linear-gradient(135deg, rgba($lions-success-500, 0.05) 0%, rgba($lions-success-700, 0.02) 100%);
}
// ============================================
// 📊 CARD GRID / LIST
// ============================================
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: $lions-spacing-6;
// Responsive
@media (max-width: $lions-breakpoint-md) {
grid-template-columns: 1fr;
gap: $lions-spacing-4;
}
}
.card-list {
display: flex;
flex-direction: column;
gap: $lions-spacing-4;
}
// ============================================
// 🖼️ CARD WITH IMAGE
// ============================================
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
@include lions-rounded('md');
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.card-image-container {
position: relative;
overflow: hidden;
@include lions-rounded('md');
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
img {
width: 100%;
height: 200px;
object-fit: cover;
@include lions-transition();
}
&:hover img {
transform: scale(1.05);
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.card {
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
transition: none !important;
&:hover {
transform: none !important;
}
}
}
.ui-panel {
@media (prefers-reduced-motion: reduce) {
.ui-panel-content {
transition: none !important;
}
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.card,
.ui-panel {
.card-content,
.ui-panel-content {
padding: $lions-spacing-4;
}
.card-header,
.ui-panel-titlebar {
padding: $lions-spacing-3 $lions-spacing-4;
}
.card-footer {
padding: $lions-spacing-3 $lions-spacing-4;
flex-direction: column;
align-items: stretch;
}
}
}

View File

@@ -0,0 +1,662 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Chart Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Charts (visualisation de données avec Chart.js)
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: MOYENNE #44
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 📊 CHART CONTAINER STYLES
// ============================================
.ui-chart {
position: relative;
display: block;
width: 100%;
@include lions-rounded('lg');
background: $lions-white;
padding: $lions-spacing-5;
border: 1px solid $lions-gray-200;
@include lions-shadow('sm');
// Canvas element
canvas {
max-width: 100%;
height: auto !important;
}
// Chart title
.ui-chart-title {
@include lions-font-size('lg');
@include lions-font-weight('bold');
color: $lions-text-primary;
margin-bottom: $lions-spacing-4;
text-align: center;
}
// Chart subtitle
.ui-chart-subtitle {
@include lions-font-size('sm');
@include lions-font-weight('medium');
color: $lions-gray-600;
margin-bottom: $lions-spacing-3;
text-align: center;
}
// Loading state
&.ui-chart-loading {
position: relative;
min-height: 300px;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba($lions-white, 0.9);
@include lions-flex-center;
z-index: 10;
}
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 3rem;
height: 3rem;
border: 3px solid $lions-gray-200;
border-top-color: $lions-blue-500;
border-radius: 50%;
animation: lions-chart-spin 1s linear infinite;
z-index: 11;
}
}
// Size variants
&.ui-chart-sm {
padding: $lions-spacing-3;
canvas {
max-height: 200px;
}
}
&.ui-chart-lg {
padding: $lions-spacing-6;
canvas {
min-height: 400px;
}
}
// Borderless variant
&.ui-chart-borderless {
border: none;
box-shadow: none;
padding: 0;
}
}
// ============================================
// 🎨 LIONS.DEV COLOR PALETTE FOR CHARTS
// ============================================
// Palette de couleurs Lions.dev pour les datasets
$lions-chart-colors: (
primary: $lions-blue-500,
secondary: $lions-gray-500,
success: $lions-success-500,
info: $lions-info-500,
warning: $lions-warning-500,
danger: $lions-danger-500,
gold: $lions-gold-500,
purple: #9333EA,
pink: #EC4899,
teal: #14B8A6,
indigo: #6366F1,
orange: #F97316
);
// Couleurs avec transparence pour les zones (area charts)
$lions-chart-colors-alpha: (
primary: rgba($lions-blue-500, 0.2),
secondary: rgba($lions-gray-500, 0.2),
success: rgba($lions-success-500, 0.2),
info: rgba($lions-info-500, 0.2),
warning: rgba($lions-warning-500, 0.2),
danger: rgba($lions-danger-500, 0.2),
gold: rgba($lions-gold-500, 0.2),
purple: rgba(#9333EA, 0.2),
pink: rgba(#EC4899, 0.2),
teal: rgba(#14B8A6, 0.2),
indigo: rgba(#6366F1, 0.2),
orange: rgba(#F97316, 0.2)
);
// ============================================
// 🏷️ CHART LEGEND STYLES
// ============================================
.chartjs-legend {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: $lions-spacing-3;
margin-top: $lions-spacing-4;
padding: $lions-spacing-3;
background: $lions-gray-50;
@include lions-rounded('md');
ul {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: $lions-spacing-3;
list-style: none;
margin: 0;
padding: 0;
}
li {
display: flex;
align-items: center;
gap: $lions-spacing-2;
cursor: pointer;
@include lions-transition();
&:hover {
opacity: 0.8;
}
&.hidden {
opacity: 0.4;
text-decoration: line-through;
}
span {
@include lions-font-size('sm');
@include lions-font-weight('medium');
color: $lions-text-primary;
}
.legend-color {
width: 1rem;
height: 1rem;
@include lions-rounded('sm');
border: 2px solid transparent;
@include lions-transition();
&:hover {
border-color: $lions-gray-400;
}
}
}
}
// ============================================
// 💬 CHART TOOLTIP STYLES
// ============================================
.chartjs-tooltip {
position: absolute;
background: rgba($lions-gray-900, 0.95);
color: $lions-white;
@include lions-rounded('md');
@include lions-shadow('lg');
padding: $lions-spacing-3 $lions-spacing-4;
pointer-events: none;
z-index: 1000;
@include lions-transition();
opacity: 0;
transform: translateY(10px);
&.active {
opacity: 1;
transform: translateY(0);
}
.tooltip-title {
@include lions-font-size('sm');
@include lions-font-weight('bold');
color: $lions-white;
margin-bottom: $lions-spacing-2;
padding-bottom: $lions-spacing-2;
border-bottom: 1px solid rgba($lions-white, 0.2);
}
.tooltip-body {
@include lions-font-size('sm');
color: $lions-white;
.tooltip-item {
display: flex;
align-items: center;
gap: $lions-spacing-2;
margin-bottom: $lions-spacing-1;
&:last-child {
margin-bottom: 0;
}
.tooltip-color {
width: 0.75rem;
height: 0.75rem;
@include lions-rounded('sm');
flex-shrink: 0;
}
.tooltip-label {
@include lions-font-weight('medium');
color: rgba($lions-white, 0.9);
}
.tooltip-value {
@include lions-font-weight('bold');
color: $lions-white;
margin-left: auto;
}
}
}
}
// ============================================
// 📈 CHART TYPE SPECIFIC STYLES
// ============================================
// Line Chart
.ui-chart-line {
canvas {
min-height: 300px;
}
}
// Bar Chart
.ui-chart-bar {
canvas {
min-height: 300px;
}
}
// Pie/Doughnut Chart
.ui-chart-pie,
.ui-chart-doughnut {
canvas {
max-width: 400px;
margin: 0 auto;
}
.chartjs-legend {
margin-top: $lions-spacing-5;
}
}
// Radar Chart
.ui-chart-radar {
canvas {
max-width: 400px;
margin: 0 auto;
}
}
// Polar Area Chart
.ui-chart-polararea {
canvas {
max-width: 400px;
margin: 0 auto;
}
}
// ============================================
// 📊 CHART WRAPPER & GRID
// ============================================
.ui-chart-wrapper {
position: relative;
display: block;
width: 100%;
// Chart header avec actions
.ui-chart-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $lions-spacing-4;
padding-bottom: $lions-spacing-3;
border-bottom: 1px solid $lions-gray-200;
.ui-chart-title {
@include lions-font-size('lg');
@include lions-font-weight('bold');
color: $lions-text-primary;
margin: 0;
}
.ui-chart-actions {
display: flex;
gap: $lions-spacing-2;
button {
@include lions-button-base;
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-rounded('md');
background: $lions-gray-100;
border: 1px solid $lions-gray-300;
color: $lions-gray-700;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-gray-200;
border-color: $lions-gray-400;
}
i {
font-size: 1rem;
}
}
}
}
// Chart footer avec légende ou infos
.ui-chart-footer {
margin-top: $lions-spacing-4;
padding-top: $lions-spacing-3;
border-top: 1px solid $lions-gray-200;
@include lions-font-size('sm');
color: $lions-gray-600;
text-align: center;
}
}
// Chart Grid (multiple charts)
.ui-chart-grid {
display: grid;
gap: $lions-spacing-5;
&.ui-chart-grid-2 {
grid-template-columns: repeat(2, 1fr);
}
&.ui-chart-grid-3 {
grid-template-columns: repeat(3, 1fr);
}
&.ui-chart-grid-4 {
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: $lions-breakpoint-lg) {
&.ui-chart-grid-3,
&.ui-chart-grid-4 {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: $lions-breakpoint-md) {
&.ui-chart-grid-2,
&.ui-chart-grid-3,
&.ui-chart-grid-4 {
grid-template-columns: 1fr;
}
}
}
// ============================================
// 🎬 ANIMATIONS
// ============================================
@keyframes lions-chart-spin {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
@keyframes lions-chart-fade-in {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.ui-chart {
animation: lions-chart-fade-in 0.4s ease-out;
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-chart {
padding: $lions-spacing-4;
canvas {
min-height: 250px !important;
}
&.ui-chart-sm canvas {
max-height: 180px;
}
&.ui-chart-lg canvas {
min-height: 300px !important;
}
}
.ui-chart-pie,
.ui-chart-doughnut,
.ui-chart-radar,
.ui-chart-polararea {
canvas {
max-width: 100% !important;
}
}
.chartjs-legend {
padding: $lions-spacing-2;
ul {
gap: $lions-spacing-2;
}
li {
@include lions-font-size('xs');
.legend-color {
width: 0.875rem;
height: 0.875rem;
}
}
}
.ui-chart-wrapper {
.ui-chart-header {
flex-direction: column;
align-items: flex-start;
gap: $lions-spacing-3;
.ui-chart-actions {
width: 100%;
justify-content: flex-end;
}
}
}
}
@media (max-width: $lions-breakpoint-sm) {
.ui-chart {
padding: $lions-spacing-3;
canvas {
min-height: 200px !important;
}
}
.chartjs-tooltip {
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-font-size('xs');
.tooltip-title {
@include lions-font-size('xs');
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-chart {
// Focus visible for interactive elements
button:focus-visible {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
.chartjs-legend li {
border: 2px solid currentColor;
padding: $lions-spacing-1 $lions-spacing-2;
@include lions-rounded('md');
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
animation: none !important;
* {
transition: none !important;
animation: none !important;
}
}
// Screen reader only text for data
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
}
// ============================================
// 🎨 UTILITY CLASSES
// ============================================
// Chart with gradient background
.ui-chart-gradient {
background: linear-gradient(135deg, rgba($lions-blue-50, 0.5) 0%, rgba($lions-white, 1) 100%);
}
// Chart with dark theme
.ui-chart-dark {
background: $lions-gray-900;
border-color: $lions-gray-700;
.ui-chart-title,
.ui-chart-subtitle {
color: $lions-white;
}
.ui-chart-header {
border-bottom-color: $lions-gray-700;
.ui-chart-title {
color: $lions-white;
}
}
.ui-chart-footer {
border-top-color: $lions-gray-700;
color: $lions-gray-400;
}
.chartjs-legend {
background: rgba($lions-gray-800, 0.5);
li span {
color: $lions-white;
}
}
}
// Chart full height
.ui-chart-fullheight {
height: 100%;
canvas {
height: 100% !important;
}
}
// Chart with hover effect
.ui-chart-hoverable {
@include lions-transition();
&:hover {
@include lions-shadow('md');
transform: translateY(-2px);
}
}
// Chart export button
.ui-chart-export {
position: absolute;
top: $lions-spacing-3;
right: $lions-spacing-3;
button {
@include lions-button-base;
width: 2rem;
height: 2rem;
min-width: 2rem;
padding: 0;
@include lions-flex-center;
background: $lions-white;
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('sm');
color: $lions-gray-600;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-blue-50;
border-color: $lions-blue-300;
color: $lions-blue-600;
@include lions-shadow('md');
}
i {
font-size: 1rem;
}
}
}

View File

@@ -0,0 +1,538 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Data Display Component Styles
* ═══════════════════════════════════════════════════════════
* Styles complets pour DataTable, DataView et composants de données
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #16-17
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 📊 DATATABLE BASE STYLES
// ============================================
.ui-datatable {
@include lions-card;
overflow: hidden;
// Table wrapper
.ui-datatable-tablewrapper {
overflow-x: auto;
}
// Table element
table {
width: 100%;
border-collapse: collapse;
background: $lions-white;
@include lions-font-size('base');
}
// Header
.ui-datatable-thead {
background: linear-gradient(135deg, rgba($lions-blue-50, 0.5) 0%, rgba($lions-blue-100, 0.3) 100%);
border-bottom: 2px solid $lions-blue-500;
th {
padding: $lions-spacing-4 $lions-spacing-5;
text-align: left;
@include lions-font-weight('bold');
color: $lions-text-primary;
border-bottom: 1px solid $lions-gray-300;
white-space: nowrap;
@include lions-transition();
// Sortable column
&.ui-sortable-column {
cursor: pointer;
&:hover {
background: rgba($lions-blue-100, 0.5);
color: $lions-blue-700;
}
.ui-sortable-column-icon {
margin-left: $lions-spacing-2;
color: $lions-gray-500;
@include lions-transition();
}
&.ui-state-active {
background: rgba($lions-blue-200, 0.6);
color: $lions-blue-700;
.ui-sortable-column-icon {
color: $lions-blue-600;
}
}
}
// Filter inputs in header
.ui-column-filter {
width: 100%;
margin-top: $lions-spacing-2;
@include lions-input-base;
@include lions-input-size('sm');
}
}
}
// Body
.ui-datatable-data {
tr {
border-bottom: 1px solid $lions-gray-200;
@include lions-transition();
&:hover {
background: rgba($lions-blue-50, 0.4);
}
&.ui-state-highlight {
background: rgba($lions-blue-100, 0.6);
color: $lions-blue-900;
}
// Striped rows
&:nth-child(even) {
background: rgba($lions-gray-50, 0.3);
&:hover {
background: rgba($lions-blue-50, 0.5);
}
}
td {
padding: $lions-spacing-4 $lions-spacing-5;
color: $lions-text-primary;
vertical-align: middle;
border-bottom: 1px solid $lions-gray-200;
// Row editor controls
.ui-row-editor {
display: flex;
gap: $lions-spacing-2;
.ui-icon {
cursor: pointer;
color: $lions-blue-500;
padding: $lions-spacing-1;
@include lions-rounded('sm');
@include lions-transition();
&:hover {
background: rgba($lions-blue-100, 0.8);
color: $lions-blue-700;
}
}
}
}
}
}
// Empty message
.ui-datatable-empty-message {
td {
text-align: center;
padding: $lions-spacing-8 !important;
color: $lions-text-secondary;
@include lions-font-size('lg');
}
}
// Small size variant
&.ui-datatable-sm {
th, td {
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-font-size('sm');
}
}
// Large size variant
&.ui-datatable-lg {
th, td {
padding: $lions-spacing-5 $lions-spacing-6;
@include lions-font-size('lg');
}
}
// Gridlines variant
&.ui-datatable-gridlines {
td, th {
border: 1px solid $lions-gray-300;
}
}
}
// ============================================
// 📄 DATATABLE PAGINATOR
// ============================================
.ui-paginator {
background: linear-gradient(135deg, rgba($lions-gray-50, 0.8) 0%, rgba($lions-gray-100, 0.5) 100%);
border-top: 1px solid $lions-gray-300;
padding: $lions-spacing-3 $lions-spacing-5;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: $lions-spacing-3;
// Current page report
.ui-paginator-current {
@include lions-font-size('sm');
color: $lions-text-secondary;
margin-right: auto;
}
// Page links container
.ui-paginator-pages {
display: flex;
gap: $lions-spacing-1;
}
// Page link buttons
.ui-paginator-page,
.ui-paginator-first,
.ui-paginator-prev,
.ui-paginator-next,
.ui-paginator-last {
@include lions-button-base;
min-width: 2.5rem;
height: 2.5rem;
padding: 0;
@include lions-flex-center;
background: $lions-white;
border: 1px solid $lions-gray-300;
color: $lions-text-primary;
@include lions-transition();
&:hover:not(.ui-state-disabled):not(.ui-state-active) {
background: $lions-blue-50;
border-color: $lions-blue-500;
color: $lions-blue-700;
}
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-700;
color: $lions-white;
font-weight: $lions-font-bold;
}
&.ui-state-disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// Rows per page dropdown
.ui-paginator-rpp-options {
@include lions-input-base;
@include lions-input-size('sm');
min-width: 80px;
}
}
// ============================================
// 🔍 DATATABLE SELECTION
// ============================================
.ui-datatable {
// Checkbox selection column
.ui-selection-column {
width: 3rem;
text-align: center;
.ui-chkbox {
@include lions-flex-center;
}
}
// Radio selection column
.ui-radiobutton-column {
width: 3rem;
text-align: center;
.ui-radiobutton {
@include lions-flex-center;
}
}
}
// ============================================
// 📋 DATAVIEW BASE STYLES
// ============================================
.ui-dataview {
.ui-dataview-header {
background: linear-gradient(135deg, rgba($lions-blue-50, 0.5) 0%, rgba($lions-blue-100, 0.3) 100%);
border-bottom: 2px solid $lions-blue-500;
padding: $lions-spacing-4 $lions-spacing-5;
display: flex;
justify-content: space-between;
align-items: center;
.ui-dataview-layout-options {
display: flex;
gap: $lions-spacing-1;
.ui-button {
@include lions-button-base;
@include lions-button-size('sm');
background: $lions-white;
border: 1px solid $lions-gray-300;
color: $lions-text-primary;
&:hover {
background: $lions-blue-50;
border-color: $lions-blue-500;
color: $lions-blue-700;
}
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-700;
color: $lions-white;
}
}
}
}
.ui-dataview-content {
padding: $lions-spacing-5;
background: $lions-white;
}
// List layout
.ui-dataview-list {
.ui-dataview-row {
padding: $lions-spacing-4;
border-bottom: 1px solid $lions-gray-200;
@include lions-transition();
&:hover {
background: rgba($lions-blue-50, 0.4);
}
&:last-child {
border-bottom: none;
}
}
}
// Grid layout
.ui-dataview-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: $lions-spacing-5;
@media (max-width: $lions-breakpoint-md) {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: $lions-spacing-4;
}
@media (max-width: $lions-breakpoint-sm) {
grid-template-columns: 1fr;
}
}
// Empty message
.ui-dataview-emptymessage {
text-align: center;
padding: $lions-spacing-8;
color: $lions-text-secondary;
@include lions-font-size('lg');
}
}
// ============================================
// 📊 SCROLLABLE DATATABLE
// ============================================
.ui-datatable-scrollable {
.ui-datatable-scrollable-header,
.ui-datatable-scrollable-footer {
background: linear-gradient(135deg, rgba($lions-blue-50, 0.5) 0%, rgba($lions-blue-100, 0.3) 100%);
border-bottom: 2px solid $lions-blue-500;
}
.ui-datatable-scrollable-body {
overflow: auto;
// Custom scrollbar
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: $lions-gray-100;
}
&::-webkit-scrollbar-thumb {
background: $lions-gray-400;
@include lions-rounded('full');
&:hover {
background: $lions-blue-500;
}
}
}
}
// ============================================
// 🔄 RESIZABLE & REORDERABLE COLUMNS
// ============================================
.ui-datatable {
// Column resizer
.ui-column-resizer {
width: 2px;
background: $lions-blue-500;
cursor: col-resize;
position: absolute;
right: 0;
top: 0;
height: 100%;
opacity: 0;
@include lions-transition();
&:hover {
opacity: 1;
}
}
th:hover .ui-column-resizer {
opacity: 0.5;
}
// Reorderable column indicator
.ui-datatable-reorderablerow-handle {
cursor: move;
color: $lions-gray-500;
padding: 0 $lions-spacing-2;
&:hover {
color: $lions-blue-500;
}
}
}
// ============================================
// 🎯 ROW EXPANSION
// ============================================
.ui-datatable {
.ui-row-toggler {
cursor: pointer;
color: $lions-blue-500;
@include lions-transition();
&:hover {
color: $lions-blue-700;
}
}
.ui-expanded-row-content {
td {
background: rgba($lions-blue-50, 0.2);
border-left: 4px solid $lions-blue-500;
}
}
}
// ============================================
// 📱 RESPONSIVE DATATABLE
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-datatable {
th, td {
padding: $lions-spacing-3 $lions-spacing-4;
@include lions-font-size('sm');
}
}
.ui-paginator {
flex-direction: column;
align-items: stretch;
.ui-paginator-current {
text-align: center;
margin: 0 0 $lions-spacing-2 0;
}
.ui-paginator-pages {
justify-content: center;
}
}
.ui-dataview-grid {
grid-template-columns: 1fr;
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-datatable,
.ui-dataview {
// Focus visible
th:focus-visible,
td:focus-visible,
.ui-paginator-page:focus-visible {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
th, td {
border-width: 2px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
tr, th, td, .ui-paginator-page {
transition: none !important;
}
}
}
// ============================================
// 🎨 DATATABLE SEVERITY ROW STYLES
// ============================================
.ui-datatable-data {
tr {
&.row-success {
background: rgba($lions-success-50, 0.3);
border-left: 4px solid $lions-success-500;
}
&.row-warning {
background: rgba($lions-warning-50, 0.3);
border-left: 4px solid $lions-warning-500;
}
&.row-danger {
background: rgba($lions-danger-50, 0.3);
border-left: 4px solid $lions-danger-500;
}
&.row-info {
background: rgba($lions-info-50, 0.3);
border-left: 4px solid $lions-info-500;
}
}
}

View File

@@ -0,0 +1,577 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Display Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Avatar, Badge, Tag et composants d'affichage
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: HAUTE #34-36
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 👤 AVATAR STYLES
// ============================================
.ui-avatar {
display: inline-flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
vertical-align: middle;
@include lions-transition();
// Base avatar (40px)
width: 2.5rem;
height: 2.5rem;
@include lions-rounded('full');
@include lions-gradient-blue;
color: $lions-white;
@include lions-font-weight('semibold');
@include lions-font-size('base');
// Avatar image
img {
width: 100%;
height: 100%;
object-fit: cover;
}
// Avatar label/initials
.ui-avatar-text {
@include lions-font-weight('semibold');
text-transform: uppercase;
user-select: none;
}
// Avatar icon
.ui-avatar-icon {
font-size: 1.25rem;
}
// Size variants
&.ui-avatar-xs {
width: 1.5rem;
height: 1.5rem;
@include lions-font-size('xs');
.ui-avatar-icon {
font-size: 0.75rem;
}
}
&.ui-avatar-sm {
width: 2rem;
height: 2rem;
@include lions-font-size('sm');
.ui-avatar-icon {
font-size: 1rem;
}
}
&.ui-avatar-lg {
width: 3rem;
height: 3rem;
@include lions-font-size('lg');
.ui-avatar-icon {
font-size: 1.5rem;
}
}
&.ui-avatar-xl {
width: 4rem;
height: 4rem;
@include lions-font-size('xl');
.ui-avatar-icon {
font-size: 2rem;
}
}
// Shape variants
&.ui-avatar-circle {
@include lions-rounded('full');
}
&.ui-avatar-square {
@include lions-rounded(0);
}
&.ui-avatar-rounded {
@include lions-rounded('lg');
}
// Severity variants
&.ui-avatar-secondary {
background: $lions-gray-500;
}
&.ui-avatar-success {
@include lions-gradient-success;
}
&.ui-avatar-info {
@include lions-gradient-info;
}
&.ui-avatar-warning {
@include lions-gradient-warning;
}
&.ui-avatar-danger {
@include lions-gradient-danger;
}
// Avatar group
&-group {
display: inline-flex;
.ui-avatar {
margin-left: -0.5rem;
border: 2px solid $lions-white;
@include lions-shadow('sm');
&:first-child {
margin-left: 0;
}
&:hover {
z-index: 10;
transform: scale(1.1);
}
}
}
// Status indicator
&-status {
position: absolute;
bottom: 0;
right: 0;
width: 0.75rem;
height: 0.75rem;
@include lions-rounded('full');
border: 2px solid $lions-white;
&-online {
background: $lions-success-500;
}
&-offline {
background: $lions-gray-400;
}
&-busy {
background: $lions-danger-500;
}
&-away {
background: $lions-warning-500;
}
}
}
// ============================================
// 🏷️ BADGE STYLES
// ============================================
.ui-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.25rem 0.5rem;
@include lions-rounded('md');
@include lions-font-size('xs');
@include lions-font-weight('semibold');
line-height: 1;
white-space: nowrap;
vertical-align: middle;
@include lions-transition();
// Default (primary)
background: $lions-blue-100;
color: $lions-blue-700;
// Severity variants
&.ui-badge-secondary {
background: $lions-gray-100;
color: $lions-gray-700;
}
&.ui-badge-success {
background: $lions-success-100;
color: $lions-success-700;
}
&.ui-badge-info {
background: $lions-info-100;
color: $lions-info-700;
}
&.ui-badge-warning {
background: $lions-warning-100;
color: $lions-warning-700;
}
&.ui-badge-danger {
background: $lions-danger-100;
color: $lions-danger-700;
}
// Size variants
&.ui-badge-sm {
padding: 0.125rem 0.375rem;
@include lions-font-size('2xs');
}
&.ui-badge-lg {
padding: 0.375rem 0.75rem;
@include lions-font-size('sm');
}
// Pill variant
&.ui-badge-pill {
@include lions-rounded('full');
}
// Dot variant
&.ui-badge-dot {
padding: 0;
width: 0.5rem;
height: 0.5rem;
@include lions-rounded('full');
&.ui-badge-sm {
width: 0.375rem;
height: 0.375rem;
}
&.ui-badge-lg {
width: 0.625rem;
height: 0.625rem;
}
}
// Outlined variant
&.ui-badge-outlined {
background: transparent;
border: 1px solid currentColor;
&.ui-badge-primary {
color: $lions-blue-600;
border-color: $lions-blue-600;
}
&.ui-badge-secondary {
color: $lions-gray-600;
border-color: $lions-gray-600;
}
&.ui-badge-success {
color: $lions-success-600;
border-color: $lions-success-600;
}
&.ui-badge-info {
color: $lions-info-600;
border-color: $lions-info-600;
}
&.ui-badge-warning {
color: $lions-warning-600;
border-color: $lions-warning-600;
}
&.ui-badge-danger {
color: $lions-danger-600;
border-color: $lions-danger-600;
}
}
// Overlay badge (for icons, avatars)
&-overlay {
position: absolute;
top: -0.25rem;
right: -0.25rem;
min-width: 1.25rem;
height: 1.25rem;
padding: 0 0.375rem;
@include lions-rounded('full');
@include lions-shadow('sm');
}
}
// Badge on button
.ui-button {
position: relative;
.ui-badge-overlay {
top: -0.5rem;
right: -0.5rem;
}
}
// ============================================
// 🔖 TAG STYLES
// ============================================
.ui-tag {
display: inline-flex;
align-items: center;
gap: $lions-spacing-2;
padding: 0.375rem 0.75rem;
@include lions-rounded('md');
@include lions-font-size('sm');
@include lions-font-weight('medium');
line-height: 1.5;
vertical-align: middle;
@include lions-transition();
// Default (primary)
background: $lions-blue-100;
color: $lions-blue-700;
// Tag icon
.ui-tag-icon {
font-size: 1rem;
line-height: 1;
&.ui-tag-icon-left {
margin-right: -$lions-spacing-1;
}
&.ui-tag-icon-right {
margin-left: -$lions-spacing-1;
}
}
// Tag value
.ui-tag-value {
line-height: 1.5;
}
// Close button
.ui-tag-close {
@include lions-button-base;
width: 1rem;
height: 1rem;
min-width: 1rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
color: currentColor;
opacity: 0.6;
cursor: pointer;
@include lions-rounded('full');
@include lions-transition();
&:hover {
opacity: 1;
background: rgba($lions-black, 0.1);
}
&:active {
transform: scale(0.9);
}
.ui-icon {
font-size: 0.75rem;
}
}
// Severity variants
&.ui-tag-secondary {
background: $lions-gray-100;
color: $lions-gray-700;
}
&.ui-tag-success {
background: $lions-success-100;
color: $lions-success-700;
}
&.ui-tag-info {
background: $lions-info-100;
color: $lions-info-700;
}
&.ui-tag-warning {
background: $lions-warning-100;
color: $lions-warning-700;
}
&.ui-tag-danger {
background: $lions-danger-100;
color: $lions-danger-700;
}
// Size variants
&.ui-tag-sm {
padding: 0.25rem 0.5rem;
@include lions-font-size('xs');
.ui-tag-icon {
font-size: 0.875rem;
}
.ui-tag-close {
width: 0.875rem;
height: 0.875rem;
.ui-icon {
font-size: 0.625rem;
}
}
}
&.ui-tag-lg {
padding: 0.5rem 1rem;
@include lions-font-size('base');
.ui-tag-icon {
font-size: 1.125rem;
}
.ui-tag-close {
width: 1.125rem;
height: 1.125rem;
.ui-icon {
font-size: 0.875rem;
}
}
}
// Pill variant
&.ui-tag-pill {
@include lions-rounded('full');
}
// Outlined variant
&.ui-tag-outlined {
background: transparent;
border: 1px solid currentColor;
&.ui-tag-primary {
color: $lions-blue-600;
border-color: $lions-blue-600;
}
&.ui-tag-secondary {
color: $lions-gray-600;
border-color: $lions-gray-600;
}
&.ui-tag-success {
color: $lions-success-600;
border-color: $lions-success-600;
}
&.ui-tag-info {
color: $lions-info-600;
border-color: $lions-info-600;
}
&.ui-tag-warning {
color: $lions-warning-600;
border-color: $lions-warning-600;
}
&.ui-tag-danger {
color: $lions-danger-600;
border-color: $lions-danger-600;
}
.ui-tag-close:hover {
background: rgba(currentColor, 0.1);
}
}
}
// Tag group
.ui-tag-group {
display: inline-flex;
flex-wrap: wrap;
gap: $lions-spacing-2;
align-items: center;
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-avatar {
&.ui-avatar-xl {
width: 3rem;
height: 3rem;
@include lions-font-size('lg');
}
}
.ui-avatar-group {
.ui-avatar {
margin-left: -0.375rem;
}
}
.ui-tag-group {
gap: $lions-spacing-1;
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-avatar,
.ui-badge,
.ui-tag {
// High contrast mode
@media (prefers-contrast: high) {
border: 2px solid currentColor !important;
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
transition: none !important;
animation: none !important;
}
}
// ============================================
// 🎨 UTILITY CLASSES
// ============================================
// Avatar with border
.ui-avatar-bordered {
border: 2px solid $lions-white;
@include lions-shadow('md');
}
// Badge pulse animation
.ui-badge-pulse {
animation: lions-badge-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes lions-badge-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
// Tag removable
.ui-tag-removable {
padding-right: $lions-spacing-2;
}

View File

@@ -0,0 +1,789 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Form Field Component Styles
* ═══════════════════════════════════════════════════════════
* Amélioration visuelle complète des champs de formulaire avec identité Lions.dev
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #5-8
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 🎯 BASE FIELD STYLES
// ============================================
.field {
margin-bottom: $lions-spacing-6;
&:last-child {
margin-bottom: 0;
}
}
// ============================================
// 🏷️ LABEL STYLES
// ============================================
.ui-outputlabel,
label {
display: block;
margin-bottom: $lions-spacing-2;
@include lions-font-size('sm');
@include lions-font-weight('semibold');
color: $lions-text-primary;
// Required indicator (red asterisk)
.p-error {
color: $lions-danger-500;
margin-left: 2px;
}
}
// ============================================
// 📝 INPUT TEXT BASE STYLES
// ============================================
.ui-inputtext,
.ui-inputfield {
@include lions-input-base;
@include lions-input-size('base');
// Override PrimeFaces defaults
font-family: $lions-font-family-primary !important;
}
// ============================================
// 📏 INPUT SIZES
// ============================================
// Small inputs
.ui-inputtext.ui-inputtext-sm,
.ui-inputfield.ui-inputfield-sm,
.field-sm .ui-inputtext,
.field-sm .ui-inputfield {
@include lions-input-size('sm');
}
// Large inputs
.ui-inputtext.ui-inputtext-lg,
.ui-inputfield.ui-inputfield-lg,
.field-lg .ui-inputtext,
.field-lg .ui-inputfield {
@include lions-input-size('lg');
}
// ============================================
// ✅ INPUT STATES
// ============================================
// Success state (valid input)
.ui-inputtext.ui-state-valid,
.ui-inputfield.ui-state-valid {
@include lions-input-state-success;
}
// Error state (invalid input)
.ui-inputtext.ui-state-error,
.ui-inputfield.ui-state-error,
.ui-inputtext.ui-state-invalid,
.ui-inputfield.ui-state-invalid {
@include lions-input-state-error;
}
// ============================================
// 📝 TEXTAREA STYLES
// ============================================
.ui-inputtextarea {
@include lions-input-base;
min-height: 80px;
padding: $lions-input-padding-y $lions-input-padding-x;
resize: vertical;
line-height: $lions-line-height-normal;
// Size variants
&.ui-inputtextarea-sm {
@include lions-font-size('sm');
padding: $lions-spacing-2 $lions-spacing-3;
}
&.ui-inputtextarea-lg {
@include lions-font-size('lg');
padding: $lions-spacing-4 $lions-spacing-5;
}
// States
&.ui-state-error {
@include lions-input-state-error;
}
&.ui-state-valid {
@include lions-input-state-success;
}
}
// ============================================
// 🔽 SELECT / DROPDOWN STYLES
// ============================================
.ui-selectonemenu {
width: 100%;
.ui-selectonemenu-trigger {
@include lions-flex-center;
background: $lions-gray-50;
border-left: 1px solid $lions-gray-300;
width: $lions-button-height-base;
color: $lions-gray-600;
@include lions-transition();
&:hover {
background: $lions-gray-100;
color: $lions-blue-500;
}
}
.ui-selectonemenu-label {
@include lions-input-base;
@include lions-input-size('base');
border-radius: $lions-radius-base 0 0 $lions-radius-base !important;
}
&.ui-state-focus {
.ui-selectonemenu-label {
outline: none;
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
.ui-selectonemenu-trigger {
border-color: $lions-blue-500;
}
}
&.ui-state-error {
.ui-selectonemenu-label {
@include lions-input-state-error;
border-radius: $lions-radius-base 0 0 $lions-radius-base !important;
}
.ui-selectonemenu-trigger {
border-color: $lions-danger-500;
}
}
// Size variants
&.ui-selectonemenu-sm {
.ui-selectonemenu-label {
@include lions-input-size('sm');
}
.ui-selectonemenu-trigger {
width: $lions-button-height-sm;
}
}
&.ui-selectonemenu-lg {
.ui-selectonemenu-label {
@include lions-input-size('lg');
}
.ui-selectonemenu-trigger {
width: $lions-button-height-lg;
}
}
}
// Dropdown panel
.ui-selectonemenu-panel {
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('lg');
background: $lions-white;
margin-top: $lions-spacing-1;
.ui-selectonemenu-items {
padding: $lions-spacing-1 0;
.ui-selectonemenu-item {
padding: $lions-spacing-3 $lions-spacing-4;
color: $lions-text-primary;
@include lions-transition();
cursor: pointer;
&:hover,
&.ui-state-highlight {
background: rgba($lions-blue-50, 0.8);
color: $lions-blue-700;
}
&.ui-state-highlight {
@include lions-font-weight('semibold');
}
}
}
.ui-selectonemenu-filter-container {
padding: $lions-spacing-2;
border-bottom: 1px solid $lions-gray-200;
.ui-selectonemenu-filter {
@include lions-input-base;
@include lions-input-size('sm');
width: 100%;
}
}
}
// ============================================
// 🔒 PASSWORD INPUT STYLES
// ============================================
.ui-password {
width: 100%;
.ui-inputtext {
width: 100%;
}
// Password strength meter
.ui-password-panel {
background: $lions-white;
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('md');
padding: $lions-spacing-4;
margin-top: $lions-spacing-2;
.ui-password-meter {
height: 8px;
background: $lions-gray-200;
@include lions-rounded('full');
overflow: hidden;
margin-bottom: $lions-spacing-3;
.ui-password-strength {
height: 100%;
@include lions-transition(width);
&.weak {
width: 33%;
background: $lions-danger-500;
}
&.medium {
width: 66%;
background: $lions-warning-500;
}
&.strong {
width: 100%;
background: $lions-success-500;
}
}
}
.ui-password-info {
@include lions-font-size('xs');
color: $lions-text-secondary;
margin-top: $lions-spacing-2;
}
}
}
// ============================================
// 📅 CALENDAR / DATE PICKER STYLES
// ============================================
.ui-calendar {
.ui-inputtext {
width: 100%;
}
.ui-datepicker-trigger {
@include lions-button-base;
@include lions-button-size('base');
@include lions-button-solid($lions-blue-500, $lions-blue-700);
margin-left: $lions-spacing-2;
width: auto;
min-width: $lions-button-height-base;
}
}
// ============================================
// 🔢 NUMBER INPUT (InputNumber)
// ============================================
.ui-inputnumber {
.ui-inputtext {
width: 100%;
}
.ui-inputnumber-button-group {
.ui-inputnumber-button {
@include lions-button-base;
background: $lions-gray-100;
border-color: $lions-gray-300;
color: $lions-text-primary;
width: 2rem;
height: 50%;
&:hover {
background: $lions-gray-200;
color: $lions-blue-500;
}
&:active {
background: $lions-gray-300;
}
}
}
}
// ============================================
// 🔍 AUTOCOMPLETE STYLES
// ============================================
.ui-autocomplete {
width: 100%;
.ui-inputtext {
width: 100%;
}
.ui-autocomplete-dropdown {
@include lions-button-base;
@include lions-button-size('base');
@include lions-button-solid($lions-blue-500, $lions-blue-700);
margin-left: $lions-spacing-2;
width: auto;
min-width: $lions-button-height-base;
}
.ui-autocomplete-multiple-container {
@include lions-input-base;
@include lions-input-size('base');
display: flex;
flex-wrap: wrap;
gap: $lions-spacing-2;
min-height: $lions-input-height-base;
padding: $lions-spacing-2;
.ui-autocomplete-token {
@include lions-rounded('md');
background: $lions-blue-100;
color: $lions-blue-700;
padding: $lions-spacing-1 $lions-spacing-3;
display: flex;
align-items: center;
gap: $lions-spacing-2;
@include lions-font-size('sm');
@include lions-transition();
&:hover {
background: $lions-blue-200;
}
.ui-autocomplete-token-icon {
cursor: pointer;
color: $lions-blue-600;
&:hover {
color: $lions-danger-500;
}
}
}
.ui-autocomplete-input-token {
flex: 1;
min-width: 120px;
input {
border: none;
outline: none;
background: transparent;
width: 100%;
@include lions-font-size('base');
color: $lions-text-primary;
}
}
}
}
// AutoComplete panel
.ui-autocomplete-panel {
background: $lions-white;
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('lg');
margin-top: $lions-spacing-1;
max-height: 300px;
overflow-y: auto;
.ui-autocomplete-items {
padding: $lions-spacing-1 0;
list-style: none;
margin: 0;
.ui-autocomplete-item {
padding: $lions-spacing-3 $lions-spacing-4;
cursor: pointer;
@include lions-transition();
@include lions-font-size('base');
color: $lions-text-primary;
&:hover,
&.ui-state-highlight {
background: rgba($lions-blue-50, 0.8);
color: $lions-blue-700;
}
&:active {
background: rgba($lions-blue-100, 0.8);
}
}
.ui-autocomplete-group {
padding: $lions-spacing-2 $lions-spacing-4;
@include lions-font-size('sm');
@include lions-font-weight('bold');
color: $lions-text-secondary;
background: $lions-gray-50;
border-bottom: 1px solid $lions-gray-200;
}
}
.ui-autocomplete-empty-message {
padding: $lions-spacing-4;
@include lions-font-size('sm');
color: $lions-text-secondary;
text-align: center;
}
}
// ============================================
// 📋 MULTISELECT STYLES
// ============================================
.ui-multiselect {
width: 100%;
.ui-multiselect-label-container {
.ui-multiselect-label {
@include lions-input-base;
@include lions-input-size('base');
border-radius: $lions-radius-base 0 0 $lions-radius-base !important;
}
}
.ui-multiselect-trigger {
@include lions-flex-center;
background: $lions-gray-50;
border-left: 1px solid $lions-gray-300;
width: $lions-button-height-base;
color: $lions-gray-600;
&:hover {
background: $lions-gray-100;
color: $lions-blue-500;
}
}
&.ui-state-focus {
.ui-multiselect-label {
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
}
}
.ui-multiselect-panel {
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('lg');
.ui-multiselect-header {
background: $lions-gray-50;
padding: $lions-spacing-3 $lions-spacing-4;
border-bottom: 1px solid $lions-gray-200;
.ui-chkbox {
margin-right: $lions-spacing-2;
}
}
.ui-multiselect-items {
padding: $lions-spacing-1 0;
.ui-multiselect-item {
padding: $lions-spacing-3 $lions-spacing-4;
@include lions-transition();
&:hover {
background: rgba($lions-blue-50, 0.8);
}
.ui-chkbox {
margin-right: $lions-spacing-2;
}
}
}
}
// ============================================
// ☑️ CHECKBOX & RADIO STYLES
// ============================================
.ui-chkbox,
.ui-radiobutton {
.ui-chkbox-box,
.ui-radiobutton-box {
width: 20px;
height: 20px;
border: 2px solid $lions-gray-400;
background: $lions-white;
@include lions-transition();
@include lions-flex-center;
.ui-chkbox-icon,
.ui-radiobutton-icon {
color: $lions-white;
font-size: 12px;
}
&:hover {
border-color: $lions-blue-500;
background: rgba($lions-blue-50, 0.5);
}
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-700;
.ui-chkbox-icon,
.ui-radiobutton-icon {
color: $lions-white;
}
}
&.ui-state-focus {
@include lions-focus-ring($lions-blue-500);
}
}
// Checkbox specific
.ui-chkbox-box {
@include lions-rounded('sm');
}
// Radio specific
.ui-radiobutton-box {
border-radius: $lions-radius-full;
&.ui-state-active {
.ui-radiobutton-icon {
width: 8px;
height: 8px;
background: $lions-white;
border-radius: $lions-radius-full;
}
}
}
}
// ============================================
// 🔘 SWITCH / TOGGLE STYLES
// ============================================
.ui-inputswitch {
width: 48px;
height: 24px;
background: $lions-gray-300;
@include lions-rounded('full');
@include lions-transition();
cursor: pointer;
.ui-inputswitch-slider {
width: 20px;
height: 20px;
background: $lions-white;
@include lions-rounded('full');
@include lions-shadow('sm');
@include lions-transition();
position: absolute;
top: 2px;
left: 2px;
}
&.ui-inputswitch-checked {
@include lions-gradient-blue;
.ui-inputswitch-slider {
left: 26px;
}
}
&:hover {
background: $lions-gray-400;
}
&.ui-inputswitch-checked:hover {
background: linear-gradient(135deg, $lions-blue-600 0%, $lions-blue-800 100%);
}
&.ui-inputswitch-focus {
@include lions-focus-ring($lions-blue-500);
}
}
// ============================================
// 📄 FILE UPLOAD STYLES
// ============================================
.ui-fileupload {
border: 2px dashed $lions-gray-300;
@include lions-rounded('md');
background: $lions-gray-50;
padding: $lions-spacing-6;
@include lions-transition();
&:hover {
border-color: $lions-blue-500;
background: rgba($lions-blue-50, 0.3);
}
.ui-fileupload-buttonbar {
background: transparent;
border: none;
padding: $lions-spacing-4;
.ui-button {
@include lions-button-solid($lions-blue-500, $lions-blue-700);
}
}
.ui-fileupload-content {
background: $lions-white;
border: 1px solid $lions-gray-200;
@include lions-rounded('md');
padding: $lions-spacing-4;
}
}
// ============================================
// 💬 VALIDATION MESSAGES
// ============================================
.ui-message,
.ui-messages {
@include lions-rounded('md');
padding: $lions-spacing-3 $lions-spacing-4;
@include lions-font-size('sm');
margin-top: $lions-spacing-2;
display: flex;
align-items: center;
gap: $lions-spacing-2;
&.ui-message-error,
&.ui-messages-error {
background: rgba($lions-danger-50, 0.8);
border-left: 4px solid $lions-danger-500;
color: $lions-danger-700;
.ui-message-icon,
.ui-messages-icon {
color: $lions-danger-500;
}
}
&.ui-message-warn,
&.ui-messages-warn {
background: rgba($lions-warning-50, 0.8);
border-left: 4px solid $lions-warning-500;
color: $lions-warning-700;
.ui-message-icon,
.ui-messages-icon {
color: $lions-warning-500;
}
}
&.ui-message-info,
&.ui-messages-info {
background: rgba($lions-info-50, 0.8);
border-left: 4px solid $lions-info-500;
color: $lions-info-700;
.ui-message-icon,
.ui-messages-icon {
color: $lions-info-500;
}
}
&.ui-message-success,
&.ui-messages-success {
background: rgba($lions-success-50, 0.8);
border-left: 4px solid $lions-success-500;
color: $lions-success-700;
.ui-message-icon,
.ui-messages-icon {
color: $lions-success-500;
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
// Focus visible for all inputs
.ui-inputtext,
.ui-inputfield,
.ui-inputtextarea,
.ui-selectonemenu,
.ui-multiselect,
.ui-chkbox-box,
.ui-radiobutton-box {
&:focus-visible {
@include lions-focus-ring();
}
}
// High contrast mode
@media (prefers-contrast: high) {
.ui-inputtext,
.ui-inputfield,
.ui-inputtextarea {
border-width: 2px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
.ui-inputtext,
.ui-inputfield,
.ui-inputtextarea,
.ui-selectonemenu,
.ui-multiselect {
transition: none !important;
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
// Touch-friendly on mobile
@media (hover: none) and (pointer: coarse) {
.ui-inputtext,
.ui-inputfield {
min-height: 44px; // iOS touch target minimum
}
}

View File

@@ -0,0 +1,890 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Advanced Form Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour composants de formulaire avancés
* Spinner, Mask, Chips, ColorPicker, Editor, FileUpload
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: BASSE #40-44
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 🔢 SPINNER STYLES
// ============================================
.ui-spinner {
display: inline-flex;
position: relative;
width: 100%;
.ui-spinner-input {
width: 100%;
height: $lions-input-height-base;
padding: $lions-spacing-3 $lions-spacing-4;
padding-right: 2.5rem;
@include lions-rounded('md');
border: 1px solid $lions-gray-300;
@include lions-font-size('base');
color: $lions-text-primary;
background: $lions-white;
@include lions-transition();
&:hover {
border-color: $lions-gray-400;
}
&:focus {
outline: none;
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
&:disabled {
background: $lions-gray-100;
cursor: not-allowed;
opacity: 0.6;
}
}
.ui-spinner-button-group {
position: absolute;
right: 0;
top: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.ui-spinner-button {
@include lions-button-base;
width: 2rem;
height: 50%;
min-width: 2rem;
padding: 0;
@include lions-flex-center;
background: $lions-gray-100;
border: 1px solid $lions-gray-300;
border-left: none;
color: $lions-gray-600;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-gray-200;
color: $lions-blue-600;
}
&:active {
background: $lions-gray-300;
}
&.ui-spinner-up {
border-top-right-radius: $lions-rounded-md;
border-bottom: none;
}
&.ui-spinner-down {
border-bottom-right-radius: $lions-rounded-md;
}
.ui-icon {
font-size: 0.75rem;
}
}
// Size variants
&.ui-spinner-sm {
.ui-spinner-input {
height: $lions-input-height-sm;
padding: $lions-spacing-2 $lions-spacing-3;
padding-right: 2rem;
@include lions-font-size('sm');
}
.ui-spinner-button {
width: 1.75rem;
min-width: 1.75rem;
}
}
&.ui-spinner-lg {
.ui-spinner-input {
height: $lions-input-height-lg;
padding: $lions-spacing-4 $lions-spacing-5;
padding-right: 3rem;
@include lions-font-size('lg');
}
.ui-spinner-button {
width: 2.5rem;
min-width: 2.5rem;
.ui-icon {
font-size: 1rem;
}
}
}
}
// ============================================
// 🎭 INPUT MASK STYLES
// ============================================
.ui-inputmask {
width: 100%;
height: $lions-input-height-base;
padding: $lions-spacing-3 $lions-spacing-4;
@include lions-rounded('md');
border: 1px solid $lions-gray-300;
@include lions-font-size('base');
color: $lions-text-primary;
background: $lions-white;
@include lions-transition();
font-family: $lions-font-family-mono;
&::placeholder {
color: $lions-gray-400;
font-family: $lions-font-family-primary;
}
&:hover {
border-color: $lions-gray-400;
}
&:focus {
outline: none;
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
&:disabled {
background: $lions-gray-100;
cursor: not-allowed;
opacity: 0.6;
}
// Size variants
&.ui-inputmask-sm {
height: $lions-input-height-sm;
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-font-size('sm');
}
&.ui-inputmask-lg {
height: $lions-input-height-lg;
padding: $lions-spacing-4 $lions-spacing-5;
@include lions-font-size('lg');
}
}
// ============================================
// 🏷️ CHIPS STYLES
// ============================================
.ui-chips {
display: block;
width: 100%;
.ui-chips-container {
display: flex;
flex-wrap: wrap;
gap: $lions-spacing-2;
padding: $lions-spacing-2;
@include lions-rounded('md');
border: 1px solid $lions-gray-300;
background: $lions-white;
min-height: $lions-input-height-base;
@include lions-transition();
&:hover {
border-color: $lions-gray-400;
}
&.ui-state-focus {
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
&.ui-state-disabled {
background: $lions-gray-100;
cursor: not-allowed;
opacity: 0.6;
}
}
.ui-chips-token {
display: inline-flex;
align-items: center;
gap: $lions-spacing-2;
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-rounded('md');
background: $lions-blue-100;
color: $lions-blue-700;
@include lions-font-size('sm');
@include lions-font-weight('medium');
@include lions-transition();
&:hover {
background: $lions-blue-200;
}
}
.ui-chips-token-icon {
@include lions-button-base;
width: 1rem;
height: 1rem;
min-width: 1rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
color: $lions-blue-600;
cursor: pointer;
@include lions-rounded('full');
@include lions-transition();
&:hover {
background: rgba($lions-blue-700, 0.2);
color: $lions-blue-800;
}
.ui-icon {
font-size: 0.75rem;
}
}
.ui-chips-input-token {
flex: 1;
min-width: 120px;
input {
width: 100%;
border: none;
outline: none;
background: transparent;
padding: $lions-spacing-2;
@include lions-font-size('base');
color: $lions-text-primary;
&::placeholder {
color: $lions-gray-400;
}
}
}
// Severity variants
&.ui-chips-success .ui-chips-token {
background: $lions-success-100;
color: $lions-success-700;
.ui-chips-token-icon {
color: $lions-success-600;
}
}
&.ui-chips-warning .ui-chips-token {
background: $lions-warning-100;
color: $lions-warning-700;
.ui-chips-token-icon {
color: $lions-warning-600;
}
}
&.ui-chips-danger .ui-chips-token {
background: $lions-danger-100;
color: $lions-danger-700;
.ui-chips-token-icon {
color: $lions-danger-600;
}
}
}
// ============================================
// 🎨 COLOR PICKER STYLES
// ============================================
.ui-colorpicker {
display: inline-flex;
position: relative;
.ui-colorpicker-preview {
width: 2.5rem;
height: 2.5rem;
@include lions-rounded('md');
border: 2px solid $lions-gray-300;
cursor: pointer;
@include lions-shadow('sm');
@include lions-transition();
position: relative;
overflow: hidden;
&:hover {
border-color: $lions-blue-400;
transform: scale(1.05);
}
&:active {
transform: scale(0.98);
}
.ui-colorpicker-color {
width: 100%;
height: 100%;
}
}
.ui-colorpicker-panel {
position: absolute;
top: 100%;
left: 0;
margin-top: $lions-spacing-2;
@include lions-rounded('lg');
@include lions-shadow('xl');
background: $lions-white;
border: 1px solid $lions-gray-200;
padding: $lions-spacing-4;
z-index: 1000;
.ui-colorpicker-content {
display: flex;
flex-direction: column;
gap: $lions-spacing-3;
}
.ui-colorpicker-hue {
width: 100%;
height: 1rem;
@include lions-rounded('md');
cursor: crosshair;
}
.ui-colorpicker-color-selector {
width: 200px;
height: 150px;
@include lions-rounded('md');
cursor: crosshair;
position: relative;
background: linear-gradient(to right, #fff, transparent),
linear-gradient(to top, #000, transparent);
}
.ui-colorpicker-color-handle {
position: absolute;
width: 12px;
height: 12px;
@include lions-rounded('full');
border: 2px solid $lions-white;
@include lions-shadow('md');
transform: translate(-50%, -50%);
pointer-events: none;
}
}
// Inline variant
&.ui-colorpicker-inline {
.ui-colorpicker-panel {
position: static;
margin-top: 0;
display: block;
}
}
// Size variants
&.ui-colorpicker-sm .ui-colorpicker-preview {
width: 2rem;
height: 2rem;
}
&.ui-colorpicker-lg .ui-colorpicker-preview {
width: 3rem;
height: 3rem;
}
}
// ============================================
// 📝 RICH TEXT EDITOR STYLES
// ============================================
.ui-editor {
display: block;
width: 100%;
@include lions-rounded('lg');
border: 1px solid $lions-gray-300;
background: $lions-white;
@include lions-shadow('sm');
overflow: hidden;
.ui-editor-toolbar {
display: flex;
flex-wrap: wrap;
gap: $lions-spacing-1;
padding: $lions-spacing-3;
background: $lions-gray-50;
border-bottom: 1px solid $lions-gray-200;
}
.ui-editor-toolbar-group {
display: flex;
gap: $lions-spacing-1;
padding-right: $lions-spacing-2;
border-right: 1px solid $lions-gray-300;
&:last-child {
border-right: none;
padding-right: 0;
}
}
.ui-editor-toolbar-button {
@include lions-button-base;
width: 2rem;
height: 2rem;
min-width: 2rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: 1px solid transparent;
@include lions-rounded('md');
color: $lions-gray-600;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-white;
border-color: $lions-gray-300;
color: $lions-blue-600;
}
&:active,
&.ui-state-active {
background: $lions-blue-100;
border-color: $lions-blue-300;
color: $lions-blue-700;
}
.ui-icon {
font-size: 1rem;
}
}
.ui-editor-content {
min-height: 200px;
max-height: 500px;
overflow-y: auto;
padding: $lions-spacing-4;
@include lions-font-size('base');
line-height: 1.6;
color: $lions-text-primary;
&:focus {
outline: none;
}
// Typography inside editor
h1, h2, h3, h4, h5, h6 {
margin-top: $lions-spacing-4;
margin-bottom: $lions-spacing-3;
@include lions-font-weight('bold');
color: $lions-text-primary;
}
h1 { @include lions-font-size('3xl'); }
h2 { @include lions-font-size('2xl'); }
h3 { @include lions-font-size('xl'); }
h4 { @include lions-font-size('lg'); }
h5 { @include lions-font-size('base'); }
h6 { @include lions-font-size('sm'); }
p {
margin-bottom: $lions-spacing-3;
}
a {
color: $lions-blue-600;
text-decoration: underline;
&:hover {
color: $lions-blue-700;
}
}
ul, ol {
margin-left: $lions-spacing-6;
margin-bottom: $lions-spacing-3;
}
li {
margin-bottom: $lions-spacing-1;
}
blockquote {
border-left: 4px solid $lions-blue-500;
padding-left: $lions-spacing-4;
margin-left: 0;
margin-bottom: $lions-spacing-3;
color: $lions-gray-600;
font-style: italic;
}
pre {
background: $lions-gray-100;
@include lions-rounded('md');
padding: $lions-spacing-4;
margin-bottom: $lions-spacing-3;
overflow-x: auto;
font-family: $lions-font-family-mono;
@include lions-font-size('sm');
}
code {
background: $lions-gray-100;
padding: $lions-spacing-1 $lions-spacing-2;
@include lions-rounded('sm');
font-family: $lions-font-family-mono;
@include lions-font-size('sm');
color: $lions-danger-600;
}
img {
max-width: 100%;
height: auto;
@include lions-rounded('md');
margin: $lions-spacing-3 0;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: $lions-spacing-3;
th, td {
border: 1px solid $lions-gray-300;
padding: $lions-spacing-3;
text-align: left;
}
th {
background: $lions-gray-100;
@include lions-font-weight('semibold');
}
}
}
// Focused state
&.ui-state-focus {
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
// Disabled state
&.ui-state-disabled {
opacity: 0.6;
cursor: not-allowed;
.ui-editor-toolbar,
.ui-editor-content {
pointer-events: none;
}
}
// Size variants
&.ui-editor-sm {
.ui-editor-toolbar {
padding: $lions-spacing-2;
}
.ui-editor-toolbar-button {
width: 1.75rem;
height: 1.75rem;
min-width: 1.75rem;
.ui-icon {
font-size: 0.875rem;
}
}
.ui-editor-content {
min-height: 150px;
padding: $lions-spacing-3;
@include lions-font-size('sm');
}
}
&.ui-editor-lg {
.ui-editor-toolbar {
padding: $lions-spacing-4;
}
.ui-editor-toolbar-button {
width: 2.5rem;
height: 2.5rem;
min-width: 2.5rem;
.ui-icon {
font-size: 1.125rem;
}
}
.ui-editor-content {
min-height: 300px;
padding: $lions-spacing-5;
@include lions-font-size('lg');
}
}
}
// ============================================
// 📂 FILE UPLOAD STYLES
// ============================================
.ui-fileupload {
display: block;
width: 100%;
@include lions-rounded('lg');
border: 1px solid $lions-gray-300;
background: $lions-white;
overflow: hidden;
.ui-fileupload-buttonbar {
display: flex;
gap: $lions-spacing-3;
padding: $lions-spacing-4;
background: $lions-gray-50;
border-bottom: 1px solid $lions-gray-200;
}
.ui-fileupload-choose {
@include lions-button-primary;
}
.ui-fileupload-upload,
.ui-fileupload-cancel {
@include lions-button-base;
padding: $lions-spacing-3 $lions-spacing-5;
@include lions-rounded('md');
@include lions-font-weight('semibold');
@include lions-transition();
}
.ui-fileupload-upload {
background: $lions-success-500;
color: $lions-white;
&:hover {
background: $lions-success-600;
}
}
.ui-fileupload-cancel {
background: $lions-gray-200;
color: $lions-gray-700;
&:hover {
background: $lions-gray-300;
}
}
.ui-fileupload-content {
padding: $lions-spacing-4;
}
.ui-fileupload-files {
display: flex;
flex-direction: column;
gap: $lions-spacing-3;
}
.ui-fileupload-row {
display: flex;
align-items: center;
gap: $lions-spacing-3;
padding: $lions-spacing-3;
@include lions-rounded('md');
background: $lions-gray-50;
border: 1px solid $lions-gray-200;
@include lions-transition();
&:hover {
background: $lions-gray-100;
}
}
.ui-fileupload-filename {
flex: 1;
@include lions-font-size('sm');
@include lions-font-weight('medium');
color: $lions-text-primary;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ui-fileupload-size {
@include lions-font-size('xs');
color: $lions-gray-600;
}
.ui-fileupload-remove {
@include lions-button-base;
width: 2rem;
height: 2rem;
min-width: 2rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
color: $lions-danger-600;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-danger-50;
border-color: $lions-danger-300;
}
.ui-icon {
font-size: 1rem;
}
}
// Drag & Drop zone
&.ui-fileupload-advanced {
.ui-fileupload-content {
position: relative;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed $lions-gray-300;
@include lions-rounded('lg');
margin: $lions-spacing-4;
@include lions-transition();
&.ui-fileupload-highlight {
border-color: $lions-blue-500;
background: rgba($lions-blue-50, 0.5);
}
}
.ui-fileupload-empty {
text-align: center;
padding: $lions-spacing-6;
.ui-fileupload-drag-icon {
font-size: 3rem;
color: $lions-gray-400;
margin-bottom: $lions-spacing-3;
}
.ui-fileupload-drag-text {
@include lions-font-size('base');
color: $lions-gray-600;
margin-bottom: $lions-spacing-2;
}
.ui-fileupload-drag-hint {
@include lions-font-size('sm');
color: $lions-gray-500;
}
}
}
// Progress bar
.ui-fileupload-progress {
height: 0.5rem;
@include lions-rounded('full');
background: $lions-gray-200;
overflow: hidden;
margin-top: $lions-spacing-2;
.ui-fileupload-progress-bar {
height: 100%;
@include lions-gradient-blue;
@include lions-transition();
}
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-editor {
.ui-editor-toolbar {
padding: $lions-spacing-2;
}
.ui-editor-toolbar-button {
width: 1.75rem;
height: 1.75rem;
min-width: 1.75rem;
}
.ui-editor-content {
min-height: 150px;
}
}
.ui-fileupload {
.ui-fileupload-buttonbar {
flex-wrap: wrap;
}
&.ui-fileupload-advanced .ui-fileupload-content {
min-height: 150px;
}
}
.ui-colorpicker-panel {
.ui-colorpicker-color-selector {
width: 150px;
height: 120px;
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-spinner,
.ui-inputmask,
.ui-chips,
.ui-colorpicker,
.ui-editor,
.ui-fileupload {
// Focus visible
&:focus-visible,
.ui-state-focus {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
button,
.ui-button {
border-width: 2px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
}

View File

@@ -0,0 +1,980 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Form Controls Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Checkbox, Radio, Switch, Toggle, Slider, Rating
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #28-33
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// ☑️ CHECKBOX STYLES
// ============================================
.ui-chkbox {
display: inline-flex;
align-items: center;
gap: $lions-spacing-2;
cursor: pointer;
@include lions-transition();
.ui-chkbox-box {
width: 1.25rem;
height: 1.25rem;
@include lions-rounded('md');
border: 2px solid $lions-gray-400;
background: $lions-white;
@include lions-flex-center;
@include lions-transition();
flex-shrink: 0;
.ui-chkbox-icon {
font-size: 0.875rem;
color: $lions-white;
opacity: 0;
transform: scale(0.5);
@include lions-transition();
}
&:hover {
border-color: $lions-blue-500;
background: rgba($lions-blue-50, 0.3);
}
&:focus-visible {
@include lions-focus-ring();
}
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-600;
box-shadow: 0 2px 4px rgba($lions-blue-500, 0.2);
.ui-chkbox-icon {
opacity: 1;
transform: scale(1);
}
}
&.ui-state-disabled {
background: $lions-gray-100;
border-color: $lions-gray-300;
cursor: not-allowed;
opacity: 0.6;
}
}
.ui-chkbox-label {
cursor: pointer;
color: $lions-text-primary;
@include lions-font-size('base');
@include lions-font-weight('medium');
user-select: none;
@include lions-transition();
&:hover {
color: $lions-blue-700;
}
}
// Size variants
&.ui-chkbox-sm {
.ui-chkbox-box {
width: 1rem;
height: 1rem;
.ui-chkbox-icon {
font-size: 0.75rem;
}
}
.ui-chkbox-label {
@include lions-font-size('sm');
}
}
&.ui-chkbox-lg {
.ui-chkbox-box {
width: 1.5rem;
height: 1.5rem;
.ui-chkbox-icon {
font-size: 1rem;
}
}
.ui-chkbox-label {
@include lions-font-size('lg');
}
}
// State error
&.ui-state-error {
.ui-chkbox-box {
border-color: $lions-danger-500;
&.ui-state-active {
@include lions-gradient-danger;
}
}
.ui-chkbox-label {
color: $lions-danger-700;
}
}
// State disabled
&.ui-state-disabled {
cursor: not-allowed;
opacity: 0.6;
.ui-chkbox-label {
cursor: not-allowed;
color: $lions-gray-500;
}
}
}
// ============================================
// 🔘 RADIO BUTTON STYLES
// ============================================
.ui-radiobutton {
display: inline-flex;
align-items: center;
gap: $lions-spacing-2;
cursor: pointer;
@include lions-transition();
.ui-radiobutton-box {
width: 1.25rem;
height: 1.25rem;
@include lions-rounded('full');
border: 2px solid $lions-gray-400;
background: $lions-white;
@include lions-flex-center;
@include lions-transition();
flex-shrink: 0;
.ui-radiobutton-icon {
width: 0.625rem;
height: 0.625rem;
@include lions-rounded('full');
background: $lions-white;
opacity: 0;
transform: scale(0);
@include lions-transition();
}
&:hover {
border-color: $lions-blue-500;
background: rgba($lions-blue-50, 0.3);
}
&:focus-visible {
@include lions-focus-ring();
}
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-600;
box-shadow: 0 2px 4px rgba($lions-blue-500, 0.2);
.ui-radiobutton-icon {
opacity: 1;
transform: scale(1);
}
}
&.ui-state-disabled {
background: $lions-gray-100;
border-color: $lions-gray-300;
cursor: not-allowed;
opacity: 0.6;
}
}
.ui-radiobutton-label {
cursor: pointer;
color: $lions-text-primary;
@include lions-font-size('base');
@include lions-font-weight('medium');
user-select: none;
@include lions-transition();
&:hover {
color: $lions-blue-700;
}
}
// Size variants
&.ui-radiobutton-sm {
.ui-radiobutton-box {
width: 1rem;
height: 1rem;
.ui-radiobutton-icon {
width: 0.5rem;
height: 0.5rem;
}
}
.ui-radiobutton-label {
@include lions-font-size('sm');
}
}
&.ui-radiobutton-lg {
.ui-radiobutton-box {
width: 1.5rem;
height: 1.5rem;
.ui-radiobutton-icon {
width: 0.75rem;
height: 0.75rem;
}
}
.ui-radiobutton-label {
@include lions-font-size('lg');
}
}
// State error
&.ui-state-error {
.ui-radiobutton-box {
border-color: $lions-danger-500;
&.ui-state-active {
@include lions-gradient-danger;
}
}
.ui-radiobutton-label {
color: $lions-danger-700;
}
}
// State disabled
&.ui-state-disabled {
cursor: not-allowed;
opacity: 0.6;
.ui-radiobutton-label {
cursor: not-allowed;
color: $lions-gray-500;
}
}
}
// ============================================
// 🔀 SWITCH TOGGLE STYLES
// ============================================
.ui-inputswitch {
display: inline-flex;
align-items: center;
gap: $lions-spacing-2;
.ui-inputswitch-slider {
width: 2.75rem;
height: 1.5rem;
@include lions-rounded('full');
background: $lions-gray-300;
border: 2px solid $lions-gray-400;
position: relative;
cursor: pointer;
@include lions-transition();
flex-shrink: 0;
&::before {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 1.125rem;
height: 1.125rem;
@include lions-rounded('full');
background: $lions-white;
@include lions-shadow('sm');
@include lions-transition();
}
&:hover {
background: $lions-gray-400;
border-color: $lions-gray-500;
}
&:focus-visible {
@include lions-focus-ring();
}
&.ui-inputswitch-checked {
@include lions-gradient-blue;
border-color: $lions-blue-600;
&::before {
left: calc(100% - 1.125rem - 2px);
}
&:hover {
background: linear-gradient(135deg, darken($lions-blue-500, 5%) 0%, darken($lions-blue-700, 5%) 100%);
}
}
&.ui-state-disabled {
background: $lions-gray-200;
border-color: $lions-gray-300;
cursor: not-allowed;
opacity: 0.6;
&::before {
background: $lions-gray-300;
}
}
}
// Size variants
&.ui-inputswitch-sm {
.ui-inputswitch-slider {
width: 2.25rem;
height: 1.25rem;
&::before {
width: 0.875rem;
height: 0.875rem;
}
&.ui-inputswitch-checked::before {
left: calc(100% - 0.875rem - 2px);
}
}
}
&.ui-inputswitch-lg {
.ui-inputswitch-slider {
width: 3.25rem;
height: 1.75rem;
&::before {
width: 1.375rem;
height: 1.375rem;
}
&.ui-inputswitch-checked::before {
left: calc(100% - 1.375rem - 2px);
}
}
}
// Success variant
&.ui-inputswitch-success {
.ui-inputswitch-slider.ui-inputswitch-checked {
@include lions-gradient-success;
border-color: $lions-success-600;
}
}
// Danger variant
&.ui-inputswitch-danger {
.ui-inputswitch-slider.ui-inputswitch-checked {
@include lions-gradient-danger;
border-color: $lions-danger-600;
}
}
}
// ============================================
// 🎚️ SLIDER STYLES
// ============================================
.ui-slider {
background: $lions-gray-200;
border: none;
@include lions-rounded('full');
position: relative;
height: 0.5rem;
cursor: pointer;
.ui-slider-range {
@include lions-gradient-blue;
@include lions-rounded('full');
position: absolute;
height: 100%;
}
.ui-slider-handle {
width: 1.25rem;
height: 1.25rem;
@include lions-rounded('full');
background: $lions-white;
border: 3px solid $lions-blue-500;
position: absolute;
top: 50%;
transform: translateY(-50%);
cursor: grab;
@include lions-shadow('md');
@include lions-transition();
&:hover {
transform: translateY(-50%) scale(1.1);
border-color: $lions-blue-600;
@include lions-shadow('lg');
}
&:active {
cursor: grabbing;
transform: translateY(-50%) scale(1.15);
@include lions-shadow('xl');
}
&:focus-visible {
@include lions-focus-ring();
}
}
// Horizontal slider
&.ui-slider-horizontal {
width: 100%;
.ui-slider-handle {
left: 0;
margin-left: -0.625rem;
}
.ui-slider-range {
left: 0;
}
}
// Vertical slider
&.ui-slider-vertical {
width: 0.5rem;
height: 200px;
.ui-slider-handle {
left: 50%;
bottom: 0;
margin-left: -0.625rem;
margin-bottom: -0.625rem;
}
.ui-slider-range {
bottom: 0;
width: 100%;
}
}
// Size variants
&.ui-slider-sm {
height: 0.375rem;
.ui-slider-handle {
width: 1rem;
height: 1rem;
}
&.ui-slider-horizontal .ui-slider-handle {
margin-left: -0.5rem;
}
&.ui-slider-vertical {
width: 0.375rem;
.ui-slider-handle {
margin-left: -0.5rem;
margin-bottom: -0.5rem;
}
}
}
&.ui-slider-lg {
height: 0.625rem;
.ui-slider-handle {
width: 1.5rem;
height: 1.5rem;
}
&.ui-slider-horizontal .ui-slider-handle {
margin-left: -0.75rem;
}
&.ui-slider-vertical {
width: 0.625rem;
.ui-slider-handle {
margin-left: -0.75rem;
margin-bottom: -0.75rem;
}
}
}
// State disabled
&.ui-state-disabled {
opacity: 0.6;
cursor: not-allowed;
.ui-slider-handle {
cursor: not-allowed;
background: $lions-gray-200;
border-color: $lions-gray-400;
}
.ui-slider-range {
background: $lions-gray-400;
}
}
// Color variants
&.ui-slider-success {
.ui-slider-range {
@include lions-gradient-success;
}
.ui-slider-handle {
border-color: $lions-success-500;
}
}
&.ui-slider-danger {
.ui-slider-range {
@include lions-gradient-danger;
}
.ui-slider-handle {
border-color: $lions-danger-500;
}
}
&.ui-slider-warning {
.ui-slider-range {
@include lions-gradient-warning;
}
.ui-slider-handle {
border-color: $lions-warning-500;
}
}
}
// ============================================
// ⭐ RATING STYLES
// ============================================
.ui-rating {
display: inline-flex;
align-items: center;
gap: $lions-spacing-1;
.ui-rating-icon {
font-size: 1.5rem;
color: $lions-gray-300;
cursor: pointer;
@include lions-transition();
&:hover {
color: $lions-gold-400;
transform: scale(1.15);
}
&:active {
transform: scale(1.05);
}
&.ui-rating-icon-active {
color: $lions-gold-500;
text-shadow: 0 2px 4px rgba($lions-gold-500, 0.3);
}
&.ui-rating-icon-hover {
color: $lions-gold-400;
}
}
.ui-rating-cancel {
color: $lions-danger-500;
margin-right: $lions-spacing-2;
&:hover {
color: $lions-danger-600;
transform: scale(1.15);
}
}
// Size variants
&.ui-rating-sm {
.ui-rating-icon {
font-size: 1.125rem;
}
}
&.ui-rating-lg {
.ui-rating-icon {
font-size: 2rem;
}
}
// State disabled
&.ui-state-disabled {
opacity: 0.6;
.ui-rating-icon {
cursor: not-allowed;
&:hover {
transform: none;
color: $lions-gray-300;
}
&.ui-rating-icon-active {
color: $lions-gold-500;
}
}
}
// Readonly
&.ui-rating-readonly {
.ui-rating-icon {
cursor: default;
&:hover {
transform: none;
}
&.ui-rating-icon-active {
color: $lions-gold-500;
}
}
}
// Heart variant
&.ui-rating-hearts {
.ui-rating-icon {
color: $lions-gray-300;
&.ui-rating-icon-active {
color: $lions-danger-500;
text-shadow: 0 2px 4px rgba($lions-danger-500, 0.3);
}
&:hover {
color: $lions-danger-400;
}
}
}
}
// ============================================
// 🔢 SPINNER STYLES
// ============================================
.ui-spinner {
display: inline-flex;
align-items: stretch;
@include lions-rounded('md');
overflow: hidden;
border: 1px solid $lions-gray-300;
background: $lions-white;
@include lions-transition();
&:hover {
border-color: $lions-gray-400;
}
&:focus-within {
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
.ui-spinner-input {
flex: 1;
border: none;
padding: $lions-spacing-3 $lions-spacing-4;
font-family: $lions-font-family-primary;
@include lions-font-size('base');
color: $lions-text-primary;
text-align: center;
min-width: 80px;
&:focus {
outline: none;
}
&::placeholder {
color: $lions-gray-400;
}
}
.ui-spinner-button {
@include lions-button-base;
width: 2.5rem;
background: $lions-gray-100;
border: none;
border-left: 1px solid $lions-gray-300;
color: $lions-gray-700;
@include lions-flex-center;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-blue-50;
color: $lions-blue-600;
}
&:active {
background: $lions-blue-100;
}
&:first-of-type {
border-right: 1px solid $lions-gray-300;
border-left: none;
}
.ui-icon {
font-size: 1rem;
}
}
// Vertical layout
&.ui-spinner-vertical {
flex-direction: column;
.ui-spinner-button {
border-left: none;
border-top: 1px solid $lions-gray-300;
width: 100%;
height: 1.5rem;
&:first-of-type {
border-top: none;
border-bottom: 1px solid $lions-gray-300;
}
}
}
// Size variants
&.ui-spinner-sm {
.ui-spinner-input {
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-font-size('sm');
min-width: 60px;
}
.ui-spinner-button {
width: 2rem;
}
}
&.ui-spinner-lg {
.ui-spinner-input {
padding: $lions-spacing-4 $lions-spacing-5;
@include lions-font-size('lg');
min-width: 100px;
}
.ui-spinner-button {
width: 3rem;
}
}
// State disabled
&.ui-state-disabled {
background: $lions-gray-100;
opacity: 0.6;
cursor: not-allowed;
.ui-spinner-input {
cursor: not-allowed;
color: $lions-gray-500;
}
.ui-spinner-button {
cursor: not-allowed;
background: $lions-gray-100;
color: $lions-gray-400;
&:hover {
background: $lions-gray-100;
color: $lions-gray-400;
}
}
}
// State error
&.ui-state-error {
border-color: $lions-danger-500;
&:focus-within {
box-shadow: 0 0 0 3px rgba($lions-danger-500, 0.15);
}
}
}
// ============================================
// 🔄 TOGGLE BUTTON STYLES
// ============================================
.ui-togglebutton {
@include lions-button-base;
padding: $lions-spacing-3 $lions-spacing-5;
@include lions-rounded('md');
border: 2px solid $lions-gray-400;
background: $lions-white;
color: $lions-text-primary;
@include lions-font-weight('medium');
cursor: pointer;
@include lions-transition();
.ui-button-icon-left {
margin-right: $lions-spacing-2;
@include lions-transition();
}
.ui-button-text {
@include lions-font-size('base');
}
&:hover {
border-color: $lions-blue-500;
background: rgba($lions-blue-50, 0.5);
color: $lions-blue-700;
}
&:active {
transform: scale(0.98);
}
&:focus-visible {
@include lions-focus-ring();
}
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-600;
color: $lions-white;
@include lions-shadow('md');
.ui-button-icon-left {
color: $lions-white;
}
&:hover {
background: linear-gradient(135deg, darken($lions-blue-500, 5%) 0%, darken($lions-blue-700, 5%) 100%);
}
}
// Size variants
&.ui-togglebutton-sm {
padding: $lions-spacing-2 $lions-spacing-4;
.ui-button-text {
@include lions-font-size('sm');
}
.ui-button-icon-left {
font-size: 0.875rem;
}
}
&.ui-togglebutton-lg {
padding: $lions-spacing-4 $lions-spacing-6;
.ui-button-text {
@include lions-font-size('lg');
}
.ui-button-icon-left {
font-size: 1.25rem;
}
}
// State disabled
&.ui-state-disabled {
background: $lions-gray-100;
border-color: $lions-gray-300;
color: $lions-gray-500;
cursor: not-allowed;
opacity: 0.6;
&:hover {
background: $lions-gray-100;
border-color: $lions-gray-300;
color: $lions-gray-500;
transform: none;
}
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-chkbox,
.ui-radiobutton {
gap: $lions-spacing-3;
.ui-chkbox-box,
.ui-radiobutton-box {
width: 1.375rem;
height: 1.375rem;
}
}
.ui-rating {
.ui-rating-icon {
font-size: 1.25rem;
}
}
.ui-slider {
&.ui-slider-vertical {
height: 150px;
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-chkbox,
.ui-radiobutton,
.ui-inputswitch,
.ui-slider,
.ui-rating,
.ui-spinner,
.ui-togglebutton {
// Focus visible
*:focus-visible {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
.ui-chkbox-box,
.ui-radiobutton-box,
.ui-inputswitch-slider,
.ui-slider-handle {
border-width: 3px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
}

View File

@@ -0,0 +1,608 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Messages & Notifications Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Messages, Toast, Growl et composants de notification
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #18-20
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 💬 MESSAGES BASE STYLES
// ============================================
.ui-messages,
.ui-message {
@include lions-rounded('md');
padding: $lions-spacing-4 $lions-spacing-5;
margin-bottom: $lions-spacing-3;
display: flex;
align-items: flex-start;
gap: $lions-spacing-3;
@include lions-transition();
border-left: 4px solid;
@include lions-shadow('sm');
// Icon
.ui-messages-icon,
.ui-message-icon {
font-size: 1.5rem;
margin-top: 2px;
flex-shrink: 0;
}
// Content
.ui-messages-detail,
.ui-message-detail,
.ui-messages-summary,
.ui-message-summary {
@include lions-font-size('base');
margin: 0;
line-height: $lions-line-height-normal;
}
.ui-messages-summary,
.ui-message-summary {
@include lions-font-weight('bold');
margin-bottom: $lions-spacing-1;
}
.ui-messages-detail,
.ui-message-detail {
@include lions-font-weight('normal');
}
// Close button
.ui-messages-close,
.ui-message-close {
@include lions-button-base;
width: 2rem;
height: 2rem;
min-width: 2rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
margin-left: auto;
flex-shrink: 0;
@include lions-transition();
&:hover {
transform: scale(1.1);
}
&:focus-visible {
@include lions-focus-ring();
}
}
}
// ============================================
// 🎨 MESSAGE SEVERITY VARIANTS
// ============================================
// Info messages
.ui-messages-info,
.ui-message-info {
background: rgba($lions-info-50, 0.9);
border-left-color: $lions-info-500;
color: $lions-info-900;
.ui-messages-icon,
.ui-message-icon {
color: $lions-info-600;
}
.ui-messages-close,
.ui-message-close {
color: $lions-info-700;
&:hover {
background: rgba($lions-info-200, 0.8);
}
}
}
// Success messages
.ui-messages-success,
.ui-message-success {
background: rgba($lions-success-50, 0.9);
border-left-color: $lions-success-500;
color: $lions-success-900;
.ui-messages-icon,
.ui-message-icon {
color: $lions-success-600;
}
.ui-messages-close,
.ui-message-close {
color: $lions-success-700;
&:hover {
background: rgba($lions-success-200, 0.8);
}
}
}
// Warning messages
.ui-messages-warn,
.ui-message-warn {
background: rgba($lions-warning-50, 0.9);
border-left-color: $lions-warning-500;
color: $lions-warning-900;
.ui-messages-icon,
.ui-message-icon {
color: $lions-warning-600;
}
.ui-messages-close,
.ui-message-close {
color: $lions-warning-700;
&:hover {
background: rgba($lions-warning-200, 0.8);
}
}
}
// Error messages
.ui-messages-error,
.ui-message-error {
background: rgba($lions-danger-50, 0.9);
border-left-color: $lions-danger-500;
color: $lions-danger-900;
.ui-messages-icon,
.ui-message-icon {
color: $lions-danger-600;
}
.ui-messages-close,
.ui-message-close {
color: $lions-danger-700;
&:hover {
background: rgba($lions-danger-200, 0.8);
}
}
}
// ============================================
// 🔔 TOAST / GROWL NOTIFICATIONS
// ============================================
.ui-toast,
.ui-growl {
position: fixed;
z-index: 9999;
.ui-toast-message,
.ui-growl-item {
@include lions-rounded('lg');
@include lions-shadow('xl');
margin-bottom: $lions-spacing-3;
overflow: hidden;
background: $lions-white;
border-left: 6px solid;
min-width: 300px;
max-width: 400px;
@include lions-transition(all, 300ms);
&:hover {
@include lions-shadow('2xl');
transform: translateY(-2px);
}
}
.ui-toast-message-content,
.ui-growl-item-container {
padding: $lions-spacing-4 $lions-spacing-5;
display: flex;
align-items: flex-start;
gap: $lions-spacing-3;
}
// Icon
.ui-toast-icon,
.ui-growl-icon {
font-size: 1.75rem;
flex-shrink: 0;
margin-top: 2px;
}
// Text container
.ui-toast-message-text,
.ui-growl-message {
flex: 1;
min-width: 0;
.ui-toast-summary,
.ui-growl-title {
@include lions-font-size('lg');
@include lions-font-weight('bold');
margin: 0 0 $lions-spacing-1 0;
line-height: $lions-line-height-tight;
}
.ui-toast-detail,
.ui-growl-detail {
@include lions-font-size('base');
margin: 0;
line-height: $lions-line-height-normal;
color: $lions-text-secondary;
}
}
// Close button
.ui-toast-icon-close,
.ui-growl-icon-close {
@include lions-button-base;
width: 2rem;
height: 2rem;
min-width: 2rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
flex-shrink: 0;
@include lions-transition();
cursor: pointer;
&:hover {
transform: scale(1.15) rotate(90deg);
}
&:focus-visible {
@include lions-focus-ring();
}
}
}
// ============================================
// 🎨 TOAST/GROWL SEVERITY VARIANTS
// ============================================
.ui-toast,
.ui-growl {
// Info notifications
.ui-toast-message-info,
.ui-growl-item-info {
border-left-color: $lions-info-500;
background: linear-gradient(135deg, rgba($lions-info-50, 0.95) 0%, $lions-white 100%);
.ui-toast-icon,
.ui-growl-icon {
color: $lions-info-600;
}
.ui-toast-summary,
.ui-growl-title {
color: $lions-info-800;
}
.ui-toast-icon-close,
.ui-growl-icon-close {
color: $lions-info-700;
&:hover {
background: rgba($lions-info-200, 0.6);
}
}
}
// Success notifications
.ui-toast-message-success,
.ui-growl-item-success {
border-left-color: $lions-success-500;
background: linear-gradient(135deg, rgba($lions-success-50, 0.95) 0%, $lions-white 100%);
.ui-toast-icon,
.ui-growl-icon {
color: $lions-success-600;
}
.ui-toast-summary,
.ui-growl-title {
color: $lions-success-800;
}
.ui-toast-icon-close,
.ui-growl-icon-close {
color: $lions-success-700;
&:hover {
background: rgba($lions-success-200, 0.6);
}
}
}
// Warning notifications
.ui-toast-message-warn,
.ui-growl-item-warn {
border-left-color: $lions-warning-500;
background: linear-gradient(135deg, rgba($lions-warning-50, 0.95) 0%, $lions-white 100%);
.ui-toast-icon,
.ui-growl-icon {
color: $lions-warning-600;
}
.ui-toast-summary,
.ui-growl-title {
color: $lions-warning-800;
}
.ui-toast-icon-close,
.ui-growl-icon-close {
color: $lions-warning-700;
&:hover {
background: rgba($lions-warning-200, 0.6);
}
}
}
// Error notifications
.ui-toast-message-error,
.ui-growl-item-error {
border-left-color: $lions-danger-500;
background: linear-gradient(135deg, rgba($lions-danger-50, 0.95) 0%, $lions-white 100%);
.ui-toast-icon,
.ui-growl-icon {
color: $lions-danger-600;
}
.ui-toast-summary,
.ui-growl-title {
color: $lions-danger-800;
}
.ui-toast-icon-close,
.ui-growl-icon-close {
color: $lions-danger-700;
&:hover {
background: rgba($lions-danger-200, 0.6);
}
}
}
}
// ============================================
// 📍 TOAST/GROWL POSITIONS
// ============================================
.ui-toast,
.ui-growl {
&.ui-toast-top-right,
&.ui-growl-top-right {
top: $lions-spacing-5;
right: $lions-spacing-5;
}
&.ui-toast-top-left,
&.ui-growl-top-left {
top: $lions-spacing-5;
left: $lions-spacing-5;
}
&.ui-toast-bottom-right,
&.ui-growl-bottom-right {
bottom: $lions-spacing-5;
right: $lions-spacing-5;
}
&.ui-toast-bottom-left,
&.ui-growl-bottom-left {
bottom: $lions-spacing-5;
left: $lions-spacing-5;
}
&.ui-toast-top-center,
&.ui-growl-top-center {
top: $lions-spacing-5;
left: 50%;
transform: translateX(-50%);
}
&.ui-toast-bottom-center,
&.ui-growl-bottom-center {
bottom: $lions-spacing-5;
left: 50%;
transform: translateX(-50%);
}
&.ui-toast-center,
&.ui-growl-center {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
// ============================================
// ✨ TOAST/GROWL ANIMATIONS
// ============================================
@keyframes lions-toast-fade-in {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes lions-toast-fade-out {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-20px);
}
}
.ui-toast-message,
.ui-growl-item {
animation: lions-toast-fade-in 300ms ease-out;
&.ui-toast-message-exit,
&.ui-state-leaving {
animation: lions-toast-fade-out 200ms ease-in;
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-toast,
.ui-growl {
&.ui-toast-top-right,
&.ui-toast-top-left,
&.ui-toast-bottom-right,
&.ui-toast-bottom-left,
&.ui-growl-top-right,
&.ui-growl-top-left,
&.ui-growl-bottom-right,
&.ui-growl-bottom-left {
left: $lions-spacing-3 !important;
right: $lions-spacing-3 !important;
transform: none !important;
}
.ui-toast-message,
.ui-growl-item {
min-width: auto;
max-width: none;
}
}
.ui-messages,
.ui-message {
padding: $lions-spacing-3 $lions-spacing-4;
@include lions-font-size('sm');
.ui-messages-icon,
.ui-message-icon {
font-size: 1.25rem;
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-messages,
.ui-message,
.ui-toast-message,
.ui-growl-item {
// High contrast mode
@media (prefers-contrast: high) {
border-width: 3px !important;
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
transition: none !important;
animation: none !important;
&:hover {
transform: none !important;
}
}
}
// ARIA live regions
[role="alert"],
[aria-live] {
// Ensure screen readers can access messages
&:empty {
display: none;
}
}
// ============================================
// 🎯 MESSAGE LIST STYLES
// ============================================
.ui-messages {
&.ui-messages-list {
.ui-messages-item {
margin-bottom: $lions-spacing-2;
&:last-child {
margin-bottom: 0;
}
}
}
}
// ============================================
// 🔧 UTILITY CLASSES
// ============================================
// Compact messages
.ui-messages-compact,
.ui-message-compact {
padding: $lions-spacing-2 $lions-spacing-3;
.ui-messages-icon,
.ui-message-icon {
font-size: 1.25rem;
}
.ui-messages-summary,
.ui-message-summary {
@include lions-font-size('sm');
}
.ui-messages-detail,
.ui-message-detail {
@include lions-font-size('xs');
}
}
// Large messages
.ui-messages-large,
.ui-message-large {
padding: $lions-spacing-5 $lions-spacing-6;
.ui-messages-icon,
.ui-message-icon {
font-size: 2rem;
}
.ui-messages-summary,
.ui-message-summary {
@include lions-font-size('xl');
}
.ui-messages-detail,
.ui-message-detail {
@include lions-font-size('lg');
}
}
// Inline messages (no margin)
.ui-messages-inline,
.ui-message-inline {
margin: 0;
display: inline-flex;
}

View File

@@ -0,0 +1,633 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Navigation Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Menu, Menubar, TabView et composants de navigation
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #25-27
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 📋 MENU VERTICAL STYLES
// ============================================
.ui-menu {
background: $lions-white;
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('md');
padding: $lions-spacing-2 0;
.ui-menuitem {
.ui-menuitem-link {
padding: $lions-spacing-3 $lions-spacing-5;
display: flex;
align-items: center;
gap: $lions-spacing-3;
color: $lions-text-primary;
text-decoration: none;
@include lions-transition();
cursor: pointer;
.ui-menuitem-icon {
font-size: 1.125rem;
color: $lions-gray-600;
@include lions-transition();
}
.ui-menuitem-text {
@include lions-font-size('base');
@include lions-font-weight('medium');
}
&:hover {
background: rgba($lions-blue-50, 0.8);
color: $lions-blue-700;
.ui-menuitem-icon {
color: $lions-blue-600;
}
}
&:focus-visible {
@include lions-focus-ring();
}
&.ui-state-active,
&.ui-state-highlight {
background: linear-gradient(135deg, rgba($lions-blue-100, 0.8) 0%, rgba($lions-blue-50, 0.6) 100%);
color: $lions-blue-800;
border-left: 4px solid $lions-blue-500;
padding-left: calc(#{$lions-spacing-5} - 4px);
.ui-menuitem-icon {
color: $lions-blue-600;
}
}
}
// Submenu indicator
&.ui-menu-parent {
.ui-menuitem-link {
position: relative;
&::after {
content: '\e902'; // PrimeIcons chevron-right
font-family: 'primeicons';
position: absolute;
right: $lions-spacing-4;
color: $lions-gray-500;
@include lions-transition();
}
&:hover::after {
color: $lions-blue-600;
}
}
}
}
// Submenu
.ui-menu-child {
background: $lions-white;
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('lg');
padding: $lions-spacing-2 0;
margin-left: $lions-spacing-2;
.ui-menuitem-link {
padding-left: $lions-spacing-6;
}
}
// Separator
.ui-menu-separator {
border-top: 1px solid $lions-gray-300;
margin: $lions-spacing-2 0;
}
}
// ============================================
// 📊 MENUBAR HORIZONTAL STYLES
// ============================================
.ui-menubar {
background: linear-gradient(135deg, rgba($lions-blue-500, 0.08) 0%, rgba($lions-blue-700, 0.04) 100%);
border: 1px solid $lions-gray-300;
border-left: none;
border-right: none;
padding: $lions-spacing-2 $lions-spacing-5;
@include lions-shadow('sm');
.ui-menubar-root-list {
display: flex;
align-items: center;
gap: $lions-spacing-1;
list-style: none;
margin: 0;
padding: 0;
> .ui-menuitem {
> .ui-menuitem-link {
padding: $lions-spacing-3 $lions-spacing-4;
display: flex;
align-items: center;
gap: $lions-spacing-2;
color: $lions-text-primary;
text-decoration: none;
@include lions-rounded('md');
@include lions-transition();
cursor: pointer;
@include lions-font-weight('medium');
.ui-menuitem-icon {
font-size: 1.125rem;
color: $lions-gray-600;
@include lions-transition();
}
.ui-menuitem-text {
@include lions-font-size('base');
}
&:hover {
background: rgba($lions-blue-100, 0.6);
color: $lions-blue-700;
.ui-menuitem-icon {
color: $lions-blue-600;
}
}
&:focus-visible {
@include lions-focus-ring();
}
&.ui-state-active,
&.ui-state-highlight {
background: linear-gradient(135deg, rgba($lions-blue-500, 0.15) 0%, rgba($lions-blue-600, 0.1) 100%);
color: $lions-blue-800;
.ui-menuitem-icon {
color: $lions-blue-600;
}
}
}
// Submenu indicator
&.ui-menu-parent {
> .ui-menuitem-link {
&::after {
content: '\e933'; // PrimeIcons chevron-down
font-family: 'primeicons';
margin-left: $lions-spacing-1;
color: $lions-gray-500;
@include lions-transition();
}
&:hover::after {
color: $lions-blue-600;
}
}
}
}
}
// Dropdown submenu
.ui-menu-child {
background: $lions-white;
border: 1px solid $lions-gray-300;
@include lions-rounded('md');
@include lions-shadow('xl');
padding: $lions-spacing-2 0;
margin-top: $lions-spacing-1;
min-width: 200px;
.ui-menuitem-link {
padding: $lions-spacing-3 $lions-spacing-5;
display: flex;
align-items: center;
gap: $lions-spacing-3;
color: $lions-text-primary;
text-decoration: none;
@include lions-transition();
.ui-menuitem-icon {
font-size: 1rem;
color: $lions-gray-600;
}
&:hover {
background: rgba($lions-blue-50, 0.8);
color: $lions-blue-700;
.ui-menuitem-icon {
color: $lions-blue-600;
}
}
}
.ui-menu-separator {
border-top: 1px solid $lions-gray-300;
margin: $lions-spacing-2 0;
}
}
// Right-aligned items
.ui-menubar-options {
margin-left: auto;
display: flex;
align-items: center;
gap: $lions-spacing-2;
}
}
// ============================================
// 📑 TABVIEW STYLES
// ============================================
.ui-tabview {
background: $lions-white;
@include lions-rounded('md');
@include lions-shadow('sm');
overflow: hidden;
// Tab navigation
.ui-tabview-nav {
background: linear-gradient(135deg, rgba($lions-gray-100, 0.8) 0%, rgba($lions-gray-50, 0.6) 100%);
border-bottom: 2px solid $lions-gray-300;
padding: 0;
margin: 0;
list-style: none;
display: flex;
gap: $lions-spacing-1;
padding: $lions-spacing-2 $lions-spacing-4;
.ui-tabview-nav-link {
padding: $lions-spacing-3 $lions-spacing-5;
display: flex;
align-items: center;
gap: $lions-spacing-2;
color: $lions-text-secondary;
text-decoration: none;
@include lions-rounded('md');
@include lions-rounded-bottom(0);
@include lions-transition();
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
position: relative;
@include lions-font-weight('medium');
.ui-tabview-left-icon {
font-size: 1.125rem;
color: $lions-gray-600;
@include lions-transition();
}
.ui-tabview-title {
@include lions-font-size('base');
}
&:hover {
background: rgba($lions-blue-50, 0.6);
color: $lions-blue-700;
.ui-tabview-left-icon {
color: $lions-blue-600;
}
}
&:focus-visible {
@include lions-focus-ring();
}
}
.ui-state-active {
.ui-tabview-nav-link {
background: $lions-white;
color: $lions-blue-700;
border-color: $lions-gray-300;
border-bottom-color: $lions-white;
@include lions-font-weight('bold');
&::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 3px;
background: $lions-blue-500;
@include lions-rounded('full');
}
.ui-tabview-left-icon {
color: $lions-blue-600;
}
}
}
// Close icon
.ui-tabview-close {
@include lions-button-base;
width: 1.5rem;
height: 1.5rem;
min-width: 1.5rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
color: $lions-gray-500;
@include lions-transition();
margin-left: $lions-spacing-2;
&:hover {
background: rgba($lions-danger-500, 0.1);
color: $lions-danger-600;
transform: scale(1.1);
}
}
}
// Tab panels
.ui-tabview-panels {
padding: $lions-spacing-6;
background: $lions-white;
.ui-tabview-panel {
&:not(.ui-state-active) {
display: none;
}
}
}
// Position variants
&.ui-tabview-top {
.ui-tabview-nav {
border-bottom: 2px solid $lions-gray-300;
.ui-state-active .ui-tabview-nav-link::after {
bottom: -2px;
}
}
}
&.ui-tabview-bottom {
display: flex;
flex-direction: column-reverse;
.ui-tabview-nav {
border-top: 2px solid $lions-gray-300;
border-bottom: none;
.ui-tabview-nav-link {
@include lions-rounded('md');
@include lions-rounded-top(0);
}
.ui-state-active .ui-tabview-nav-link {
border-bottom-color: $lions-gray-300;
border-top-color: $lions-white;
&::after {
bottom: auto;
top: -2px;
}
}
}
}
&.ui-tabview-left {
display: flex;
.ui-tabview-nav {
border-right: 2px solid $lions-gray-300;
border-bottom: none;
flex-direction: column;
padding: $lions-spacing-4 $lions-spacing-2;
.ui-tabview-nav-link {
@include lions-rounded('md');
@include lions-rounded-right(0);
}
.ui-state-active .ui-tabview-nav-link {
border-right-color: $lions-white;
&::after {
right: -2px;
left: auto;
top: 0;
bottom: 0;
width: 3px;
height: auto;
}
}
}
}
&.ui-tabview-right {
display: flex;
flex-direction: row-reverse;
.ui-tabview-nav {
border-left: 2px solid $lions-gray-300;
border-bottom: none;
flex-direction: column;
padding: $lions-spacing-4 $lions-spacing-2;
.ui-tabview-nav-link {
@include lions-rounded('md');
@include lions-rounded-left(0);
}
.ui-state-active .ui-tabview-nav-link {
border-left-color: $lions-white;
&::after {
left: -2px;
right: auto;
top: 0;
bottom: 0;
width: 3px;
height: auto;
}
}
}
}
}
// ============================================
// 🎨 MENU SEVERITY VARIANTS
// ============================================
.ui-menu,
.ui-menubar {
// Primary menu items
.ui-menuitem-primary {
.ui-menuitem-link {
background: rgba($lions-blue-100, 0.3);
color: $lions-blue-700;
.ui-menuitem-icon {
color: $lions-blue-600;
}
&:hover {
background: rgba($lions-blue-100, 0.6);
}
}
}
// Success menu items
.ui-menuitem-success {
.ui-menuitem-link {
background: rgba($lions-success-100, 0.3);
color: $lions-success-700;
.ui-menuitem-icon {
color: $lions-success-600;
}
&:hover {
background: rgba($lions-success-100, 0.6);
}
}
}
// Danger menu items
.ui-menuitem-danger {
.ui-menuitem-link {
background: rgba($lions-danger-100, 0.3);
color: $lions-danger-700;
.ui-menuitem-icon {
color: $lions-danger-600;
}
&:hover {
background: rgba($lions-danger-100, 0.6);
}
}
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-menubar {
.ui-menubar-root-list {
flex-direction: column;
align-items: stretch;
gap: $lions-spacing-1;
> .ui-menuitem {
> .ui-menuitem-link {
width: 100%;
}
}
}
.ui-menubar-options {
margin-left: 0;
margin-top: $lions-spacing-2;
padding-top: $lions-spacing-2;
border-top: 1px solid $lions-gray-300;
}
}
.ui-tabview {
.ui-tabview-nav {
overflow-x: auto;
flex-wrap: nowrap;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-track {
background: $lions-gray-100;
}
&::-webkit-scrollbar-thumb {
background: $lions-gray-400;
@include lions-rounded('full');
}
}
.ui-tabview-panels {
padding: $lions-spacing-4;
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-menu,
.ui-menubar,
.ui-tabview {
// Focus visible
.ui-menuitem-link:focus-visible,
.ui-tabview-nav-link:focus-visible {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
.ui-menuitem-link,
.ui-tabview-nav-link {
border-width: 2px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
}
// ============================================
// 🎯 UTILITY CLASSES
// ============================================
// Compact menus
.ui-menu-compact {
.ui-menuitem-link {
padding: $lions-spacing-2 $lions-spacing-4;
@include lions-font-size('sm');
}
}
// Large menus
.ui-menu-large {
.ui-menuitem-link {
padding: $lions-spacing-4 $lions-spacing-6;
@include lions-font-size('lg');
}
}
// Vertical menubar (stacked)
.ui-menubar-vertical {
.ui-menubar-root-list {
flex-direction: column;
align-items: stretch;
}
}

View File

@@ -0,0 +1,704 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Overlay Components Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Dialog, ConfirmDialog, Sidebar, OverlayPanel
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* Priority: CRITIQUE #21-24
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 🌑 OVERLAY MASK (BACKDROP)
// ============================================
.ui-dialog-mask,
.ui-confirmdialog-mask,
.ui-sidebar-mask,
.ui-overlaypanel-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
backdrop-filter: blur(3px);
@include lions-transition(opacity, 200ms);
&.ui-dialog-mask-scrollblocker {
overflow: hidden;
}
}
// ============================================
// 💬 DIALOG STYLES
// ============================================
.ui-dialog {
position: fixed;
background: $lions-white;
@include lions-rounded('lg');
@include lions-shadow('2xl');
z-index: 1001;
overflow: hidden;
max-height: 90vh;
display: flex;
flex-direction: column;
@include lions-transition(transform, 300ms);
// Dialog header
.ui-dialog-titlebar {
background: linear-gradient(135deg, rgba($lions-blue-500, 0.1) 0%, rgba($lions-blue-700, 0.05) 100%);
border-bottom: 2px solid $lions-blue-500;
padding: $lions-spacing-5 $lions-spacing-6;
display: flex;
justify-content: space-between;
align-items: center;
.ui-dialog-title {
@include lions-font-size('xl');
@include lions-font-weight('bold');
color: $lions-text-primary;
margin: 0;
display: flex;
align-items: center;
gap: $lions-spacing-3;
i {
color: $lions-blue-500;
font-size: 1.5rem;
}
}
.ui-dialog-titlebar-icon {
@include lions-button-base;
width: 2.5rem;
height: 2.5rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
color: $lions-gray-600;
@include lions-transition();
&:hover {
background: rgba($lions-danger-500, 0.1);
color: $lions-danger-600;
transform: scale(1.1) rotate(90deg);
}
&:focus-visible {
@include lions-focus-ring();
}
}
}
// Dialog content
.ui-dialog-content {
padding: $lions-spacing-6;
overflow-y: auto;
flex: 1;
color: $lions-text-primary;
line-height: $lions-line-height-normal;
// Custom scrollbar
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: $lions-gray-100;
}
&::-webkit-scrollbar-thumb {
background: $lions-gray-400;
@include lions-rounded('full');
&:hover {
background: $lions-blue-500;
}
}
}
// Dialog footer
.ui-dialog-footer {
background: linear-gradient(135deg, rgba($lions-gray-50, 0.8) 0%, rgba($lions-gray-100, 0.5) 100%);
border-top: 1px solid $lions-gray-300;
padding: $lions-spacing-4 $lions-spacing-6;
display: flex;
justify-content: flex-end;
gap: $lions-spacing-3;
}
// Resizable handle
.ui-resizable-handle {
position: absolute;
background: transparent;
&.ui-resizable-se {
bottom: 0;
right: 0;
width: 20px;
height: 20px;
cursor: se-resize;
&::after {
content: '';
position: absolute;
bottom: 4px;
right: 4px;
width: 12px;
height: 12px;
border-right: 2px solid $lions-gray-400;
border-bottom: 2px solid $lions-gray-400;
}
}
}
}
// ============================================
// ✅ CONFIRM DIALOG STYLES
// ============================================
.ui-confirmdialog {
@extend .ui-dialog;
max-width: 500px;
.ui-confirmdialog-icon {
font-size: 3rem;
margin-right: $lions-spacing-4;
flex-shrink: 0;
}
.ui-confirmdialog-message {
flex: 1;
@include lions-font-size('lg');
color: $lions-text-primary;
line-height: $lions-line-height-relaxed;
}
// Severity variants
&.ui-confirmdialog-info {
.ui-dialog-titlebar {
background: linear-gradient(135deg, rgba($lions-info-500, 0.1) 0%, rgba($lions-info-700, 0.05) 100%);
border-bottom-color: $lions-info-500;
}
.ui-confirmdialog-icon {
color: $lions-info-600;
}
}
&.ui-confirmdialog-warn {
.ui-dialog-titlebar {
background: linear-gradient(135deg, rgba($lions-warning-500, 0.1) 0%, rgba($lions-warning-700, 0.05) 100%);
border-bottom-color: $lions-warning-500;
}
.ui-confirmdialog-icon {
color: $lions-warning-600;
}
}
&.ui-confirmdialog-error {
.ui-dialog-titlebar {
background: linear-gradient(135deg, rgba($lions-danger-500, 0.1) 0%, rgba($lions-danger-700, 0.05) 100%);
border-bottom-color: $lions-danger-500;
}
.ui-confirmdialog-icon {
color: $lions-danger-600;
}
}
}
// ============================================
// 📱 SIDEBAR STYLES
// ============================================
.ui-sidebar {
position: fixed;
background: $lions-white;
@include lions-shadow('2xl');
z-index: 1001;
overflow: hidden;
display: flex;
flex-direction: column;
@include lions-transition(transform, 300ms);
// Sidebar header
.ui-sidebar-header {
background: linear-gradient(135deg, rgba($lions-blue-500, 0.1) 0%, rgba($lions-blue-700, 0.05) 100%);
border-bottom: 2px solid $lions-blue-500;
padding: $lions-spacing-5 $lions-spacing-6;
display: flex;
justify-content: space-between;
align-items: center;
h3, h4, h5 {
margin: 0;
@include lions-font-weight('bold');
color: $lions-text-primary;
}
.ui-sidebar-close {
@include lions-button-base;
width: 2.5rem;
height: 2.5rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
color: $lions-gray-600;
@include lions-transition();
&:hover {
background: rgba($lions-danger-500, 0.1);
color: $lions-danger-600;
transform: scale(1.1) rotate(90deg);
}
&:focus-visible {
@include lions-focus-ring();
}
}
}
// Sidebar content
.ui-sidebar-content {
padding: $lions-spacing-6;
overflow-y: auto;
flex: 1;
// Custom scrollbar
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: $lions-gray-100;
}
&::-webkit-scrollbar-thumb {
background: $lions-gray-400;
@include lions-rounded('full');
&:hover {
background: $lions-blue-500;
}
}
}
// Sidebar footer
.ui-sidebar-footer {
background: linear-gradient(135deg, rgba($lions-gray-50, 0.8) 0%, rgba($lions-gray-100, 0.5) 100%);
border-top: 1px solid $lions-gray-300;
padding: $lions-spacing-4 $lions-spacing-6;
}
// Position variants
&.ui-sidebar-left {
top: 0;
left: 0;
height: 100%;
width: 400px;
border-right: 1px solid $lions-gray-300;
&.ui-sidebar-active {
transform: translateX(0);
}
&:not(.ui-sidebar-active) {
transform: translateX(-100%);
}
}
&.ui-sidebar-right {
top: 0;
right: 0;
height: 100%;
width: 400px;
border-left: 1px solid $lions-gray-300;
&.ui-sidebar-active {
transform: translateX(0);
}
&:not(.ui-sidebar-active) {
transform: translateX(100%);
}
}
&.ui-sidebar-top {
top: 0;
left: 0;
width: 100%;
height: 300px;
border-bottom: 1px solid $lions-gray-300;
&.ui-sidebar-active {
transform: translateY(0);
}
&:not(.ui-sidebar-active) {
transform: translateY(-100%);
}
}
&.ui-sidebar-bottom {
bottom: 0;
left: 0;
width: 100%;
height: 300px;
border-top: 1px solid $lions-gray-300;
&.ui-sidebar-active {
transform: translateY(0);
}
&:not(.ui-sidebar-active) {
transform: translateY(100%);
}
}
&.ui-sidebar-full {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
// ============================================
// 📌 OVERLAY PANEL STYLES
// ============================================
.ui-overlaypanel {
position: absolute;
background: $lions-white;
@include lions-rounded('lg');
@include lions-shadow('xl');
z-index: 1002;
padding: 0;
@include lions-transition(opacity, 200ms);
// Arrow pointer
&::before,
&::after {
content: '';
position: absolute;
width: 0;
height: 0;
border: solid transparent;
}
&::before {
border-width: 11px;
}
&::after {
border-width: 10px;
}
// Arrow positions
&.ui-overlaypanel-flipped {
&::before,
&::after {
bottom: 100%;
left: 50%;
}
&::before {
margin-left: -11px;
border-bottom-color: $lions-gray-300;
}
&::after {
margin-left: -10px;
border-bottom-color: $lions-white;
}
}
&:not(.ui-overlaypanel-flipped) {
&::before,
&::after {
top: 100%;
left: 50%;
}
&::before {
margin-left: -11px;
border-top-color: $lions-gray-300;
}
&::after {
margin-left: -10px;
border-top-color: $lions-white;
}
}
// Header
.ui-overlaypanel-header {
background: linear-gradient(135deg, rgba($lions-blue-500, 0.1) 0%, rgba($lions-blue-700, 0.05) 100%);
border-bottom: 2px solid $lions-blue-500;
padding: $lions-spacing-4 $lions-spacing-5;
@include lions-rounded('lg');
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
display: flex;
justify-content: space-between;
align-items: center;
h5, h6 {
margin: 0;
@include lions-font-weight('bold');
color: $lions-text-primary;
}
.ui-overlaypanel-close {
@include lions-button-base;
width: 2rem;
height: 2rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: none;
color: $lions-gray-600;
@include lions-transition();
&:hover {
background: rgba($lions-danger-500, 0.1);
color: $lions-danger-600;
transform: scale(1.1);
}
&:focus-visible {
@include lions-focus-ring();
}
}
}
// Content
.ui-overlaypanel-content {
padding: $lions-spacing-5;
max-height: 400px;
overflow-y: auto;
color: $lions-text-primary;
// Custom scrollbar
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: $lions-gray-100;
}
&::-webkit-scrollbar-thumb {
background: $lions-gray-400;
@include lions-rounded('full');
&:hover {
background: $lions-blue-500;
}
}
}
// Footer
.ui-overlaypanel-footer {
background: linear-gradient(135deg, rgba($lions-gray-50, 0.8) 0%, rgba($lions-gray-100, 0.5) 100%);
border-top: 1px solid $lions-gray-300;
padding: $lions-spacing-3 $lions-spacing-5;
@include lions-rounded('lg');
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-dialog {
max-width: 95vw !important;
max-height: 95vh !important;
margin: $lions-spacing-3 !important;
.ui-dialog-titlebar {
padding: $lions-spacing-4 $lions-spacing-5;
.ui-dialog-title {
@include lions-font-size('lg');
}
}
.ui-dialog-content {
padding: $lions-spacing-5;
}
.ui-dialog-footer {
padding: $lions-spacing-3 $lions-spacing-5;
flex-wrap: wrap;
}
}
.ui-sidebar {
&.ui-sidebar-left,
&.ui-sidebar-right {
width: 85vw !important;
}
&.ui-sidebar-top,
&.ui-sidebar-bottom {
height: 50vh !important;
}
.ui-sidebar-header,
.ui-sidebar-content,
.ui-sidebar-footer {
padding: $lions-spacing-4 $lions-spacing-5;
}
}
.ui-overlaypanel {
max-width: 90vw !important;
.ui-overlaypanel-content {
max-height: 300px;
padding: $lions-spacing-4;
}
}
}
// ============================================
// ✨ ANIMATIONS
// ============================================
@keyframes lions-dialog-fade-in {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes lions-dialog-fade-out {
from {
opacity: 1;
transform: scale(1) translateY(0);
}
to {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
}
.ui-dialog,
.ui-confirmdialog {
animation: lions-dialog-fade-in 300ms ease-out;
&.ui-dialog-exit {
animation: lions-dialog-fade-out 200ms ease-in;
}
}
.ui-overlaypanel {
animation: lions-dialog-fade-in 200ms ease-out;
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-dialog,
.ui-confirmdialog,
.ui-sidebar,
.ui-overlaypanel {
// Focus trap
&[aria-modal="true"] {
outline: none;
}
// High contrast mode
@media (prefers-contrast: high) {
border: 3px solid currentColor !important;
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
animation: none !important;
transition: none !important;
* {
animation: none !important;
transition: none !important;
}
}
}
// ============================================
// 🎯 DIALOG POSITIONS
// ============================================
.ui-dialog {
&.ui-dialog-center {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&.ui-dialog-top {
top: $lions-spacing-6;
left: 50%;
transform: translateX(-50%);
}
&.ui-dialog-bottom {
bottom: $lions-spacing-6;
left: 50%;
transform: translateX(-50%);
}
&.ui-dialog-left {
top: 50%;
left: $lions-spacing-6;
transform: translateY(-50%);
}
&.ui-dialog-right {
top: 50%;
right: $lions-spacing-6;
transform: translateY(-50%);
}
&.ui-dialog-top-left {
top: $lions-spacing-6;
left: $lions-spacing-6;
}
&.ui-dialog-top-right {
top: $lions-spacing-6;
right: $lions-spacing-6;
}
&.ui-dialog-bottom-left {
bottom: $lions-spacing-6;
left: $lions-spacing-6;
}
&.ui-dialog-bottom-right {
bottom: $lions-spacing-6;
right: $lions-spacing-6;
}
}

View File

@@ -0,0 +1,229 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Progress Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour ProgressBar (barre de progression)
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: MOYENNE #39
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 📊 PROGRESSBAR STYLES
// ============================================
.ui-progressbar {
position: relative;
overflow: hidden;
height: 1rem;
@include lions-rounded('md');
background: $lions-gray-200;
@include lions-shadow('sm');
.ui-progressbar-value {
height: 100%;
@include lions-gradient-blue;
@include lions-rounded('md');
@include lions-transition();
position: relative;
overflow: hidden;
// Animated stripes
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-image: linear-gradient(
45deg,
rgba($lions-white, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba($lions-white, 0.15) 50%,
rgba($lions-white, 0.15) 75%,
transparent 75%,
transparent
);
background-size: 1rem 1rem;
}
}
.ui-progressbar-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
@include lions-font-size('xs');
@include lions-font-weight('semibold');
color: $lions-white;
text-shadow: 0 1px 2px rgba($lions-black, 0.3);
z-index: 2;
white-space: nowrap;
}
// Severity variants
&.ui-progressbar-success {
.ui-progressbar-value {
@include lions-gradient-success;
}
}
&.ui-progressbar-info {
.ui-progressbar-value {
@include lions-gradient-info;
}
}
&.ui-progressbar-warning {
.ui-progressbar-value {
@include lions-gradient-warning;
}
}
&.ui-progressbar-danger {
.ui-progressbar-value {
@include lions-gradient-danger;
}
}
// Size variants
&.ui-progressbar-sm {
height: 0.5rem;
.ui-progressbar-label {
@include lions-font-size('2xs');
}
.ui-progressbar-value::after {
background-size: 0.75rem 0.75rem;
}
}
&.ui-progressbar-lg {
height: 1.5rem;
.ui-progressbar-label {
@include lions-font-size('sm');
}
.ui-progressbar-value::after {
background-size: 1.5rem 1.5rem;
}
}
// Animated stripes
&.ui-progressbar-striped {
.ui-progressbar-value::after {
animation: lions-progress-stripes 1s linear infinite;
}
}
// Indeterminate mode
&.ui-progressbar-indeterminate {
.ui-progressbar-value {
width: 100% !important;
background: $lions-gray-200;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 30%;
@include lions-gradient-blue;
animation: lions-progress-indeterminate 1.5s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
&::after {
display: none;
}
}
&.ui-progressbar-success .ui-progressbar-value::before {
@include lions-gradient-success;
}
&.ui-progressbar-info .ui-progressbar-value::before {
@include lions-gradient-info;
}
&.ui-progressbar-warning .ui-progressbar-value::before {
@include lions-gradient-warning;
}
&.ui-progressbar-danger .ui-progressbar-value::before {
@include lions-gradient-danger;
}
}
}
// ============================================
// 🎬 ANIMATIONS
// ============================================
@keyframes lions-progress-stripes {
0% {
background-position: 1rem 0;
}
100% {
background-position: 0 0;
}
}
@keyframes lions-progress-indeterminate {
0% {
left: -30%;
}
100% {
left: 100%;
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-progressbar {
&.ui-progressbar-lg {
height: 1.25rem;
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-progressbar {
// High contrast mode
@media (prefers-contrast: high) {
border: 2px solid currentColor;
.ui-progressbar-value {
border: 1px solid currentColor;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
.ui-progressbar-value::after {
animation: none !important;
}
&.ui-progressbar-indeterminate .ui-progressbar-value::before {
animation: none !important;
left: 0;
width: 100%;
}
}
}

View File

@@ -0,0 +1,677 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Tree Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour Tree et TreeTable (affichage hiérarchique)
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: MOYENNE #31-32
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// 🌳 TREE STYLES
// ============================================
.ui-tree {
@include lions-rounded('lg');
border: 1px solid $lions-gray-200;
background: $lions-white;
padding: $lions-spacing-3;
@include lions-shadow('sm');
.ui-tree-container {
list-style: none;
margin: 0;
padding: 0;
}
.ui-treenode {
margin: 0;
padding: 0;
list-style: none;
.ui-treenode-content {
display: flex;
align-items: center;
gap: $lions-spacing-2;
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-rounded('md');
@include lions-transition();
cursor: pointer;
position: relative;
&:hover {
background: $lions-gray-50;
}
&.ui-treenode-selectable {
&.ui-state-highlight {
background: $lions-blue-50;
color: $lions-blue-700;
@include lions-font-weight('semibold');
.ui-treenode-icon {
color: $lions-blue-600;
}
}
}
&:focus-visible {
@include lions-focus-ring();
}
}
.ui-tree-toggler {
@include lions-button-base;
width: 1.5rem;
height: 1.5rem;
min-width: 1.5rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: 1px solid transparent;
@include lions-rounded('sm');
color: $lions-gray-600;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-gray-100;
border-color: $lions-gray-300;
color: $lions-blue-600;
}
.ui-tree-toggler-icon {
font-size: 0.875rem;
@include lions-transition();
}
&.ui-tree-toggler-expanded {
.ui-tree-toggler-icon {
transform: rotate(90deg);
}
}
}
.ui-treenode-icon {
font-size: 1rem;
color: $lions-gray-500;
@include lions-transition();
}
.ui-treenode-label {
flex: 1;
@include lions-font-size('base');
color: $lions-text-primary;
@include lions-transition();
}
// Child nodes
.ui-treenode-children {
list-style: none;
margin: 0;
padding-left: $lions-spacing-6;
position: relative;
// Indent guide lines
&::before {
content: '';
position: absolute;
left: $lions-spacing-3;
top: 0;
bottom: $lions-spacing-2;
width: 1px;
background: $lions-gray-200;
}
}
// Leaf nodes (no children)
&.ui-treenode-leaf {
.ui-tree-toggler {
visibility: hidden;
}
}
}
// Drag & Drop support
&.ui-tree-dragdrop {
.ui-treenode-content {
&.ui-treenode-droppable-active {
background: rgba($lions-blue-50, 0.5);
border: 2px dashed $lions-blue-400;
}
}
.ui-treenode-droppoint {
height: 2px;
background: $lions-blue-500;
margin: $lions-spacing-1 0;
@include lions-rounded('full');
opacity: 0;
@include lions-transition();
&.ui-treenode-droppoint-active {
opacity: 1;
}
}
}
// Filter input
.ui-tree-filter-container {
margin-bottom: $lions-spacing-3;
.ui-tree-filter {
width: 100%;
height: $lions-input-height-base;
padding: $lions-spacing-3 $lions-spacing-4;
padding-left: 2.5rem;
@include lions-rounded('md');
border: 1px solid $lions-gray-300;
@include lions-font-size('base');
color: $lions-text-primary;
background: $lions-white;
@include lions-transition();
&::placeholder {
color: $lions-gray-400;
}
&:focus {
outline: none;
border-color: $lions-blue-500;
box-shadow: 0 0 0 3px rgba($lions-blue-500, 0.15);
}
}
.ui-tree-filter-icon {
position: absolute;
left: $lions-spacing-3;
top: 50%;
transform: translateY(-50%);
color: $lions-gray-500;
font-size: 1rem;
}
}
// Checkbox variant
&.ui-tree-checkbox {
.ui-chkbox {
margin-right: $lions-spacing-2;
.ui-chkbox-box {
width: 1.125rem;
height: 1.125rem;
@include lions-rounded('sm');
border: 2px solid $lions-gray-400;
background: $lions-white;
@include lions-transition();
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-600;
.ui-chkbox-icon {
color: $lions-white;
font-size: 0.75rem;
}
}
}
}
}
// Loading state
&.ui-tree-loading {
position: relative;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba($lions-white, 0.8);
@include lions-flex-center;
z-index: 10;
}
}
}
// ============================================
// 📊 TREE TABLE STYLES
// ============================================
.ui-treetable {
@include lions-rounded('lg');
border: 1px solid $lions-gray-200;
background: $lions-white;
overflow: hidden;
@include lions-shadow('sm');
.ui-treetable-header {
background: $lions-gray-100;
border-bottom: 2px solid $lions-gray-300;
}
.ui-treetable-thead {
th {
padding: $lions-spacing-4;
@include lions-font-size('sm');
@include lions-font-weight('semibold');
color: $lions-text-primary;
text-align: left;
background: $lions-gray-100;
border-bottom: 2px solid $lions-gray-300;
@include lions-transition();
&.ui-sortable-column {
cursor: pointer;
&:hover {
background: $lions-gray-200;
}
}
&.ui-state-active {
background: $lions-blue-50;
color: $lions-blue-700;
.ui-sortable-column-icon {
color: $lions-blue-600;
}
}
.ui-sortable-column-icon {
margin-left: $lions-spacing-2;
color: $lions-gray-400;
font-size: 0.875rem;
@include lions-transition();
}
}
}
.ui-treetable-tbody {
tr {
@include lions-transition();
&:hover {
background: rgba($lions-blue-50, 0.3);
}
&.ui-state-highlight {
background: $lions-blue-50;
td {
color: $lions-blue-700;
@include lions-font-weight('medium');
}
}
&.ui-treetable-row-selected {
background: $lions-blue-50;
border-left: 3px solid $lions-blue-500;
}
}
td {
padding: $lions-spacing-4;
border-bottom: 1px solid $lions-gray-200;
@include lions-font-size('base');
color: $lions-text-primary;
@include lions-transition();
&.ui-treetable-toggler {
width: 3rem;
}
}
.ui-treetable-toggler {
@include lions-button-base;
width: 1.75rem;
height: 1.75rem;
min-width: 1.75rem;
padding: 0;
@include lions-flex-center;
background: transparent;
border: 1px solid transparent;
@include lions-rounded('md');
color: $lions-gray-600;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-gray-100;
border-color: $lions-gray-300;
color: $lions-blue-600;
}
.ui-treetable-toggler-icon {
font-size: 0.875rem;
@include lions-transition();
}
&.ui-icon-triangle-1-e {
.ui-treetable-toggler-icon {
transform: rotate(0deg);
}
}
&.ui-icon-triangle-1-s {
.ui-treetable-toggler-icon {
transform: rotate(90deg);
}
}
}
// Indent for child rows
tr[data-level="1"] .ui-treetable-indent {
padding-left: $lions-spacing-6;
}
tr[data-level="2"] .ui-treetable-indent {
padding-left: calc(#{$lions-spacing-6} * 2);
}
tr[data-level="3"] .ui-treetable-indent {
padding-left: calc(#{$lions-spacing-6} * 3);
}
tr[data-level="4"] .ui-treetable-indent {
padding-left: calc(#{$lions-spacing-6} * 4);
}
}
.ui-treetable-footer {
padding: $lions-spacing-4;
background: $lions-gray-50;
border-top: 1px solid $lions-gray-200;
@include lions-font-size('sm');
color: $lions-gray-600;
}
// Scrollable variant
&.ui-treetable-scrollable {
.ui-treetable-scrollable-header,
.ui-treetable-scrollable-footer {
overflow: hidden;
}
.ui-treetable-scrollable-body {
overflow: auto;
position: relative;
}
.ui-treetable-scrollable-header-box {
border-right: 1px solid $lions-gray-200;
}
}
// Striped rows
&.ui-treetable-striped {
.ui-treetable-tbody {
tr:nth-child(odd) {
background: $lions-gray-50;
&:hover {
background: rgba($lions-blue-50, 0.5);
}
}
}
}
// Resizable columns
&.ui-treetable-resizable {
.ui-treetable-thead {
th {
position: relative;
.ui-column-resizer {
position: absolute;
top: 0;
right: 0;
width: 8px;
height: 100%;
cursor: col-resize;
background: transparent;
@include lions-transition();
&:hover {
background: $lions-blue-200;
}
}
}
}
}
// Checkbox selection
&.ui-treetable-checkbox {
.ui-chkbox {
.ui-chkbox-box {
width: 1.125rem;
height: 1.125rem;
@include lions-rounded('sm');
border: 2px solid $lions-gray-400;
background: $lions-white;
@include lions-transition();
&.ui-state-active {
@include lions-gradient-blue;
border-color: $lions-blue-600;
.ui-chkbox-icon {
color: $lions-white;
font-size: 0.75rem;
}
}
}
}
}
// Loading overlay
.ui-treetable-loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba($lions-white, 0.8);
@include lions-flex-center;
z-index: 10;
.ui-treetable-loading-icon {
font-size: 2rem;
color: $lions-blue-500;
animation: lions-spin 1s linear infinite;
}
}
// Empty message
.ui-treetable-emptymessage {
padding: $lions-spacing-8;
text-align: center;
color: $lions-gray-500;
@include lions-font-size('base');
}
}
// ============================================
// 🎬 ANIMATIONS
// ============================================
@keyframes lions-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes lions-tree-expand {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
// Tree node expand animation
.ui-treenode-children {
animation: lions-tree-expand 0.2s ease-out;
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-tree {
.ui-treenode-content {
padding: $lions-spacing-2;
}
.ui-treenode-children {
padding-left: $lions-spacing-4;
}
.ui-tree-filter-container {
.ui-tree-filter {
@include lions-font-size('sm');
}
}
}
.ui-treetable {
.ui-treetable-thead th,
.ui-treetable-tbody td {
padding: $lions-spacing-3;
@include lions-font-size('sm');
}
// Responsive table mode
&.ui-treetable-responsive {
.ui-treetable-thead {
display: none;
}
.ui-treetable-tbody {
tr {
display: block;
margin-bottom: $lions-spacing-3;
border: 1px solid $lions-gray-200;
@include lions-rounded('md');
}
td {
display: block;
text-align: right;
padding: $lions-spacing-3;
border-bottom: 1px solid $lions-gray-100;
&::before {
content: attr(data-label);
float: left;
font-weight: $lions-font-semibold;
color: $lions-gray-700;
}
&:last-child {
border-bottom: none;
}
}
}
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-tree,
.ui-treetable {
// Focus visible
.ui-treenode-content:focus-visible,
.ui-tree-toggler:focus-visible,
.ui-treetable-toggler:focus-visible {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
.ui-treenode-content,
.ui-tree-toggler,
.ui-treetable-toggler,
th,
td {
border-width: 2px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
.ui-tree-toggler-icon,
.ui-treetable-toggler-icon {
transform: none !important;
}
}
}
// ============================================
// 🎨 UTILITY CLASSES
// ============================================
// Compact tree
.ui-tree-compact {
.ui-treenode-content {
padding: $lions-spacing-1 $lions-spacing-2;
}
.ui-treenode-label {
@include lions-font-size('sm');
}
.ui-treenode-children {
padding-left: $lions-spacing-4;
}
}
// Large tree
.ui-tree-lg {
.ui-treenode-content {
padding: $lions-spacing-3 $lions-spacing-4;
}
.ui-treenode-label {
@include lions-font-size('lg');
}
.ui-tree-toggler {
width: 2rem;
height: 2rem;
min-width: 2rem;
}
}
// Borderless tree
.ui-tree-borderless {
border: none;
box-shadow: none;
padding: 0;
}

View File

@@ -0,0 +1,670 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Utility Component Styles
* ═══════════════════════════════════════════════════════════
* Styles pour composants utilitaires
* Divider, Spacer, Inplace, ThemeSelector
*
* Version: 1.0.0
* Date: 2 Janvier 2026
* Author: Lions Development Team
* Priority: BASSE #27-28, #45-46
* ═══════════════════════════════════════════════════════════
*/
@import '../variables';
@import '../mixins';
// ============================================
// DIVIDER STYLES
// ============================================
.ui-divider {
display: flex;
align-items: center;
margin: $lions-spacing-5 0;
@include lions-transition();
// Horizontal divider (default)
&.ui-divider-horizontal {
flex-direction: row;
&::before,
&::after {
content: '';
flex: 1;
height: 1px;
background: $lions-gray-300;
}
&.ui-divider-left::before {
flex: 0;
margin-right: $lions-spacing-4;
}
&.ui-divider-right::after {
flex: 0;
margin-left: $lions-spacing-4;
}
&.ui-divider-center {
&::before {
margin-right: $lions-spacing-4;
}
&::after {
margin-left: $lions-spacing-4;
}
}
// No content variant
&.ui-divider-no-content {
&::before {
margin-right: 0;
}
&::after {
display: none;
}
}
}
// Vertical divider
&.ui-divider-vertical {
flex-direction: column;
min-height: 100px;
margin: 0 $lions-spacing-5;
&::before,
&::after {
content: '';
flex: 1;
width: 1px;
background: $lions-gray-300;
}
&.ui-divider-top::before {
flex: 0;
margin-bottom: $lions-spacing-4;
}
&.ui-divider-bottom::after {
flex: 0;
margin-top: $lions-spacing-4;
}
&.ui-divider-center {
&::before {
margin-bottom: $lions-spacing-4;
}
&::after {
margin-top: $lions-spacing-4;
}
}
}
// Content
.ui-divider-content {
@include lions-font-size('sm');
@include lions-font-weight('medium');
color: $lions-gray-600;
padding: 0 $lions-spacing-3;
white-space: nowrap;
}
// Style variants
&.ui-divider-solid {
&::before,
&::after {
border-style: solid;
}
}
&.ui-divider-dashed {
&::before,
&::after {
border-style: dashed;
background: none;
border-width: 1px 0 0 0;
height: 0;
}
&.ui-divider-vertical {
&::before,
&::after {
border-width: 0 0 0 1px;
width: 0;
}
}
}
&.ui-divider-dotted {
&::before,
&::after {
border-style: dotted;
background: none;
border-width: 1px 0 0 0;
height: 0;
}
&.ui-divider-vertical {
&::before,
&::after {
border-width: 0 0 0 1px;
width: 0;
}
}
}
// Color variants
&.ui-divider-primary {
&::before,
&::after {
background: $lions-blue-300;
border-color: $lions-blue-300;
}
.ui-divider-content {
color: $lions-blue-700;
}
}
&.ui-divider-success {
&::before,
&::after {
background: $lions-success-300;
border-color: $lions-success-300;
}
.ui-divider-content {
color: $lions-success-700;
}
}
&.ui-divider-warning {
&::before,
&::after {
background: $lions-warning-300;
border-color: $lions-warning-300;
}
.ui-divider-content {
color: $lions-warning-700;
}
}
&.ui-divider-danger {
&::before,
&::after {
background: $lions-danger-300;
border-color: $lions-danger-300;
}
.ui-divider-content {
color: $lions-danger-700;
}
}
}
// ============================================
// ⬜ SPACER STYLES
// ============================================
.ui-spacer {
display: block;
// Default spacing
&.ui-spacer-horizontal {
width: 100%;
height: $lions-spacing-5;
}
&.ui-spacer-vertical {
display: inline-block;
width: $lions-spacing-5;
height: auto;
min-height: 1px;
}
// Size variants
&.ui-spacer-xs {
&.ui-spacer-horizontal {
height: $lions-spacing-1;
}
&.ui-spacer-vertical {
width: $lions-spacing-1;
}
}
&.ui-spacer-sm {
&.ui-spacer-horizontal {
height: $lions-spacing-3;
}
&.ui-spacer-vertical {
width: $lions-spacing-3;
}
}
&.ui-spacer-md {
&.ui-spacer-horizontal {
height: $lions-spacing-5;
}
&.ui-spacer-vertical {
width: $lions-spacing-5;
}
}
&.ui-spacer-lg {
&.ui-spacer-horizontal {
height: $lions-spacing-8;
}
&.ui-spacer-vertical {
width: $lions-spacing-8;
}
}
&.ui-spacer-xl {
&.ui-spacer-horizontal {
height: $lions-spacing-12;
}
&.ui-spacer-vertical {
width: $lions-spacing-12;
}
}
}
// ============================================
// ✏️ INPLACE STYLES
// ============================================
.ui-inplace {
display: inline-block;
position: relative;
.ui-inplace-display {
display: inline-flex;
align-items: center;
gap: $lions-spacing-2;
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-rounded('md');
cursor: pointer;
@include lions-transition();
@include lions-font-size('base');
color: $lions-text-primary;
&:hover {
background: $lions-gray-50;
}
&:focus-visible {
@include lions-focus-ring();
}
.ui-inplace-display-icon {
font-size: 1rem;
color: $lions-gray-500;
margin-left: $lions-spacing-2;
}
}
.ui-inplace-content {
display: inline-block;
padding: $lions-spacing-2;
.ui-inplace-save,
.ui-inplace-cancel {
@include lions-button-base;
padding: $lions-spacing-2 $lions-spacing-3;
@include lions-rounded('md');
@include lions-font-weight('semibold');
@include lions-font-size('sm');
margin-left: $lions-spacing-2;
cursor: pointer;
@include lions-transition();
}
.ui-inplace-save {
@include lions-gradient-blue;
color: $lions-white;
border: none;
&:hover {
@include lions-shadow('md');
transform: translateY(-1px);
}
}
.ui-inplace-cancel {
background: $lions-gray-200;
color: $lions-gray-700;
border: 1px solid $lions-gray-300;
&:hover {
background: $lions-gray-300;
}
}
}
&.ui-inplace-disabled {
.ui-inplace-display {
cursor: not-allowed;
opacity: 0.6;
&:hover {
background: transparent;
}
}
}
}
// ============================================
// 🎨 THEME SELECTOR STYLES
// ============================================
.ui-theme-selector {
position: relative;
display: inline-block;
.ui-theme-selector-button {
@include lions-button-base;
padding: $lions-spacing-2 $lions-spacing-4;
@include lions-rounded('md');
background: $lions-gray-100;
border: 1px solid $lions-gray-300;
color: $lions-gray-700;
@include lions-flex-center;
gap: $lions-spacing-2;
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-gray-200;
border-color: $lions-gray-400;
}
&:focus-visible {
@include lions-focus-ring();
}
.ui-theme-selector-icon {
font-size: 1.125rem;
color: $lions-blue-600;
}
.ui-theme-selector-label {
@include lions-font-size('sm');
@include lions-font-weight('medium');
}
.ui-theme-selector-chevron {
font-size: 0.875rem;
color: $lions-gray-500;
@include lions-transition();
}
&.ui-state-active {
.ui-theme-selector-chevron {
transform: rotate(180deg);
}
}
}
.ui-theme-selector-panel {
position: absolute;
top: calc(100% + $lions-spacing-2);
right: 0;
min-width: 200px;
background: $lions-white;
@include lions-rounded('lg');
@include lions-shadow('xl');
border: 1px solid $lions-gray-200;
padding: $lions-spacing-2;
z-index: 1000;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
@include lions-transition();
&.ui-state-active {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
}
.ui-theme-selector-list {
list-style: none;
margin: 0;
padding: 0;
}
.ui-theme-selector-item {
display: flex;
align-items: center;
gap: $lions-spacing-3;
padding: $lions-spacing-3;
@include lions-rounded('md');
cursor: pointer;
@include lions-transition();
&:hover {
background: $lions-gray-50;
}
&.ui-state-active {
background: $lions-blue-50;
.ui-theme-selector-item-label {
color: $lions-blue-700;
@include lions-font-weight('semibold');
}
.ui-theme-selector-item-check {
opacity: 1;
}
}
.ui-theme-selector-item-preview {
width: 2rem;
height: 2rem;
@include lions-rounded('md');
border: 2px solid $lions-gray-300;
display: flex;
overflow: hidden;
.ui-theme-color {
flex: 1;
height: 100%;
}
}
.ui-theme-selector-item-label {
flex: 1;
@include lions-font-size('sm');
color: $lions-text-primary;
}
.ui-theme-selector-item-check {
font-size: 1rem;
color: $lions-blue-600;
opacity: 0;
@include lions-transition();
}
}
}
// ============================================
// 🎯 SKELETON LOADER (Bonus Utility)
// ============================================
.ui-skeleton {
background: linear-gradient(
90deg,
$lions-gray-200 0%,
$lions-gray-100 50%,
$lions-gray-200 100%
);
background-size: 200% 100%;
animation: lions-skeleton-loading 1.5s ease-in-out infinite;
@include lions-rounded('md');
&.ui-skeleton-circle {
@include lions-rounded('full');
width: 3rem;
height: 3rem;
}
&.ui-skeleton-text {
height: 1rem;
width: 100%;
&.ui-skeleton-sm {
height: 0.75rem;
}
&.ui-skeleton-lg {
height: 1.25rem;
}
}
&.ui-skeleton-rect {
width: 100%;
height: 10rem;
}
&.ui-skeleton-button {
width: 8rem;
height: 2.5rem;
}
&.ui-skeleton-card {
width: 100%;
height: 15rem;
}
}
@keyframes lions-skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
// ============================================
// 📱 RESPONSIVE ADJUSTMENTS
// ============================================
@media (max-width: $lions-breakpoint-md) {
.ui-divider {
&.ui-divider-horizontal {
margin: $lions-spacing-4 0;
.ui-divider-content {
@include lions-font-size('xs');
}
}
&.ui-divider-vertical {
min-height: 60px;
margin: 0 $lions-spacing-3;
}
}
.ui-theme-selector {
.ui-theme-selector-panel {
right: auto;
left: 0;
}
}
.ui-inplace {
.ui-inplace-display {
padding: $lions-spacing-2;
@include lions-font-size('sm');
}
}
}
// ============================================
// ♿ ACCESSIBILITY ENHANCEMENTS
// ============================================
.ui-inplace,
.ui-theme-selector {
// Focus visible
button:focus-visible,
.ui-inplace-display:focus-visible,
.ui-theme-selector-button:focus-visible {
@include lions-focus-ring();
}
// High contrast mode
@media (prefers-contrast: high) {
border-width: 2px !important;
button,
.ui-inplace-display,
.ui-theme-selector-button {
border-width: 2px !important;
}
}
// Reduced motion
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}
}
// Skeleton - Reduced motion
@media (prefers-reduced-motion: reduce) {
.ui-skeleton {
animation: none !important;
background: $lions-gray-200;
}
}
// ============================================
// 🎨 UTILITY CLASSES
// ============================================
// Hidden divider (just for spacing)
.ui-divider-hidden {
&::before,
&::after {
display: none !important;
}
}
// Thick divider
.ui-divider-thick {
&::before,
&::after {
height: 2px !important;
&.ui-divider-vertical {
width: 2px !important;
height: auto !important;
}
}
}
// Inplace full width
.ui-inplace-fullwidth {
display: block;
width: 100%;
.ui-inplace-display {
width: 100%;
}
}

View File

@@ -0,0 +1,73 @@
/*
* ═══════════════════════════════════════════════════════════
* Lions.dev - Main SCSS Entry Point
* ═══════════════════════════════════════════════════════════
* Système de design complet Lions.dev pour Freya Extension
*
* Version: 1.0.0
* Date: 1er Janvier 2026
* Author: Lions Development Team
* ═══════════════════════════════════════════════════════════
*/
// ============================================
// 🏗️ FONDATIONS
// ============================================
// Variables globales
@import 'variables';
// Mixins utilitaires
@import 'mixins';
// ============================================
// 🧩 COMPOSANTS
// ============================================
// Boutons (✅ COMPLÉTÉ - Priority #1-4)
@import 'components/button';
// Champs de formulaire (✅ COMPLÉTÉ - Priority #5-8)
@import 'components/field';
// Layout - Card & Panel (✅ COMPLÉTÉ - Priority #9-10)
@import 'components/card';
// Data Display - DataTable & DataView (✅ COMPLÉTÉ - Priority #16-17)
@import 'components/data';
// Messages & Notifications - Messages, Toast, Growl (✅ COMPLÉTÉ - Priority #18-20)
@import 'components/messages';
// Overlays - Dialog, ConfirmDialog, Sidebar, OverlayPanel (✅ COMPLÉTÉ - Priority #21-24)
@import 'components/overlays';
// Navigation - Menu, Menubar, TabView (✅ COMPLÉTÉ - Priority #25-27)
@import 'components/navigation';
// Form Controls - Checkbox, Radio, Switch, Toggle, Slider, Rating (✅ COMPLÉTÉ - Priority #28-33)
@import 'components/form-controls';
// Display - Avatar, Badge, Tag (✅ COMPLÉTÉ - Priority #34-36)
@import 'components/display';
// Breadcrumb & Steps - Navigation secondaire (✅ COMPLÉTÉ - Priority #37-38)
@import 'components/breadcrumb-steps';
// Progress - Barre de progression (✅ COMPLÉTÉ - Priority #39)
@import 'components/progress';
// Form Advanced - Spinner, Mask, Chips, ColorPicker, Editor, FileUpload (✅ COMPLÉTÉ - Priority #40-44)
@import 'components/form-advanced';
// Tree & TreeTable - Affichage hiérarchique (✅ COMPLÉTÉ - Priority #31-32)
@import 'components/tree';
// Utilities - Divider, Spacer, Inplace, ThemeSelector (✅ COMPLÉTÉ - Priority #27-28, #45-46)
@import 'components/utilities';
// Chart - Visualisation de données avec Chart.js (✅ COMPLÉTÉ - Priority #44)
@import 'components/chart';
// ✅ TOUS LES COMPOSANTS LIONS.DEV COMPLÉTÉS!
// Total: 46 composants avec styles complets