K
KoreUI

Providers

Spotlight

Sistema de providers PHP para organizar items del Spotlight. Soporta navegación, acciones, búsqueda remota y flujos multi-paso.

Clase base

Todos los providers extienden SpotlightProvider. La clase base proporciona los métodos que puedes sobreescribir.

SpotlightProvider (abstract)
abstract class SpotlightProvider
{
    public function __construct(
        protected readonly ?Component $livewireComponent = null
    ) {}

    // Items que siempre se muestran (input vacío)
    public function items(): array { return []; }

    // Items filtrados por query — default devuelve items(), Alpine filtra
    // Override para búsqueda server-side
    public function search(string $query): array { return $this->items(); }

    // Orden entre providers (menor = primero)
    public function priority(): int { return 100; }

    // Nombre del grupo/sección
    public function group(): string { return 'General'; }
}
abstract class SpotlightProvider
{
    public function __construct(
        protected readonly ?Component $livewireComponent = null
    ) {}

    // Items que siempre se muestran (input vacío)
    public function items(): array { return []; }

    // Items filtrados por query — default devuelve items(), Alpine filtra
    // Override para búsqueda server-side
    public function search(string $query): array { return $this->items(); }

    // Orden entre providers (menor = primero)
    public function priority(): int { return 100; }

    // Nombre del grupo/sección
    public function group(): string { return 'General'; }
}

Provider de navegación

Ejemplo de provider local que define items estáticos de navegación. Alpine filtra con búsqueda fuzzy.

app/Spotlight/NavigationProvider.php
namespace App\Spotlight;

use KoreUi\Spotlight\SpotlightItem;
use KoreUi\Spotlight\SpotlightProvider;

class NavigationProvider extends SpotlightProvider
{
    public function group(): string { return 'Navegación'; }
    public function priority(): int { return 10; }

    public function items(): array
    {
        return [
            SpotlightItem::make('Inicio')->icon('home')->route('home'),
            SpotlightItem::make('Usuarios')->icon('users')->route('users.index')->gate('view-users'),
            SpotlightItem::make('Reportes')->icon('bar-chart-2')->route('reports.index')->shortcut('⌘R'),
        ];
    }
}
namespace App\Spotlight;

use KoreUi\Spotlight\SpotlightItem;
use KoreUi\Spotlight\SpotlightProvider;

class NavigationProvider extends SpotlightProvider
{
    public function group(): string { return 'Navegación'; }
    public function priority(): int { return 10; }

    public function items(): array
    {
        return [
            SpotlightItem::make('Inicio')->icon('home')->route('home'),
            SpotlightItem::make('Usuarios')->icon('users')->route('users.index')->gate('view-users'),
            SpotlightItem::make('Reportes')->icon('bar-chart-2')->route('reports.index')->shortcut('⌘R'),
        ];
    }
}

Provider de acciones

Provider que usa el contexto del componente Livewire para mostrar acciones condicionales.

app/Spotlight/ActionProvider.php
namespace App\Spotlight;

use KoreUi\Spotlight\SpotlightItem;
use KoreUi\Spotlight\SpotlightProvider;

class ActionProvider extends SpotlightProvider
{
    public function group(): string { return 'Acciones'; }
    public function priority(): int { return 20; }

    public function items(): array
    {
        return [
            SpotlightItem::make('Crear usuario')
                ->icon('user-plus')
                ->shortcut('⌘U')
                ->action('openCreateUser')
                ->gate('create-users'),

            SpotlightItem::make('Exportar CSV')
                ->icon('download')
                ->action('exportCsv')
                ->when($this->livewireComponent?->hasSelection() ?? false),
        ];
    }
}
namespace App\Spotlight;

use KoreUi\Spotlight\SpotlightItem;
use KoreUi\Spotlight\SpotlightProvider;

class ActionProvider extends SpotlightProvider
{
    public function group(): string { return 'Acciones'; }
    public function priority(): int { return 20; }

    public function items(): array
    {
        return [
            SpotlightItem::make('Crear usuario')
                ->icon('user-plus')
                ->shortcut('⌘U')
                ->action('openCreateUser')
                ->gate('create-users'),

            SpotlightItem::make('Exportar CSV')
                ->icon('download')
                ->action('exportCsv')
                ->when($this->livewireComponent?->hasSelection() ?? false),
        ];
    }
}

Provider de búsqueda remota

