FEATURE Big menu refactoring to match dynamlique menu
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Antoine M 2025-12-09 09:32:48 +01:00
parent d3edfad1df
commit 946e3d3cdf
13 changed files with 674 additions and 92 deletions

View File

@ -19,81 +19,15 @@
<div id="page-<?php echo $post ? $post->post_name : 'home'; ?>" class="min-h-screen flex flex-col">
<?php do_action('tailpress_header'); ?>
<header id="primary-header">
<div class="secondary-menu-container">
<div class="secondary-menu-nav">
<?php
wp_nav_menu(
array(
'container' => 'false',
'theme_location' => 'secondary',
'li_class' => 'menu-navlink',
'fallback_cb' => false,
)
); ?>
<div class="network-redirector">
<a href="<?php echo get_site_url(2); ?>">
Revue Dynamiques
</a>
</div>
</div>
<?php get_template_part('template-parts/header/mobile-menu-brand'); ?>
<?php get_template_part('template-parts/header/secondary-menu'); ?>
<?php get_template_part('template-parts/header/primary-menu'); ?>
</div>
<div class="primary-menu-container lg:flex lg:justify-between lg:items-center">
<nav id="primary-menu-nav" class="flex justify-between items-center">
<div class="website_logo">
<?php if (has_custom_logo()) { ?>
<?php the_custom_logo(); ?>
<?php } else { ?>
<a href=" <?php echo get_bloginfo('url'); ?>" class="font-extrabold text-lg uppercase">
<?php echo get_bloginfo('name'); ?>
</a>
<?php get_template_part('template-parts/search-module'); ?>
<p class="text-sm font-light text-gray-600">
<?php echo get_bloginfo('description'); ?>
</p>
<?php } ?>
</div>
<button id="burger-menu-toggle" aria-label="<?php echo esc_html_e("Ouvrir le menu", 'deligraph-theme') ?>">
<div class="menu-toggle-bar menu-toggle-bar--top"></div>
<div class="menu-toggle-bar menu-toggle-bar--middle"></div>
<div class="menu-toggle-bar menu-toggle-bar--bottom"></div>
<!-- <?php echo get_template_part('resources/svg/burger-menu-icon.svg'); ?> -->
</button>
<div id="primary-menu">
<!-- <button id="close-menu-btn" aria-label="<?php echo esc_html_e("Fermer le menu", 'deligraph-theme') ?>">
<?php echo get_template_part('resources/svg/close-menu-icon.svg'); ?>
</button> -->
<?php
wp_nav_menu(
array(
'container' => 'false',
// 'container_id' => 'primary-menu',
// 'container_class' => '',
'theme_location' => 'primary',
'li_class' => 'menu-navlink',
'fallback_cb' => false,
)
); ?>
</div>
</nav>
</div>
<!-- qsdqds -->
</header>

View File

