ajout dropdown de deconnection
This commit is contained in:
parent
50360a590b
commit
c14507a716
7 changed files with 199 additions and 16 deletions
|
|
@ -13,6 +13,8 @@
|
|||
/* Backgrounds de navigation */
|
||||
--nav-bg-light: rgba(255, 255, 255, 0.5);
|
||||
--nav-bg-dark: rgba(12, 10, 18, 0.4);
|
||||
--nav-bg-light-so: rgba(255, 255, 255, 0.82);
|
||||
--nav-bg-dark-so: rgba(12, 10, 18, 0.8);
|
||||
|
||||
/* Couleurs de texte */
|
||||
--text-light: #1a1625;
|
||||
|
|
@ -30,7 +32,7 @@
|
|||
|
||||
/* Effets de blur */
|
||||
--blur-sm: 8px;
|
||||
--blur-md: 20px;
|
||||
--blur-md: 25px;
|
||||
|
||||
/* Durées de transition */
|
||||
--duration-fast: 0.15s; /* Transforms rapides */
|
||||
|
|
|
|||
|
|
@ -73,13 +73,7 @@ header nav {
|
|||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
/* ── LIENS DE NAVIGATION — style calqué sur .login-btn ─────────── */
|
||||
/*
|
||||
"all: unset" neutralise Bootstrap 5 entièrement sur .nav-link,
|
||||
puis on réapplique exactement ce qu'on veut.
|
||||
Les !important sur padding/border/background garantissent
|
||||
qu'aucune règle Bootstrap résiduelle ne peut l'écraser.
|
||||
*/
|
||||
/* ── LIENS DE NAVIGATION ───────────────────────────────────────── */
|
||||
header nav .nav-link {
|
||||
all: unset;
|
||||
|
||||
|
|
@ -90,7 +84,7 @@ header nav .nav-link {
|
|||
cursor: pointer;
|
||||
text-decoration: none !important;
|
||||
|
||||
/* Dimensions — identiques au .login-btn */
|
||||
/* Dimensions */
|
||||
padding: 0.55rem 1.1rem !important;
|
||||
border-radius: 10px !important;
|
||||
border: 1px solid var(--border-light) !important;
|
||||
|
|
@ -153,6 +147,80 @@ header nav .nav-link.active:hover {
|
|||
header nav .nav-link::before,
|
||||
header nav .nav-link::after { display: none !important; }
|
||||
|
||||
/* ── LIENS DROPDOWN ─────────── */
|
||||
header .header-dropdown .dropdown-link {
|
||||
all: unset;
|
||||
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0.45rem;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
text-decoration: none !important;
|
||||
|
||||
/* Dimensions */
|
||||
padding: 0.55rem 1.1rem !important;
|
||||
border-radius: 10px !important;
|
||||
border: 1px solid var(--border-light) !important;
|
||||
background: transparent !important;
|
||||
|
||||
/* Typographie */
|
||||
font-family: 'Syne', sans-serif !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 0.875rem !important;
|
||||
letter-spacing: 0.03em !important;
|
||||
text-transform: uppercase !important;
|
||||
color: var(--muted-light) !important;
|
||||
|
||||
transition:
|
||||
color var(--duration-normal) ease,
|
||||
background var(--duration-normal) ease,
|
||||
border-color var(--duration-normal) ease,
|
||||
box-shadow var(--duration-normal) ease,
|
||||
transform var(--duration-fast) ease !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] header .header-dropdown .dropdown-link {
|
||||
border-color: var(--border-dark) !important;
|
||||
color: var(--muted-dark) !important;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
header .header-dropdown .dropdown-link:hover {
|
||||
color: var(--violet) !important;
|
||||
background: rgba(0, 0, 0, 0.05) !important;
|
||||
border-color: rgba(0, 0, 0, 0.15) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
[data-theme="dark"] header .header-dropdown .dropdown-link:hover {
|
||||
color: var(--violet-light) !important;
|
||||
background: rgba(255, 255, 255, 0.07) !important;
|
||||
border-color: rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
|
||||
/* Actif — fond violet léger + bordure violet */
|
||||
header .header-dropdown .dropdown-link.active {
|
||||
color: var(--violet) !important;
|
||||
background: var(--pill-bg-light) !important;
|
||||
border-color: rgba(124, 58, 237, 0.30) !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
[data-theme="dark"] header .header-dropdown .dropdown-link.active {
|
||||
color: var(--violet-light) !important;
|
||||
background: var(--pill-bg-dark) !important;
|
||||
border-color: rgba(167, 139, 250, 0.30) !important;
|
||||
}
|
||||
|
||||
/* Actif + hover — glow subtil */
|
||||
header .header-dropdown .dropdown-link.active:hover {
|
||||
box-shadow: 0 4px 16px rgba(124, 58, 237, 0.20) !important;
|
||||
}
|
||||
|
||||
/* Pas de pseudo-élément soulignement */
|
||||
header .header-dropdown .dropdown-link::before,
|
||||
header .header-dropdown .dropdown-link::after { display: none !important; }
|
||||
|
||||
/* ── ACTIONS (droite) ──────────────────────────────────────────── */
|
||||
.header-actions {
|
||||
display: flex;
|
||||
|
|
@ -223,14 +291,15 @@ header nav .nav-link::after { display: none !important; }
|
|||
}
|
||||
|
||||
/* ── DROPDOWN CONNEXION ────────────────────────────────────────── */
|
||||
.login-wrapper { position: relative; }
|
||||
.header-wrapper { position: relative; }
|
||||
|
||||
.login-dropdown {
|
||||
.header-dropdown {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
width: 260px;
|
||||
background: var(--nav-bg-light);
|
||||
background: var(--nav-bg-light-so);
|
||||
backdrop-filter: blur(var(--blur-md));
|
||||
-webkit-backdrop-filter: blur(var(--blur-md));
|
||||
border: 1px solid var(--border-light);
|
||||
|
|
@ -242,12 +311,12 @@ header nav .nav-link::after { display: none !important; }
|
|||
gap: 0.75rem;
|
||||
animation: dropdown-in var(--duration-normal) ease;
|
||||
}
|
||||
[data-theme="dark"] .login-dropdown {
|
||||
background: var(--nav-bg-dark);
|
||||
[data-theme="dark"] .header-dropdown {
|
||||
background: var(--nav-bg-dark-so);
|
||||
border-color: var(--border-dark);
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.login-dropdown.open { display: flex; }
|
||||
.header-dropdown.open { display: flex; }
|
||||
|
||||
@keyframes dropdown-in {
|
||||
from { opacity: 0; transform: translateY(-6px); }
|
||||
|
|
|
|||
6
config/routes/routing.controllers.yaml
Normal file
6
config/routes/routing.controllers.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Load routes from all controllers in src/Controller/
|
||||
controllers:
|
||||
resource: ../../src/Controller/
|
||||
type: attribute
|
||||
|
||||
|
||||
40
src/EventSubscriber/TwigGlobalSubscriber.php
Normal file
40
src/EventSubscriber/TwigGlobalSubscriber.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\ControllerEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Twig\Environment;
|
||||
|
||||
class TwigGlobalSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Environment $twig,
|
||||
private readonly AuthenticationUtils $authUtils,
|
||||
) {}
|
||||
|
||||
public function onKernelController(ControllerEvent $event): void
|
||||
{
|
||||
if (!$event->isMainRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->twig->addGlobal('error', $this->authUtils->getLastAuthenticationError());
|
||||
$this->twig->addGlobal('last_username', $this->authUtils->getLastUsername());
|
||||
} catch (\LogicException) {
|
||||
// Route hors pare-feu (assets, profiler...) — on ignore
|
||||
$this->twig->addGlobal('error', null);
|
||||
$this->twig->addGlobal('last_username', '');
|
||||
}
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::CONTROLLER => 'onKernelController',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,11 @@
|
|||
{# ── Droite : Actions (thème + connexion) ─────────────────── #}
|
||||
<div class="header-actions">
|
||||
{% include 'partials/_theme_toggle.html.twig' %}
|
||||
{% include 'partials/_login_dropdown.html.twig' %}
|
||||
{% if is_granted('ROLE_USER') %}
|
||||
{% include 'partials/_logout_dropdown.html.twig' %}
|
||||
{% else %}
|
||||
{% include 'partials/_login_dropdown.html.twig' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
|
|
|||
62
templates/partials/_logout_dropdown.html.twig
Normal file
62
templates/partials/_logout_dropdown.html.twig
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{# ══════════════════════════════════════════════════════════════
|
||||
templates/partials/_login_dropdown.html.twig
|
||||
Bouton + dropdown formulaire de connexion
|
||||
══════════════════════════════════════════════════════════════ #}
|
||||
|
||||
<div class="header-wrapper" id="loginWrapper"
|
||||
data-controller="header-dropdown"
|
||||
data-header-dropdown-target="wrapper">
|
||||
|
||||
{# Bouton déclencheur #}
|
||||
<button class="login-btn" id="loginToggle"
|
||||
aria-haspopup="true" aria-expanded="false"
|
||||
data-header-dropdown-target="toggle">
|
||||
{% if app.user.uriPp is not null %}
|
||||
<img src="{{ asset(app.user.uri_pp) }}" alt="Photo de profil de {{ app.user.pseudo }}" class="profile-pic">
|
||||
{% else %}
|
||||
<svg width="15" height="15" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
{{ app.user.pseudo }}
|
||||
</button>
|
||||
|
||||
{# Dropdown #}
|
||||
<div class="header-dropdown" id="headerDropdown"
|
||||
role="dialog" aria-label="Formulaire de connexion"
|
||||
data-header-dropdown-target="dropdown">
|
||||
|
||||
<span class="dropdown-title">Bonjour {{ app.user.pseudo }}</span>
|
||||
|
||||
{% if is_granted("ROLE_ADMIN") %}
|
||||
<a href="{{ path('app_accueil') }}"
|
||||
class="dropdown-link {{ current_route == 'app_home' ? 'active' }}">
|
||||
Administration
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ path('app_accueil') }}"
|
||||
class="dropdown-link {{ current_route == 'app_home' ? 'active' }}">
|
||||
Parametre
|
||||
</a>
|
||||
<form action="{{ path('app_logout') }}" method="post" >
|
||||
<input type="hidden"
|
||||
name="_csrf_token"
|
||||
value="{{ csrf_token('authenticate') }}">
|
||||
|
||||
{# Retenir la page courante pour y revenir en cas d'erreur #}
|
||||
<input type="hidden"
|
||||
name="_failure_path"
|
||||
value="{{ app.request.uri }}">
|
||||
|
||||
<button class="submit-btn" type="submit">Se déconnecter</button>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Reference in a new issue