Para providers que buscan en base de datos, sobreescribe search() en lugar de items().

app/Spotlight/UserSearchProvider.php
namespace App\Spotlight;

use App\Models\User;
use KoreUi\Spotlight\SpotlightItem;
use KoreUi\Spotlight\SpotlightProvider;

class UserSearchProvider extends SpotlightProvider
{
    public function group(): string { return 'Usuarios'; }
    public function priority(): int { return 50; }

    public function items(): array { return []; }  // vacío sin input

    public function search(string $query): array
    {
        return User::search($query)->limit(5)->get()
            ->map(fn($user) => SpotlightItem::make($user->name)
                ->description($user->email)
                ->icon('user')
                ->route('users.show', $user)
            )->all();
    }
}
namespace App\Spotlight;

use App\Models\User;
use KoreUi\Spotlight\SpotlightItem;
use KoreUi\Spotlight\SpotlightProvider;

class UserSearchProvider extends SpotlightProvider
{
    public function group(): string { return 'Usuarios'; }
    public function priority(): int { return 50; }

    public function items(): array { return []; }  // vacío sin input

    public function search(string $query): array
    {
        return User::search($query)->limit(5)->get()
            ->map(fn($user) => SpotlightItem::make($user->name)
                ->description($user->email)
                ->icon('user')
                ->route('users.show', $user)
            )->all();
    }
}

Nota: La diferencia entre items() y search(): items() se carga en el mount y Alpine filtra con fuzzy search. search() se llama con debounce por $wire -- es para consultas pesadas a base de datos.

Registro de providers

Registra tus providers en la configuración global o por instancia del componente.

config/kore-ui.php
'spotlight' => [
    'providers' => [
        \App\Spotlight\NavigationProvider::class,
        \App\Spotlight\ActionProvider::class,
        \App\Spotlight\UserSearchProvider::class,
    ],
],
'spotlight' => [
    'providers' => [
        \App\Spotlight\NavigationProvider::class,
        \App\Spotlight\ActionProvider::class,
        \App\Spotlight\UserSearchProvider::class,
    ],
],
Override por instancia
<x-kore::spotlight :providers="[\App\Spotlight\NavigationProvider::class]" />
<x-kore::spotlight :providers="[\App\Spotlight\NavigationProvider::class]" />

Prioridad y grupos

Controla el orden y la organización visual de los items.

  • priority() determina el orden en que aparecen los grupos (menor numero = arriba).
  • group() del provider se usa como grupo para los items que no definen su propio grupo.
  • Si un SpotlightItem define ->group('Custom'), ese grupo se respeta sobre el del provider.

SpotlightItem API

Builder fluido para definir items del Spotlight. El ID se genera con Str::slug($name) para estabilidad entre sesiones.

API completa de SpotlightItem
SpotlightItem::make('Nombre del item')
    // Contenido
    ->description('Descripción opcional')
    ->icon('user-plus')        // Nombre de blade-lucide-icons
    ->group('Acciones')        // Sección en la lista
    ->shortcut('⌘U')           // Badge visual de atajo

    // Acciones (mutuamente excluyentes)
    ->route('users.create')                      // Navegar a ruta nombrada
    ->route('users.show', ['id' => 1])           // Con parámetros
    ->url('https://example.com')                 // URL externa (misma pestaña)
    ->url('https://example.com', true)           // URL externa (nueva pestaña)
    ->action('exportCsv')                        // Llamar método Livewire
    ->action('createUser', ['role' => 'admin'])  // Con params
    ->dispatch('kore:open', ['name' => 'overlays.my-modal']) // Evento Alpine/Livewire

    // Búsqueda
    ->synonym('crear', 'nuevo', 'agregar')       // Términos extra para fuzzy

    // Visibilidad
    ->hidden()             // Solo aparece en búsqueda (no en lista vacía)
    ->when($condition)     // Solo si la condición es true
    ->gate('create-users') // Solo si el usuario pasa el gate

    // Multi-paso
    ->dependency(SpotlightDependency::search(...))
    ->dependency(SpotlightDependency::input(...))
    ->dependency(SpotlightDependency::select(...));