@ -1,34 +1,156 @@
<?php
/** ------------------------------
ENABLE SVG
------------------------------*/
/* <?xml version="1.0" encoding="utf-8"?> */
function cc_mime_types($mimes)
function carhop_allow_svg_upload($mimes)
{
$mimes['svg'] = 'image/svg+xml';
return $mimes;
// Autoriser SVG pour tous les utilisateurs connectés
$mimes['svg'] = 'image/svg+xml';
$mimes['svgz'] = 'image/svg+xml';
return $mimes;
}
add_filter('upload_mimes', 'cc_mime_types');
add_filter('upload_mimes', 'carhop_allow_svg_upload', 10, 1);
// Vérifier les permissions utilisateur pour SVG
function carhop_check_svg_permissions($file)
{
if ($file['type'] === 'image/svg+xml') {
// Autoriser pour les administrateurs et éditeurs
if (current_user_can('upload_files')) {
return $file;
} else {
$file['error'] = 'Vous n\'avez pas les permissions pour uploader des fichiers SVG.';
}
}
return $file;
}
add_filter('wp_handle_upload_prefilter', 'carhop_check_svg_permissions');
// Debug: afficher les types MIME autorisés
// write_log(get_allowed_mime_types());
// Solution alternative : utiliser un hook plus tôt
function carhop_early_svg_support()
{
add_filter('upload_mimes', function ($mimes) {
$mimes['svg'] = 'image/svg+xml';
$mimes['svgz'] = 'image/svg+xml';
return $mimes;
}, 1);
}
add_action('init', 'carhop_early_svg_support', 1);
// Désactiver la vérification stricte des types MIME pour SVG
function carhop_disable_mime_check($data, $file, $filename, $mimes)
{
if (pathinfo($filename, PATHINFO_EXTENSION) === 'svg') {
$data['ext'] = 'svg';
$data['type'] = 'image/svg+xml';
$data['proper_filename'] = $filename;
}
return $data;
}
add_filter('wp_check_filetype_and_ext', 'carhop_disable_mime_check', 10, 4);
// #############################
// AJOUT D'UN ESPACE LOGO CUSTOM
// #############################
/** ------------------------------
LOGO ALTERNATIF CUSTOMIZER
------------------------------*/
// function add_logo_customizer_settings($wp_customize)
// {
// $wp_customize->add_setting('logo_semlex_dark');
/**
* Ajouter l'option de logo alternatif dans le customizer
* (Le logo principal est géré nativement par WordPress dans "Identité du site")
*/
function carhop_customize_register($wp_customize)
{
// Ajouter le logo alternatif dans la section "Identité du site" existante
$wp_customize->add_setting('carhop_logo_secondary', array(
'default' => '',
'sanitize_callback' => 'esc_url_raw',
'transport' => 'refresh',
));
// // Add a control to upload the hover logo
// $wp_customize->add_control(new WP_Customize_Image_Control($wp_customize, 'logo_semlex_dark', array(
// 'label' => 'Logo Semlex Sombre',
// 'section' => 'title_tagline', //this is the section where the custom-logo from WordPress is
// 'settings' => 'logo_semlex_dark',
// 'priority' => 8 // show it just below the custom-logo
// )));
// }
$wp_customize->add_control(new WP_Customize_Image_Control($wp_customize, 'carhop_logo_secondary', array(
'label' => __('Logo alternatif (Au survol)', 'carhop'),
'section' => 'title_tagline', // Section native "Identité du site"
'settings' => 'carhop_logo_secondary',
'description' => __('Logo alternatif utilisé au survol du logo principal', 'carhop'),
'priority' => 9, // Juste après le logo principal
)));
}
add_action('customize_register', 'carhop_customize_register');
// add_action('customize_register', 'add_logo_customizer_settings');
/** ------------------------------
LOGO SECONDAIRE UTILITIES
------------------------------*/
/**
* Récupérer l'URL du logo secondaire
* @return string|false L'URL du logo secondaire ou false si non configuré
*/
function carhop_get_secondary_logo_url()
{
return get_theme_mod('carhop_logo_secondary', false);
}
/**
* Récupérer le HTML complet du logo secondaire
* @param string $size Taille de l'image (par défaut 'full')
* @param array $attr Attributs HTML supplémentaires
* @return string|false Le HTML du logo ou false si non configuré
*/
function carhop_get_secondary_logo($size = 'full', $attr = array())
{
$secondary_logo_url = get_secondary_logo_url();
if (!$secondary_logo_url) {
return false;
}
// Attributs par défaut
$default_attr = array(
'alt' => get_bloginfo('name') . ' - Logo alternatif',
'class' => 'secondary-logo'
);
// Fusionner avec les attributs personnalisés
$attr = array_merge($default_attr, $attr);
// Essayer de récupérer l'ID de l'attachement
$secondary_logo_id = attachment_url_to_postid($secondary_logo_url);
if ($secondary_logo_id) {
// Utiliser wp_get_attachment_image si on a l'ID
return wp_get_attachment_image($secondary_logo_id, $size, false, $attr);
} else {
// Fallback : créer la balise img manuellement
$attr_string = '';
foreach ($attr as $key => $value) {
$attr_string .= ' ' . $key . '="' . esc_attr($value) . '"';
}
return '<img src="' . esc_url($secondary_logo_url) . '"' . $attr_string . '>';
}
}
/**
* Afficher le logo secondaire directement
* @param string $size Taille de l'image
* @param array $attr Attributs HTML supplémentaires
*/
function carhop_the_secondary_logo($size = 'full', $attr = array())
{
echo get_secondary_logo($size, $attr);
}
/**
* Vérifier si un logo secondaire est configuré
* @return bool
*/
function carhop_has_secondary_logo()
{
return !empty(get_secondary_logo_url());
}

