diff --git a/README.md b/README.md index 0679715..3a620be 100644 --- a/README.md +++ b/README.md @@ -130,3 +130,44 @@ class MainMenuFilter extends MenusFilter } } ``` + +## Packaged Menus + +`Menus` comes with some pre-made menus which can be used immediately or built on to create +your own variants. All menus are in the `Tatter\Menus\Menus` namespace and extend the `Menu` +class so can be used with the Filter or as any other menu you would make. + +### Breadcrumbs + +The `BreadcrumbsMenu` is a special menu, using horizontal-style navigation links for nested +content. This menu comes pre-styled for [Bootstrap](https://getbootstrap.com/docs/4.3/components/breadcrumb/) +and defaults to the segments retrieved from the framework's `IncomingRequest::$uri`, but +you may provide your own using the static methods `set`, `get`, `push`, and `pop`. Additionally, +`BreadcrumbsMenu::discover()` will attempt to create a default menu. All these methods use +the `Breadcrumb` class, a simple wrapper for the URL and display value. +For example: +``` +use Tatter\Menus\Breadcrumb; +use Tatter\Menus\Menus\BreadcrumbsMenu; + +class Users extends Controller +{ + public function show(int $userId) + { + // Get the User + $user = model('UserModel')->find($userId); + + // Start with the default breadcrumbs + BreadcrumbsMenu::discover(); + + // Pop off the numeric last segment + BreadcrumbsMenu::pop(); + + // Replace it with the user's name + BreadcrumbsMenu::push(new Breadcrumb(current_url(), $user->name)); + + return view('users/show', ['user' => $user]); + } +} +``` +... if you have the filter in place the rest is handled for you. diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php new file mode 100644 index 0000000..0231617 --- /dev/null +++ b/src/Breadcrumb.php @@ -0,0 +1,44 @@ +getSegments(); + $display = ucfirst(end($segments) ?: lang('Menus.home')); + } + + $this->url = $url; + $this->display = $display; + } +} diff --git a/src/Language/en/Menus.php b/src/Language/en/Menus.php new file mode 100644 index 0000000..dbad769 --- /dev/null +++ b/src/Language/en/Menus.php @@ -0,0 +1,5 @@ + 'Home', +]; diff --git a/src/Menus/BreadcrumbsMenu.php b/src/Menus/BreadcrumbsMenu.php new file mode 100644 index 0000000..d29a810 --- /dev/null +++ b/src/Menus/BreadcrumbsMenu.php @@ -0,0 +1,130 @@ +uri; + + // Always start with the base URL + $breadcrumbs = [ + new Breadcrumb(base_url(), lang('Menus.home')), + ]; + + // Add each segment + foreach ($uri->getSegments() as $segment) + { + $breadcrumbs[] = new Breadcrumb(site_url($segment)); + } + self::set($breadcrumbs); + + return self::get(); + } + + /** + * Returns the currently-configured crumbs. + * + * @return Breadcrumb[]|null + */ + public static function get(): ?array + { + return self::$breadcrumbs; + } + + /** + * Sets the crumbs used to build the Menu. + * + * @param Breadcrumb[]|null $breadcrumbs + */ + public static function set(?array $breadcrumbs) + { + self::$breadcrumbs = $breadcrumbs; + } + + /** + * Removes and returns the last crumb. + * + * @return Breadcrumb + */ + public static function pop(): ?Breadcrumb + { + return is_null(self::$breadcrumbs) ? null : array_pop(self::$breadcrumbs); + } + + /** + * Adds a new Breadcrumb to the Menu. + * + * @param Breadcrumb $breadcrumb + * + * @return int New number of items in the Menu + */ + public static function push(Breadcrumb $breadcrumb): int + { + self::$breadcrumbs = self::$breadcrumbs ?? []; + + return array_push(self::$breadcrumbs, $breadcrumb); + } + + //-------------------------------------------------------------------- + + /** + * Builds the Menu and returns the + * rendered HTML string. + * + * @return string + */ + public function __toString(): string + { + // If no breadcrumbs are set then initiate discovery + if (is_null(self::$breadcrumbs)) + { + self::discover(); + } + + // Use the last item without a link + $last = self::pop(); + + foreach (self::$breadcrumbs as $breadcrumb) + { + $this->builder->link($breadcrumb->url, $breadcrumb->display); + } + + return $this->builder + ->html($last->display, ['class' => 'breadcrumb-item active']) + ->render(); + } +} diff --git a/src/Traits/Bootstrap.php b/src/Traits/BootstrapStyle.php similarity index 83% rename from src/Traits/Bootstrap.php rename to src/Traits/BootstrapStyle.php index 14635a4..a5cbdd4 100644 --- a/src/Traits/Bootstrap.php +++ b/src/Traits/BootstrapStyle.php @@ -10,9 +10,9 @@ * * @mixin \Tatter\Menus\Menu */ -trait Bootstrap +trait BootstrapStyle { - protected function applyBootstrap(): void + protected function applyBootstrapStyle(): void { $this->builder->registerFilter(function (Link $link) { $link->addParentClass('nav-item'); diff --git a/src/Traits/BreadcrumbsStyle.php b/src/Traits/BreadcrumbsStyle.php new file mode 100644 index 0000000..c159cdb --- /dev/null +++ b/src/Traits/BreadcrumbsStyle.php @@ -0,0 +1,26 @@ +builder + ->addClass('breadcrumb') + ->setWrapperTag('ol') + ->wrap('nav', ['aria-label' => 'breadcrumb']) + ->registerFilter(function (Link $link) { + $link->addParentClass('breadcrumb-item'); + }); + } +} diff --git a/tests/breadcrumbs/BreadcrumbTest.php b/tests/breadcrumbs/BreadcrumbTest.php new file mode 100644 index 0000000..09de7c7 --- /dev/null +++ b/tests/breadcrumbs/BreadcrumbTest.php @@ -0,0 +1,21 @@ +assertSame('banana', $breadcrumb->display); + } + + public function testGuessesDisplay() + { + $breadcrumb = new Breadcrumb(site_url('fruit')); + + $this->assertSame('Fruit', $breadcrumb->display); + } +} diff --git a/tests/breadcrumbs/BreadcrumbsMenuTest.php b/tests/breadcrumbs/BreadcrumbsMenuTest.php new file mode 100644 index 0000000..d04a143 --- /dev/null +++ b/tests/breadcrumbs/BreadcrumbsMenuTest.php @@ -0,0 +1,110 @@ +menu = new BreadcrumbsMenu(); + } + + /** + * Removes any Breadcrumbs. + */ + protected function tearDown(): void + { + parent::tearDown(); + + BreadcrumbsMenu::set(null); + } + + public function testDiscovery() + { + $expected = [ + new Breadcrumb('http://example.com', 'Home'), + new Breadcrumb('http://example.com/current', 'Current'), + ]; + + $result = BreadcrumbsMenu::discover(); + + $this->assertEquals($expected, $result); + } + + public function testDefaultUsesDiscovery() + { + $expected = ''; + $result = (string) $this->menu; + + $this->assertSame($expected, $result); + } + + public function testGet() + { + $breadcrumbs = BreadcrumbsMenu::discover(); + + $result = BreadcrumbsMenu::get(); + + $this->assertSame($breadcrumbs, $result); + } + + public function testSetNull() + { + BreadcrumbsMenu::discover(); + BreadcrumbsMenu::set(null); + + $result = BreadcrumbsMenu::get(); + + $this->assertNull($result); + } + + public function testPopNull() + { + $result = BreadcrumbsMenu::pop(); + + $this->assertNull($result); + } + + public function testPop() + { + $breadcrumbs = BreadcrumbsMenu::discover(); + + $result = BreadcrumbsMenu::pop(); + + $this->assertSame($breadcrumbs[1], $result); + } + + public function testPushNull() + { + $breadcrumb = new Breadcrumb('food'); + + $result = BreadcrumbsMenu::push($breadcrumb); + $this->assertSame(1, $result); + + $this->assertSame($breadcrumb, BreadcrumbsMenu::pop()); + } + + public function testPush() + { + $breadcrumbs = BreadcrumbsMenu::discover(); + + $breadcrumb = new Breadcrumb('food'); + + $result = BreadcrumbsMenu::push($breadcrumb); + $this->assertSame(3, $result); + + $this->assertSame($breadcrumb, BreadcrumbsMenu::pop()); + } +} diff --git a/tests/menu/MenuTest.php b/tests/menu/MenuTest.php index 82aea72..042c8bd 100644 --- a/tests/menu/MenuTest.php +++ b/tests/menu/MenuTest.php @@ -12,8 +12,7 @@ class MenuTest extends MenusTestCase private $menu; /** - * Sets the current URL and creates - * a basic Menu to use for testing. + * Creates a basic Menu to use for testing. */ protected function setUp(): void { diff --git a/tests/menu/TraitsTest.php b/tests/menu/TraitsTest.php index 0142cae..bdd64a7 100644 --- a/tests/menu/TraitsTest.php +++ b/tests/menu/TraitsTest.php @@ -1,7 +1,7 @@