Skip to content

Commit a673e4c

Browse files
authored
Merge pull request #334 from SebastiaanKloos/global-search
Global Spotlight Search Feature
2 parents f1aded0 + f2fb4e5 commit a673e4c

File tree

12 files changed

+673
-59
lines changed

12 files changed

+673
-59
lines changed

app/Livewire/Header.php

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Enums\UserRole;
88
use App\Models\Project;
99
use Livewire\Component;
10+
use Livewire\Attributes\On;
1011
use Filament\Actions\Action;
1112
use App\Rules\ProfanityCheck;
1213
use App\Settings\GeneralSettings;
@@ -44,29 +45,23 @@ public function mount()
4445
->get();
4546
}
4647

47-
public function render()
48+
#[On('create-item-from-search')]
49+
public function openSubmitItemWithQuery(string $query): void
4850
{
49-
return view('livewire.header');
51+
// Mount the submit item action with the query as an argument
52+
$this->mountAction('submitItem', ['prefilledTitle' => $query]);
5053
}
5154

52-
public function searchItemAction(): Action
55+
#[On('open-submit-item-modal')]
56+
public function openSubmitItemModal(): void
5357
{
54-
return Action::make('searchItem')
55-
->link()
56-
->color('gray')
57-
->label(function () {
58-
return view('components.search-button-label');
59-
})
60-
->icon('heroicon-o-magnifying-glass')
61-
->extraAttributes(['class' => '!px-3 !py-1.5 rounded-lg text-white hover:text-white hover:bg-white/10 focus:ring-white/20 [&_svg]:text-white'])
62-
->modalWidth('4xl')
63-
->modalFooterActions([
64-
Action::make('👀')->hidden()
65-
])
66-
->modalHeading($this->getRandomFunnyPlaceholder())
67-
->modalIcon('heroicon-o-magnifying-glass')
68-
->modalAlignment(Alignment::Left)
69-
->modalContent(view('modals.search'));
58+
// Mount the submit item action without pre-filled data
59+
$this->mountAction('submitItem');
60+
}
61+
62+
public function render()
63+
{
64+
return view('livewire.header');
7065
}
7166

7267
public function submitItemAction(): Action
@@ -101,8 +96,23 @@ public function submitItemAction(): Action
10196
->modalIcon('heroicon-o-plus-circle')
10297
->modalWidth('3xl')
10398
->modalSubmitActionLabel('Confirm')
104-
->fillForm(function () {
105-
return $this->currentProjectId ? ['project_id' => $this->currentProjectId] : [];
99+
->mountUsing(function ($form, array $arguments) {
100+
// Fill the form with prefilled data if available
101+
$data = [];
102+
103+
// Add project_id if currentProjectId is set
104+
if ($this->currentProjectId) {
105+
$data['project_id'] = $this->currentProjectId;
106+
}
107+
108+
// Add prefilled title if provided from search
109+
if ($prefilledTitle = $arguments['prefilledTitle'] ?? null) {
110+
$data['title'] = $prefilledTitle;
111+
// Also set similar items for the prefilled title
112+
$this->setSimilarItems($prefilledTitle);
113+
}
114+
115+
$form->fill($data);
106116
})
107117
->schema(function () {
108118
$inputs = [];
@@ -215,28 +225,4 @@ public function setSimilarItems($state): void
215225
return $query;
216226
})->get(['title', 'slug']) : collect([]);
217227
}
218-
219-
protected function getRandomFunnyPlaceholder(): string
220-
{
221-
$placeholders = [
222-
"Type here to find your lost keys... or your sanity",
223-
"Searching for Wi-Fi signals and lost socks",
224-
"Type something brilliant or your cat will judge you",
225-
"Looking for answers, memes, and cat videos",
226-
"Search for unicorns, we might find one",
227-
"Finding a needle in a digital haystack",
228-
"Type your thoughts, we'll pretend to understand",
229-
"Searching for dinosaurs in the digital age",
230-
"Lost in the web? We've got a virtual map",
231-
"Hunting for pixels and easter eggs",
232-
"Type something epic or order pizza, your choice",
233-
"Lost in the code? We'll be your debugger",
234-
"Looking for a shortcut to success",
235-
"Searching for the meaning of life... or cute cat videos"
236-
];
237-
238-
shuffle($placeholders);
239-
240-
return $placeholders[0];
241-
}
242228
}

