Bootstrap->tabs([ * 'horizontal-position' => 'top', * 'header-variant' => 'danger', * 'card' => true, * 'data' => [ * 'navs' => [ * ['text' => 'nav 1'], * ['html' => 'nav 2', 'active' => true], * ], * 'content' => [ * 'content 1', * 'content 2', * ] * ] * ]); * * ## Simple formatted tabs using the card option and vertical options * echo $this->Bootstrap->tabs([ * 'pills' => true, * 'vertical' => true, * 'vertical-position' => 'start', * 'card' => true, * 'data' => [ * 'navs' => [ * ['text' => 'nav 1'], * ['html' => 'nav 2', 'disabled' => true], * ], * 'content' => [ * 'content 1', * 'content 2', * ] * ] * ]); */ class BootstrapTabs extends BootstrapGeneric { private $defaultOptions = [ 'fill-header' => false, 'justify-header' => false, 'pills' => false, 'vertical' => false, 'vertical-size' => 3, 'vertical-position' => 'start', 'horizontal-position' => 'top', 'card' => false, 'header-variant' => '', 'body-variant' => '', 'body-class' => [], 'nav-class' => [], 'nav-item-class' => [], 'content-class' => [], 'data' => [ 'navs' => [], 'content' => [], ], ]; private $bsClasses = null; function __construct(array $options) { $this->allowedOptionValues = [ 'justify-header' => [false, 'center', 'end', 'start'], 'vertical-position' => ['start', 'end'], 'horizontal-position' => ['top', 'bottom'], 'body-variant' => array_merge(BootstrapGeneric::$variants, ['']), 'header-variant' => array_merge(BootstrapGeneric::$variants, ['']), ]; $this->processOptions($options); } public function tabs(): string { return $this->genTabs(); } private function processOptions(array $options): void { $this->options = array_merge($this->defaultOptions, $options); $this->data = $this->options['data']; $this->checkOptionValidity(); if (empty($this->data['navs'])) { throw new InvalidArgumentException(__('No navigation data provided')); } $this->bsClasses = [ 'nav' => [], 'nav-item' => $this->options['nav-item-class'], ]; if (!empty($this->options['justify-header'])) { $this->bsClasses['nav'][] = 'justify-content-' . $this->options['justify-header']; } if ($this->options['vertical'] && !isset($options['pills']) && !isset($options['card'])) { $this->options['pills'] = true; $this->options['card'] = true; } if ($this->options['pills']) { $this->bsClasses['nav'][] = 'nav-pills'; if ($this->options['vertical']) { $this->bsClasses['nav'][] = 'flex-column'; } if ($this->options['card']) { $this->bsClasses['nav'][] = 'card-header-pills'; } } else { $this->bsClasses['nav'][] = 'nav-tabs'; if ($this->options['card']) { $this->bsClasses['nav'][] = 'card-header-tabs'; } } if ($this->options['fill-header']) { $this->bsClasses['nav'][] = 'nav-fill'; } if ($this->options['justify-header']) { $this->bsClasses['nav'][] = 'nav-justify'; } $activeTab = array_key_first($this->data['navs']); foreach ($this->data['navs'] as $i => $nav) { if (!is_array($nav)) { $this->data['navs'][$i] = ['text' => $nav]; } if (!isset($this->data['navs'][$i]['id'])) { $this->data['navs'][$i]['id'] = 't-' . Security::randomString(8); } if (!empty($nav['active'])) { $activeTab = $i; } } $this->data['navs'][$activeTab]['active'] = true; if (!empty($this->options['vertical-size']) && $this->options['vertical-size'] != 'auto') { $this->options['vertical-size'] = ($this->options['vertical-size'] < 0 || $this->options['vertical-size'] > 11) ? 3 : $this->options['vertical-size']; } if (!is_array($this->options['nav-class'])) { $this->options['nav-class'] = [$this->options['nav-class']]; } if (!is_array($this->options['content-class'])) { $this->options['content-class'] = [$this->options['content-class']]; } } private function genTabs(): string { return $this->options['vertical'] ? $this->genVerticalTabs() : $this->genHorizontalTabs(); } private function genHorizontalTabs(): string { if ($this->options['card']) { $cardOptions = [ 'bodyHTML' => $this->genContent(), 'bodyVariant' => $this->options['body-variant'], ]; if ($this->options['horizontal-position'] === 'bottom') { $cardOptions['footerHTML'] = $this->genNav(); $cardOptions['footerVariant'] = $this->options['header-variant']; $cardOptions['headerVariant'] = $this->options['header-variant']; } else { $cardOptions['headerHTML'] = $this->genNav(); $cardOptions['headerVariant'] = $this->options['header-variant']; } $bsCard = new BootstrapCard($cardOptions); return $bsCard->card(); } else { return $this->genNav() . $this->genContent(); } } private function genVerticalTabs(): string { $header = $this->node('div', ['class' => array_merge( [ ($this->options['vertical-size'] != 'auto' ? 'col-' . $this->options['vertical-size'] : ''), ($this->options['card'] ? 'card-header border-end' : '') ], [ "bg-{$this->options['header-variant']}", ] )], $this->genNav()); $content = $this->node('div', ['class' => array_merge( [ ($this->options['vertical-size'] != 'auto' ? 'col-' . (12 - $this->options['vertical-size']) : ''), ($this->options['card'] ? 'card-body2' : '') ], [ "bg-{$this->options['body-variant']}", ] )], $this->genContent()); $containerContent = $this->options['vertical-position'] === 'start' ? [$header, $content] : [$content, $header]; $container = $this->node('div', ['class' => array_merge( [ 'row', ($this->options['card'] ? 'card flex-row' : ''), ($this->options['vertical-size'] == 'auto' ? 'flex-nowrap' : '') ], [ ] )], $containerContent); return $container; } private function genNav(): string { $html = $this->nodeOpen('ul', [ 'class' => array_merge(['nav'], $this->bsClasses['nav'], $this->options['nav-class']), 'role' => 'tablist', ]); foreach ($this->data['navs'] as $navItem) { $html .= $this->genNavItem($navItem); } $html .= $this->nodeClose('ul'); return $html; } private function genNavItem(array $navItem): string { $html = $this->nodeOpen('li', [ 'class' => array_merge(['nav-item'], $this->bsClasses['nav-item'], $this->options['nav-item-class']), 'role' => 'presentation', ]); $html .= $this->nodeOpen('a', [ 'class' => array_merge( ['nav-link'], [!empty($navItem['active']) ? 'active' : ''], [!empty($navItem['disabled']) ? 'disabled' : ''] ), 'data-bs-toggle' => $this->options['pills'] ? 'pill' : 'tab', 'id' => $navItem['id'] . '-tab', 'href' => '#' . $navItem['id'], 'aria-controls' => $navItem['id'], 'aria-selected' => !empty($navItem['active']), 'role' => 'tab', ]); $html .= $navItem['html'] ?? h($navItem['text']); $html .= $this->nodeClose('a'); $html .= $this->nodeClose('li'); return $html; } private function genContent(): string { $html = $this->nodeOpen('div', [ 'class' => array_merge(['tab-content'], $this->options['content-class']), ]); foreach ($this->data['content'] as $i => $content) { $navItem = $this->data['navs'][$i]; $html .= $this->genContentItem($navItem, $content); } $html .= $this->nodeClose('div'); return $html; } private function genContentItem(array $navItem, string $content): string { return $this->node('div', [ 'class' => array_merge(['tab-pane', 'fade'], [!empty($navItem['active']) ? 'show active' : '']), 'role' => 'tabpanel', 'id' => $navItem['id'], 'aria-labelledby' => $navItem['id'] . '-tab' ], $content); } }