View File

@ -33,6 +33,7 @@
@import './components/social-networks-links.css';
@import './components/scroll-top.css';
@import './components/member-author-card.css';
@import './components/search-module.css';
/* ########### EDITOR CONTENT ############ */
@import './editor-content/entry-content.css';
@ -42,6 +43,7 @@
@import './layout/nav/header.css';
@import './layout/nav/secondary-menu.css';
@import './layout/nav/primary-menu.css';
@import './layout/menu-mobile-brand.css';
@import './layout/footer.css';
@import './layout/section.css';

View File

@ -0,0 +1,128 @@
.search-module {
@apply w-full absolute
bg-carhop-green-700
/* bg-white */
text-white
left-0
px-16
py-6
z-10
bottom-0
/* overflow-x-hidden */
transform translate-y-full;
@apply block overflow-x-hidden;
animation: translate-in 700ms forwards cubic-bezier(0, 0.51, 0.23, 0.99),
fade-in 600ms forwards ease-out;
@keyframes translate-in {
from {
transform: translateY(calc(100% - 100px));
}
to {
transform: translateY(100%);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes translate-out {
from {
transform: translateY(100%);
}
to {
transform: translateY(calc(100% - 100px));
}
}
&[closed] {
@apply hidden;
}
&[closing] {
animation: translate-out 800ms forwards ease-in, fade-out 600ms forwards ease-in;
}
&[opened] {
animation: translate-in 700ms forwards cubic-bezier(0, 0.51, 0.23, 0.99),
fade-in 600ms forwards ease-out;
}
a {
@apply text-white;
}
input::placeholder {
@apply !text-white;
}
&__wrapper-container {
@apply max-w-screen-xl mx-auto;
}
&__search-form {
@apply flex flex-wrap;
&__title {
@apply block font-bold text-lg w-full;
}
&__separator {
@apply mt-2 mb-8 bg-neutral-500 border-none opacity-50 w-full;
height: 1px;
}
&__input {
box-sizing: border-box;
@apply block max-w-full w-full flex-grow !py-4 !border-white px-4 !pl-12 focus-visible:ring-primary focus-visible:ring-2;
@apply border rounded-none;
outline: none !important;
/* border-right: none;
border-top-left-radius: 999px;
border-bottom-left-radius: 999px;
border: 1px solid; */
/* box-shadow: 0 0 1px 0px white inset, 0 0 1px 0px white; */
}
button[type='submit'] {
@apply bg-primary hidden text-white shrink-0 flex justify-center items-center gap-3 rounded-full md:rounded-l-none px-4 py-3 focus-visible:ring-primary focus-visible:ring-2;
max-width: 300px;
outline: none !important;
transform: translateX(-1px);
.search_icon {
@apply invert;
}
}
}
&__searchbar-group {
@apply w-full flex flex-col md:flex-row gap-y-4 relative;
&:before {
@apply content-[''] absolute inset-0 bg-contain bg-center bg-no-repeat w-6 h-6 left-4 top-1/2 -translate-y-1/2;
background-image: url('../resources/img/icons/carhop-rechercher.svg');
filter: invert(1);
}
}
&__suggestions {
@apply pt-4;
.suggestion-item {
@apply underline underline-offset-4 block w-full mb-2 font-light text-base;
word-break: break-word;
text-decoration-thickness: 1px;
}
}
}
body:has(.search-module[opened]) main {
filter: blur(2px) brightness(0.8);
}

View File

@ -0,0 +1,27 @@
.menu-mobile-brand {
@apply bg-primary text-white pb-8;
@screen lg {
@apply hidden;
}
#mobile-menu-toggle {
@apply underline underline-offset-4;
}
&__inner-elements {
@apply flex justify-between items-center px-6 gap-12;
}
.website_logo {
@apply shrink;
}
.tools-container {
@apply flex w-fit gap-2;
.search-button,
.subscribe-button {
@apply w-6 h-6 border-2 flex border-white rounded-full p-2 justify-center items-center shrink-0;
box-sizing: content-box;
}
}
}

View File

@ -3,10 +3,13 @@ import initFooterShapes from './footer';
import handleScrollTop from './utilities/scroll-top';
import handleInsidePageScrolling from './page-scrolling';
import alternatePictures from './alternate-pictures';
import { searchBarInit } from './search-bar';
window.addEventListener('load', function () {
menuInit();
initFooterShapes();
handleScrollTop();
handleInsidePageScrolling();
alternatePictures();
searchBarInit();
});

164
resources/js/search-bar.ts Normal file
View File

@ -0,0 +1,164 @@
interface SearchBarOptions {
searchBarSelector?: string;
buttonSelector?: string;
}
export default class SearchBar {
private searchBar: HTMLDivElement | null;
private searchButtons: NodeListOf<HTMLButtonElement> | null;
private searchInput: HTMLInputElement | null;
private isOpen: boolean = false;
private lastScrollY: number = 0;
private activeButton: HTMLButtonElement | null = null;
constructor(options: SearchBarOptions = {}) {
this.searchBar = document.querySelector('#search-module') as HTMLDivElement;
this.searchButtons = document.querySelectorAll(
'.tools-container .search-button'
) as NodeListOf<HTMLButtonElement>; // Il y a 2 boutons de recherche (1 mobile; 1 desktop)
this.searchInput = document.querySelector(
'.search-module__search-form__input'
) as HTMLInputElement;
this.init();
}
private init(): void {
if (!this.searchBar || !this.searchButtons) return;
// Initialiser l'état
this.isOpen = this.searchBar.hasAttribute('opened');
this.lastScrollY = window.scrollY;
this.updateAriaHidden();
// Ajouter les event listeners
this.searchButtons.forEach((button) =>
button.addEventListener('click', (event) => this.toggle(event))
);
this.searchBar.addEventListener('transitionend', this.handleTransitionEnd.bind(this));
document.addEventListener('keydown', this.handleKeyDown.bind(this));
document.addEventListener('click', this.handleClickOutside.bind(this));
window.addEventListener('scroll', this.handleScroll.bind(this));
}
private toggle(event: Event): void {
if (!this.searchBar) return;
// Stocker le bouton qui a été cliqué
this.activeButton = event.currentTarget as HTMLButtonElement;
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.open();
} else {
this.close();
}
}
public open(): void {
if (!this.searchBar) return;
this.searchBar.removeAttribute('closed');
this.searchBar.setAttribute('opened', '');
this.isOpen = true;
// Focus automatique sur le champ de recherche après un petit délai pour laisser l'animation commencer
setTimeout(() => {
if (this.searchInput && this.isOpen) {
this.searchInput.focus();
}
}, 100);
}
public close(): void {
if (!this.searchBar) return;
// Défocus l'input de recherche
if (this.searchInput) {
this.searchInput.blur();
}
this.searchBar.setAttribute('closing', '');
this.searchBar.removeAttribute('opened');
this.isOpen = false;
// Écouter la fin de l'animation de fermeture (on attend la plus longue: translate-out 800ms)
const handleAnimationEnd = (event: AnimationEvent) => {
if (event.animationName === 'translate-out') {
this.searchBar?.removeAttribute('closing');
this.searchBar?.setAttribute('closed', '');
this.searchBar?.removeEventListener('animationend', handleAnimationEnd);
// Refocus sur le bouton qui avait ouvert la barre après la fermeture
if (this.activeButton) {
this.activeButton.focus();
}
}
};
this.searchBar.addEventListener('animationend', handleAnimationEnd);
}
private handleTransitionEnd(): void {
this.updateAriaHidden();
}
private handleKeyDown(event: KeyboardEvent): void {
if (event.key === 'Escape' && this.isOpen) {
this.close();
}
}
private handleScroll(): void {
if (!this.isOpen) return;
this.close();
}
private handleClickOutside(event: MouseEvent): void {
if (!this.isOpen || !this.searchBar) return;
const target = event.target as Node;
// Vérifier si le clic est en dehors de la barre de recherche ET des boutons
const isClickOutsideSearchBar = !this.searchBar.contains(target);
const isClickOnButton = Array.from(this.searchButtons || []).some((button) =>
button.contains(target)
);
if (isClickOutsideSearchBar && !isClickOnButton) {
this.close();
}
}
private updateAriaHidden(): void {
if (!this.searchBar) return;
if (this.searchBar.hasAttribute('closed') || this.searchBar.hasAttribute('closing')) {
this.searchBar.setAttribute('aria-hidden', 'true');
} else {
this.searchBar.setAttribute('aria-hidden', 'false');
}
}
public isSearchBarOpen(): boolean {
return this.isOpen;
}
public destroy(): void {
if (!this.searchBar || !this.searchButtons) return;
// Note: Les event listeners anonymes ne peuvent pas être supprimés facilement
// Dans un vrai projet, il faudrait stocker les références des fonctions
this.searchBar.removeEventListener('transitionend', this.handleTransitionEnd.bind(this));
document.removeEventListener('keydown', this.handleKeyDown.bind(this));
document.removeEventListener('click', this.handleClickOutside.bind(this));
window.removeEventListener('scroll', this.handleScroll.bind(this));
}
}
// Fonction de compatibilité pour l'import existant
export function searchBarInit(): SearchBar {
return new SearchBar();
}

