Skip to content

Commit

Permalink
Adds support for submenu classes thanks to new filters released in Wo…
Browse files Browse the repository at this point in the history
…rdPress 6.3
  • Loading branch information
davidwebca committed Aug 31, 2023
1 parent d599c7a commit 44a6bd8
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 87 deletions.
58 changes: 38 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WordPress Menu Classes

Allow adding custom classes to WordPress menu ul, li, a and at different depths. Perfect for TailwindCSS and AlpineJS usage.
Allow adding custom classes to WordPress li, a and submenus at different depths. Perfect for TailwindCSS and AlpineJS usage.

This package adds WordPress filters to allow custom arguments to wp_nav_menu to, in turn, allow custom classes to every element of a menu. You can apply a class only to certain depth of your menu as well.

Expand All @@ -18,22 +18,25 @@ Install via Composer:
$ composer require davidwebca/wordpress-menu-classes
```

If your theme already uses composer, the filters will be automatically added thanks to the auto-loading and auto-instantiating class. Otherwise, if you're looking for a standalone file you want to add to your theme, either look for src/WordPressMenuClasses.php in this repository or add this [gist](https://gist.github.com/davidwebca/a7b278bbb0c0ce1d1ec5620126e863bb) in your theme's functions.php.
If your theme already uses composer, the filters will be automatically added thanks to the auto-loading and auto-instantiating class. Otherwise, if you're looking for a standalone file you want to add to your theme, either look for src/WordPressMenuClasses.php in this repository.

## Instructions

The filters use the depth argument given by WordPress which is an index, thus starts with level 0 (zero).

Here's a list of the custom arguments you can pass to wp_nav_menu that are supported by this package :

- ```link_atts``` or ```link_atts_$depth``` or ```link_atts_order_$order```
- Add any attribute to ```<a>``` elements
- ```a_atts``` or ```a_atts_$depth``` or ```a_atts_order_$order```
- ```a_class``` or ```a_class_$depth``` or ```a_class_order_$order```
- Add classes to ```<a>``` elements
- Add any attribute or class to ```<a>``` elements

- ```li_atts``` or ```li_atts_$depth``` or ```li_atts_order_$order```
- ```li_class``` or ```li_class_$depth``` or ```li_class_order_$order```
- Add classes to ```<li>``` elements
- Add any attribute or class to ```<li>``` elements

- ```submenu_atts``` or ```submenu_atts_$depth```
- ```submenu_class``` or ```submenu_class_$depth```
- Add classes to submenu ```<ul>``` elements
- Add any attribute or class to submenu ```<ul>``` elements. Note that submenus do not support order.

Ex.: add a "text-black" class to all links and "text-blue" class only to 3rd level links

Expand All @@ -46,24 +49,39 @@ wp_nav_menu([
]);
```

Ex.: More complete example with some TailwindCSS classes and AlpineJS sugar
Ex.: Supports classes as array (this is non-native to WordPress, but provided by this package as a convenience)

```php
wp_nav_menu([
'theme_location' => 'primary_navigation',
'a_class' => ['text-white', 'bg-blue-500'],
'li_atts' => [
'class' => ['focus:ring-2', 'ring-orange-500']
]
// ...
]);
```

