| | |
| | | */ |
| | | class Navigation { |
| | | private string $id; |
| | | private array $items = []; |
| | | public array $items = []; |
| | | private array $classes = []; |
| | | protected array $defaultMenuClasses = []; |
| | | private bool $isNav = true; |
| | | private bool $hasToggle = false; |
| | | protected array $defaultItemClasses = []; |
| | | private int $counter = 0; |
| | | private bool $isDrawer = false; |
| | | private bool $drawerCollapsed = true; |
| | | |
| | | public function __construct(string $id = '') { |
| | | $this->id = $id ?: 'menu-' . uniqid(); |
| | |
| | | if ($this->isNav) { |
| | | $html = '<nav id="' . esc_attr($this->id).'"' . $classStr.'>'; |
| | | |
| | | if ($this->hasToggle) { |
| | | // Drawer toggle or regular toggle |
| | | if ($this->isDrawer) { |
| | | $html .= '<button class="toggle main" type="button" |
| | | aria-expanded="' . ($this->drawerCollapsed ? 'false' : 'true') . '" |
| | | aria-controls="' . esc_attr($this->id) . '-list"> |
| | | ' . jvbIcon('caret-left') . ' |
| | | <span class="screen-reader-text">Toggle Menu</span> |
| | | </button>'; |
| | | } elseif ($this->hasToggle) { |
| | | $html .= '<button class="toggle main" type="button" aria-expanded="false" aria-controls="' . esc_attr($this->id) . '"> |
| | | ' . jvbIcon('list') . ' |
| | | <span class="screen-reader-text">Toggle Menu</span> |
| | | </button>'; |
| | | ' . jvbIcon('list') . ' |
| | | <span class="screen-reader-text">Toggle Menu</span> |
| | | </button>'; |
| | | } |
| | | } |
| | | if (!$this->isNav) { |
| | |
| | | echo $html; |
| | | return $html; |
| | | } |
| | | |
| | | /** |
| | | * Configure as a drawer-style menu |
| | | * |
| | | * @param bool $collapsed Initial state |
| | | * @return self |
| | | */ |
| | | public function asDrawer(bool $collapsed = true): self { |
| | | $this->isDrawer = true; |
| | | $this->drawerCollapsed = $collapsed; |
| | | $this->addClass('drawer'); |
| | | if (!$collapsed) { |
| | | $this->addClass('open'); |
| | | } |
| | | return $this; |
| | | } |
| | | |
| | | public function isDrawer(): bool { |
| | | return $this->isDrawer; |
| | | } |
| | | /** |
| | | * Add a section header |
| | | * |
| | | * @param string $title |
| | | * @return MenuSection |
| | | */ |
| | | public function addSection(string $title): MenuSection { |
| | | $section = new MenuSection($title, ++$this->counter); |
| | | $this->items[] = $section; |
| | | |
| | | if (!empty($this->defaultItemClasses)) { |
| | | foreach ($this->defaultItemClasses as $class) { |
| | | $section->addItemClass($class); |
| | | } |
| | | } |
| | | |
| | | return $section; |
| | | } |
| | | |
| | | /** |
| | | * Populate menu from array structure |
| | | * |
| | | * @param array $items Array of menu items |
| | | * @return self |
| | | */ |
| | | public function populateFromArray(array $items): self { |
| | | foreach ($items as $item) { |
| | | // Handle sections |
| | | if (!empty($item['section'])) { |
| | | $section = $this->addSection($item['section']); |
| | | if (!empty($item['items'])) { |
| | | $this->populateSection($section, $item['items']); |
| | | } |
| | | continue; |
| | | } |
| | | |
| | | // Handle regular items |
| | | $menuItem = $this->addItem($item['text'] ?? '', $item['icon'] ?? ''); |
| | | |
| | | if (!empty($item['class'])) { |
| | | $menuItem->addClass($item['class']); |
| | | } |
| | | if (!empty($item['url'])) { |
| | | $menuItem->url($item['url']); |
| | | } |
| | | |
| | | |
| | | if (!empty($item['submenu'])) { |
| | | $submenu = $menuItem->submenu(); |
| | | $submenu->populateFromArray($item['submenu']); |
| | | } |
| | | } |
| | | |
| | | return $this; |
| | | } |
| | | |
| | | private function populateSection(MenuSection $section, array $items): void { |
| | | foreach ($items as $item) { |
| | | $menuItem = $section->addItem($item['text'] ?? '', $item['icon'] ?? null); |
| | | |
| | | if (!empty($item['url'])) { |
| | | $menuItem->url($item['url']); |
| | | } |
| | | |
| | | if (!empty($item['submenu'])) { |
| | | $submenu = $menuItem->submenu(); |
| | | $submenu->populateFromArray($item['submenu']); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return string |
| | | */ |
| | | public function render(): string { |
| | | if (!$this->url && (!$this->submenu || empty($this->submenu->items))) { |
| | | return ''; |
| | | } |
| | | $classes = $this->classes; |
| | | if ($this->submenu) { |
| | | $classes[] = 'has-submenu'; |
| | |
| | | return $html; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Menu section with header and items |
| | | */ |
| | | class MenuSection { |
| | | private int $id; |
| | | private string $title; |
| | | private array $items = []; |
| | | private array $classes = []; |
| | | private array $defaultItemClasses = []; |
| | | private int $counter = 0; |
| | | |
| | | public function __construct(string $title, int $id) { |
| | | $this->title = $title; |
| | | $this->id = $id; |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | public function addClass(string $class): self { |
| | | $this->classes[] = $class; |
| | | return $this; |
| | | } |
| | | |
| | | public function addItemClass(string $class): self { |
| | | $this->defaultItemClasses[] = $class; |
| | | return $this; |
| | | } |
| | | |
| | | public function render(): string { |
| | | if (empty($this->items)) { |
| | | return ''; |
| | | } |
| | | |
| | | $classStr = !empty($this->classes) ? ' class="menu-section ' . esc_attr(implode(' ', $this->classes)) . '"' : ' class="menu-section"'; |
| | | |
| | | $html = '<li' . $classStr . '>'; |
| | | $html .= '<span class="section-title">' . esc_html($this->title) . '</span>'; |
| | | $html .= '<ul class="section-items">'; |
| | | |
| | | foreach ($this->items as $item) { |
| | | $html .= $item->render(); |
| | | } |
| | | |
| | | $html .= '</ul></li>'; |
| | | |
| | | return $html; |
| | | } |
| | | } |