From 85ddf652e20a4776268e3e88aa426870e5e1d637 Mon Sep 17 00:00:00 2001 From: Sebastiaan Kloos Date: Wed, 26 Nov 2025 16:37:11 +0100 Subject: [PATCH 01/11] feat(search): add spotlight search livewire component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement a global spotlight search component with keyboard navigation and real-time search functionality. The component searches both items and projects, displays results in categorized sections, and includes quick actions for common tasks. Features: - Real-time search with debouncing - Keyboard navigation (arrow keys, enter, escape) - Search items by title and content - Search projects by title and description - Quick actions for creating items and accessing key pages - Support for both authenticated and guest users - Smooth transitions and backdrop blur effects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Livewire/SpotlightSearch.php | 116 +++++ .../views/livewire/spotlight-search.blade.php | 414 ++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 app/Livewire/SpotlightSearch.php create mode 100644 resources/views/livewire/spotlight-search.blade.php diff --git a/app/Livewire/SpotlightSearch.php b/app/Livewire/SpotlightSearch.php new file mode 100644 index 00000000..4cf7b09c --- /dev/null +++ b/app/Livewire/SpotlightSearch.php @@ -0,0 +1,116 @@ +isOpen = true; + $this->query = ''; + $this->items = []; + $this->projects = []; + $this->totalResults = 0; + + // Dispatch browser event for Alpine.js + $this->dispatch('spotlight-opened'); + } + + public function close(): void + { + $this->isOpen = false; + $this->query = ''; + $this->items = []; + $this->projects = []; + $this->totalResults = 0; + } + + public function updatedQuery(): void + { + if (empty(trim($this->query))) { + $this->items = []; + $this->projects = []; + $this->totalResults = 0; + return; + } + + $this->searchItems(); + $this->searchProjects(); + $this->totalResults = count($this->items) + count($this->projects); + } + + protected function searchItems(): void + { + $query = Item::query() + ->visibleForCurrentUser() + ->with(['project', 'board', 'user']) + ->where(function ($q) { + $q->where('title', 'like', '%' . $this->query . '%') + ->orWhere('content', 'like', '%' . $this->query . '%'); + }) + ->orderByDesc('created_at') + ->limit(8); + + $this->items = $query->get()->map(function (Item $item) { + return [ + 'id' => $item->id, + 'title' => $item->title, + 'slug' => $item->slug, + 'project_title' => $item->project?->title, + 'board_title' => $item->board?->title, + 'votes_count' => $item->total_votes ?? 0, + 'created_at' => $item->created_at?->diffForHumans(), + 'url' => route('items.show', $item), + ]; + })->toArray(); + } + + protected function searchProjects(): void + { + $query = Project::query() + ->visibleForCurrentUser() + ->with(['boards']) + ->where(function ($q) { + $q->where('title', 'like', '%' . $this->query . '%') + ->orWhere('description', 'like', '%' . $this->query . '%'); + }) + ->orderBy('title') + ->limit(8); + + $this->projects = $query->get()->map(function (Project $project) { + return [ + 'id' => $project->id, + 'title' => $project->title, + 'slug' => $project->slug, + 'description' => $project->description, + 'icon' => $project->icon, + 'boards_count' => $project->boards->count(), + 'url' => route('projects.show', $project), + ]; + })->toArray(); + } + + public function createNewItem(): void + { + $this->close(); + $this->dispatch('create-item-from-search', query: $this->query); + } + + public function render() + { + return view('livewire.spotlight-search'); + } +} diff --git a/resources/views/livewire/spotlight-search.blade.php b/resources/views/livewire/spotlight-search.blade.php new file mode 100644 index 00000000..a26dec44 --- /dev/null +++ b/resources/views/livewire/spotlight-search.blade.php @@ -0,0 +1,414 @@ +
+ +
+ + +
+
+ +
+ + + +
+ + +
+ @if($query && ($items || $projects || true)) +
+ {{-- Items Section --}} + @if($items) + + @endif + + {{-- Projects Section --}} + @if($projects) + + @endif + + {{-- Create New Item Action --}} + @if($query) + @php + $createNewIndex = count($items) + count($projects); + @endphp +
+
+ {{ trans('spotlight.section_actions') }} +
+ +
+ @endif +
+ @elseif($query) + {{-- Empty State --}} +
+ +

+ {{ trans('spotlight.no_results') }} "{{ $query }}" +

