From e1141f8afd93f58abd36161925bed7ac01495c79 Mon Sep 17 00:00:00 2001 From: antoine Date: Thu, 22 May 2025 14:01:32 +0200 Subject: [PATCH 01/18] wip POC --- demo/app/Sharp/SharpMenu.php | 21 ++++++++++++++++++++- src/Utils/Menu/HasSharpMenuItems.php | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/demo/app/Sharp/SharpMenu.php b/demo/app/Sharp/SharpMenu.php index 23040fecc..ccd4c8f0e 100644 --- a/demo/app/Sharp/SharpMenu.php +++ b/demo/app/Sharp/SharpMenu.php @@ -2,12 +2,14 @@ namespace App\Sharp; +use App\Models\Post; use App\Sharp\Entities\AuthorEntity; use App\Sharp\Entities\CategoryEntity; use App\Sharp\Entities\DemoDashboardEntity; use App\Sharp\Entities\PostEntity; use App\Sharp\Entities\ProfileEntity; use App\Sharp\Entities\TestEntity; +use App\Sharp\Utils\Filters\StateFilter; use Code16\Sharp\Utils\Menu\SharpMenu as BaseSharpMenu; use Code16\Sharp\Utils\Menu\SharpMenuItemSection; use Code16\Sharp\Utils\Menu\SharpMenuUserMenu; @@ -25,7 +27,24 @@ public function build(): self ->addSection('Blog', function (SharpMenuItemSection $section) { $section ->setCollapsible(false) - ->addEntityLink(PostEntity::class, 'Posts', icon: 'lucide-file-text') + ->addEntityLink( + PostEntity::class, 'Posts', + icon: 'lucide-file-text', + // badge: new class extends SharpBadge { + // public function getFilters(): array + // { + // return [ + // StateFilter::class => 'draft', + // ]; + // } + // public function label() + // { + // return Post::query() + // ->where('state', 'draft') + // ->count(); + // } + // } + ) ->addEntityLink(CategoryEntity::class, 'Categories', icon: 'lucide-tags') ->addEntityLink(AuthorEntity::class, 'Authors', icon: 'lucide-signature'); }) diff --git a/src/Utils/Menu/HasSharpMenuItems.php b/src/Utils/Menu/HasSharpMenuItems.php index e4a25fb9e..01dce25b4 100644 --- a/src/Utils/Menu/HasSharpMenuItems.php +++ b/src/Utils/Menu/HasSharpMenuItems.php @@ -8,7 +8,7 @@ trait HasSharpMenuItems { protected array $items = []; - public function addEntityLink(string $entityKeyOrClassName, ?string $label = null, ?string $icon = null): self + public function addEntityLink(string $entityKeyOrClassName, ?string $label = null, ?string $icon = null, ?\Closure $badge = null): self { if (class_exists($entityKeyOrClassName)) { $entityKeyOrClassName = app(SharpEntityManager::class) From df375b9b5eff78f36f530333beaada161f71104f Mon Sep 17 00:00:00 2001 From: antoine Date: Thu, 22 May 2025 15:56:00 +0200 Subject: [PATCH 02/18] Allow badge closure --- demo/app/Sharp/SharpMenu.php | 18 ++---------- resources/js/Layouts/Layout.vue | 21 +++++++++++--- .../ui/sidebar/SidebarMenuBadge.vue | 8 ++--- resources/js/types/generated.d.ts | 1 + src/Data/MenuItemData.php | 29 ++++++++++++------- src/Utils/Menu/HasSharpMenuItems.php | 21 ++++++++++---- src/Utils/Menu/SharpMenuItemLink.php | 16 +++++++++- 7 files changed, 74 insertions(+), 40 deletions(-) diff --git a/demo/app/Sharp/SharpMenu.php b/demo/app/Sharp/SharpMenu.php index ccd4c8f0e..3346cfbe2 100644 --- a/demo/app/Sharp/SharpMenu.php +++ b/demo/app/Sharp/SharpMenu.php @@ -9,7 +9,6 @@ use App\Sharp\Entities\PostEntity; use App\Sharp\Entities\ProfileEntity; use App\Sharp\Entities\TestEntity; -use App\Sharp\Utils\Filters\StateFilter; use Code16\Sharp\Utils\Menu\SharpMenu as BaseSharpMenu; use Code16\Sharp\Utils\Menu\SharpMenuItemSection; use Code16\Sharp\Utils\Menu\SharpMenuUserMenu; @@ -30,20 +29,9 @@ public function build(): self ->addEntityLink( PostEntity::class, 'Posts', icon: 'lucide-file-text', - // badge: new class extends SharpBadge { - // public function getFilters(): array - // { - // return [ - // StateFilter::class => 'draft', - // ]; - // } - // public function label() - // { - // return Post::query() - // ->where('state', 'draft') - // ->count(); - // } - // } + badge: fn () => Post::query() + ->where('state', 'draft') + ->count(), ) ->addEntityLink(CategoryEntity::class, 'Categories', icon: 'lucide-tags') ->addEntityLink(AuthorEntity::class, 'Authors', icon: 'lucide-signature'); diff --git a/resources/js/Layouts/Layout.vue b/resources/js/Layouts/Layout.vue index 77c66c97a..e2b93aa49 100644 --- a/resources/js/Layouts/Layout.vue +++ b/resources/js/Layouts/Layout.vue @@ -49,6 +49,7 @@ export function useMenuBoundaryElement() { SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, + SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger @@ -161,9 +162,16 @@ export function useMenuBoundaryElement() { - {{ childItem.label }} + + {{ childItem.label }} + + @@ -190,11 +198,16 @@ export function useMenuBoundaryElement() { - + {{ item.label }} + diff --git a/resources/js/components/ui/sidebar/SidebarMenuBadge.vue b/resources/js/components/ui/sidebar/SidebarMenuBadge.vue index bcec34585..84b3182ab 100644 --- a/resources/js/components/ui/sidebar/SidebarMenuBadge.vue +++ b/resources/js/components/ui/sidebar/SidebarMenuBadge.vue @@ -11,11 +11,11 @@ const props = defineProps<{
isSection()) { + if ($item instanceof SharpMenuItemSection) { return new self( label: $item->getLabel(), children: self::collection( @@ -39,20 +43,25 @@ public static function from(SharpMenuItem $item) ); } - if ($item->isSeparator()) { + if ($item instanceof SharpMenuItemSeparator) { return new self( label: $item->getLabel(), isSeparator: true, ); } - return new self( - icon: IconData::optional(app(IconManager::class)->iconToArray($item->getIcon())), - label: $item->getLabel(), - url: $item->getUrl(), - isExternalLink: $item->isExternalLink(), - entityKey: $item->isEntity() ? $item->getEntityKey() : null, - current: $item->isEntity() && $item->isCurrent(), - ); + if ($item instanceof SharpMenuItemLink) { + return new self( + icon: IconData::optional(app(IconManager::class)->iconToArray($item->getIcon())), + label: $item->getLabel(), + badge: $item->getBadge(), + url: $item->getUrl(), + isExternalLink: $item->isExternalLink(), + entityKey: $item->isEntity() ? $item->getEntityKey() : null, + current: $item->isEntity() && $item->isCurrent(), + ); + } + + return new self(); } } diff --git a/src/Utils/Menu/HasSharpMenuItems.php b/src/Utils/Menu/HasSharpMenuItems.php index 01dce25b4..f7d1c1511 100644 --- a/src/Utils/Menu/HasSharpMenuItems.php +++ b/src/Utils/Menu/HasSharpMenuItems.php @@ -2,28 +2,37 @@ namespace Code16\Sharp\Utils\Menu; +use Closure; use Code16\Sharp\Utils\Entities\SharpEntityManager; trait HasSharpMenuItems { protected array $items = []; - public function addEntityLink(string $entityKeyOrClassName, ?string $label = null, ?string $icon = null, ?\Closure $badge = null): self - { + public function addEntityLink( + string $entityKeyOrClassName, + ?string $label = null, + ?string $icon = null, + ?Closure $badge = null, + ): self { if (class_exists($entityKeyOrClassName)) { $entityKeyOrClassName = app(SharpEntityManager::class) ->entityKeyFor($entityKeyOrClassName); } - $this->items[] = (new SharpMenuItemLink($label, $icon)) + $this->items[] = (new SharpMenuItemLink($label, $icon, $badge)) ->setEntity($entityKeyOrClassName); return $this; } - public function addExternalLink(string $url, ?string $label = null, ?string $icon = null): self - { - $this->items[] = (new SharpMenuItemLink($label, $icon))->setUrl($url); + public function addExternalLink( + string $url, + ?string $label = null, + ?string $icon = null, + ?Closure $badge = null, + ): self { + $this->items[] = (new SharpMenuItemLink($label, $icon, $badge))->setUrl($url); return $this; } diff --git a/src/Utils/Menu/SharpMenuItemLink.php b/src/Utils/Menu/SharpMenuItemLink.php index d6f5d7294..c9127ce9c 100644 --- a/src/Utils/Menu/SharpMenuItemLink.php +++ b/src/Utils/Menu/SharpMenuItemLink.php @@ -2,6 +2,7 @@ namespace Code16\Sharp\Utils\Menu; +use Closure; use Code16\Sharp\Utils\Entities\SharpDashboardEntity; use Code16\Sharp\Utils\Entities\SharpEntity; use Code16\Sharp\Utils\Entities\SharpEntityManager; @@ -12,7 +13,11 @@ class SharpMenuItemLink extends SharpMenuItem protected ?string $entityKey = null; protected ?string $url = null; - public function __construct(protected ?string $label, protected ?string $icon) {} + public function __construct( + protected ?string $label, + protected ?string $icon, + protected ?Closure $badge, + ) {} public function setEntity(string $entityKey): self { @@ -34,6 +39,15 @@ public function getIcon(): ?string return $this->icon; } + public function getBadge(): ?string + { + if ($this->badge instanceof Closure) { + return ($this->badge)(); + } + + return null; + } + public function isDashboardEntity(): bool { return $this->isEntity() && $this->entity->isDashboard(); From 8197db0a79fd3c24638cc6d59fe19f75b76ac4b1 Mon Sep 17 00:00:00 2001 From: antoine Date: Thu, 22 May 2025 17:57:12 +0200 Subject: [PATCH 03/18] Improve UI --- resources/js/Layouts/Layout.vue | 9 +++++---- resources/js/components/ui/badge/index.ts | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/resources/js/Layouts/Layout.vue b/resources/js/Layouts/Layout.vue index e2b93aa49..78e98b6e0 100644 --- a/resources/js/Layouts/Layout.vue +++ b/resources/js/Layouts/Layout.vue @@ -58,6 +58,7 @@ export function useMenuBoundaryElement() { import GlobalSearch from "@/components/GlobalSearch.vue"; import { vScrollIntoView } from "@/directives/scroll-into-view"; import Content from "@/components/Content.vue"; + import { Badge } from "@/components/ui/badge"; const dialogs = useDialogs(); const menu = useMenu(); @@ -166,9 +167,9 @@ export function useMenuBoundaryElement() { {{ childItem.label }} From 561930bbb203a439cc551d9b827015052b510804 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 27 May 2025 15:44:34 +0200 Subject: [PATCH 18/18] badge hover --- resources/js/Pages/Show/Show.vue | 2 +- resources/js/components/ui/StateBadge.vue | 3 ++- resources/js/entity-list/components/EntityList.vue | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/resources/js/Pages/Show/Show.vue b/resources/js/Pages/Show/Show.vue index cb7e3a064..88a06ee60 100644 --- a/resources/js/Pages/Show/Show.vue +++ b/resources/js/Pages/Show/Show.vue @@ -218,7 +218,7 @@