app/Livewire/SpotlightSearch.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
namespace App\Livewire;
4+
5+
use App\Models\Item;
6+
use App\Models\Project;
7+
use Illuminate\Support\Collection;
8+
use Livewire\Attributes\On;
9+
use Livewire\Component;
10+
11+
class SpotlightSearch extends Component
12+
{
13+
public string $query = '';
14+
public bool $isOpen = false;
15+
public array $items = [];
16+
public array $projects = [];
17+
public int $totalResults = 0;
18+
19+
#[On('open-spotlight')]
20+
public function open(): void
21+
{
22+
$this->isOpen = true;
23+
$this->query = '';
24+
$this->items = [];
25+
$this->projects = [];
26+
$this->totalResults = 0;
27+
28+
// Dispatch browser event for Alpine.js
29+
$this->dispatch('spotlight-opened');
30+
}
31+
32+
public function close(): void
33+
{
34+
$this->isOpen = false;
35+
$this->query = '';
36+
$this->items = [];
37+
$this->projects = [];
38+
$this->totalResults = 0;
39+
}
40+
41+
public function updatedQuery(): void
42+
{
43+
if (empty(trim($this->query))) {
44+
$this->items = [];
45+
$this->projects = [];
46+
$this->totalResults = 0;
47+
return;
48+
}
49+
50+
$this->searchItems();
51+
$this->searchProjects();
52+
$this->totalResults = count($this->items) + count($this->projects);
53+
}
54+
55+
protected function searchItems(): void
56+
{
57+
$query = Item::query()
58+
->visibleForCurrentUser()
59+
->with(['project', 'board', 'user'])
60+
->where(function ($q) {
61+
$q->where('title', 'like', '%' . $this->query . '%')
62+
->orWhere('content', 'like', '%' . $this->query . '%');
63+
})
64+
->orderByDesc('created_at')
65+
->limit(8);
66+
67+
$this->items = $query->get()->map(function (Item $item) {
68+
return [
69+
'id' => $item->id,
70+
'title' => $item->title,
71+
'slug' => $item->slug,
72+
'project_title' => $item->project?->title,
73+
'board_title' => $item->board?->title,
74+
'votes_count' => $item->total_votes ?? 0,
75+
'created_at' => $item->created_at?->diffForHumans(),
76+
'url' => route('items.show', $item),
77+
];
78+
})->toArray();
79+
}
80+
81+
protected function searchProjects(): void
82+
{
83+
$query = Project::query()
84+
->visibleForCurrentUser()
85+
->with(['boards'])
86+
->where(function ($q) {
87+
$q->where('title', 'like', '%' . $this->query . '%')
88+
->orWhere('description', 'like', '%' . $this->query . '%');
89+
})
90+
->orderBy('title')
91+
->limit(8);
92+
93+
$this->projects = $query->get()->map(function (Project $project) {
94+
return [
95+
'id' => $project->id,
96+
'title' => $project->title,
97+
'slug' => $project->slug,
98+
'description' => $project->description,
99+
'icon' => $project->icon,
100+
'boards_count' => $project->boards->count(),
101+
'url' => route('projects.show', $project),
102+
];
103+
})->toArray();
104+
}
105+
106+
public function createNewItem(): void
107+
{
108+
$query = $this->query;
109+
$this->close();
110+
$this->dispatch('create-item-from-search', query: $query);
111+
}
112+
113+
public function render()
114+
{
115+
return view('livewire.spotlight-search');
116+
}
117+
}