SpotlightItem::make('Nombre del item')
    // Contenido
    ->description('Descripción opcional')
    ->icon('user-plus')        // Nombre de blade-lucide-icons
    ->group('Acciones')        // Sección en la lista
    ->shortcut('⌘U')           // Badge visual de atajo

    // Acciones (mutuamente excluyentes)
    ->route('users.create')                      // Navegar a ruta nombrada
    ->route('users.show', ['id' => 1])           // Con parámetros
    ->url('https://example.com')                 // URL externa (misma pestaña)
    ->url('https://example.com', true)           // URL externa (nueva pestaña)
    ->action('exportCsv')                        // Llamar método Livewire
    ->action('createUser', ['role' => 'admin'])  // Con params
    ->dispatch('kore:open', ['name' => 'overlays.my-modal']) // Evento Alpine/Livewire

    // Búsqueda
    ->synonym('crear', 'nuevo', 'agregar')       // Términos extra para fuzzy

    // Visibilidad
    ->hidden()             // Solo aparece en búsqueda (no en lista vacía)
    ->when($condition)     // Solo si la condición es true
    ->gate('create-users') // Solo si el usuario pasa el gate

    // Multi-paso
    ->dependency(SpotlightDependency::search(...))
    ->dependency(SpotlightDependency::input(...))
    ->dependency(SpotlightDependency::select(...));

Tipos de acción

Cada item puede tener un tipo de acción. Son mutuamente excluyentes.

Método Comportamiento
->route('name', $params)Navega via Livewire redirect
->url($url)window.location.href = $url
->url($url, true)window.open($url, '_blank')
->action('method', $params)$wire.call('method', ...params)
->dispatch('event', $data)$dispatch('event', data)

Visibilidad y autorización

Controla qué items son visibles según condiciones y permisos.

Filtrado condicional
// Solo incluir si hay filas seleccionadas (contexto del componente)
SpotlightItem::make('Exportar selección')
    ->when($this->livewireComponent?->hasSelection() ?? false)

// Solo si el usuario tiene el permiso
SpotlightItem::make('Crear usuario')
    ->gate('create-users')

// Combinado
SpotlightItem::make('Eliminar')
    ->when(!$isReadOnly)
    ->gate('delete-records')
// Solo incluir si hay filas seleccionadas (contexto del componente)
SpotlightItem::make('Exportar selección')
    ->when($this->livewireComponent?->hasSelection() ?? false)

// Solo si el usuario tiene el permiso
SpotlightItem::make('Crear usuario')
    ->gate('create-users')

// Combinado
SpotlightItem::make('Eliminar')
    ->when(!$isReadOnly)
    ->gate('delete-records')

Los items filtrados por when() o gate() nunca llegan al frontend (filtrado en SpotlightProvider::toArray()).

Dependencias multi-paso

Las dependencias permiten flujos multi-paso: seleccionar un item puede requerir inputs previos antes de ejecutar la acción final. Cada paso completado aparece como una 'pill' en el input.

Flujo visual
Paso 0: Usuario busca "Asignar rol"
        ┌──────────────────────────────────────┐
        │  Asignar rol a usuario    >          │
        └──────────────────────────────────────┘
        ↓ selecciona el item

Paso 1: SpotlightDependency::search
        ┌───────────────────────────────────────────────────┐
        │  [Asignar rol a usuario x]  Seleccionar usuario…  │
        ├───────────────────────────────────────────────────┤
        │  Juan García                                       │
        │  Ana López                                         │
        └───────────────────────────────────────────────────┘
        ↓ selecciona Juan García

Paso 2: SpotlightDependency::select
        ┌──────────────────────────────────────────────────────────┐
        │  [Asignar rol x] [Juan García x]  Seleccionar rol…      │
        ├──────────────────────────────────────────────────────────┤
        │  Administrador                                            │
        │  Editor                                                   │
        │  Viewer                                                   │
        └──────────────────────────────────────────────────────────┘
        ↓ selecciona Editor

Ejecuta: $wire.call('assignRole', ['juan-garcia', 'editor'])
Paso 0: Usuario busca "Asignar rol"
        ┌──────────────────────────────────────┐
        │  Asignar rol a usuario    >          │
        └──────────────────────────────────────┘
        ↓ selecciona el item

Paso 1: SpotlightDependency::search
        ┌───────────────────────────────────────────────────┐
        │  [Asignar rol a usuario x]  Seleccionar usuario…  │
        ├───────────────────────────────────────────────────┤
        │  Juan García                                       │
        │  Ana López                                         │
        └───────────────────────────────────────────────────┘
        ↓ selecciona Juan García