+ +
+ @else + {{-- Initial State - Quick Actions --}} +
+
+ {{ trans('spotlight.quick_actions') }} +
+ + {{-- Create New Item Action --}} + + + {{-- Profile Link (only for authenticated users) --}} + @auth + + +
+
+ {{ trans('spotlight.profile') }} +
+
+ {{ trans('spotlight.profile_desc') }} +
+
+
+ @endauth + + {{-- Activity Link --}} + + +
+
+ {{ trans('spotlight.activity') }} +
+
+ {{ trans('spotlight.activity_desc') }} +
+
+
+ + {{-- Admin Panel Link (only if user has access) --}} + @if(auth()->check() && auth()->user()->hasAdminAccess()) + + +
+
+ {{ trans('spotlight.admin_panel') }} +
+
+ {{ trans('spotlight.admin_panel_desc') }} +
+
+
+ @endif + +
+ {{ trans('spotlight.keyboard_hint') }} +
+
+ @endif +
+ + {{-- Footer with Keyboard Shortcuts --}} + @if($query) +
+
+
+
+ ↑ + ↓ + {{ trans('spotlight.keyboard_navigate') }} +
+
+ ↵ + {{ trans('spotlight.keyboard_open') }} +
+
+
+ {{ trans_choice('spotlight.results_count', $totalResults, ['count' => $totalResults]) }} +
+
+
+ @endif +
+
+
From ba1ec1bf001633a912268d53ddc3155b89058d4a Mon Sep 17 00:00:00 2001 From: Sebastiaan Kloos Date: Wed, 26 Nov 2025 16:37:20 +0100 Subject: [PATCH 02/11] feat(search): add spotlight event handlers to header component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add event listeners in the Header component to handle spotlight search interactions. When users create items from the search, the query can be pre-filled in the submit item modal. Also add support for opening the submit item modal without pre-filled data. Changes: - Add On attribute import for Livewire events - Implement openSubmitItemWithQuery() to pre-fill item title from search - Implement openSubmitItemModal() to open modal without data - Remove unnecessary fillForm call from submitItemAction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Livewire/Header.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Header.php b/app/Livewire/Header.php index f4da2828..e46700df 100644 --- a/app/Livewire/Header.php +++ b/app/Livewire/Header.php @@ -7,6 +7,7 @@ use App\Enums\UserRole; use App\Models\Project; use Livewire\Component; +use Livewire\Attributes\On; use Filament\Actions\Action; use App\Rules\ProfanityCheck; use App\Settings\GeneralSettings; @@ -43,6 +44,22 @@ public function mount() ->get(); } + #[On('create-item-from-search')] + public function openSubmitItemWithQuery(string $query): void + { + // Mount the action with pre-filled title from search + $this->mountAction('submitItem', [ + 'title' => $query, + ]); + } + + #[On('open-submit-item-modal')] + public function openSubmitItemModal(): void + { + // Mount the submit item action without pre-filled data + $this->mountAction('submitItem'); + } + public function render() { return view('livewire.header'); @@ -100,7 +117,6 @@ public function submitItemAction(): Action ->modalIcon('heroicon-o-plus-circle') ->modalWidth('3xl') ->modalSubmitActionLabel('Confirm') - ->fillForm([]) ->schema(function () { $inputs = []; From 3bef0e223d0f813f6b144f4df23e66b288712237 Mon Sep 17 00:00:00 2001 From: Sebastiaan Kloos Date: Wed, 26 Nov 2025 16:37:27 +0100 Subject: [PATCH 03/11] feat(search): integrate spotlight search in header and layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the old search action with spotlight search triggers throughout the header navigation. Add the spotlight search component to the main app layout for global accessibility. Changes: - Replace searchItemAction with spotlight open event triggers - Update keyboard shortcuts (Cmd+K/Ctrl+K) to open spotlight - Add search button with keyboard hint in desktop navigation - Add search button in mobile navigation menu - Include spotlight-search component in app layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- resources/views/components/app.blade.php | 1 + resources/views/livewire/header.blade.php | 30 +++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/resources/views/components/app.blade.php b/resources/views/components/app.blade.php index 9ed93673..eb5257cb 100644 --- a/resources/views/components/app.blade.php +++ b/resources/views/components/app.blade.php @@ -97,6 +97,7 @@ function updateTheme() { @livewireScriptConfig @filamentScripts @livewire('notifications') +@livewire('spotlight-search') @stack('javascript') diff --git a/resources/views/livewire/header.blade.php b/resources/views/livewire/header.blade.php index a4b51218..c6da631c 100644 --- a/resources/views/livewire/header.blade.php +++ b/resources/views/livewire/header.blade.php @@ -1,7 +1,7 @@
+ @keydown.window.cmd.k.prevent="$dispatch('open-spotlight')" + @keydown.window.ctrl.k.prevent="$dispatch('open-spotlight')">