50
search.php Normal file
View File

@ -0,0 +1,50 @@
<?php get_header(); ?>
<div class="search-results-page ">
<section class="search-results-page__page-header">
<div class="inner">
<p class="search-results-page__page-header__subtitle title-small">Rechercher</p>
<h1 class="search-results-page__page-header__title">
<?php echo __("Résultats de recherche pour : ", "carhop") ?>
<span class="search-results-page__current-term">« <?php echo get_search_query(); ?> »</span>
</h1>
</div>
</section>
<div class="search-results-page__results-container">
<div class="search-results-page__results-counter">
<p class="post-count">
<span class="post-count__count"><?php echo $wp_query->found_posts; ?></span>
<span class="post-count__text">résultats</span>
</p>
</div>
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<?php get_template_part('template-parts/search/search-results-card', null, array(
'post_type' => get_post_type(),
'post_id' => get_the_ID(),
'search' => get_search_query(),
)); ?>
<?php endwhile; ?>
<!-- Pagination -->
<div class="search-results-pagination">
<?php
the_posts_pagination(array(
'mid_size' => 2,
'prev_text' => __('« Précédent', 'homegrade-theme__texte-fonctionnel'),
'next_text' => __('Suivant »', 'homegrade-theme__texte-fonctionnel'),
));
?>
</div>
<?php else : ?>
<p class="no-results"><?php echo __('Aucun résultat trouvé pour votre recherche.', 'homegrade-theme__texte-fonctionnel'); ?></p>
<?php endif; ?>
</div>
</div>
<?php
get_footer();

