addItem()->text('Home')->url('/')->icon('house'); * * $menu->addItem()->text('About') * ->url('/about/') * ->submenu(function($submenu) { * $submenu->addItem()->text('Team')->url('/about/team/'); * $submenu->addItem()->text('History')->url('/about/history/'); * }); * * echo $menu->render(); */ class Navigation { private string $id; private array $items = []; private array $classes = []; protected array $defaultMenuClasses = []; private bool $isNav = true; private bool $hasToggle = false; protected array $defaultItemClasses = []; private int $counter = 0; public function __construct(string $id = '') { $this->id = $id ?: 'menu-' . uniqid(); } public function getID():string { return $this->id; } /** * Add a menu item * * @return MenuItem */ public function addItem(?string $text = null, ?string $icon = null): MenuItem { $item = new MenuItem(++$this->counter); $this->items[] = $item; if ($text) { $item->text($text); } if ($icon) { $item->icon($icon); } if (!empty($this->defaultItemClasses)) { foreach ($this->defaultItemClasses as $class) { $item->addClass($class); } } return $item; } /** * Add CSS class to the nav element * * @param string $class * @return self */ public function addClass(string $class): self { $this->classes[] = $class; return $this; } public function addMenuClass(string $class):self { $this->menuClasses[] = $class; return $this; } public function defaultMenuClasses(array $classes):self { $classes = array_filter($classes, fn ($class) => is_string($class)); $this->defaultMenuClasses = $classes; return $this; } public function defaultItemClasses(array $classes): self { $classes = array_filter($classes, fn ($class) => is_string($class)); $this->defaultItemClasses = $classes; return $this; } /** * Set whether this nav has a toggle button * * @param bool $hasToggle * @return self */ public function hasToggle(bool $hasToggle = true): self { $this->hasToggle = $hasToggle; return $this; } public function isNav(bool $isNav = true):self { $this->isNav = $isNav; return $this; } /** * Render the menu HTML * * @param bool $return Whether to return or echo * @return string */ public function render(bool $return = true): string { if (empty($this->items)) { return ''; } $classStr = !empty($this->classes) ? ' class="' . esc_attr(implode(' ', array_merge([$this->id],$this->classes))) . '"' : ''; $html = ''; if ($this->isNav) { $html = ''; if ($this->hasToggle) { $html .= ' ' . jvbIcon('list') . ' Toggle Menu '; } } if (!$this->isNav) { $classStr = (empty($this->defaultMenuClasses)) ? '' : ' class="'.implode(' ', $this->defaultMenuClasses).'"'; } $html .= ''; foreach ($this->items as $item) { $html .= $item->render(); } $html .= ''; if ($this->isNav) { $html .= ''; } if ($return) { return $html; } echo $html; return $html; } } /** * Individual menu item with support for submenus */ class MenuItem { private int $id; private string $text = ''; private ?string $url = null; private ?string $icon = null; private ?Navigation $submenu = null; private array $classes = []; private array $menuClasses = []; private array $attributes = []; private bool $current = false; public function __construct(int $id) { $this->id = $id; } /** * Set the menu item text * * @param string $text * @return self */ public function text(string $text): self { $this->text = $text; return $this; } /** * Set the menu item URL * * @param string $url * @return self */ public function url(string $url): self { $this->url = $url; return $this; } /** * Set the menu item icon * * @param string $icon * @return self */ public function icon(string $icon): self { $this->icon = (str_starts_with($icon, 'isNav(false); if (!empty($this->defaultMenuClasses)) { foreach ($this->defaultMenuClasses as $class) { $submenu->addClass($class); } } $this->submenu = $submenu; return $submenu; } /** * Add CSS class to the list item * * @param string $class * @return self */ public function addClass(string $class): self { $this->classes[] = $class; return $this; } /** * Mark this item as current/active * * @param bool $current * @return self */ public function current(bool $current = true): self { $this->current = $current; if ($current) { $this->addClass('current'); } return $this; } /** * Add custom attribute to the link element * * @param string $key * @param string $value * @return self */ public function attribute(string $key, string $value): self { $this->attributes[$key] = $value; return $this; } /** * Render the menu item HTML * * @return string */ public function render(): string { $classes = $this->classes; if ($this->submenu) { $classes[] = 'has-submenu'; } $classStr = $this->renderClasses($classes); $html = ''; $html .= ''; // Render link or button if ($this->url) { $attrs = ''; foreach ($this->attributes as $key => $value) { $attrs .= ' ' . esc_attr($key) . '="' . esc_attr($value) . '"'; } $html .= ''; } else { $html .= ''; } if ($this->icon) { $html .= $this->icon; } $html .= ''.esc_html($this->text) . ''; $html .= ($this->url) ? '' : ''; // Render submenu if exists if ($this->submenu) { $html .= ''. jvbIcon('caret-down', ['title'=>'Toggle Submenu']). ''; $html .= ''; $html .= $this->submenu->render(); }else { $html .= ''; } $html .= ''; return $html; } }