lara-zeus / sky

CMS for your website. it include posts, pages, tags, and categories. with a frontend scaffolding ready to use
https://larazeus.com/sky
MIT License
136 stars 25 forks source link

Reimagine Navigation rendering features #199

Open mallardduck opened 5 months ago

mallardduck commented 5 months ago

Motivation

As I was building my site using Sky/filament I was considering ways this DX can be improved. I really liked how easy it was to add in my own item type. After fixing bug #196 things we working very well with my route based links. However I wanted to have equally as simple and clean DX when rendering the anchor link too.

So I've begun working on a similar syntax for registering custom Nav link renderers too!

This PR concept starts to add ability to register renderers like custom Nav item types. It also implements first-party examples of using the new system with the built-in nav item types.


New Feature Usage Example:

In my case, I'm using a "Local Route" custom item type; code here:

  ->itemType(
      name: "Local Route",
      fields: function(): array {
          return [
              Select::make('route')
                  ->label("Route")
                  ->options([
                      '' => "Select a Route",
                      ...RouteListOptions::getOptions(), // Not a complete example, this is only in my project
                  ])
                  ->default('')
                  ->selectablePlaceholder(false),
              Select::make('target')
                  ->label("Target")
                  ->options([
                      '' => "Current",
                      '_blank' => "New Tab",
                  ])
                  ->default('')
                  ->selectablePlaceholder(false),
          ];
      },
      slug: 'local-route'
  )

Registering the new link type's renderer via zeus-sky.php config:

    'navRenderers' => [
        \App\VendorOverrides\Sky\LocalRouteRenderer::class,
    ],
class LocalRouteRenderer extends NavLinkRenderer
{
    public static string $rendersKey = 'local-route';

    public function getModel(): ?Model
    {
        return null;
    }

    public function getLink(): string
    {
        return route($this->item['data']['route']);
    }

    public function isActiveRoute(): bool
    {
        return false;
    }
}

Customize the default classes for active/non-active links:

  public function register()
  {
      RenderNavItem::setActiveClasses("active border-b border-b-pink-300");
      RenderNavItem::setNonActiveClasses('');
  }

Within the AppServiceProvider class.

Further customizing the Anchor link:

If someone wants to add a label wrapper around the link text this is easy now. They simply publish the zeus views - then can delete all of them but views/vendor/zeus/components/sky-link.blade.php, then edit it according to their needs. In my example, I would use:

<a {{ $attributes->except('label')->except('hasLabelWrap') }}>
    <span class="link-text">{{ $label }}</span>
</a>

With the additional SPAN element which the default system would not include.

TODO:

mallardduck commented 5 months ago

I won't be mad if you feel that overall this isn't worth adding to the core project to maintain. So just feel free to LMK and close it if that's the case. Otherwise I'm going to leave a review of things I'm wondering about and would like feedback on.

atmonshi commented 5 months ago

thank you @mallardduck so much, amazing work, I love it. initially, I will accept it. but give me a few days and I'll back to this and will response to all feedback, and test it with the integration for other packages (bolt, thunder, etc) too.

thank you again.

mallardduck commented 5 months ago

appreciate the collaboration here @atmonshi !

no rush at all for a review - I've been working on this during my lunch RN. but plan to come back to this in a few hours after work. so I may take a second pass at this before it needs a through review. (maybe I can find an elegant solution for optional span wrappers).

I can also start poking around the rest of your eco-system to see how those overlap and play with these features too!

atmonshi commented 5 months ago

thank you. so I didnt check everything yet. but will this consider a breaking change! if an a app already using custom RenderNavItem?

mallardduck commented 5 months ago

but will this consider a breaking change! if an a app already using custom RenderNavItem?

I think that's OK! I actually started building this MVP based on my local project. And I was using a custom RenderNavItem that called parent::render($item, $classes); for anything I wasn't modifying. Like:

namespace App\VendorOverrides\Sky;

use LaraZeus\Sky\Classes\RenderNavItem;

class RenderNavItemLink extends RenderNavItem
{
    public static function render(array $item, string $class = ''): string
    {
        if ($item['type' ] === 'local-route' || $item['type'] === 'local_route') {
            return '<a class="' . $class . '"
                    target="' . ($item['data']['target'] ?? '_self') . '"
                    href="' . route($item['data']['route']) . '"
                >' .
                $item['label'] .
                '</a>';
        }

        return parent::render($item, $class);
    }
}

And I can still even use this method to render the nav. So I don't think this should introduce any breaking changes for apps already using custom RenderNavItem - assuming doing it like I show above was the "best-practice". Since while the code is very different looking internally, it does still produce identical results in terms of output. 😄

mallardduck commented 5 months ago

@atmonshi - I think this is a more user friendly way to do it now. Being able to publish the view makes it super easy to customize. And anyone needing more than this can copy the component fully and render their project local version. Let me know when you have time to review and give feedback. Once we land on stable API I can work on docs.

Edit: Actually just realized i still have a TODO of "figure out how to let users configure activeClasses and defaultActiveClass. So that should be sorted out - and I think potentially the names of those might be a little off. defaultActiveClass maybe should be defaultNonActiveClass but I named it wrong?

atmonshi commented 5 months ago

the component is 👌🔥