Paso 2: SpotlightDependency::select
        ┌──────────────────────────────────────────────────────────┐
        │  [Asignar rol x] [Juan García x]  Seleccionar rol…      │
        ├──────────────────────────────────────────────────────────┤
        │  Administrador                                            │
        │  Editor                                                   │
        │  Viewer                                                   │
        └──────────────────────────────────────────────────────────┘
        ↓ selecciona Editor

Ejecuta: $wire.call('assignRole', ['juan-garcia', 'editor'])

El usuario puede presionar Backspace con el input vacío para eliminar la última pill y volver al paso anterior.

Tipos de dependencia

Tres tipos de dependencia disponibles para flujos multi-paso.

::search — Búsqueda remota con selección
SpotlightDependency::search(
    placeholder: 'Seleccionar usuario',
    searchUrl: 'users.spotlight',   // ruta nombrada o URL
    method: 'GET'                   // opcional, default: GET
)
SpotlightDependency::search(
    placeholder: 'Seleccionar usuario',
    searchUrl: 'users.spotlight',   // ruta nombrada o URL
    method: 'GET'                   // opcional, default: GET
)
::input — Texto libre
SpotlightDependency::input(
    placeholder: 'Motivo de la acción',
    validation: 'min:3'  // opcional, validación básica
)
SpotlightDependency::input(
    placeholder: 'Motivo de la acción',
    validation: 'min:3'  // opcional, validación básica
)
::select — Lista fija de opciones
SpotlightDependency::select(
    placeholder: 'Seleccionar rol',
    options: [
        'admin'  => 'Administrador',
        'editor' => 'Editor',
        'viewer' => 'Viewer',
    ]
)
SpotlightDependency::select(
    placeholder: 'Seleccionar rol',
    options: [
        'admin'  => 'Administrador',
        'editor' => 'Editor',
        'viewer' => 'Viewer',
    ]
)

Ejemplo multi-paso

Ejemplo completo de un flujo de 2 pasos con su método Livewire receptor.

Definición del item con dependencias
SpotlightItem::make('Asignar rol a usuario')
    ->icon('shield')
    ->group('Acciones')
    ->action('assignRole')  // se ejecuta con los valores resueltos
    ->dependency(
        SpotlightDependency::search('Seleccionar usuario', 'users.spotlight')
    )
    ->dependency(
        SpotlightDependency::select('Seleccionar rol', [
            'admin'  => 'Administrador',
            'editor' => 'Editor',
            'viewer' => 'Viewer',
        ])
    );
SpotlightItem::make('Asignar rol a usuario')
    ->icon('shield')
    ->group('Acciones')
    ->action('assignRole')  // se ejecuta con los valores resueltos
    ->dependency(
        SpotlightDependency::search('Seleccionar usuario', 'users.spotlight')
    )
    ->dependency(
        SpotlightDependency::select('Seleccionar rol', [
            'admin'  => 'Administrador',
            'editor' => 'Editor',
            'viewer' => 'Viewer',
        ])
    );
Método Livewire receptor
// Los valores resueltos se pasan como argumentos al método
public function assignRole(string $userId, string $role): void
{
    User::find($userId)->assignRole($role);
    $this->toast()->success('Rol asignado')->send();
}
// Los valores resueltos se pasan como argumentos al método
public function assignRole(string $userId, string $role): void
{
    User::find($userId)->assignRole($role);
    $this->toast()->success('Rol asignado')->send();
}

Endpoint para búsqueda

El endpoint para dependencias de tipo search debe devolver un array de items compatibles.

Formato de respuesta esperado
[
    {
        "id": "user-1",
        "name": "Juan García",
        "description": "juan@empresa.com",
        "icon": "user"
    }
]
[
    {
        "id": "user-1",
        "name": "Juan García",
        "description": "juan@empresa.com",
        "icon": "user"
    }
]
Ejemplo de ruta
// routes/web.php
Route::get('/spotlight/users', function (Request $request) {
    return User::search($request->query)->limit(10)->get()
        ->map(fn($u) => [
            'id'          => (string) $u->id,
            'name'        => $u->name,
            'description' => $u->email,
            'icon'        => 'user',
        ]);
});
// routes/web.php
Route::get('/spotlight/users', function (Request $request) {
    return User::search($request->query)->limit(10)->get()
        ->map(fn($u) => [
            'id'          => (string) $u->id,
            'name'        => $u->name,
            'description' => $u->email,
            'icon'        => 'user',
        ]);
});