-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from Fohn-Group/feature-tabs
Tabs Component
- Loading branch information
Showing
18 changed files
with
521 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Fohn\Ui\Component\Tabs; | ||
use Fohn\Ui\Component\Tabs\Tab; | ||
use Fohn\Ui\Service\Ui; | ||
use Fohn\Ui\View; | ||
|
||
require_once __DIR__ . '/../init-ui.php'; | ||
|
||
$tabs = Tabs::addTo(Ui::layout()); | ||
$tabs->setTabsMenuTemplate(Ui::templateFromFile(__DIR__ . '/template/tabs-menu.html')); | ||
|
||
$homeTab = $tabs->addTab(new Tab(['name' => 'home'])); | ||
View::addTo($homeTab)->setTextContent('This is Home content.'); | ||
|
||
$profileTab = $tabs->addTab(new Tab(['name' => 'profile'])); | ||
View::addTo($profileTab)->setTextContent('This is Profile content.'); | ||
|
||
$userTab = $tabs->addTab(new Tab(['name' => 'preferences'])); | ||
View::addTo($userTab)->setTextContent('This is Preferences content.'); | ||
|
||
$adminTab = $tabs->addTab(new Tab(['name' => 'admin']))->disabled(); | ||
View::addTo($adminTab)->setTextContent('This is Admin content.'); | ||
|
||
View\Divider::addTo(Ui::layout(), ['verticalSpace' => '12']); | ||
|
||
View::addTo(Ui::layout())->setTextContent('Show how a Vue property, like an icon name, can be added to Tab component and be available | ||
within the menu template.'); | ||
|
||
$tabs = Tabs::addTo(Ui::layout()); | ||
$tabs->setTabsMenuTemplate(Ui::templateFromFile(__DIR__ . '/template/tabs-menu-icon.html')); | ||
|
||
$homeTab = $tabs->addTab(new Tab(['name' => 'home']))->addProperty('icon', 'bi-house-fill'); | ||
View::addTo($homeTab)->setTextContent('This is Home content.'); | ||
|
||
$profileTab = $tabs->addTab(new Tab(['name' => 'profile']))->addProperty('icon', 'bi-person-fill'); | ||
View::addTo($profileTab)->setTextContent('This is Profile content.'); | ||
|
||
$userTab = $tabs->addTab(new Tab(['name' => 'preferences']))->addProperty('icon', 'bi-gear-fill'); | ||
View::addTo($userTab)->setTextContent('This is Preferences content.'); | ||
|
||
$adminTab = $tabs->addTab(new Tab(['name' => 'admin']))->disabled()->addProperty('icon', 'bi-person-fill-lock'); | ||
View::addTo($adminTab)->setTextContent('This is Admin content.'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Fohn\Ui\AppTest\Model\Country; | ||
use Fohn\Ui\Component\Form; | ||
use Fohn\Ui\Component\Tabs; | ||
use Fohn\Ui\Component\Tabs\Tab; | ||
use Fohn\Ui\Js\Jquery; | ||
use Fohn\Ui\Js\JsReload; | ||
use Fohn\Ui\Js\JsToast; | ||
use Fohn\Ui\Service\Atk\FormModelController; | ||
use Fohn\Ui\Service\Data; | ||
use Fohn\Ui\Service\Ui; | ||
use Fohn\Ui\View; | ||
use Fohn\Ui\View\Button; | ||
|
||
require_once __DIR__ . '/../init-ui.php'; | ||
|
||
$modelCtrl = new FormModelController(new Country(Data::db())); | ||
$id = (string) $modelCtrl->getModel()->tryLoadAny()->get('id'); | ||
|
||
$btnGoTo = Button::addTo(Ui::layout(), ['label' => 'Go to country tab', 'type' => 'text']); | ||
$btnEnableUser = Button::addTo(Ui::layout(), ['label' => 'Enable User Tab', 'type' => 'text']); | ||
$btnDisableUser = Button::addTo(Ui::layout(), ['label' => 'Disable User Tab', 'type' => 'text']); | ||
|
||
$tabs = Tabs::addTo(Ui::layout()); | ||
Jquery::addEventTo($btnGoTo, 'click')->execute($tabs->jsActivateTabName('country')); | ||
Jquery::addEventTo($btnEnableUser, 'click')->execute($tabs->jsEnableTabName('user')); | ||
Jquery::addEventTo($btnDisableUser, 'click')->execute($tabs->jsDisableTabName('user')); | ||
|
||
$homeTab = $tabs->addTab(new Tab(['name' => 'home'])); | ||
$fn = $homeTab->jsOnInitTab(\Fohn\Ui\Js\JsFunction::arrow()); | ||
$fn->execute(\Fohn\Ui\Js\Js::from('console.log(\'homeTab on init\')')); | ||
|
||
$fn = $homeTab->jsOnShowTab(\Fohn\Ui\Js\JsFunction::arrow()); | ||
$fn->execute(\Fohn\Ui\Js\Js::from('console.log(\'homeTab on show\')')); | ||
|
||
$fn = $homeTab->jsOnHideTab(\Fohn\Ui\Js\JsFunction::arrow()); | ||
$fn->execute(\Fohn\Ui\Js\Js::from('console.log(\'homeTab on hide\')')); | ||
|
||
View::addTo($homeTab)->setTextContent('This is home tab content.'); | ||
|
||
$profileTab = $tabs->addTab(new Tab(['name' => 'country'])); | ||
|
||
$form = Form::addTo($profileTab); | ||
$form->addControls($modelCtrl->factoryFormControls($id)); | ||
$form->onSubmit(function (Form $f) use ($modelCtrl, $id) { | ||
if ($errors = $modelCtrl->saveModelUsingForm($id, $f->getControls())) { | ||
$f->addValidationErrors($errors); | ||
} | ||
|
||
return JsToast::success('Saved!'); | ||
}); | ||
|
||
$userTab = $tabs->addTab(new Tab(['name' => 'user'])); | ||
View::addTo($userTab)->setTextContent('This is user tab content.'); | ||
$b = Button::addTo($userTab, ['label' => 'Reload ' . ($_GET['test'] ?? 0), 'type' => 'text', 'size' => 'small']); | ||
Jquery::addEventTo($b, 'click')->execute(JsReload::view($b, ['test ' => random_int(0, 100)])); | ||
|
||
$tabs->activateTabName('user'); | ||
|
||
Ui::viewDump($tabs, 'tab'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<div class="border-b border-gray-200 mb-8"> | ||
<ul class="flex flex-wrap -mb-px"> | ||
<template v-for="(tab, index) in tabs" :key="index"> | ||
<li role="presentation" class="mr-4"> | ||
<button | ||
@click="activate(index)" | ||
class="inline-block p-4 border-b-2 rounded-t-lg disabled:text-gray-400 disabled:cursor-not-allowed" | ||
:class="{'border-blue-500 text-blue-500': index === currentIndex, 'border-transparent hover:text-gray-600 hover:border-gray-300': index !== currentIndex}" | ||
:data-active="index === currentIndex" | ||
:data-name="tab.name" | ||
:disabled="tab.disabled" | ||
><i class="mx-2" :class="tab.icon"></i>{{tab.caption}}</button> | ||
</li> | ||
</template> | ||
</ul> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<div class="mb-8 border-b border-gray-200"> | ||
<ul class="flex flex-wrap text-sm font-medium text-center text-gray-500"> | ||
<template v-for="(tab, index) in tabs" :key="index"> | ||
<li role="presentation" class="mr-4"> | ||
<button | ||
@click="activate(index)" | ||
class="inline-block p-4 rounded-t-lg disabled:text-gray-400 disabled:cursor-not-allowed" | ||
:class="{'bg-gray-300 text-blue-500': index === currentIndex, 'hover:bg-gray-100': index !== currentIndex}" | ||
:data-active="index === currentIndex" | ||
:data-name="tab.name" | ||
:disabled="tab.disabled" | ||
>{{tab.caption}}</button> | ||
</li> | ||
</template> | ||
</ul> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
/** | ||
* Tabs component. | ||
*/ | ||
|
||
namespace Fohn\Ui\Component; | ||
|
||
use Fohn\Ui\Component\Tabs\Tab; | ||
use Fohn\Ui\Core\Exception; | ||
use Fohn\Ui\HtmlTemplate; | ||
use Fohn\Ui\Js\JsRenderInterface; | ||
use Fohn\Ui\Js\Type\Type; | ||
use Fohn\Ui\View; | ||
|
||
class Tabs extends View implements VueInterface | ||
{ | ||
use VueTrait; | ||
|
||
protected const COMP_NAME = 'fohn-tabs'; | ||
protected const TAB_REGION_NAME = 'tabs'; | ||
|
||
private const PINIA_PREFIX = '__tabs_'; | ||
|
||
public string $defaultTemplate = 'vue-component/tabs.html'; | ||
|
||
/** @var array<Tab> */ | ||
protected array $tabs = []; | ||
|
||
protected string $activeTabName = ''; | ||
|
||
protected function initRenderTree(): void | ||
{ | ||
parent::initRenderTree(); | ||
} | ||
|
||
public function addTab(Tab $tab): Tab | ||
{ | ||
$this->registerTab($tab); | ||
$this->addView($tab, self::TAB_REGION_NAME); | ||
|
||
return $tab; | ||
} | ||
|
||
public function activateTabName(string $name): self | ||
{ | ||
$this->activeTabName = $name; | ||
|
||
return $this; | ||
} | ||
|
||
public function jsActivateTabName(string $name): JsRenderInterface | ||
{ | ||
// @phpstan-ignore-next-line | ||
return $this->jsGetStore(self::PINIA_PREFIX)->activateByName($name); | ||
} | ||
|
||
public function jsEnableTabName(string $name): JsRenderInterface | ||
{ | ||
// @phpstan-ignore-next-line | ||
return $this->jsGetStore(self::PINIA_PREFIX)->enableByName($name); | ||
} | ||
|
||
public function jsDisableTabName(string $name): JsRenderInterface | ||
{ | ||
// @phpstan-ignore-next-line | ||
return $this->jsGetStore(self::PINIA_PREFIX)->disableByName($name); | ||
} | ||
|
||
/** | ||
* Replace Tabs menu template. | ||
* The template must use Tab component template props: | ||
* - tabs, an array of {name: '', caption: '', disabled: false...} | ||
* - currentIndex, an integer value representing the current activated tab. | ||
* - activate, a function with an index argument that activate the tab. | ||
*/ | ||
public function setTabsMenuTemplate(HtmlTemplate $template, string $region = 'TabMenu'): self | ||
{ | ||
$this->getTemplate()->dangerouslySetHtml($region, $template->renderToHtml()); | ||
|
||
return $this; | ||
} | ||
|
||
protected function registerTab(Tab $tab): void | ||
{ | ||
$this->assertTabHasName($tab->getName()); | ||
$this->assertTabIsUnique($tab->getName()); | ||
|
||
if (!$tab->getCaption()) { | ||
$tab->setCaption(ucfirst($tab->getName())); | ||
} | ||
|
||
$tab->tabStoreId = $this->getPiniaStoreId(self::PINIA_PREFIX); | ||
$this->tabs[$tab->getName()] = $tab; | ||
} | ||
|
||
private function assertTabHasName(string $tabName): void | ||
{ | ||
if (!$tabName) { | ||
throw new Exception('Tab must have a name.'); | ||
} | ||
} | ||
|
||
private function assertTabIsUnique(string $tabName): void | ||
{ | ||
if (array_key_exists($tabName, $this->tabs)) { | ||
throw (new Exception('This tab name is already added.')) | ||
->addMoreInfo('Tab name:', $tabName); | ||
} | ||
} | ||
|
||
protected function setTemplateProps(): void | ||
{ | ||
$props['storeId'] = $this->getPiniaStoreId(self::PINIA_PREFIX); | ||
|
||
$tabList = []; | ||
foreach ($this->tabs as $tab) { | ||
$tabList[] = $tab->getProperties(); | ||
} | ||
$props['tabList'] = $tabList; | ||
$props['activeTabIdx'] = array_search($this->activeTabName, array_keys($this->tabs), true) ?: 0; | ||
|
||
foreach ($props as $key => $value) { | ||
$this->getTemplate()->setJs($key, Type::factory($value)); | ||
} | ||
} | ||
|
||
protected function beforeHtmlRender(): void | ||
{ | ||
$this->setTemplateProps(); | ||
|
||
$this->createVueApp(self::COMP_NAME, [], $this->getDefaultSelector()); | ||
parent::beforeHtmlRender(); | ||
} | ||
} |
Oops, something went wrong.