ui-ux-consultant-cli 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/assets/ui-ux-consultant/SKILL.md +844 -0
  2. package/assets/ui-ux-consultant/references/accessibility.md +175 -0
  3. package/assets/ui-ux-consultant/references/alt-libraries.md +90 -0
  4. package/assets/ui-ux-consultant/references/animations.md +448 -0
  5. package/assets/ui-ux-consultant/references/catalog/colors.md +91 -0
  6. package/assets/ui-ux-consultant/references/catalog/fonts.md +363 -0
  7. package/assets/ui-ux-consultant/references/catalog/products.md +340 -0
  8. package/assets/ui-ux-consultant/references/catalog/styles.md +165 -0
  9. package/assets/ui-ux-consultant/references/components.md +1116 -0
  10. package/assets/ui-ux-consultant/references/patterns.md +600 -0
  11. package/assets/ui-ux-consultant/references/performance.md +198 -0
  12. package/assets/ui-ux-consultant/references/stacks/astro.md +382 -0
  13. package/assets/ui-ux-consultant/references/stacks/flutter.md +308 -0
  14. package/assets/ui-ux-consultant/references/stacks/html-tailwind.md +415 -0
  15. package/assets/ui-ux-consultant/references/stacks/jetpack-compose.md +333 -0
  16. package/assets/ui-ux-consultant/references/stacks/laravel.md +521 -0
  17. package/assets/ui-ux-consultant/references/stacks/nextjs.md +275 -0
  18. package/assets/ui-ux-consultant/references/stacks/nuxt-ui.md +384 -0
  19. package/assets/ui-ux-consultant/references/stacks/nuxtjs.md +264 -0
  20. package/assets/ui-ux-consultant/references/stacks/react-native.md +346 -0
  21. package/assets/ui-ux-consultant/references/stacks/react.md +268 -0
  22. package/assets/ui-ux-consultant/references/stacks/shadcn.md +485 -0
  23. package/assets/ui-ux-consultant/references/stacks/svelte.md +429 -0
  24. package/assets/ui-ux-consultant/references/stacks/swiftui.md +336 -0
  25. package/assets/ui-ux-consultant/references/stacks/threejs.md +366 -0
  26. package/assets/ui-ux-consultant/references/stacks/vue.md +272 -0
  27. package/assets/ui-ux-consultant/references/theming.md +701 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +130 -0
  30. package/package.json +51 -0