41
searchform.php Normal file
View File

@ -0,0 +1,41 @@
<form action="/" method="get" class="search-module__search-form">
<label class="search-module__search-form__title" for="search"><?php echo __('Vous cherchez quelque chose en particulier ?', 'homegrade-theme__texte-fonctionnel') ?></label>
<!-- <hr class="search-module__search-form__separator separator"> -->
<div class="search-module__searchbar-group">
<input class="search-module__search-form__input" type="text" name="s" id="search" value="<?php the_search_query(); ?>" placeholder="<?php echo __('Rechercher dans la revue Dynamiques', 'homegrade-theme__texte-fonctionnel') ?>" />
<button type="submit">
<span><?php echo __('Rechercher', 'homegrade-theme__texte-fonctionnel') ?></span>
</button>
</div>
<?php
$lastRevue = $recent_posts = wp_get_recent_posts(array(
'numberposts' => 1,
'post_type' => 'revues',
'post_status' => 'publish',
));
$lastRevueUrl = get_permalink($lastRevue[0]['ID']);
?>
<ul class="search-module__suggestions">
<li class="suggestion-item">
<a href="<?php echo home_url('/contact'); ?>">Découvrir les activités du CARHOP</a>
</li>
<li class="suggestion-item">
<a href="<?php echo home_url('/contact'); ?>">Contacter le CARHOP </a>
</li>
<li class="suggestion-item">
<a href="<?php echo home_url('/a-propos'); ?>">Faire connaissance avec léquipe de Dynamiques</a>
</li>
<li class="suggestion-item">
<a href="<?php echo home_url('/revues'); ?>">Découvrir les thématiques abordées</a>
</li>
<li class="suggestion-item">
<a href="<?php echo get_the_permalink(32); ?>">Je souhaite être bénévole du CARHOP</a>
</li>
</ul>
</form>