lang/en/spotlight.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
return [
44
'search' => 'Search',
5+
'search_button' => 'Search',
56
'create-item' => [
67
'name' => 'Create item',
78
'description' => 'Create an item',
@@ -18,4 +19,26 @@
1819
'name' => 'Logout',
1920
'description' => 'Logout out of your account',
2021
],
22+
23+
// Search Interface
24+
'search_placeholder' => 'Search items and projects...',
25+
'section_items' => 'Items',
26+
'section_projects' => 'Projects',
27+
'section_actions' => 'Actions',
28+
'quick_actions' => 'Quick Actions',
29+
'create_new_item' => 'Create new item',
30+
'create_new_item_desc' => 'Create a new roadmap item',
31+
'profile_title' => 'Profile',
32+
'profile_desc' => 'View and edit your profile',
33+
'activity' => 'Activity',
34+
'activity_desc' => 'View recent activity and updates',
35+
'admin_panel' => 'Admin Panel',
36+
'admin_panel_desc' => 'Manage your roadmap settings',
37+
'no_results' => 'No results found for',
38+
'initial_message' => 'Search for items and projects',
39+
'keyboard_hint' => 'Use arrow keys to navigate',
40+
'keyboard_navigate' => 'to navigate',
41+
'keyboard_open' => 'to open',
42+
'results_count' => ':count result|:count results',
43+
'board' => 'board|boards',
2144
];

lang/nl/spotlight.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
return [
44
'search' => 'Zoeken',
5+
'search_button' => 'Zoeken',
56
'create-item' => [
67
'name' => 'Item aanmaken',
78
'description' => 'Maak een item aan',
@@ -18,4 +19,26 @@
1819
'name' => 'Uitloggen',
1920
'description' => 'Log jezelf uit',
2021
],
22+
23+
// Search Interface
24+
'search_placeholder' => 'Zoek items en projecten...',
25+
'section_items' => 'Items',
26+
'section_projects' => 'Projecten',
27+
'section_actions' => 'Acties',
28+
'quick_actions' => 'Snelle Acties',
29+
'create_new_item' => 'Nieuw item aanmaken',
30+
'create_new_item_desc' => 'Maak een nieuw roadmap item aan',
31+
'profile_title' => 'Profiel',
32+
'profile_desc' => 'Bekijk en bewerk je profiel',
33+
'activity' => 'Activiteit',
34+
'activity_desc' => 'Bekijk recente activiteit en updates',
35+
'admin_panel' => 'Beheerpaneel',
36+
'admin_panel_desc' => 'Beheer je roadmap instellingen',
37+
'no_results' => 'Geen resultaten gevonden voor',
38+
'initial_message' => 'Zoek op items en projecten',
39+
'keyboard_hint' => 'Gebruik de pijltjestoetsen om te navigeren',
40+
'keyboard_navigate' => 'om te navigeren',
41+
'keyboard_open' => 'om te openen',
42+
'results_count' => ':count resultaat|:count resultaten',
43+
'board' => 'board|boards',
2144
];

resources/css/app.css

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,42 @@
134134
.fi-ta {
135135
@apply w-full;
136136
}
137+
138+
/* Spotlight Search Styles */
139+
.spotlight-container {
140+
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
141+
}
142+
143+
@media (prefers-color-scheme: dark) {
144+
.spotlight-container {
145+
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);
146+
}
147+
}
148+
149+
/* Custom scrollbar for spotlight results */
150+
.spotlight-container .max-h-\[60vh\]::-webkit-scrollbar {
151+
width: 8px;
152+
}
153+
154+
.spotlight-container .max-h-\[60vh\]::-webkit-scrollbar-track {
155+
background: transparent;
156+
}
157+
158+
.spotlight-container .max-h-\[60vh\]::-webkit-scrollbar-thumb {
159+
background: rgba(156, 163, 175, 0.3);
160+
border-radius: 4px;
161+
}
162+
163+
.spotlight-container .max-h-\[60vh\]::-webkit-scrollbar-thumb:hover {
164+
background: rgba(156, 163, 175, 0.5);
165+
}
166+
167+
@media (prefers-color-scheme: dark) {
168+
.spotlight-container .max-h-\[60vh\]::-webkit-scrollbar-thumb {
169+
background: rgba(75, 85, 99, 0.5);
170+
}
171+
172+
.spotlight-container .max-h-\[60vh\]::-webkit-scrollbar-thumb:hover {
173+
background: rgba(75, 85, 99, 0.7);
174+
}
175+
}

resources/views/components/app.blade.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ function updateTheme() {
9797
@livewireScriptConfig
9898
@filamentScripts
9999
@livewire('notifications')
100+
@livewire('spotlight-search')
100101

101102
@stack('javascript')
102103
<x-impersonate::banner/>

resources/views/components/search-button-label.blade.php

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)