@@ -0,0 +1,521 @@
1
+ # Laravel — UI/UX Reference
2
+
3
+ ## When to Read
4
+ Use this file when building UI with Laravel. Covers three approaches: Blade + Livewire (reactive SSR), Inertia.js + Vue/React (SPA feel), and Blade + Alpine.js (simple interactivity). Includes Filament for admin panels.
5
+
6
+ ---
7
+
8
+ ## Three UI Approaches
9
+
10
+ | Approach | When to Use | Stack |
11
+ |---|---|---|
12
+ | **Blade + Livewire 3** | Reactive UI without SPA overhead | Livewire 3, Alpine.js, Tailwind CSS |
13
+ | **Inertia.js + Vue 3** | SPA feel with Laravel backend | Inertia.js, Vue 3, Vite |
14
+ | **Inertia.js + React** | SPA feel, React ecosystem | Inertia.js, React 18, shadcn/ui |
15
+ | **Blade + Alpine.js** | Simple interactivity, no reactive backend | Alpine.js, Tailwind CSS |
16
+ | **Filament** | Admin panels and dashboards | Filament 3 (built on Livewire) |
17
+
18
+ ---
19
+
20
+ ## Recommended Packages
21
+
22
+ | Package | Purpose | Install |
23
+ |---|---|---|
24
+ | Livewire 3 | Reactive server-side components | `composer require livewire/livewire` |
25
+ | Inertia.js | SPA with Laravel backend | `composer require inertiajs/inertia-laravel` |
26
+ | Laravel Breeze | Auth scaffolding (Blade/Inertia/API) | `php artisan breeze:install` |
27
+ | Laravel Jetstream | Full auth + teams (Livewire or Inertia) | `composer require laravel/jetstream` |
28
+ | Filament | Admin panels, CRUD, resources | `composer require filament/filament` |
29
+ | Wire Elements | Pre-built Livewire modal component | `composer require wire-elements/modal` |
30
+ | Spatie Permission | Roles and permissions | `composer require spatie/laravel-permission` |
31
+ | Laravel Horizon | Queue monitoring UI | `composer require laravel/horizon` |
32
+
33
+ ---
34
+
35
+ ## Style Recommendations
36
+
37
+ - **Admin / dashboard:** Filament (built-in design system) or custom Tailwind grid
38
+ - **Marketing / landing:** Tailwind CSS + Minimalism or Aurora UI aesthetic
39
+ - **SaaS application:** Livewire + Tailwind + Flat Design
40
+ - **Content-heavy:** Blade + Tailwind Typography plugin
41
+ - **Internal tools:** Filament or Breeze + Tailwind
42
+
43
+ ---
44
+
45
+ ## Approach 1: Livewire 3
46
+
47
+ ### Basic Component
48
+
49
+ ```php
50
+ // app/Livewire/UserSearch.php
51
+ namespace App\Livewire;
52
+
53
+ use App\Models\User;
54
+ use Livewire\Component;
55
+ use Livewire\Attributes\Computed;
56
+ use Livewire\Attributes\Url;
57
+
58
+ class UserSearch extends Component
59
+ {
60
+ #[Url] // Sync with URL query string
61
+ public string $query = '';
62
+ public string $sortBy = 'name';
63
+
64
+ #[Computed]
65
+ public function users()
66
+ {
67
+ return User::query()
68
+ ->when($this->query, fn($q) => $q->where('name', 'like', "%{$this->query}%"))
69
+ ->orderBy($this->sortBy)
70
+ ->paginate(15);
71
+ }
72
+
73
+ public function render()
74
+ {
75
+ return view('livewire.user-search');
76
+ }
77
+ }
78
+ ```
79
+
80
+ ```html
81
+ {{-- resources/views/livewire/user-search.blade.php --}}
82
+ <div>
83
+ {{-- Search input with debounce --}}
84
+ <input
85
+ wire:model.live.debounce.300ms="query"
86
+ type="search"
87
+ placeholder="Search users..."
88
+ class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
89
+ />
90
+
91
+ {{-- Results --}}
92
+ <div class="mt-4 space-y-2">
93
+ @foreach($this->users as $user)
94
+ <div class="flex items-center justify-between p-4 bg-white border rounded-lg">
95
+ <div>
96
+ <p class="font-medium">{{ $user->name }}</p>
97
+ <p class="text-sm text-gray-500">{{ $user->email }}</p>
98
+ </div>
99
+ <button wire:click="impersonate({{ $user->id }})" class="text-sm text-blue-600 hover:underline">
100
+ Impersonate
101
+ </button>
102
+ </div>
103
+ @endforeach
104
+ </div>
105
+
106
+ {{ $this->users->links() }}
107
+ </div>
108
+ ```
109
+
110
+ ### Livewire Loading States
111
+
112
+ ```html
113
+ {{-- Loading spinner on button --}}
114
+ <button wire:click="save" wire:loading.attr="disabled" class="btn-primary">
115
+ <span wire:loading.remove>Save changes</span>
116
+ <span wire:loading class="flex items-center gap-2">
117
+ <svg class="animate-spin h-4 w-4" ...></svg>
118
+ Saving...
119
+ </span>
120
+ </button>
121
+
122
+ {{-- Loading overlay on a section --}}
123
+ <div class="relative">
124
+ <div wire:loading.flex class="absolute inset-0 bg-white/70 items-center justify-center z-10">
125
+ <svg class="animate-spin h-6 w-6 text-blue-600" ...></svg>
126
+ </div>
127
+ <div wire:loading.class="opacity-50">
128
+ {{-- Content --}}
129
+ </div>
130
+ </div>
131
+
132
+ {{-- Target specific actions --}}
133
+ <button wire:click="delete({{ $id }})" wire:loading.attr="disabled" wire:target="delete({{ $id }})">
134
+ Delete
135
+ </button>
136
+ ```
137
+
138
+ ### Livewire Form with Validation
139
+
140
+ ```php
141
+ // app/Livewire/CreatePost.php
142
+ use Livewire\Attributes\Rule;
143
+
144
+ class CreatePost extends Component
145
+ {
146
+ #[Rule('required|min:3|max:100')]
147
+ public string $title = '';
148
+
149
+ #[Rule('required|min:10')]
150
+ public string $body = '';
151
+
152
+ #[Rule('required|exists:categories,id')]
153
+ public ?int $categoryId = null;
154
+
155
+ public function save()
156
+ {
157
+ $validated = $this->validate();
158
+
159
+ Post::create([
160
+ ...$validated,
161
+ 'user_id' => auth()->id(),
162
+ ]);
163
+
164
+ $this->reset();
165
+ $this->dispatch('post-created');
166
+ session()->flash('message', 'Post created successfully.');
167
+ }
168
+ }
169
+ ```
170
+
171
+ ```html
172
+ <form wire:submit="save" class="space-y-4">
173
+ <div>
174
+ <label class="block text-sm font-medium text-gray-700">Title</label>
175
+ <input wire:model="title" type="text"
176
+ class="mt-1 w-full border rounded-lg px-3 py-2 @error('title') border-red-500 @enderror" />
177
+ @error('title')
178
+ <p class="mt-1 text-sm text-red-500">{{ $message }}</p>
179
+ @enderror
180
+ </div>
181
+
182
+ @if(session('message'))
183
+ <div class="p-3 bg-green-50 text-green-700 rounded-lg">{{ session('message') }}</div>
184
+ @endif
185
+
186
+ <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
187
+ Create post
188
+ </button>
189
+ </form>
190
+ ```
191
+
192
+ ### Livewire Lazy Loading
193
+
194
+ ```php
195
+ // Defer rendering until user scrolls to it
196
+ use Livewire\Attributes\Lazy;
197
+
198
+ #[Lazy]
199
+ class ExpensiveReport extends Component
200
+ {
201
+ public function placeholder()
202
+ {
203
+ return <<<HTML
204
+ <div class="animate-pulse h-48 bg-gray-100 rounded-xl"></div>
205
+ HTML;
206
+ }
207
+
208
+ public function render()
209
+ {
210
+ return view('livewire.expensive-report', [
211
+ 'data' => $this->generateReport(),
212
+ ]);
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Event System
218
+
219
+ ```php
220
+ // Dispatch from child component
221
+ $this->dispatch('user-updated', id: $user->id);
222
+
223
+ // Listen in parent component
224
+ use Livewire\Attributes\On;
225
+
226
+ #[On('user-updated')]
227
+ public function refreshUser(int $id): void
228
+ {
229
+ $this->user = User::find($id);
230
+ }
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Approach 2: Inertia.js + Vue 3
236
+
237
+ ### Controller
238
+
239
+ ```php
240
+ // app/Http/Controllers/UsersController.php
241
+ use Inertia\Inertia;
242
+
243
+ class UsersController extends Controller
244
+ {
245
+ public function index(Request $request)
246
+ {
247
+ return Inertia::render('Users/Index', [
248
+ 'users' => User::query()
249
+ ->when($request->search, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
250
+ ->paginate(15)
251
+ ->withQueryString(),
252
+ 'filters' => $request->only('search'),
253
+ ]);
254
+ }
255
+
256
+ public function store(StoreUserRequest $request)
257
+ {
258
+ User::create($request->validated());
259
+ return redirect()->route('users.index')->with('success', 'User created.');
260
+ }
261
+ }
262
+ ```
263
+
264
+ ### Vue 3 Page Component
265
+
266
+ ```vue
267
+ <!-- resources/js/Pages/Users/Index.vue -->
268
+ <script setup lang="ts">
269
+ import { ref, watch } from 'vue';
270
+ import { router, Link } from '@inertiajs/vue3';
271
+ import AppLayout from '@/Layouts/AppLayout.vue';
272
+ import Pagination from '@/Components/Pagination.vue';
273
+
274
+ interface User { id: number; name: string; email: string; }
275
+ interface Props {
276
+ users: { data: User[]; links: object[] };
277
+ filters: { search?: string };
278
+ }
279
+
280
+ const props = defineProps<Props>();
281
+ const search = ref(props.filters.search ?? '');
282
+
283
+ // Debounced search — updates URL without full reload
284
+ watch(search, (value) => {
285
+ router.get(route('users.index'), { search: value }, {
286
+ preserveState: true,
287
+ replace: true,
288
+ debounce: 300,
289
+ });
290
+ });
291
+ </script>
292
+
293
+ <template>
294
+ <AppLayout title="Users">
295
+ <div class="max-w-4xl mx-auto p-6 space-y-6">
296
+ <input v-model="search" type="search" placeholder="Search users..."
297
+ class="w-full px-4 py-2 border rounded-lg" />
298
+
299
+ <div class="bg-white rounded-xl border overflow-hidden">
300
+ <table class="min-w-full divide-y divide-gray-200">
301
+ <thead class="bg-gray-50">
302
+ <tr>
303
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
304
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
305
+ <th class="relative px-6 py-3"><span class="sr-only">Actions</span></th>
306
+ </tr>
307
+ </thead>
308
+ <tbody class="divide-y divide-gray-200">
309
+ <tr v-for="user in users.data" :key="user.id" class="hover:bg-gray-50">
310
+ <td class="px-6 py-4 text-sm font-medium text-gray-900">{{ user.name }}</td>
311
+ <td class="px-6 py-4 text-sm text-gray-500">{{ user.email }}</td>
312
+ <td class="px-6 py-4 text-right">
313
+ <Link :href="route('users.edit', user.id)"
314
+ class="text-sm text-blue-600 hover:underline">Edit</Link>
315
+ </td>
316
+ </tr>
317
+ </tbody>
318
+ </table>
319
+ </div>
320
+
321
+ <Pagination :links="users.links" />
322
+ </div>
323
+ </AppLayout>
324
+ </template>
325
+ ```
326
+
327
+ ### Inertia Form Handling
328
+
329
+ ```vue
330
+ <script setup lang="ts">
331
+ import { useForm } from '@inertiajs/vue3';
332
+
333
+ const form = useForm({
334
+ name: '',
335
+ email: '',
336
+ role: 'user',
337
+ });
338
+
339
+ function submit() {
340
+ form.post(route('users.store'), {
341
+ onSuccess: () => form.reset(),
342
+ });
343
+ }
344
+ </script>
345
+
346
+ <template>
347
+ <form @submit.prevent="submit" class="space-y-4">
348
+ <div>
349
+ <label class="block text-sm font-medium text-gray-700">Name</label>
350
+ <input v-model="form.name" type="text"
351
+ :class="form.errors.name && 'border-red-500'"
352
+ class="mt-1 w-full border rounded-lg px-3 py-2" />
353
+ <p v-if="form.errors.name" class="mt-1 text-sm text-red-500">{{ form.errors.name }}</p>
354
+ </div>
355
+
356
+ <button type="submit" :disabled="form.processing"
357
+ class="px-4 py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50">
358
+ {{ form.processing ? 'Creating...' : 'Create user' }}
359
+ </button>
360
+ </form>
361
+ </template>
362
+ ```
363
+
364
+ ---
365
+
366
+ ## Approach 3: Blade + Alpine.js
367
+
368
+ ```html
369
+ {{-- Simple dropdown --}}
370
+ <div x-data="{ open: false }" class="relative">
371
+ <button @click="open = !open" @keydown.escape="open = false"
372
+ class="flex items-center gap-2 px-4 py-2 border rounded-lg">
373
+ Options
374
+ <svg class="w-4 h-4" :class="open && 'rotate-180 transition-transform'" ...></svg>
375
+ </button>
376
+ <div x-show="open" x-transition @click.outside="open = false"
377
+ class="absolute top-full mt-1 w-48 bg-white border rounded-lg shadow-lg py-1 z-10">
378
+ <a href="{{ route('settings') }}" class="block px-4 py-2 text-sm hover:bg-gray-50">Settings</a>
379
+ <form method="POST" action="{{ route('logout') }}">
380
+ @csrf
381
+ <button type="submit" class="w-full text-left px-4 py-2 text-sm hover:bg-gray-50">
382
+ Log out
383
+ </button>
384
+ </form>
385
+ </div>
386
+ </div>
387
+
388
+ {{-- Tabs --}}
389
+ <div x-data="{ activeTab: 'overview' }">
390
+ <nav class="flex border-b">
391
+ <button @click="activeTab = 'overview'"
392
+ :class="activeTab === 'overview' ? 'border-b-2 border-blue-600 text-blue-600' : 'text-gray-500'"
393
+ class="px-4 py-2 text-sm font-medium">Overview</button>
394
+ <button @click="activeTab = 'activity'"
395
+ :class="activeTab === 'activity' ? 'border-b-2 border-blue-600 text-blue-600' : 'text-gray-500'"
396
+ class="px-4 py-2 text-sm font-medium">Activity</button>
397
+ </nav>
398
+ <div x-show="activeTab === 'overview'" class="py-4">
399
+ @include('partials.overview')
400
+ </div>
401
+ <div x-show="activeTab === 'activity'" class="py-4">
402
+ @include('partials.activity')
403
+ </div>
404
+ </div>
405
+ ```
406
+
407
+ ---
408
+
409
+ ## Filament Admin Panels
410
+
411
+ ```php
412
+ // app/Filament/Resources/UserResource.php
413
+ use Filament\Resources\Resource;
414
+ use Filament\Tables\Columns\TextColumn;
415
+ use Filament\Forms\Components\TextInput;
416
+ use Filament\Forms\Components\Select;
417
+
418
+ class UserResource extends Resource
419
+ {
420
+ protected static ?string $model = User::class;
421
+ protected static ?string $navigationIcon = 'heroicon-o-users';
422
+
423
+ public static function form(Form $form): Form
424
+ {
425
+ return $form->schema([
426
+ TextInput::make('name')->required()->maxLength(255),
427
+ TextInput::make('email')->email()->required()->unique(ignoreRecord: true),
428
+ Select::make('role')
429
+ ->options(['admin' => 'Admin', 'user' => 'User'])
430
+ ->required(),
431
+ ]);
432
+ }
433
+
434
+ public static function table(Table $table): Table
435
+ {
436
+ return $table
437
+ ->columns([
438
+ TextColumn::make('name')->searchable()->sortable(),
439
+ TextColumn::make('email')->searchable(),
440
+ TextColumn::make('role')->badge()->color(fn($state) => match($state) {
441
+ 'admin' => 'danger',
442
+ default => 'gray',
443
+ }),
444
+ TextColumn::make('created_at')->dateTime()->sortable()->toggleable(),
445
+ ])
446
+ ->filters([TrashedFilter::make()])
447
+ ->actions([EditAction::make(), DeleteAction::make()])
448
+ ->bulkActions([BulkActionGroup::make([DeleteBulkAction::make()])]);
449
+ }
450
+ }
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Best Practices by Category
456
+
457
+ ### Livewire
458
+ - Use `#[Computed]` for derived data — cached per request, not re-queried on every re-render
459
+ - Use `#[Url]` to sync component state with URL — enables bookmarkable filtered views
460
+ - Use `wire:model.live.debounce.300ms` for search inputs — never `.live` alone
461
+ - Use `#[Lazy]` for below-fold components and heavy data processing
462
+ - Break large Livewire components into smaller ones with event communication
463
+ - Use `$this->dispatch()` instead of the old `$this->emit()` (deprecated in v3)
464
+
465
+ ### Inertia.js
466
+ - Use `useForm` from `@inertiajs/vue3` — provides `errors`, `processing`, `reset()` automatically
467
+ - `preserveState: true` on router visits to keep scroll position and form state
468
+ - Use `<Link preserve-scroll>` for pagination links to avoid scroll-to-top
469
+ - Share global data via `HandleInertiaRequests` middleware (auth user, flash messages)
470
+ - Type `defineProps` with TypeScript interfaces for IDE support on all page props
471
+
472
+ ### Database / Eloquent
473
+ - Always eager load relationships: `User::with(['posts', 'roles'])->get()`
474
+ - Use Eloquent scopes for reusable query logic: `User::active()->verified()->paginate()`
475
+ - Cache expensive queries: `Cache::remember('stats', 3600, fn() => computeStats())`
476
+ - Use `paginate()` not `get()` for lists — never load unbounded result sets
477
+
478
+ ### Security
479
+ - Always use `@csrf` in Blade forms
480
+ - Validate all input in Form Requests, not controllers
481
+ - Use Laravel's authorization (`$this->authorize()`, policies) not manual checks
482
+ - Never expose Eloquent models directly — use API Resources or explicit arrays
483
+
484
+ ---
485
+
486
+ ## Common Anti-Patterns
487
+
488
+ 1. **N+1 queries in Livewire** — Livewire re-renders on every state change. A query inside `@foreach` with a relationship access creates N+1. Always eager load: `User::with('posts')->get()`.
489
+
490
+ 2. **`wire:model` without `.debounce` on search inputs** — `.live` fires on every keystroke. Use `wire:model.live.debounce.300ms` for text inputs.
491
+
492
+ 3. **Mixing Livewire and Inertia on the same page** — they are fundamentally different rendering paradigms. Pick one per page. Inertia pages cannot contain Livewire components.
493
+
494
+ 4. **Large Blade components (> 150 lines)** — extract repeated HTML into Blade components (`x-card`, `x-modal`) or Livewire components. Monolithic Blade templates are hard to test.
495
+
496
+ 5. **Client-side routing in Blade without Inertia** — manually managing SPA navigation in Blade + Fetch is fragile. Use Inertia.js for SPA behavior or accept full-page navigation in Blade.
497
+
498
+ 6. **Returning data directly from controllers without authorization** — check policies before returning resources. `$this->authorize('view', $user)` before `return Inertia::render(...)`.
499
+
500
+ 7. **Using `session()->flash()` with Inertia** — flash messages in Inertia require sharing them in `HandleInertiaRequests::share()`. Raw session flashes are not auto-passed to Vue/React.
501
+
502
+ 8. **No `withQueryString()` on paginator** — without it, Eloquent pagination links lose current search/filter parameters.
503
+
504
+ 9. **`wire:click` on non-button elements** — use `<button>` elements for actions, not `<div wire:click>`. Screen readers and keyboard users expect buttons for interactive elements.
505
+
506
+ 10. **Filament without proper authorization** — Filament resources are accessible to all authenticated users by default. Implement `canAccess()`, `canCreate()`, `canEdit()`, `canDelete()` methods.
507
+
508
+ ---
509
+
510
+ ## Performance Checklist
511
+
512
+ - [ ] `wire:model.live.debounce.300ms` for all search/filter inputs
513
+ - [ ] Eager load Eloquent relationships (`with()`) in all Livewire computed properties
514
+ - [ ] `#[Lazy]` attribute for below-fold or expensive Livewire components
515
+ - [ ] `Cache::remember()` for queries that don't change frequently
516
+ - [ ] Inertia `<Link preserve-scroll>` to avoid scroll-to-top on pagination
517
+ - [ ] `withQueryString()` on all paginators
518
+ - [ ] Laravel Horizon for queue monitoring (async jobs for emails, reports)
519
+ - [ ] Database indexes on all filtered/sorted columns
520
+ - [ ] `php artisan optimize` in production (caches config, routes, views)
521
+ - [ ] Vite asset bundling with `npm run build` — not `mix` (deprecated)