Ex.: More complete example with some TailwindCSS classes and AlpineJS sugar. This is a fully functional accordion navigation without additional JavaScript (requires Alpine's x-collapse plugin).

```php
wp_nav_menu([
'theme_location' => 'primary_navigation',
'menu_class' => 'relative w-full z-10 pl-0 list-none flex',
'link_atts_0' => [
":class" => "{ 'active': tab === 'foo' }",
"@click" => "tab = 'foo'"
'container' => 'nav',
'menu_class' => 'list-none p-0 m-0',
'a_class_0' => "font-bold inline-flex items-center text-xl",
'li_atts_0' => [
'class' => "w-full px-6 before:mr-4 before:cursor-pointer before:shrink-0 before:grow-0 before:inline-flex before:justify-center before:items-center before:w-6 before:h-6 before:rounded before:bg-black before:text-white before:p-1 before:hover:opacity-50 before:transition",
':class' => "{'before:content-[\'+\']': !opened, 'before:content-[\'-\']': opened}",
'x-data' => "{opened: false}",
'x-on:click' => 'opened = !opened'
],
'li_class' => 'w-full',
'li_class_0' => 'mb-12',
'a_class' => 'text-sm xl:text-xl text-white border-b hover:border-white',
'a_class_0' => 'text-3xl xl:text-5xl relative after:bg-primary',
'li_class_1' => 'after:bg-primary hidden lg:block',
'a_class_1' => 'flex h-full items-center uppercase py-2 relative border-white border-opacity-40 hover:border-opacity-100',
'submenu_class' => 'list-none pl-0 grid grid-cols-1 lg:grid-cols-2 lg:gap-x-12 xl:gap-x-24 xxl:gap-x-32',
'container'=>false
'submenu_class_0' => 'wowza',
'submenu_atts_0' => [
'x-show' => 'opened',
'x-collapse' => "_",
'class' => 'list-none pl-10'
]
]);
```

Expand Down
155 changes: 88 additions & 67 deletions src/WordPressMenuClasses.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class WordPressMenuClasses
public function __construct()
{
add_filter('nav_menu_link_attributes', [$this, 'navMenuLinkAttributes'], 10, 4);
add_filter('nav_menu_css_class', [$this, 'navMenuCSSClass'], 10, 4);
add_filter('nav_menu_submenu_css_class', [$this, 'navMenuSubmenuCSSClass'], 10, 3);
add_filter('nav_menu_item_attributes', [$this, 'navMenuItemAttributes'], 10, 4);
add_filter('nav_menu_submenu_attributes', [$this, 'navSubmenuAttributes'], 10, 3);
}

/**
Expand All @@ -30,41 +30,8 @@ public function navMenuLinkAttributes($atts, $item, $args, $depth)
{
$index = $item->menu_order;

if (property_exists($args, 'link_atts')) {
$atts = array_merge($atts, $args->link_atts);
}
if (property_exists($args, "link_atts_$depth")) {
$atts = array_merge($atts, $args->{"link_atts_$depth"});
}
if (property_exists($args, "link_atts_order_$index")) {
$atts = array_merge($atts, $args->{"link_atts_order_$index"});
}

if (empty($atts['class'])) {
$atts['class'] = '';
}

$classes = explode(' ', $atts['class']);

if (property_exists($args, 'a_class')) {
$arr_classes = explode(' ', $args->a_class);
$classes = array_merge($classes, $arr_classes);
}
if (property_exists($args, "a_class_$depth")) {
$arr_classes = explode(' ', $args->{"a_class_$depth"});
$classes = array_merge($classes, $arr_classes);
}
if (property_exists($args, "a_class_order_$index")) {
$arr_classes = explode(' ', $args->{"a_class_order_$index"});
$classes = array_merge($classes, $arr_classes);
}

// Applying this here too just in case, but there's
// no default user interface to add a class directly to a link in the menu
// (classes are applied to li elements by default)
$classes = $this->fixWordPressClasses($classes);

$atts['class'] = implode(' ', $classes);
$atts = $this->buildAttributes('a', $atts, $args, $depth, $index);
$atts = $this->buildClasses('a', $atts, $args, $depth, $index);

return $atts;
}
Expand All @@ -79,54 +46,108 @@ public function navMenuLinkAttributes($atts, $item, $args, $depth)
* This is an index, thus starts with 0 for the root level.
* @return array Modified classes for the current li element
*/
public function navMenuCSSClass($classes, $item, $args, $depth)
public function navMenuItemAttributes($atts, $item, $args, $depth)
{
$index = $item->menu_order;

if (property_exists($args, 'li_class')) {
$arr_classes = explode(' ', $args->li_class);
$classes = array_merge($classes, $arr_classes);
}
if (property_exists($args, "li_class_$depth")) {
$arr_classes = explode(' ', $args->{"li_class_$depth"});
$classes = array_merge($classes, $arr_classes);
}
if (property_exists($args, "li_class_order_$index")) {
$arr_classes = explode(' ', $args->{"li_class_order_$index"});
$classes = array_merge($classes, $arr_classes);
}


$classes = $this->fixWordPressClasses($classes);
$atts = $this->buildAttributes('li', $atts, $args, $depth, $index);
$atts = $this->buildClasses('li', $atts, $args, $depth, $index);

return $classes;
return $atts;
}

/**
* Add custom classes to ul.sub-menu in wp_nav_menu
* Add custom classes and attributes to ul.submenu in wp_nav_menu
*
* @param array $classes CSS classes added to all ul submenu of our menu.
* @param object $args wp_nav_menu args object
* @param int $depth Depth of the current menu item being parsed.
* @param object $atts wp_nav_menu attributes object
* @param object $args wp_nav_menu args object
* @param int $depth Depth of the current submenu being parsed.
* This is an index, thus starts with 0 for the root level.
* @return object Modified attributes for the current ul submenu
*/
public function navMenuSubmenuCSSClass($classes, $args, $depth)
public function navSubmenuAttributes($atts, $args, $depth)
{
if (property_exists($args, 'submenu_class')) {
$arr_classes = explode(' ', $args->submenu_class);
$classes = array_merge($classes, $arr_classes);
$atts = $this->buildAttributes('submenu', $atts, $args, $depth);
$atts = $this->buildClasses('submenu', $atts, $args, $depth);

return $atts;
}

/**
* Utility function to build the attributes
*
* @param String $prefix The prefix (a, li, submenu)
* @param object $atts wp_nav_menu attributes object
* @param object $args wp_nav_menu args object
* @param int $depth Depth of the current submenu being parsed.
* @param int $index The index of menu order, -1 is considered absent
*
* @return object Modified attributes for the current element
*/
public function buildAttributes($prefix, $atts, $args, $depth, $index = -1) {
if (property_exists($args, "{$prefix}_atts")) {
$atts = array_merge($atts, $args->{"{$prefix}_atts"});
}
if (property_exists($args, "{$prefix}_atts_{$depth}")) {
$atts = array_merge($atts, $args->{"{$prefix}_atts_{$depth}"});
}
if ($index !== -1 && property_exists($args, "{$prefix}_atts_order_{$index}")) {
$atts = array_merge($atts, $args->{"{$prefix}_atts_order_{$index}"});
}

if (property_exists($args, "submenu_class_$depth")) {
$arr_classes = explode(' ', $args->{"submenu_class_$depth"});
$classes = array_merge($classes, $arr_classes);
if (empty($atts['class'])) {
$atts['class'] = '';
}
return $atts;
}


// Applying this here too just in case, but there's
// no default user interface to add a class to a submenu

/**
* Utility function to build the classes
*
* @param String $prefix The prefix (a, li, submenu)
* @param object $atts wp_nav_menu attributes object
* @param object $args wp_nav_menu args object
* @param int $depth Depth of the current submenu being parsed.
* @param int $index The index of menu order, -1 is considered absent
*
* @return object Modified attributes for the current element
*/
public function buildClasses($prefix, $atts, $args, $depth, $index = -1) {
$classes = explode(' ', $atts['class']);

$classes = array_merge($classes, $this->arrayOrStringClasses("{$prefix}_class", $args));
$classes = array_merge($classes, $this->arrayOrStringClasses("{$prefix}_class_$depth", $args));
$classes = array_merge($classes, $this->arrayOrStringClasses("{$prefix}_class_order_$depth", $args));

// Applying this fix everywhere even though there's only
// a user interface to add classes to links so far
$classes = $this->fixWordPressClasses($classes);

$atts['class'] = implode(' ', $classes);

return $atts;
}

/**
* Utility function to accept array or string classes
*
* @param String $prop The property to check on our custom arguments (ex.: ul_class, li_class_order_1)
* @param object $args wp_nav_menu args object
*
* @return object Modified attributes for the current element
*/
public function arrayOrStringClasses($prop, $args) {
$classes = [];
if (property_exists($args, $prop)) {
$temp_classes = $args->{$prop};
if(is_string($temp_classes)) {
$temp_classes = explode(' ', $temp_classes);
}
$classes = array_merge($classes, $temp_classes);
}

return $classes;
}

Expand Down

0 comments on commit 44a6bd8

Please sign in to comment.