View File

@ -0,0 +1,32 @@
<?php
?>
<div class="carhop-mobile-menu-brand menu-mobile-brand" id="menu-mobile-brand">
<button id="mobile-menu-toggle" class="menu-mobile-brand__mobile-menu-toggle cta cta--outline cta--button" data-text-open="Menu" data-text-close="Fermer" aria-expanded="true">
<span class="text-content">Menu</span>
</button>
<div class="menu-mobile-brand__inner-elements">
<div class="website_logo">
<?php if (has_custom_logo()) { ?>
<?php the_custom_logo(); ?>
<?php } else { ?>
<a href=" <?php echo get_bloginfo('url'); ?>" class="font-extrabold text-lg uppercase">
<?php echo get_bloginfo('name'); ?>
</a>
<p class="text-sm font-light text-gray-600">
<?php echo get_bloginfo('description'); ?>
</p>
<?php } ?>
</div>
<div class="tools-container">
<button class="search-button">
<img src="<?php echo get_template_directory_uri(); ?>/resources/img/icons/carhop-rechercher.svg" alt="Rechercher">
</button>
<a class="subscribe-button" href="<?php echo home_url(); ?>/infolettre">
<img src="<?php echo get_template_directory_uri(); ?>/resources/img/icons/carhop-abonner.svg" alt="Sabonner">
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,53 @@
<?php
?>
<div class="primary-menu-container lg:flex lg:justify-between lg:items-center">
<nav id="primary-menu-nav" class="flex justify-between items-center">
<?php $secondary_logo_url = carhop_get_secondary_logo_url(); ?>
<div class="website_logo <?php echo $secondary_logo_url ? 'has-secondary-logo' : ''; ?>">
<?php if (has_custom_logo()) { ?>
<?php the_custom_logo(); ?>
<?php if ($secondary_logo_url) : ?>
<img id="secondary-logo" src="<?php echo $secondary_logo_url; ?>" alt="<?php echo get_bloginfo('name'); ?> - Logo alternatif">
<?php endif; ?>
<?php } else { ?>
<a href=" <?php echo get_bloginfo('url'); ?>" class="font-extrabold text-lg uppercase">
<?php echo get_bloginfo('name'); ?>
</a>
<p class="text-sm font-light text-gray-600">
<?php echo get_bloginfo('description'); ?>
</p>
<?php } ?>
</div>
<div id="primary-menu">
<?php
wp_nav_menu(
array(
'container' => 'false',
'theme_location' => 'primary',
'li_class' => 'menu-navlink',
'fallback_cb' => false,
)
); ?>
</div>
<div class="tools-container">
<button class="search-button">
<img src="<?php echo get_template_directory_uri(); ?>/resources/img/icons/carhop-rechercher.svg" alt="">
Rechercher
</button>
<a class="support-button" href="<?php echo home_url(); ?>/infolettre">
<img src="<?php echo get_template_directory_uri(); ?>/resources/img/icons/carhop-soutenir.svg" alt="">
Soutenir
</a>
</div>
</nav>
</div>

View File

@ -0,0 +1,21 @@
<?php
?>
<div class="secondary-menu-container">
<div class="secondary-menu-nav">
<?php
wp_nav_menu(
array(
'container' => 'false',
'theme_location' => 'secondary',
'li_class' => 'menu-navlink',
'fallback_cb' => false,
)
); ?>
<div class="network-redirector">
<a href="<?php echo get_site_url(2); ?>">
Revue Dynamiques
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,5 @@
<div id="search-module" class="search-module" closed aria-hidden role="search">
<div class="search-module__wrapper-container">
<?php get_search_form(); ?>
</div>
</div>