major update
This commit is contained in:
@@ -1,198 +0,0 @@
|
||||
<laravel-boost-guidelines>
|
||||
=== foundation rules ===
|
||||
|
||||
# Laravel Boost Guidelines
|
||||
|
||||
The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to ensure the best experience when building Laravel applications.
|
||||
|
||||
## Foundational Context
|
||||
|
||||
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
|
||||
|
||||
- php - 8.3
|
||||
- filament/filament (FILAMENT) - v5
|
||||
- laravel/breeze (BREEZE) - v2
|
||||
- laravel/fortify (FORTIFY) - v1
|
||||
- laravel/framework (LARAVEL) - v13
|
||||
- laravel/horizon (HORIZON) - v5
|
||||
- laravel/octane (OCTANE) - v2
|
||||
- laravel/prompts (PROMPTS) - v0
|
||||
- livewire/flux (FLUXUI_FREE) - v2
|
||||
- livewire/livewire (LIVEWIRE) - v4
|
||||
- laravel/boost (BOOST) - v2
|
||||
- laravel/mcp (MCP) - v0
|
||||
- laravel/pail (PAIL) - v1
|
||||
- laravel/pint (PINT) - v1
|
||||
- laravel/sail (SAIL) - v1
|
||||
- phpunit/phpunit (PHPUNIT) - v12
|
||||
- tailwindcss (TAILWINDCSS) - v4
|
||||
|
||||
## Skills Activation
|
||||
|
||||
This project has domain-specific skills available in `**/skills/**`. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck.
|
||||
|
||||
## Conventions
|
||||
|
||||
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming.
|
||||
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
|
||||
- Check for existing components to reuse before writing a new one.
|
||||
|
||||
## Verification Scripts
|
||||
|
||||
- Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important.
|
||||
|
||||
## Application Structure & Architecture
|
||||
|
||||
- Stick to existing directory structure; don't create new base folders without approval.
|
||||
- Do not change the application's dependencies without approval.
|
||||
|
||||
## Frontend Bundling
|
||||
|
||||
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.
|
||||
|
||||
## Documentation Files
|
||||
|
||||
- You must only create documentation files if explicitly requested by the user.
|
||||
|
||||
## Replies
|
||||
|
||||
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
|
||||
|
||||
=== boost rules ===
|
||||
|
||||
# Laravel Boost
|
||||
|
||||
## Tools
|
||||
|
||||
- Laravel Boost is an MCP server with tools designed specifically for this application. Prefer Boost tools over manual alternatives like shell commands or file reads.
|
||||
- Use `database-query` to run read-only queries against the database instead of writing raw SQL in tinker.
|
||||
- Use `database-schema` to inspect table structure before writing migrations or models.
|
||||
- Use `get-absolute-url` to resolve the correct scheme, domain, and port for project URLs. Always use this before sharing a URL with the user.
|
||||
- Use `browser-logs` to read browser logs, errors, and exceptions. Only recent logs are useful, ignore old entries.
|
||||
|
||||
## Searching Documentation (IMPORTANT)
|
||||
|
||||
- Always use `search-docs` before making code changes. Do not skip this step. It returns version-specific docs based on installed packages automatically.
|
||||
- Pass a `packages` array to scope results when you know which packages are relevant.
|
||||
- Use multiple broad, topic-based queries: `['rate limiting', 'routing rate limiting', 'routing']`. Expect the most relevant results first.
|
||||
- Do not add package names to queries because package info is already shared. Use `test resource table`, not `filament 4 test resource table`.
|
||||
|
||||
### Search Syntax
|
||||
|
||||
1. Use words for auto-stemmed AND logic: `rate limit` matches both "rate" AND "limit".
|
||||
2. Use `"quoted phrases"` for exact position matching: `"infinite scroll"` requires adjacent words in order.
|
||||
3. Combine words and phrases for mixed queries: `middleware "rate limit"`.
|
||||
4. Use multiple queries for OR logic: `queries=["authentication", "middleware"]`.
|
||||
|
||||
## Artisan
|
||||
|
||||
- Run Artisan commands directly via the command line (e.g., `php artisan route:list`). Use `php artisan list` to discover available commands and `php artisan [command] --help` to check parameters.
|
||||
- Inspect routes with `php artisan route:list`. Filter with: `--method=GET`, `--name=users`, `--path=api`, `--except-vendor`, `--only-vendor`.
|
||||
- Read configuration values using dot notation: `php artisan config:show app.name`, `php artisan config:show database.default`. Or read config files directly from the `config/` directory.
|
||||
|
||||
## Tinker
|
||||
|
||||
- Execute PHP in app context for debugging and testing code. Do not create models without user approval, prefer tests with factories instead. Prefer existing Artisan commands over custom tinker code.
|
||||
- Always use single quotes to prevent shell expansion: `php artisan tinker --execute 'Your::code();'`
|
||||
- Double quotes for PHP strings inside: `php artisan tinker --execute 'User::where("active", true)->count();'`
|
||||
|
||||
=== php rules ===
|
||||
|
||||
# PHP
|
||||
|
||||
- Always use curly braces for control structures, even for single-line bodies.
|
||||
- Use PHP 8 constructor property promotion: `public function __construct(public GitHub $github) { }`. Do not leave empty zero-parameter `__construct()` methods unless the constructor is private.
|
||||
- Use explicit return type declarations and type hints for all method parameters: `function isAccessible(User $user, ?string $path = null): bool`
|
||||
- Use TitleCase for Enum keys: `FavoritePerson`, `BestLake`, `Monthly`.
|
||||
- Prefer PHPDoc blocks over inline comments. Only add inline comments for exceptionally complex logic.
|
||||
- Use array shape type definitions in PHPDoc blocks.
|
||||
|
||||
=== deployments rules ===
|
||||
|
||||
# Deployment
|
||||
|
||||
- Laravel can be deployed using [Laravel Cloud](https://cloud.laravel.com/), which is the fastest way to deploy and scale production Laravel applications.
|
||||
|
||||
=== tests rules ===
|
||||
|
||||
# Test Enforcement
|
||||
|
||||
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
||||
- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter.
|
||||
|
||||
=== laravel/core rules ===
|
||||
|
||||
# Do Things the Laravel Way
|
||||
|
||||
- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using `php artisan list` and check their parameters with `php artisan [command] --help`.
|
||||
- If you're creating a generic PHP class, use `php artisan make:class`.
|
||||
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
||||
|
||||
### Model Creation
|
||||
|
||||
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `php artisan make:model --help` to check the available options.
|
||||
|
||||
## APIs & Eloquent Resources
|
||||
|
||||
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
|
||||
|
||||
## URL Generation
|
||||
|
||||
- When generating links to other pages, prefer named routes and the `route()` function.
|
||||
|
||||
## Testing
|
||||
|
||||
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
||||
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
||||
- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
||||
|
||||
## Vite Error
|
||||
|
||||
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.
|
||||
|
||||
=== octane/core rules ===
|
||||
|
||||
# Laravel Octane
|
||||
|
||||
This application uses Laravel Octane, a long-running PHP server. The application bootstraps once and handles many requests within the same process.
|
||||
|
||||
- Never store request-specific state in singletons or static properties, because it can leak across requests.
|
||||
- Use `config('octane.server')` to detect the active driver (`swoole`, `roadrunner`, or `frankenphp`).
|
||||
- Prefer scoped bindings (`$this->app->scoped()`) over singletons for per-request services.
|
||||
|
||||
When working on Octane-specific features (concurrency, shared tables, memory, driver configuration, testing), invoke `octane-development` for detailed rules.
|
||||
|
||||
=== livewire/core rules ===
|
||||
|
||||
# Livewire
|
||||
|
||||
- Livewire allow to build dynamic, reactive interfaces in PHP without writing JavaScript.
|
||||
- You can use Alpine.js for client-side interactions instead of JavaScript frameworks.
|
||||
- Keep state server-side so the UI reflects it. Validate and authorize in actions as you would in HTTP requests.
|
||||
|
||||
=== pint/core rules ===
|
||||
|
||||
# Laravel Pint Code Formatter
|
||||
|
||||
- If you have modified any PHP files, you must run `vendor/bin/pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
|
||||
- Do not run `vendor/bin/pint --test --format agent`, simply run `vendor/bin/pint --format agent` to fix any formatting issues.
|
||||
|
||||
=== phpunit/core rules ===
|
||||
|
||||
# PHPUnit
|
||||
|
||||
- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `php artisan make:test --phpunit {name}` to create a new test.
|
||||
- If you see a test using "Pest", convert it to PHPUnit.
|
||||
- Every time a test has been updated, run that singular test.
|
||||
- When the tests relating to your feature are passing, ask the user if they would like to also run the entire test suite to make sure everything is still passing.
|
||||
- Tests should cover all happy paths, failure paths, and edge cases.
|
||||
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files; these are core to the application.
|
||||
|
||||
## Running Tests
|
||||
|
||||
- Run the minimal number of tests, using an appropriate filter, before finalizing.
|
||||
- To run all tests: `php artisan test --compact`.
|
||||
- To run all tests in a file: `php artisan test --compact tests/Feature/ExampleTest.php`.
|
||||
- To filter on a particular test name: `php artisan test --compact --filter=testName` (recommended after making a change to a related file).
|
||||
|
||||
</laravel-boost-guidelines>
|
||||
@@ -1,22 +0,0 @@
|
||||
# WMS Fix Notes
|
||||
|
||||
This build patches only the current uploaded code.
|
||||
|
||||
## Fixed
|
||||
|
||||
- Duplicate two-factor migration made safe.
|
||||
- Primary two-factor migration adds columns one at a time for SQLite safety.
|
||||
- Module page slot is wrapped in a visible `.wms-content` main container.
|
||||
- Sidebar remains traditional: desktop/tablet icon rail, mobile drawer.
|
||||
- Sidebar widened for "Warehouse Management System".
|
||||
- Laravel cache folders are included so "Please provide a valid cache path" does not recur after unzip.
|
||||
|
||||
## After unzip
|
||||
|
||||
```bash
|
||||
composer install
|
||||
npm install
|
||||
php artisan optimize:clear
|
||||
php artisan test
|
||||
npm run build
|
||||
```
|
||||
@@ -1,35 +0,0 @@
|
||||
# Platform config and module menu fix
|
||||
|
||||
Applied fixes:
|
||||
|
||||
- Added `Modules/Platform/config/platform.php`.
|
||||
- Hardened `Modules/Platform/Providers/PlatformServiceProvider.php` so config loading is safe.
|
||||
- Removed stale generated files from `bootstrap/cache`.
|
||||
- Updated `resources/views/layouts/app.blade.php` so it works for both:
|
||||
- component pages using `<x-layouts::app>`
|
||||
- legacy placeholder module pages using `@extends('layouts.app')` and `@section('content')`
|
||||
- Added placeholder module entries to the left navigation:
|
||||
- Inventory Management
|
||||
- Shipping
|
||||
- Carrier Management
|
||||
- Transportation
|
||||
- Replenishment
|
||||
- M3 Integration
|
||||
- Document Archive
|
||||
- Analytics
|
||||
- Reporting
|
||||
- Audit
|
||||
- Security
|
||||
- Configuration
|
||||
- Added compatibility CSS for the placeholder module pages that use Bootstrap-style classes.
|
||||
- Ensured cache/runtime directories exist.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
composer install
|
||||
npm install
|
||||
php artisan optimize:clear
|
||||
php artisan test
|
||||
npm run build
|
||||
```
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Fortify\Features;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuthenticationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_login_screen_can_be_rendered(): void
|
||||
{
|
||||
$response = $this->get(route('login'));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
public function test_users_can_authenticate_using_the_login_screen(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->csrfPost(route('login.store'), [
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('dashboard', absolute: false));
|
||||
|
||||
$this->assertAuthenticated();
|
||||
}
|
||||
|
||||
public function test_users_can_not_authenticate_with_invalid_password(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->csrfPost(route('login.store'), [
|
||||
'email' => $user->email,
|
||||
'password' => 'wrong-password',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrorsIn('email');
|
||||
|
||||
$this->assertGuest();
|
||||
}
|
||||
|
||||
public function test_users_with_two_factor_enabled_are_redirected_to_two_factor_challenge(): void
|
||||
{
|
||||
$this->skipUnlessFortifyHas(Features::twoFactorAuthentication());
|
||||
|
||||
Features::twoFactorAuthentication([
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
]);
|
||||
|
||||
$user = User::factory()->withTwoFactor()->create();
|
||||
|
||||
$response = $this->csrfPost(route('login.store'), [
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('two-factor.login'));
|
||||
$this->assertGuest();
|
||||
}
|
||||
|
||||
public function test_users_can_logout(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)->csrfPost(route('logout'));
|
||||
|
||||
$response->assertRedirect(route('home'));
|
||||
|
||||
$this->assertGuest();
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Laravel\Fortify\Features;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EmailVerificationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->skipUnlessFortifyHas(Features::emailVerification());
|
||||
}
|
||||
|
||||
public function test_email_verification_screen_can_be_rendered(): void
|
||||
{
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
$response = $this->actingAs($user)->get(route('verification.notice'));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
public function test_email_can_be_verified(): void
|
||||
{
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
Event::fake();
|
||||
|
||||
$verificationUrl = URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
now()->addMinutes(60),
|
||||
['id' => $user->id, 'hash' => sha1($user->email)],
|
||||
);
|
||||
|
||||
$response = $this->actingAs($user)->get($verificationUrl);
|
||||
|
||||
Event::assertDispatched(Verified::class);
|
||||
|
||||
$this->assertTrue($user->fresh()->hasVerifiedEmail());
|
||||
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
public function test_email_is_not_verified_with_invalid_hash(): void
|
||||
{
|
||||
$user = User::factory()->unverified()->create();
|
||||
|
||||
$verificationUrl = URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
now()->addMinutes(60),
|
||||
['id' => $user->id, 'hash' => sha1('wrong-email')],
|
||||
);
|
||||
|
||||
$this->actingAs($user)->get($verificationUrl);
|
||||
|
||||
$this->assertFalse($user->fresh()->hasVerifiedEmail());
|
||||
}
|
||||
|
||||
public function test_already_verified_user_visiting_verification_link_is_redirected_without_firing_event_again(): void
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
|
||||
Event::fake();
|
||||
|
||||
$verificationUrl = URL::temporarySignedRoute(
|
||||
'verification.verify',
|
||||
now()->addMinutes(60),
|
||||
['id' => $user->id, 'hash' => sha1($user->email)],
|
||||
);
|
||||
|
||||
$this->actingAs($user)->get($verificationUrl)
|
||||
->assertRedirect(route('dashboard', absolute: false).'?verified=1');
|
||||
|
||||
$this->assertTrue($user->fresh()->hasVerifiedEmail());
|
||||
Event::assertNotDispatched(Verified::class);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PasswordConfirmationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_confirm_password_screen_can_be_rendered(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)->get(route('password.confirm'));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Notifications\ResetPassword;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Laravel\Fortify\Features;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PasswordResetTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->skipUnlessFortifyHas(Features::resetPasswords());
|
||||
}
|
||||
|
||||
public function test_reset_password_link_screen_can_be_rendered(): void
|
||||
{
|
||||
$response = $this->get(route('password.request'));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
public function test_reset_password_link_can_be_requested(): void
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->csrfPost(route('password.email'), ['email' => $user->email]);
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class);
|
||||
}
|
||||
|
||||
public function test_reset_password_screen_can_be_rendered(): void
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->csrfPost(route('password.email'), ['email' => $user->email]);
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
|
||||
$response = $this->get(route('password.reset', $notification->token));
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_password_can_be_reset_with_valid_token(): void
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->csrfPost(route('password.email'), ['email' => $user->email]);
|
||||
|
||||
Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
|
||||
$response = $this->csrfPost(route('password.update'), [
|
||||
'token' => $notification->token,
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
'password_confirmation' => 'password',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('login', absolute: false));
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Fortify\Features;
|
||||
use Tests\TestCase;
|
||||
|
||||
class RegistrationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->skipUnlessFortifyHas(Features::registration());
|
||||
}
|
||||
|
||||
public function test_registration_screen_can_be_rendered(): void
|
||||
{
|
||||
$response = $this->get(route('register'));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
public function test_new_users_can_register(): void
|
||||
{
|
||||
$response = $this->csrfPost(route('register.store'), [
|
||||
'name' => 'John Doe',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password',
|
||||
'password_confirmation' => 'password',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('dashboard', absolute: false));
|
||||
|
||||
$this->assertAuthenticated();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Fortify\Features;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TwoFactorChallengeTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->skipUnlessFortifyHas(Features::twoFactorAuthentication());
|
||||
}
|
||||
|
||||
public function test_two_factor_challenge_redirects_to_login_when_not_authenticated(): void
|
||||
{
|
||||
$response = $this->get(route('two-factor.login'));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
}
|
||||
|
||||
public function test_two_factor_challenge_can_be_rendered(): void
|
||||
{
|
||||
Features::twoFactorAuthentication([
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
]);
|
||||
|
||||
$user = User::factory()->withTwoFactor()->create();
|
||||
|
||||
$this->csrfPost(route('login.store'), [
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
])->assertRedirect(route('two-factor.login'));
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DashboardTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_guests_are_redirected_to_the_login_page(): void
|
||||
{
|
||||
$response = $this->get(route('dashboard'));
|
||||
$response->assertRedirect(route('login'));
|
||||
}
|
||||
|
||||
public function test_authenticated_users_can_visit_the_dashboard(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->get(route('dashboard'));
|
||||
$response->assertOk();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_returns_a_successful_response(): void
|
||||
{
|
||||
$response = $this->get(route('home'));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Settings;
|
||||
|
||||
use App\Livewire\Settings\Profile;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProfileUpdateTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_profile_page_is_displayed(): void
|
||||
{
|
||||
$this->actingAs($user = User::factory()->create());
|
||||
|
||||
$this->get('/settings/profile')->assertOk();
|
||||
}
|
||||
|
||||
public function test_profile_information_can_be_updated(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = Livewire::test(Profile::class)
|
||||
->set('name', 'Test User')
|
||||
->set('email', 'test@example.com')
|
||||
->call('updateProfileInformation');
|
||||
|
||||
$response->assertHasNoErrors();
|
||||
|
||||
$user->refresh();
|
||||
|
||||
$this->assertEquals('Test User', $user->name);
|
||||
$this->assertEquals('test@example.com', $user->email);
|
||||
$this->assertNull($user->email_verified_at);
|
||||
}
|
||||
|
||||
public function test_email_verification_status_is_unchanged_when_email_address_is_unchanged(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = Livewire::test(Profile::class)
|
||||
->set('name', 'Test User')
|
||||
->set('email', $user->email)
|
||||
->call('updateProfileInformation');
|
||||
|
||||
$response->assertHasNoErrors();
|
||||
|
||||
$this->assertNotNull($user->refresh()->email_verified_at);
|
||||
}
|
||||
|
||||
public function test_user_can_delete_their_account(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = Livewire::test('settings.delete-user-form')
|
||||
->set('password', 'password')
|
||||
->call('deleteUser');
|
||||
|
||||
$response
|
||||
->assertHasNoErrors()
|
||||
->assertRedirect('/');
|
||||
|
||||
$this->assertNull($user->fresh());
|
||||
$this->assertFalse(auth()->check());
|
||||
}
|
||||
|
||||
public function test_correct_password_must_be_provided_to_delete_account(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = Livewire::test('settings.delete-user-form')
|
||||
->set('password', 'wrong-password')
|
||||
->call('deleteUser');
|
||||
|
||||
$response->assertHasErrors(['password']);
|
||||
|
||||
$this->assertNotNull($user->fresh());
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Settings;
|
||||
|
||||
use App\Livewire\Settings\Security;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Fortify\Features;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SecurityTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->skipUnlessFortifyHas(Features::twoFactorAuthentication());
|
||||
|
||||
Features::twoFactorAuthentication([
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
]);
|
||||
Features::passkeys([
|
||||
'confirmPassword' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_security_settings_page_can_be_rendered(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->withSession(['auth.password_confirmed_at' => time()])
|
||||
->get(route('security.edit'));
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
$response->assertSee('Passkeys');
|
||||
$response->assertSee('No passkeys yet');
|
||||
$response->assertSee('Two-factor authentication');
|
||||
$response->assertSee('Enable 2FA');
|
||||
}
|
||||
|
||||
public function test_security_settings_page_requires_password_confirmation_when_enabled(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('security.edit'));
|
||||
|
||||
$response->assertRedirect(route('password.confirm'));
|
||||
}
|
||||
|
||||
public function test_security_settings_page_renders_without_two_factor_when_feature_is_disabled(): void
|
||||
{
|
||||
config(['fortify.features' => []]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->withSession(['auth.password_confirmed_at' => time()])
|
||||
->get(route('security.edit'))
|
||||
->assertOk()
|
||||
->assertSee('Update password')
|
||||
->assertDontSee('Manage your passkeys for passwordless sign-in')
|
||||
->assertDontSee('Add a passkey to sign in without a password')
|
||||
->assertDontSee('Two-factor authentication');
|
||||
}
|
||||
|
||||
public function test_two_factor_authentication_disabled_when_confirmation_abandoned_between_requests(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$user->forceFill([
|
||||
'two_factor_secret' => encrypt('test-secret'),
|
||||
'two_factor_recovery_codes' => encrypt(json_encode(['code1', 'code2'])),
|
||||
'two_factor_confirmed_at' => null,
|
||||
])->save();
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = Livewire::test(Security::class);
|
||||
|
||||
$component->assertSet('twoFactorEnabled', false);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'two_factor_secret' => null,
|
||||
'two_factor_recovery_codes' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_password_can_be_updated(): void
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = Livewire::test(Security::class)
|
||||
->set('current_password', 'password')
|
||||
->set('password', 'new-password')
|
||||
->set('password_confirmation', 'new-password')
|
||||
->call('updatePassword');
|
||||
|
||||
$response->assertHasNoErrors();
|
||||
|
||||
$this->assertTrue(Hash::check('new-password', $user->refresh()->password));
|
||||
}
|
||||
|
||||
public function test_correct_password_must_be_provided_to_update_password(): void
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = Livewire::test(Security::class)
|
||||
->set('current_password', 'wrong-password')
|
||||
->set('password', 'new-password')
|
||||
->set('password_confirmation', 'new-password')
|
||||
->call('updatePassword');
|
||||
|
||||
$response->assertHasErrors(['current_password']);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Laravel\Fortify\Features;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config([
|
||||
'app.debug' => false,
|
||||
'session.driver' => 'array',
|
||||
'cache.default' => 'array',
|
||||
'mail.default' => 'array',
|
||||
]);
|
||||
|
||||
$this->withoutMiddleware(ValidateCsrfToken::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST with a real CSRF token for Fortify/web form tests.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
protected function csrfPost(string $uri, array $data = []): \Illuminate\Testing\TestResponse
|
||||
{
|
||||
$token = 'test-csrf-token';
|
||||
|
||||
return $this->withSession(['_token' => $token])
|
||||
->post($uri, ['_token' => $token, ...$data]);
|
||||
}
|
||||
|
||||
protected function skipUnlessFortifyHas(string $feature, ?string $message = null): void
|
||||
{
|
||||
if (! Features::enabled($feature)) {
|
||||
$this->markTestSkipped($message ?? "Fortify feature [{$feature}] is not enabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_that_true_is_true(): void
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<div {{ $getExtraAttributeBag() }}>
|
||||
{{ $getState() }}
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{-- Use $getChildSchema() to render the child schema() of this component. --}}
|
||||
</div>
|
||||
@@ -1,8 +0,0 @@
|
||||
<x-dynamic-component
|
||||
:component="$getEntryWrapperView()"
|
||||
:entry="$entry"
|
||||
>
|
||||
<div {{ $getExtraAttributeBag() }}>
|
||||
{{ $getState() }}
|
||||
</div>
|
||||
</x-dynamic-component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<x-dynamic-component
|
||||
:component="$getFieldWrapperView()"
|
||||
:field="$field"
|
||||
>
|
||||
<div
|
||||
x-data="{ state: $wire.$entangle(@js($getStatePath())) }"
|
||||
{{ $getExtraAttributeBag() }}
|
||||
>
|
||||
{{-- Interact with the `state` property in Alpine.js --}}
|
||||
</div>
|
||||
</x-dynamic-component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div>
|
||||
<form wire:submit="{{ submitAction }}">
|
||||
{{ $this->form }}
|
||||
|
||||
<button type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<x-filament-actions::modals />
|
||||
</div>
|
||||
@@ -1,5 +0,0 @@
|
||||
<div>
|
||||
{{ $this->content }}
|
||||
|
||||
<x-filament-actions::modals />
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<x-filament-panels::page>
|
||||
{{-- Page content --}}
|
||||
</x-filament-panels::page>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{-- Custom block preview --}}
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{-- Custom block --}}
|
||||
</div>
|
||||
@@ -1,4 +0,0 @@
|
||||
@import '../../../../vendor/filament/filament/resources/css/theme.css';
|
||||
|
||||
@source '../../../../app/Filament/{{ classDirectory }}**/*';
|
||||
@source '../../../../resources/views/filament/{{ viewDirectory }}**/*';
|
||||
@@ -1,5 +0,0 @@
|
||||
<x-filament-widgets::widget>
|
||||
<x-filament::section>
|
||||
{{-- Widget content --}}
|
||||
</x-filament::section>
|
||||
</x-filament-widgets::widget>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\AppMeta;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\AppResource;
|
||||
|
||||
#[Description('A description of what this app resource does.')]
|
||||
#[AppMeta]
|
||||
class {{ class }} extends AppResource
|
||||
{
|
||||
/**
|
||||
* Handle the app resource request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
return Response::view('{{ view }}', [
|
||||
'title' => $this->title(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<x-mcp::app :title="$title">
|
||||
<x-slot:head>
|
||||
<script type="module">
|
||||
createMcpApp(async (app) => {
|
||||
document.getElementById('run-btn').addEventListener('click', async () => {
|
||||
const result = await app.callServerTool('tool-name', {});
|
||||
document.getElementById('output').textContent = result.content[0]?.text ?? '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</x-slot:head>
|
||||
|
||||
<div id="app">
|
||||
<button id="run-btn">Run</button>
|
||||
<p id="output"></p>
|
||||
</div>
|
||||
</x-mcp::app>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\Prompt;
|
||||
use Laravel\Mcp\Server\Prompts\Argument;
|
||||
|
||||
#[Description('A description of what this prompt does.')]
|
||||
class {{ class }} extends Prompt
|
||||
{
|
||||
/**
|
||||
* Handle the prompt request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
//
|
||||
|
||||
return Response::text('The content generated by the prompt.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prompt's arguments.
|
||||
*
|
||||
* @return array<int, Argument>
|
||||
*/
|
||||
public function arguments(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\Resource;
|
||||
|
||||
#[Description('A description of what this resource contains.')]
|
||||
class {{ class }} extends Resource
|
||||
{
|
||||
/**
|
||||
* Handle the resource request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
//
|
||||
|
||||
return Response::text('The resource content.');
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Server;
|
||||
use Laravel\Mcp\Server\Attributes\Instructions;
|
||||
use Laravel\Mcp\Server\Attributes\Name;
|
||||
use Laravel\Mcp\Server\Attributes\Version;
|
||||
|
||||
#[Name('{{ serverDisplayName }}')]
|
||||
#[Version('0.0.1')]
|
||||
#[Instructions('Instructions describing how to use the server and its features.')]
|
||||
class {{ class }} extends Server
|
||||
{
|
||||
protected array $tools = [
|
||||
//
|
||||
];
|
||||
|
||||
protected array $resources = [
|
||||
//
|
||||
];
|
||||
|
||||
protected array $prompts = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
|
||||
#[Description('A description of what this tool does.')]
|
||||
class {{ class }} extends Tool
|
||||
{
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
//
|
||||
|
||||
return Response::text('The content generated by the tool.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool's input schema.
|
||||
*
|
||||
* @return array<string, JsonSchema>
|
||||
*/
|
||||
public function schema(JsonSchema $schema): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{!! value($html) !!}
|
||||
@@ -1,19 +0,0 @@
|
||||
@if (isset($data))
|
||||
<script>
|
||||
window.filamentData = @js($data)
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@foreach ($assets as $asset)
|
||||
@if (! $asset->isLoadedOnRequest())
|
||||
{{ $asset->getHtml() }}
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
<style>
|
||||
:root {
|
||||
@foreach ($cssVariables ?? [] as $cssVariableName => $cssVariableValue) --{{ $cssVariableName }}:{{ $cssVariableValue }}; @endforeach
|
||||
}
|
||||
|
||||
@foreach ($customColors ?? [] as $customColorName => $customColorShades) .fi-color-{{ $customColorName }} { @foreach ($customColorShades as $customColorShade) --color-{{ $customColorShade }}:var(--{{ $customColorName }}-{{ $customColorShade }}); @endforeach } @endforeach
|
||||
</style>
|
||||
@@ -1,57 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\Alignment;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'actions' => [],
|
||||
'alignment' => Alignment::Start,
|
||||
'fullWidth' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
if (is_array($actions)) {
|
||||
$actions = array_filter(
|
||||
$actions,
|
||||
fn ($action): bool => $action->isVisible(),
|
||||
);
|
||||
}
|
||||
|
||||
if (! $alignment instanceof Alignment) {
|
||||
$alignment = filled($alignment) ? (Alignment::tryFrom($alignment) ?? $alignment) : null;
|
||||
}
|
||||
|
||||
$hasActions = false;
|
||||
|
||||
$hasSlot = ! \Filament\Support\is_slot_empty($slot);
|
||||
$actionsAreHtmlable = $actions instanceof \Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
if ($hasSlot) {
|
||||
$hasActions = true;
|
||||
} elseif ($actionsAreHtmlable) {
|
||||
$hasActions = ! \Filament\Support\is_slot_empty($actions);
|
||||
} else {
|
||||
$hasActions = filled($actions);
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if ($hasActions)
|
||||
<div
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-ac',
|
||||
'fi-width-full' => $fullWidth,
|
||||
($alignment instanceof Alignment) ? "fi-align-{$alignment->value}" : (is_string($alignment) ? $alignment : null) => ! $fullWidth,
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if ($hasSlot)
|
||||
{{ $slot }}
|
||||
@elseif ($actionsAreHtmlable)
|
||||
{{ $actions }}
|
||||
@else
|
||||
@foreach ($actions as $action)
|
||||
{{ $action }}
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@@ -1,18 +0,0 @@
|
||||
@props([
|
||||
'circular' => true,
|
||||
'size' => 'md',
|
||||
])
|
||||
|
||||
<img
|
||||
{{
|
||||
$attributes
|
||||
->class([
|
||||
'fi-avatar',
|
||||
'fi-circular' => $circular,
|
||||
match ($size) {
|
||||
'sm', 'md', 'lg' => "fi-size-{$size}",
|
||||
default => $size,
|
||||
},
|
||||
])
|
||||
}}
|
||||
/>
|
||||
@@ -1,183 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Enums\Size;
|
||||
use Filament\Support\View\Components\BadgeComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'color' => 'primary',
|
||||
'deleteButton' => null,
|
||||
'disabled' => false,
|
||||
'form' => null,
|
||||
'formId' => null,
|
||||
'href' => null,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'iconPosition' => IconPosition::Before,
|
||||
'iconSize' => null,
|
||||
'keyBindings' => null,
|
||||
'loadingIndicator' => true,
|
||||
'size' => Size::Medium,
|
||||
'spaMode' => null,
|
||||
'tag' => 'span',
|
||||
'target' => null,
|
||||
'tooltip' => null,
|
||||
'type' => 'button',
|
||||
])
|
||||
|
||||
@php
|
||||
if (! $iconPosition instanceof IconPosition) {
|
||||
$iconPosition = filled($iconPosition) ? (IconPosition::tryFrom($iconPosition) ?? $iconPosition) : null;
|
||||
}
|
||||
|
||||
if (! $size instanceof Size) {
|
||||
$size = filled($size) ? (Size::tryFrom($size) ?? $size) : null;
|
||||
}
|
||||
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$isDeletable = count($deleteButton?->attributes->getAttributes() ?? []) > 0;
|
||||
|
||||
$wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
|
||||
|
||||
$hasLoadingIndicator = filled($wireTarget) || ($type === 'submit' && filled($form));
|
||||
|
||||
if ($hasLoadingIndicator) {
|
||||
$loadingIndicatorTarget = html_entity_decode($wireTarget ?: $form, ENT_QUOTES);
|
||||
}
|
||||
|
||||
$hasTooltip = filled($tooltip);
|
||||
@endphp
|
||||
|
||||
<{{ $tag }}
|
||||
@if (($tag === 'a') && (! ($disabled && $hasTooltip)))
|
||||
{{ \Filament\Support\generate_href_html($href, $target === '_blank', $spaMode) }}
|
||||
@endif
|
||||
@if ($keyBindings)
|
||||
x-bind:id="$id('key-bindings')"
|
||||
x-mousetrap.global.{{ collect($keyBindings)->map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.') }}="document.getElementById($el.id)?.click()"
|
||||
@endif
|
||||
@if ($hasTooltip)
|
||||
x-tooltip="{
|
||||
content: @js($tooltip),
|
||||
theme: $store.theme,
|
||||
allowHTML: @js($tooltip instanceof \Illuminate\Contracts\Support\Htmlable),
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
'aria-disabled' => $disabled ? 'true' : null,
|
||||
'disabled' => $disabled && blank($tooltip),
|
||||
'form' => $tag === 'button' ? $formId : null,
|
||||
'type' => $tag === 'button' ? $type : null,
|
||||
'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
|
||||
'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
|
||||
], escape: false)
|
||||
->when(
|
||||
$disabled && $hasTooltip,
|
||||
fn (ComponentAttributeBag $attributes) => $attributes->filter(
|
||||
fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
|
||||
),
|
||||
)
|
||||
->class([
|
||||
'fi-badge',
|
||||
'fi-disabled' => $disabled,
|
||||
($size instanceof Size) ? "fi-size-{$size->value}" : (is_string($size) ? $size : ''),
|
||||
])
|
||||
->color(BadgeComponent::class, $color)
|
||||
}}
|
||||
>
|
||||
@if ($iconPosition === IconPosition::Before)
|
||||
@if ($icon)
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
])), size: $iconSize ?? \Filament\Support\Enums\IconSize::Small)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
])), size: $iconSize ?? \Filament\Support\Enums\IconSize::Small)
|
||||
}}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<span class="fi-badge-label-ctn">
|
||||
<span class="fi-badge-label">
|
||||
{{ $slot }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@if ($isDeletable)
|
||||
@php
|
||||
$deleteButtonWireTarget = $deleteButton->attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first();
|
||||
|
||||
$deleteButtonHasLoadingIndicator = filled($deleteButtonWireTarget);
|
||||
|
||||
if ($deleteButtonHasLoadingIndicator) {
|
||||
$deleteButtonLoadingIndicatorTarget = html_entity_decode($deleteButtonWireTarget, ENT_QUOTES);
|
||||
}
|
||||
@endphp
|
||||
|
||||
<button
|
||||
type="button"
|
||||
{{
|
||||
$deleteButton->attributes
|
||||
->except(['label'])
|
||||
->class([
|
||||
'fi-badge-delete-btn',
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{
|
||||
\Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::XMark, alias: \Filament\Support\View\SupportIconAlias::BADGE_DELETE_BUTTON, attributes: (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $deleteButtonHasLoadingIndicator,
|
||||
'wire:target' => $deleteButtonHasLoadingIndicator ? $deleteButtonLoadingIndicatorTarget : false,
|
||||
])), size: \Filament\Support\Enums\IconSize::ExtraSmall)
|
||||
}}
|
||||
|
||||
@if ($deleteButtonHasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $deleteButtonLoadingIndicatorTarget,
|
||||
])), size: \Filament\Support\Enums\IconSize::ExtraSmall)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if (filled($label = $deleteButton->attributes->get('label')))
|
||||
<span class="fi-sr-only">
|
||||
{{ $label }}
|
||||
</span>
|
||||
@endif
|
||||
</button>
|
||||
@elseif ($iconPosition === IconPosition::After)
|
||||
@if ($icon)
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
])), size: $iconSize ?? \Filament\Support\Enums\IconSize::Small)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
])), size: $iconSize ?? \Filament\Support\Enums\IconSize::Small)
|
||||
}}
|
||||
@endif
|
||||
@endif
|
||||
</{{ $tag }}>
|
||||
@@ -1,44 +0,0 @@
|
||||
@php
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
|
||||
use function Filament\Support\generate_icon_html;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'breadcrumbs' => [],
|
||||
])
|
||||
|
||||
<nav {{ $attributes->class(['fi-breadcrumbs']) }}>
|
||||
<ol class="fi-breadcrumbs-list">
|
||||
@foreach ($breadcrumbs as $url => $label)
|
||||
<li class="fi-breadcrumbs-item">
|
||||
@if (! $loop->first)
|
||||
{{
|
||||
generate_icon_html(\Filament\Support\Icons\Heroicon::ChevronRight, alias: \Filament\Support\View\SupportIconAlias::BREADCRUMBS_SEPARATOR, attributes: (new ComponentAttributeBag)->class([
|
||||
'fi-breadcrumbs-item-separator fi-ltr',
|
||||
]))
|
||||
}}
|
||||
|
||||
{{
|
||||
generate_icon_html(\Filament\Support\Icons\Heroicon::ChevronLeft, alias: \Filament\Support\View\SupportIconAlias::BREADCRUMBS_SEPARATOR_RTL, attributes: (new ComponentAttributeBag)->class([
|
||||
'fi-breadcrumbs-item-separator fi-rtl',
|
||||
]))
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if (is_int($url))
|
||||
<span class="fi-breadcrumbs-item-label">
|
||||
{{ $label }}
|
||||
</span>
|
||||
@else
|
||||
<a
|
||||
{{ \Filament\Support\generate_href_html($url) }}
|
||||
class="fi-breadcrumbs-item-label"
|
||||
>
|
||||
{{ $label }}
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
</nav>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div {{ $attributes->class(['fi-btn-group']) }}>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@@ -1,238 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Enums\Size;
|
||||
use Filament\Support\View\Components\BadgeComponent;
|
||||
use Filament\Support\View\Components\ButtonComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'badge' => null,
|
||||
'badgeColor' => 'primary',
|
||||
'badgeSize' => Size::ExtraSmall,
|
||||
'color' => 'primary',
|
||||
'disabled' => false,
|
||||
'form' => null,
|
||||
'formId' => null,
|
||||
'href' => null,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'iconPosition' => IconPosition::Before,
|
||||
'iconSize' => null,
|
||||
'keyBindings' => null,
|
||||
'labeledFrom' => null,
|
||||
'labelSrOnly' => false,
|
||||
'loadingIndicator' => true,
|
||||
'outlined' => false,
|
||||
'size' => Size::Medium,
|
||||
'spaMode' => null,
|
||||
'tag' => 'button',
|
||||
'target' => null,
|
||||
'tooltip' => null,
|
||||
'type' => 'button',
|
||||
])
|
||||
|
||||
@php
|
||||
if (! $iconPosition instanceof IconPosition) {
|
||||
$iconPosition = filled($iconPosition) ? (IconPosition::tryFrom($iconPosition) ?? $iconPosition) : null;
|
||||
}
|
||||
|
||||
if (! $size instanceof Size) {
|
||||
$size = filled($size) ? (Size::tryFrom($size) ?? $size) : null;
|
||||
}
|
||||
|
||||
if (! $badgeSize instanceof Size) {
|
||||
$badgeSize = filled($badgeSize) ? (Size::tryFrom($badgeSize) ?? $badgeSize) : null;
|
||||
}
|
||||
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$iconSize ??= match ($size) {
|
||||
Size::ExtraSmall, Size::Small => IconSize::Small,
|
||||
default => null,
|
||||
};
|
||||
|
||||
$wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
|
||||
|
||||
$hasFormProcessingLoadingIndicator = $type === 'submit' && filled($form);
|
||||
$hasLoadingIndicator = filled($wireTarget) || $hasFormProcessingLoadingIndicator;
|
||||
|
||||
if ($hasLoadingIndicator) {
|
||||
$loadingIndicatorTarget = html_entity_decode($wireTarget ?: $form, ENT_QUOTES);
|
||||
}
|
||||
|
||||
$hasTooltip = filled($tooltip);
|
||||
@endphp
|
||||
|
||||
@if ($labeledFrom)
|
||||
<x-filament::icon-button
|
||||
:badge="$badge"
|
||||
:badge-color="$badgeColor"
|
||||
:badge-size="$badgeSize"
|
||||
:color="$color"
|
||||
:disabled="$disabled"
|
||||
:form="$form"
|
||||
:form-id="$formId"
|
||||
:href="$href"
|
||||
:icon="$icon"
|
||||
:icon-alias="$iconAlias"
|
||||
:icon-size="$iconSize"
|
||||
:key-bindings="$keyBindings"
|
||||
:label="$slot"
|
||||
:loading-indicator="$loadingIndicator"
|
||||
:size="$size"
|
||||
:spa-mode="$spaMode"
|
||||
:tag="$tag"
|
||||
:target="$target"
|
||||
:tooltip="$tooltip"
|
||||
:type="$type"
|
||||
:attributes="\Filament\Support\prepare_inherited_attributes($attributes)"
|
||||
/>
|
||||
@endif
|
||||
|
||||
<{{ $tag }}
|
||||
@if (($tag === 'a') && (! ($disabled && $hasTooltip)))
|
||||
{{ \Filament\Support\generate_href_html($href, $target === '_blank', $spaMode) }}
|
||||
@endif
|
||||
@if ($keyBindings)
|
||||
x-bind:id="$id('key-bindings')"
|
||||
x-mousetrap.global.{{ collect($keyBindings)->map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.') }}="document.getElementById($el.id)?.click()"
|
||||
@endif
|
||||
@if ($hasTooltip)
|
||||
x-tooltip="{
|
||||
content: @js($tooltip),
|
||||
theme: $store.theme,
|
||||
allowHTML: @js($tooltip instanceof \Illuminate\Contracts\Support\Htmlable),
|
||||
}"
|
||||
@endif
|
||||
@if ($hasFormProcessingLoadingIndicator)
|
||||
x-data="filamentFormButton"
|
||||
x-bind:class="{ 'fi-processing': isProcessing }"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
'aria-disabled' => $disabled ? 'true' : null,
|
||||
'aria-label' => $labelSrOnly ? trim(strip_tags($slot->toHtml())) : null,
|
||||
'disabled' => $disabled && blank($tooltip),
|
||||
'form' => $formId,
|
||||
'type' => $tag === 'button' ? $type : null,
|
||||
'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
|
||||
'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
|
||||
'x-bind:disabled' => $hasFormProcessingLoadingIndicator ? 'isProcessing' : null,
|
||||
'x-bind:aria-label' => ($labelSrOnly && $hasFormProcessingLoadingIndicator) ? ('isProcessing ? processingMessage : ' . \Illuminate\Support\Js::from(trim(strip_tags($slot->toHtml())))) : null,
|
||||
], escape: false)
|
||||
->when(
|
||||
$disabled && $hasTooltip,
|
||||
fn (ComponentAttributeBag $attributes) => $attributes->filter(
|
||||
fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
|
||||
),
|
||||
)
|
||||
->class([
|
||||
'fi-btn',
|
||||
'fi-disabled' => $disabled,
|
||||
'fi-outlined' => $outlined,
|
||||
($size instanceof Size) ? "fi-size-{$size->value}" : (is_string($size) ? $size : ''),
|
||||
is_string($labeledFrom) ? "fi-labeled-from-{$labeledFrom}" : null,
|
||||
])
|
||||
->color(app(ButtonComponent::class, ['isOutlined' => $outlined]), $color)
|
||||
}}
|
||||
>
|
||||
@if ($iconPosition === IconPosition::Before)
|
||||
@if ($icon)
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasFormProcessingLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'x-cloak' => 'x-cloak',
|
||||
'x-show' => 'isProcessing',
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if (! $labelSrOnly)
|
||||
@if ($hasFormProcessingLoadingIndicator)
|
||||
<span x-show="! isProcessing">
|
||||
{{ $slot }}
|
||||
</span>
|
||||
@else
|
||||
{{ $slot }}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if ($hasFormProcessingLoadingIndicator && (! $labelSrOnly))
|
||||
<span
|
||||
x-cloak
|
||||
x-show="isProcessing"
|
||||
x-text="processingMessage"
|
||||
></span>
|
||||
@endif
|
||||
|
||||
@if ($iconPosition === IconPosition::After)
|
||||
@if ($icon)
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasFormProcessingLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'x-cloak' => 'x-cloak',
|
||||
'x-show' => 'isProcessing',
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if (filled($badge))
|
||||
<div class="fi-btn-badge-ctn">
|
||||
@if ($badge instanceof \Illuminate\View\ComponentSlot)
|
||||
{{ $badge }}
|
||||
@else
|
||||
<span
|
||||
{{
|
||||
(new ComponentAttributeBag)->color(BadgeComponent::class, $badgeColor)->class([
|
||||
'fi-badge',
|
||||
($badgeSize instanceof Size) ? "fi-size-{$badgeSize->value}" : (is_string($badgeSize) ? $badgeSize : ''),
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{ $badge }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</{{ $tag }}>
|
||||
@@ -1,84 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\View\Components\CalloutComponent\IconComponent;
|
||||
|
||||
use function Filament\Support\generate_icon_html;
|
||||
use function Filament\Support\is_slot_empty;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'color' => 'gray',
|
||||
'controls' => null,
|
||||
'description' => null,
|
||||
'footer' => null,
|
||||
'heading' => null,
|
||||
'icon' => null,
|
||||
'iconColor' => null,
|
||||
'iconSize' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$iconColor ??= $color;
|
||||
|
||||
$hasDescription = filled((string) $description);
|
||||
$hasHeading = filled($heading);
|
||||
$hasFooter = ! is_slot_empty($footer);
|
||||
$hasIcon = filled($icon);
|
||||
$hasControls = ! is_slot_empty($controls);
|
||||
@endphp
|
||||
|
||||
<div
|
||||
{{
|
||||
$attributes
|
||||
->color(\Filament\Support\View\Components\CalloutComponent::class, $color)
|
||||
->class(['fi-callout'])
|
||||
}}
|
||||
>
|
||||
@if ($hasIcon)
|
||||
{{
|
||||
generate_icon_html(
|
||||
$icon,
|
||||
attributes: (new \Illuminate\View\ComponentAttributeBag)
|
||||
->color(IconComponent::class, $iconColor)
|
||||
->class(['fi-callout-icon']),
|
||||
size: $iconSize ?? IconSize::Large,
|
||||
)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasHeading || $hasDescription || $hasFooter)
|
||||
<div class="fi-callout-main">
|
||||
@if ($hasHeading || $hasDescription)
|
||||
<div class="fi-callout-text">
|
||||
@if ($hasHeading)
|
||||
<h4 class="fi-callout-heading">
|
||||
{{ $heading }}
|
||||
</h4>
|
||||
@endif
|
||||
|
||||
@if ($hasDescription)
|
||||
<p class="fi-callout-description">
|
||||
{{ $description }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($hasFooter)
|
||||
<div class="fi-callout-footer">
|
||||
{{ $footer }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($hasControls)
|
||||
<div class="fi-callout-controls">
|
||||
{{ $controls }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,5 +0,0 @@
|
||||
<x-filament::section
|
||||
:attributes="\Filament\Support\prepare_inherited_attributes($attributes)"
|
||||
>
|
||||
{{ $slot }}
|
||||
</x-filament::section>
|
||||
@@ -1,34 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\View\Components\DropdownComponent\HeaderComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'color' => 'gray',
|
||||
'icon' => null,
|
||||
'iconSize' => null,
|
||||
'tag' => 'div',
|
||||
])
|
||||
|
||||
@php
|
||||
if (! ($iconSize instanceof IconSize)) {
|
||||
$iconSize = filled($iconSize) ? (IconSize::tryFrom($iconSize) ?? $iconSize) : null;
|
||||
}
|
||||
@endphp
|
||||
|
||||
<{{ $tag }}
|
||||
{{
|
||||
$attributes
|
||||
->class([
|
||||
'fi-dropdown-header',
|
||||
])
|
||||
->color(HeaderComponent::class, $color)
|
||||
}}
|
||||
>
|
||||
{{ \Filament\Support\generate_icon_html($icon, size: $iconSize) }}
|
||||
|
||||
<span>
|
||||
{{ $slot }}
|
||||
</span>
|
||||
</{{ $tag }}>
|
||||
@@ -1,66 +0,0 @@
|
||||
@props([
|
||||
'availableHeight' => null,
|
||||
'availableWidth' => null,
|
||||
'flip' => true,
|
||||
'maxHeight' => null,
|
||||
'offset' => 8,
|
||||
'placement' => null,
|
||||
'shift' => false,
|
||||
'size' => false,
|
||||
'sizePadding' => 16,
|
||||
'teleport' => false,
|
||||
'trigger' => null,
|
||||
'width' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
$sizeConfig = collect([
|
||||
'availableHeight' => $availableHeight,
|
||||
'availableWidth' => $availableWidth,
|
||||
'padding' => $sizePadding,
|
||||
])->filter()->toJson();
|
||||
|
||||
if (is_string($width)) {
|
||||
$width = Width::tryFrom($width) ?? $width;
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div
|
||||
x-data="filamentDropdown"
|
||||
{{ $attributes->class(['fi-dropdown']) }}
|
||||
>
|
||||
<div
|
||||
x-on:keyup.enter="toggle($event)"
|
||||
x-on:keyup.space="toggle($event)"
|
||||
x-on:mousedown="if ($event.button === 0) toggle($event)"
|
||||
{{ $trigger->attributes->class(['fi-dropdown-trigger']) }}
|
||||
>
|
||||
{{ $trigger }}
|
||||
</div>
|
||||
|
||||
@if (! \Filament\Support\is_slot_empty($slot))
|
||||
<div
|
||||
x-cloak
|
||||
x-float{{ $placement ? ".placement.{$placement}" : '' }}{{ $size ? '.size' : '' }}{{ $flip ? '.flip' : '' }}{{ $shift ? '.shift' : '' }}{{ $teleport ? '.teleport' : '' }}{{ $offset ? '.offset' : '' }}="{ offset: {{ $offset }}, {{ $size ? ('size: ' . $sizeConfig) : '' }} }"
|
||||
x-ref="panel"
|
||||
x-transition:enter-start="fi-opacity-0"
|
||||
x-transition:leave-end="fi-opacity-0"
|
||||
@if ($attributes->has('wire:key'))
|
||||
wire:ignore.self
|
||||
wire:key="{{ $attributes->get('wire:key') }}.panel"
|
||||
@endif
|
||||
@class([
|
||||
'fi-dropdown-panel',
|
||||
($width instanceof Width) ? "fi-width-{$width->value}" : (is_string($width) ? $width : ''),
|
||||
'fi-scrollable' => $maxHeight || $size,
|
||||
])
|
||||
@style([
|
||||
('max-height: ' . e($maxHeight)) => $maxHeight,
|
||||
])
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div {{ $attributes->class(['fi-dropdown-list']) }}>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@@ -1,191 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Enums\Size;
|
||||
use Filament\Support\View\Components\BadgeComponent;
|
||||
use Filament\Support\View\Components\DropdownComponent\ItemComponent;
|
||||
use Filament\Support\View\Components\DropdownComponent\ItemComponent\IconComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'alpineDeferredBadgeData' => null,
|
||||
'alpineDeferredBadgeLoading' => null,
|
||||
'badge' => null,
|
||||
'badgeColor' => 'primary',
|
||||
'badgeTooltip' => null,
|
||||
'color' => 'gray',
|
||||
'disabled' => false,
|
||||
'href' => null,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'iconColor' => null,
|
||||
'iconSize' => null,
|
||||
'image' => null,
|
||||
'keyBindings' => null,
|
||||
'loadingIndicator' => true,
|
||||
'spaMode' => null,
|
||||
'tag' => 'button',
|
||||
'target' => null,
|
||||
'tooltip' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$iconColor ??= $color;
|
||||
|
||||
$wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
|
||||
|
||||
$hasLoadingIndicator = filled($wireTarget);
|
||||
|
||||
if ($hasLoadingIndicator) {
|
||||
$loadingIndicatorTarget = html_entity_decode($wireTarget, ENT_QUOTES);
|
||||
}
|
||||
|
||||
$hasDeferredBadge = filled($alpineDeferredBadgeData);
|
||||
$hasTooltip = filled($tooltip);
|
||||
@endphp
|
||||
|
||||
{!! ($tag === 'form') ? ('<form ' . $attributes->only(['action', 'class', 'method', 'wire:submit'])->toHtml() . '>') : '' !!}
|
||||
|
||||
@if ($tag === 'form')
|
||||
@csrf
|
||||
@endif
|
||||
|
||||
<{{ ($tag === 'form') ? 'button' : $tag }}
|
||||
@if (($tag === 'a') && (! ($disabled && $hasTooltip)))
|
||||
{{ \Filament\Support\generate_href_html($href, $target === '_blank', $spaMode) }}
|
||||
@endif
|
||||
@if ($keyBindings)
|
||||
x-bind:id="$id('key-bindings')"
|
||||
x-mousetrap.global.{{ collect($keyBindings)->map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.') }}="document.getElementById($el.id)?.click()"
|
||||
@endif
|
||||
@if ($hasTooltip)
|
||||
x-tooltip="{
|
||||
content: @js($tooltip),
|
||||
theme: $store.theme,
|
||||
allowHTML: @js($tooltip instanceof \Illuminate\Contracts\Support\Htmlable),
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->when(
|
||||
$tag === 'form',
|
||||
fn (ComponentAttributeBag $attributes) => $attributes->except(['action', 'class', 'method', 'wire:submit']),
|
||||
)
|
||||
->merge([
|
||||
'aria-disabled' => $disabled ? 'true' : null,
|
||||
'disabled' => $disabled && blank($tooltip),
|
||||
'type' => match ($tag) {
|
||||
'button' => 'button',
|
||||
'form' => 'submit',
|
||||
default => null,
|
||||
},
|
||||
'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
|
||||
'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
|
||||
], escape: false)
|
||||
->when(
|
||||
$disabled && $hasTooltip,
|
||||
fn (ComponentAttributeBag $attributes) => $attributes->filter(
|
||||
fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
|
||||
),
|
||||
)
|
||||
->class([
|
||||
'fi-dropdown-list-item',
|
||||
'fi-disabled' => $disabled,
|
||||
])
|
||||
->color(ItemComponent::class, $color)
|
||||
}}
|
||||
>
|
||||
@if ($icon)
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
]))->color(IconComponent::class, $iconColor), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($image)
|
||||
<div
|
||||
class="fi-dropdown-list-item-image"
|
||||
style="background-image: url('{{ $image }}')"
|
||||
@if ($hasLoadingIndicator)
|
||||
wire:loading.remove.delay.{{ config('filament.livewire_loading_delay', 'default') }}
|
||||
wire:target="{{ $loadingIndicatorTarget }}"
|
||||
@endif
|
||||
></div>
|
||||
@endif
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
]))->color(IconComponent::class, $iconColor), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
<span class="fi-dropdown-list-item-label">
|
||||
{{ $slot }}
|
||||
</span>
|
||||
|
||||
@if (filled($badge))
|
||||
@if ($badge instanceof \Illuminate\View\ComponentSlot)
|
||||
{{ $badge }}
|
||||
@else
|
||||
<span
|
||||
@if ($badgeTooltip)
|
||||
x-tooltip="{
|
||||
content: @js($badgeTooltip),
|
||||
theme: $store.theme,
|
||||
allowHTML: @js($badgeTooltip instanceof \Illuminate\Contracts\Support\Htmlable),
|
||||
}"
|
||||
@endif
|
||||
{{ (new ComponentAttributeBag)->color(BadgeComponent::class, $badgeColor)->class(['fi-badge']) }}
|
||||
>
|
||||
{{ $badge }}
|
||||
</span>
|
||||
@endif
|
||||
@elseif ($hasDeferredBadge)
|
||||
<span
|
||||
x-show="{{ $alpineDeferredBadgeLoading }}"
|
||||
x-cloak
|
||||
class="fi-dropdown-list-item-badge-placeholder"
|
||||
>
|
||||
{{ \Filament\Support\generate_loading_indicator_html(size: \Filament\Support\Enums\IconSize::Small) }}
|
||||
</span>
|
||||
|
||||
<template
|
||||
x-if="
|
||||
! {{ $alpineDeferredBadgeLoading }} &&
|
||||
{{ $alpineDeferredBadgeData }}?.badge != null
|
||||
"
|
||||
>
|
||||
<span
|
||||
x-bind:class="'fi-badge ' + ({{ $alpineDeferredBadgeData }}?.badgeColorClasses ?? '')"
|
||||
x-bind:style="{{ $alpineDeferredBadgeData }}?.badgeColorStyles ?? ''"
|
||||
x-init="
|
||||
let tooltip = {{ $alpineDeferredBadgeData }}?.badgeTooltip
|
||||
if (tooltip) {
|
||||
window.tippy?.($el, {
|
||||
content: tooltip,
|
||||
theme: $store.theme,
|
||||
})
|
||||
}
|
||||
"
|
||||
>
|
||||
<span class="fi-badge-label-ctn">
|
||||
<span
|
||||
class="fi-badge-label"
|
||||
x-text="{{ $alpineDeferredBadgeData }}?.badge"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
@endif
|
||||
</{{ ($tag === 'form') ? 'button' : $tag }}>
|
||||
|
||||
{!! ($tag === 'form') ? '</form>' : '' !!}
|
||||
@@ -1,70 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\View\Components\SectionComponent\IconComponent;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'compact' => false,
|
||||
'contained' => true,
|
||||
'description' => null,
|
||||
'footer' => null,
|
||||
'heading',
|
||||
'headingTag' => 'h2',
|
||||
'icon' => null,
|
||||
'iconColor' => 'primary',
|
||||
'iconSize' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$hasDescription = filled((string) $description);
|
||||
$hasFooter = filled((string) $footer);
|
||||
$hasIcon = filled($icon);
|
||||
@endphp
|
||||
|
||||
<section
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-empty-state',
|
||||
'fi-compact' => $compact,
|
||||
'fi-empty-state-not-contained' => ! $contained,
|
||||
])
|
||||
}}
|
||||
>
|
||||
<div class="fi-empty-state-content">
|
||||
@if ($hasIcon)
|
||||
<div
|
||||
@class([
|
||||
'fi-empty-state-icon-bg',
|
||||
'fi-color ' . ('fi-color-' . $iconColor) => $iconColor !== 'gray',
|
||||
])
|
||||
>
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, attributes: (new \Illuminate\View\ComponentAttributeBag)
|
||||
->color(IconComponent::class, $iconColor), size: $iconSize ?? IconSize::Large)
|
||||
}}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="fi-empty-state-text-ctn">
|
||||
<{{ $headingTag }} class="fi-empty-state-heading">
|
||||
{{ $heading }}
|
||||
</{{ $headingTag }}>
|
||||
|
||||
@if ($hasDescription)
|
||||
<p class="fi-empty-state-description">
|
||||
{{ $description }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if ($hasFooter)
|
||||
<footer class="fi-empty-state-footer">
|
||||
{{ $footer }}
|
||||
</footer>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,25 +0,0 @@
|
||||
@props([
|
||||
'contained' => true,
|
||||
'label' => null,
|
||||
'labelHidden' => false,
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
<fieldset
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-fieldset',
|
||||
'fi-fieldset-label-hidden' => $labelHidden,
|
||||
'fi-fieldset-not-contained' => ! $contained,
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if (filled($label))
|
||||
<legend>
|
||||
{{ $label }}@if ($required)<sup class="fi-fieldset-label-required-mark">*</sup>
|
||||
@endif
|
||||
</legend>
|
||||
@endif
|
||||
|
||||
{{ $slot }}
|
||||
</fieldset>
|
||||
@@ -1,139 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Enums\Size;
|
||||
use Filament\Support\View\Components\BadgeComponent;
|
||||
use Filament\Support\View\Components\IconButtonComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'badge' => null,
|
||||
'badgeColor' => 'primary',
|
||||
'badgeSize' => Size::ExtraSmall,
|
||||
'color' => 'primary',
|
||||
'disabled' => false,
|
||||
'form' => null,
|
||||
'formId' => null,
|
||||
'href' => null,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'iconSize' => null,
|
||||
'keyBindings' => null,
|
||||
'label' => null,
|
||||
'loadingIndicator' => true,
|
||||
'size' => Size::Medium,
|
||||
'spaMode' => null,
|
||||
'tag' => 'button',
|
||||
'target' => null,
|
||||
'tooltip' => null,
|
||||
'type' => 'button',
|
||||
])
|
||||
|
||||
@php
|
||||
if (! $size instanceof Size) {
|
||||
$size = filled($size) ? (Size::tryFrom($size) ?? $size) : null;
|
||||
}
|
||||
|
||||
if (! $badgeSize instanceof Size) {
|
||||
$badgeSize = filled($badgeSize) ? (Size::tryFrom($badgeSize) ?? $badgeSize) : null;
|
||||
}
|
||||
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$iconSize ??= match ($size) {
|
||||
Size::ExtraSmall => IconSize::Small,
|
||||
Size::Large, Size::ExtraLarge => IconSize::Large,
|
||||
default => null,
|
||||
};
|
||||
|
||||
$wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
|
||||
|
||||
$hasLoadingIndicator = filled($wireTarget) || ($type === 'submit' && filled($form));
|
||||
|
||||
if ($hasLoadingIndicator) {
|
||||
$loadingIndicatorTarget = html_entity_decode($wireTarget ?: $form, ENT_QUOTES);
|
||||
}
|
||||
|
||||
$hasTooltip = $hasTooltip = filled($tooltip);
|
||||
@endphp
|
||||
|
||||
<{{ $tag }}
|
||||
@if (($tag === 'a') && (! ($disabled && $hasTooltip)))
|
||||
{{ \Filament\Support\generate_href_html($href, $target === '_blank', $spaMode) }}
|
||||
@endif
|
||||
@if ($keyBindings)
|
||||
x-bind:id="$id('key-bindings')"
|
||||
x-mousetrap.global.{{ collect($keyBindings)->map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.') }}="document.getElementById($el.id)?.click()"
|
||||
@endif
|
||||
@if ($hasTooltip)
|
||||
x-tooltip="{
|
||||
content: @js($tooltip),
|
||||
theme: $store.theme,
|
||||
allowHTML: @js($tooltip instanceof \Illuminate\Contracts\Support\Htmlable),
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
'aria-disabled' => $disabled ? 'true' : null,
|
||||
'aria-label' => e($label),
|
||||
'disabled' => $disabled && blank($tooltip),
|
||||
'form' => $formId,
|
||||
'type' => $tag === 'button' ? $type : null,
|
||||
'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
|
||||
'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
|
||||
], escape: false)
|
||||
->merge([
|
||||
'title' => $hasTooltip ? null : $label,
|
||||
], escape: true)
|
||||
->when(
|
||||
$disabled && $hasTooltip,
|
||||
fn (ComponentAttributeBag $attributes) => $attributes->filter(
|
||||
fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
|
||||
),
|
||||
)
|
||||
->class([
|
||||
'fi-icon-btn',
|
||||
'fi-disabled' => $disabled,
|
||||
($size instanceof Size) ? "fi-size-{$size->value}" : (is_string($size) ? $size : ''),
|
||||
])
|
||||
->color(IconButtonComponent::class, $color)
|
||||
}}
|
||||
>
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if (filled($badge))
|
||||
<div class="fi-icon-btn-badge-ctn">
|
||||
@if ($badge instanceof \Illuminate\View\ComponentSlot)
|
||||
{{ $badge }}
|
||||
@else
|
||||
<span
|
||||
{{
|
||||
(new ComponentAttributeBag)->color(BadgeComponent::class, $badgeColor)->class([
|
||||
'fi-badge',
|
||||
($badgeSize instanceof Size) ? "fi-size-{$badgeSize->value}" : (is_string($badgeSize) ? $badgeSize : ''),
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{ $badge }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</{{ $tag }}>
|
||||
@@ -1,7 +0,0 @@
|
||||
@props([
|
||||
'alias' => null,
|
||||
'icon' => null,
|
||||
'size' => null,
|
||||
])
|
||||
|
||||
{{ \Filament\Support\generate_icon_html($icon, $alias, $attributes, $size) }}
|
||||
@@ -1,26 +0,0 @@
|
||||
@props([
|
||||
'alpineValid' => null,
|
||||
'valid' => true,
|
||||
])
|
||||
|
||||
@php
|
||||
$hasAlpineValidClasses = filled($alpineValid);
|
||||
@endphp
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
@if ($hasAlpineValidClasses)
|
||||
x-bind:class="{
|
||||
'fi-valid': {{ $alpineValid }},
|
||||
'fi-invalid': {{ "(! {$alpineValid})" }},
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->class([
|
||||
'fi-checkbox-input',
|
||||
'fi-valid' => (! $hasAlpineValidClasses) && $valid,
|
||||
'fi-invalid' => (! $hasAlpineValidClasses) && (! $valid),
|
||||
])
|
||||
}}
|
||||
/>
|
||||
@@ -1,14 +0,0 @@
|
||||
@props([
|
||||
'inlinePrefix' => false,
|
||||
'inlineSuffix' => false,
|
||||
])
|
||||
|
||||
<input
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-input',
|
||||
'fi-input-has-inline-prefix' => $inlinePrefix,
|
||||
'fi-input-has-inline-suffix' => $inlineSuffix,
|
||||
])
|
||||
}}
|
||||
/>
|
||||
@@ -1,43 +0,0 @@
|
||||
@props([
|
||||
'length' => 6,
|
||||
])
|
||||
|
||||
<div
|
||||
x-data="{ currentNumberOfDigits: null }"
|
||||
{{
|
||||
$attributes
|
||||
->class([
|
||||
'fi-one-time-code-input-ctn',
|
||||
])
|
||||
}}
|
||||
>
|
||||
@foreach (range(1, $length) as $digit)
|
||||
<div
|
||||
x-bind:class="{
|
||||
'fi-active':
|
||||
currentNumberOfDigits !== null &&
|
||||
currentNumberOfDigits >= {{ $digit }},
|
||||
}"
|
||||
class="fi-one-time-code-input-digit-field"
|
||||
></div>
|
||||
@endforeach
|
||||
|
||||
<input
|
||||
autocomplete="one-time-code"
|
||||
inputmode="numeric"
|
||||
minlength="{{ $length }}"
|
||||
maxlength="{{ $length }}"
|
||||
pattern="\d{{ '{' . $length . '}' }}"
|
||||
type="text"
|
||||
x-data="{}"
|
||||
x-on:focus="currentNumberOfDigits = $el.value.length"
|
||||
x-on:blur="currentNumberOfDigits = null"
|
||||
x-on:input="
|
||||
$el.value = $el.value.replace(/\D/g, '')
|
||||
currentNumberOfDigits = $el.value.length
|
||||
"
|
||||
x-bind:class="{ 'fi-valid': currentNumberOfDigits >= {{ $length }} }"
|
||||
{{ $input?->attributes }}
|
||||
class="fi-one-time-code-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
@props([
|
||||
'valid' => true,
|
||||
])
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
{{
|
||||
$attributes
|
||||
->class([
|
||||
'fi-radio-input',
|
||||
'fi-invalid' => ! $valid,
|
||||
])
|
||||
}}
|
||||
/>
|
||||
@@ -1,15 +0,0 @@
|
||||
@props([
|
||||
'inlinePrefix' => false,
|
||||
'inlineSuffix' => false,
|
||||
])
|
||||
|
||||
<select
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-select-input',
|
||||
'fi-select-input-has-inline-prefix' => $inlinePrefix,
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{ $slot }}
|
||||
</select>
|
||||
@@ -1,179 +0,0 @@
|
||||
@props([
|
||||
'alpineDisabled' => null,
|
||||
'alpineValid' => null,
|
||||
'disabled' => false,
|
||||
'inlinePrefix' => false,
|
||||
'inlineSuffix' => false,
|
||||
'prefix' => null,
|
||||
'prefixActions' => [],
|
||||
'prefixIcon' => null,
|
||||
'prefixIconColor' => 'gray',
|
||||
'prefixIconAlias' => null,
|
||||
'suffix' => null,
|
||||
'suffixActions' => [],
|
||||
'suffixIcon' => null,
|
||||
'suffixIconColor' => 'gray',
|
||||
'suffixIconAlias' => null,
|
||||
'valid' => true,
|
||||
])
|
||||
|
||||
@php
|
||||
use Filament\Support\View\Components\InputComponent\WrapperComponent\IconComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
|
||||
$prefixActions = array_filter(
|
||||
$prefixActions,
|
||||
fn (\Filament\Actions\Action $prefixAction): bool => $prefixAction->isVisible(),
|
||||
);
|
||||
|
||||
$suffixActions = array_filter(
|
||||
$suffixActions,
|
||||
fn (\Filament\Actions\Action $suffixAction): bool => $suffixAction->isVisible(),
|
||||
);
|
||||
|
||||
$hasPrefix = count($prefixActions) || $prefixIcon || filled($prefix);
|
||||
$hasSuffix = count($suffixActions) || $suffixIcon || filled($suffix);
|
||||
|
||||
$hasAlpineDisabledClasses = filled($alpineDisabled);
|
||||
$hasAlpineValidClasses = filled($alpineValid);
|
||||
$hasAlpineClasses = $hasAlpineDisabledClasses || $hasAlpineValidClasses;
|
||||
|
||||
$wireTarget = $attributes->whereStartsWith(['wire:target'])->first();
|
||||
|
||||
$hasLoadingIndicator = filled($wireTarget);
|
||||
|
||||
if ($hasLoadingIndicator) {
|
||||
$loadingIndicatorTarget = html_entity_decode($wireTarget, ENT_QUOTES);
|
||||
}
|
||||
|
||||
$hasFocusInputListener = $attributes->has('x-on:focus-input.stop');
|
||||
$canClickPrefixAffix = $hasFocusInputListener && ($prefixIcon || filled($prefix));
|
||||
$canClickSuffixAffix = $hasFocusInputListener && ($suffixIcon || filled($suffix));
|
||||
@endphp
|
||||
|
||||
<div
|
||||
@if ($hasAlpineClasses)
|
||||
x-bind:class="{
|
||||
{{ $hasAlpineDisabledClasses ? "'fi-disabled': {$alpineDisabled}," : null }}
|
||||
{{ $hasAlpineValidClasses ? "'fi-invalid': ! ({$alpineValid})," : null }}
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->except(['wire:target', 'tabindex'])
|
||||
->class([
|
||||
'fi-input-wrp',
|
||||
'fi-disabled' => (! $hasAlpineClasses) && $disabled,
|
||||
'fi-invalid' => (! $hasAlpineClasses) && (! $valid),
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if ($hasPrefix || $hasLoadingIndicator)
|
||||
<div
|
||||
@if (! $hasPrefix)
|
||||
wire:loading.delay.{{ config('filament.livewire_loading_delay', 'default') }}.flex
|
||||
wire:target="{{ $loadingIndicatorTarget }}"
|
||||
wire:key="{{ \Illuminate\Support\Str::random() }}" {{-- Makes sure the loading indicator gets hidden again. --}}
|
||||
@endif
|
||||
@if ($canClickPrefixAffix)
|
||||
x-on:click="$dispatch('focus-input')"
|
||||
@endif
|
||||
@class([
|
||||
'fi-input-wrp-prefix',
|
||||
'fi-input-wrp-prefix-has-content' => $hasPrefix,
|
||||
'fi-inline' => $inlinePrefix,
|
||||
'fi-input-wrp-prefix-has-label' => filled($prefix),
|
||||
])
|
||||
>
|
||||
@if (count($prefixActions))
|
||||
<div
|
||||
@class(['fi-input-wrp-actions'])
|
||||
@if ($canClickPrefixAffix) x-on:click.stop @endif
|
||||
>
|
||||
@foreach ($prefixActions as $prefixAction)
|
||||
{{ $prefixAction }}
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($prefixIcon, $prefixIconAlias, (new \Illuminate\View\ComponentAttributeBag)
|
||||
->merge([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
], escape: false)
|
||||
->color(IconComponent::class, $prefixIconColor))
|
||||
}}
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => $hasPrefix,
|
||||
'wire:target' => $hasPrefix ? $loadingIndicatorTarget : null,
|
||||
]))->color(IconComponent::class, 'gray'))
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if (filled($prefix))
|
||||
<span class="fi-input-wrp-label">
|
||||
{{ $prefix }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div
|
||||
@if ($hasLoadingIndicator && (! $hasPrefix))
|
||||
@if ($inlinePrefix)
|
||||
wire:loading.delay.{{ config('filament.livewire_loading_delay', 'default') }}.class.remove="ps-3"
|
||||
@endif
|
||||
|
||||
wire:target="{{ $loadingIndicatorTarget }}"
|
||||
@endif
|
||||
@class([
|
||||
'fi-input-wrp-content-ctn',
|
||||
'fi-input-wrp-content-ctn-ps' => $hasLoadingIndicator && (! $hasPrefix) && $inlinePrefix,
|
||||
])
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
@if ($hasSuffix)
|
||||
<div
|
||||
@if ($canClickSuffixAffix)
|
||||
x-on:click="$dispatch('focus-input')"
|
||||
@endif
|
||||
@class([
|
||||
'fi-input-wrp-suffix',
|
||||
'fi-inline' => $inlineSuffix,
|
||||
'fi-input-wrp-suffix-has-label' => filled($suffix),
|
||||
])
|
||||
>
|
||||
@if (filled($suffix))
|
||||
<span class="fi-input-wrp-label">
|
||||
{{ $suffix }}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($suffixIcon, $suffixIconAlias, (new \Illuminate\View\ComponentAttributeBag)
|
||||
->merge([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
], escape: false)
|
||||
->color(IconComponent::class, $suffixIconColor))
|
||||
}}
|
||||
|
||||
@if (count($suffixActions))
|
||||
<div
|
||||
@class(['fi-input-wrp-actions'])
|
||||
@if ($canClickSuffixAffix) x-on:click.stop @endif
|
||||
>
|
||||
@foreach ($suffixActions as $suffixAction)
|
||||
{{ $suffixAction }}
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,176 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Enums\Size;
|
||||
use Filament\Support\View\Components\BadgeComponent;
|
||||
use Filament\Support\View\Components\LinkComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'badge' => null,
|
||||
'badgeColor' => 'primary',
|
||||
'badgeSize' => Size::ExtraSmall,
|
||||
'color' => 'primary',
|
||||
'disabled' => false,
|
||||
'form' => null,
|
||||
'formId' => null,
|
||||
'href' => null,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'iconPosition' => IconPosition::Before,
|
||||
'iconSize' => null,
|
||||
'keyBindings' => null,
|
||||
'labelSrOnly' => false,
|
||||
'loadingIndicator' => true,
|
||||
'size' => Size::Medium,
|
||||
'spaMode' => null,
|
||||
'tag' => 'a',
|
||||
'target' => null,
|
||||
'tooltip' => null,
|
||||
'type' => 'button',
|
||||
'weight' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
if (! $iconPosition instanceof IconPosition) {
|
||||
$iconPosition = filled($iconPosition) ? (IconPosition::tryFrom($iconPosition) ?? $iconPosition) : null;
|
||||
}
|
||||
|
||||
if (! $badgeSize instanceof Size) {
|
||||
$badgeSize = filled($badgeSize) ? (Size::tryFrom($badgeSize) ?? $badgeSize) : null;
|
||||
}
|
||||
|
||||
if (! $size instanceof Size) {
|
||||
$size = filled($size) ? (Size::tryFrom($size) ?? $size) : null;
|
||||
}
|
||||
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$iconSize ??= match ($size) {
|
||||
Size::ExtraSmall, Size::Small => IconSize::Small,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (! $weight instanceof FontWeight) {
|
||||
$weight = filled($weight) ? (FontWeight::tryFrom($weight) ?? $weight) : null;
|
||||
}
|
||||
|
||||
$wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
|
||||
|
||||
$hasLoadingIndicator = filled($wireTarget) || ($type === 'submit' && filled($form));
|
||||
|
||||
if ($hasLoadingIndicator) {
|
||||
$loadingIndicatorTarget = html_entity_decode($wireTarget ?: $form, ENT_QUOTES);
|
||||
}
|
||||
|
||||
$hasTooltip = filled($tooltip);
|
||||
@endphp
|
||||
|
||||
<{{ $tag }}
|
||||
@if (($tag === 'a') && (! ($disabled && $hasTooltip)))
|
||||
{{ \Filament\Support\generate_href_html($href, $target === '_blank', $spaMode) }}
|
||||
@endif
|
||||
@if ($keyBindings)
|
||||
x-bind:id="$id('key-bindings')"
|
||||
x-mousetrap.global.{{ collect($keyBindings)->map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.') }}="document.getElementById($el.id)?.click()"
|
||||
@endif
|
||||
@if ($hasTooltip)
|
||||
x-tooltip="{
|
||||
content: @js($tooltip),
|
||||
theme: $store.theme,
|
||||
allowHTML: @js($tooltip instanceof \Illuminate\Contracts\Support\Htmlable),
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
'aria-disabled' => $disabled ? 'true' : null,
|
||||
'disabled' => $disabled && blank($tooltip),
|
||||
'form' => $formId,
|
||||
'type' => $tag === 'button' ? $type : null,
|
||||
'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
|
||||
'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
|
||||
], escape: false)
|
||||
->when(
|
||||
$disabled && $hasTooltip,
|
||||
fn (ComponentAttributeBag $attributes) => $attributes->filter(
|
||||
fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
|
||||
),
|
||||
)
|
||||
->class([
|
||||
'fi-link',
|
||||
'fi-disabled' => $disabled,
|
||||
($size instanceof Size) ? "fi-size-{$size->value}" : (is_string($size) ? $size : ''),
|
||||
($weight instanceof FontWeight) ? "fi-font-{$weight->value}" : (is_string($weight) ? $weight : ''),
|
||||
])
|
||||
->color(LinkComponent::class, $color)
|
||||
}}
|
||||
>
|
||||
@if ($iconPosition === IconPosition::Before)
|
||||
@if ($icon)
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if (! $labelSrOnly)
|
||||
{{ $slot }}
|
||||
@endif
|
||||
|
||||
@if ($iconPosition === IconPosition::After)
|
||||
@if ($icon)
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, (new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
|
||||
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if ($hasLoadingIndicator)
|
||||
{{
|
||||
\Filament\Support\generate_loading_indicator_html((new \Illuminate\View\ComponentAttributeBag([
|
||||
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
|
||||
'wire:target' => $loadingIndicatorTarget,
|
||||
])), size: $iconSize)
|
||||
}}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if (filled($badge))
|
||||
<div class="fi-link-badge-ctn">
|
||||
@if ($badge instanceof \Illuminate\View\ComponentSlot)
|
||||
{{ $badge }}
|
||||
@else
|
||||
<span
|
||||
{{
|
||||
(new ComponentAttributeBag)->color(BadgeComponent::class, $badgeColor)->class([
|
||||
'fi-badge',
|
||||
($badgeSize instanceof Size) ? "fi-size-{$badgeSize->value}" : (is_string($badgeSize) ? $badgeSize : ''),
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{ $badge }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</{{ $tag }}>
|
||||
@trim
|
||||
@@ -1 +0,0 @@
|
||||
{{ \Filament\Support\generate_loading_indicator_html($attributes) }}
|
||||
@@ -1,14 +0,0 @@
|
||||
@props([
|
||||
'columnSpan' => [],
|
||||
'columnStart' => [],
|
||||
'height' => null,
|
||||
])
|
||||
|
||||
<div
|
||||
{{
|
||||
($attributes ?? new \Illuminate\View\ComponentAttributeBag)
|
||||
->gridColumn($columnSpan, $columnStart)
|
||||
->class(['fi-section fi-loading-section'])
|
||||
->style(['height: ' . e($height ?? '8rem')])
|
||||
}}
|
||||
></div>
|
||||
@@ -1,5 +0,0 @@
|
||||
<p
|
||||
{{ $attributes->class(['fi-modal-description']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</p>
|
||||
@@ -1,5 +0,0 @@
|
||||
<h2
|
||||
{{ $attributes->class(['fi-modal-heading']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</h2>
|
||||
@@ -1,281 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Filament\Support\Enums\SlideOverPosition;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Filament\Support\View\Components\ModalComponent\IconComponent;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'alignment' => Alignment::Start,
|
||||
'ariaLabelledby' => null,
|
||||
'autofocus' => \Filament\Support\View\Components\ModalComponent::$isAutofocused,
|
||||
'closeButton' => \Filament\Support\View\Components\ModalComponent::$hasCloseButton,
|
||||
'closeByClickingAway' => \Filament\Support\View\Components\ModalComponent::$isClosedByClickingAway,
|
||||
'closeByEscaping' => \Filament\Support\View\Components\ModalComponent::$isClosedByEscaping,
|
||||
'closeEventName' => 'close-modal',
|
||||
'closeQuietlyEventName' => 'close-modal-quietly',
|
||||
'description' => null,
|
||||
'extraModalWindowAttributeBag' => null,
|
||||
'extraModalOverlayAttributeBag' => null,
|
||||
'footer' => null,
|
||||
'footerActions' => [],
|
||||
'footerActionsAlignment' => Alignment::Start,
|
||||
'header' => null,
|
||||
'heading' => null,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'iconColor' => 'primary',
|
||||
'id' => null,
|
||||
'openEventName' => 'open-modal',
|
||||
'slideOver' => false,
|
||||
'slideOverPosition' => SlideOverPosition::End,
|
||||
'stickyFooter' => false,
|
||||
'stickyHeader' => false,
|
||||
'teleport' => null,
|
||||
'trigger' => null,
|
||||
'visible' => true,
|
||||
'width' => 'sm',
|
||||
])
|
||||
|
||||
@php
|
||||
$hasContent = ! \Filament\Support\is_slot_empty($slot);
|
||||
$hasDescription = filled($description);
|
||||
$hasFooter = (! \Filament\Support\is_slot_empty($footer)) || (is_array($footerActions) && count($footerActions)) || (! is_array($footerActions) && (! \Filament\Support\is_slot_empty($footerActions)));
|
||||
$hasHeading = filled($heading);
|
||||
$hasIcon = filled($icon);
|
||||
|
||||
if (! $alignment instanceof Alignment) {
|
||||
$alignment = filled($alignment) ? (Alignment::tryFrom($alignment) ?? $alignment) : null;
|
||||
}
|
||||
|
||||
if (! $footerActionsAlignment instanceof Alignment) {
|
||||
$footerActionsAlignment = filled($footerActionsAlignment) ? (Alignment::tryFrom($footerActionsAlignment) ?? $footerActionsAlignment) : null;
|
||||
}
|
||||
|
||||
if (is_string($width)) {
|
||||
$width = Width::tryFrom($width) ?? $width;
|
||||
}
|
||||
|
||||
$closeEventHandler = filled($id) ? '$dispatch(' . \Illuminate\Support\Js::from($closeEventName) . ', { id: ' . \Illuminate\Support\Js::from($id) . ' })' : 'close()';
|
||||
|
||||
$wireSubmitHandler = $attributes->get('wire:submit.prevent');
|
||||
$attributes = $attributes->except(['wire:submit.prevent']);
|
||||
@endphp
|
||||
|
||||
@if ($trigger)
|
||||
{!! '<div>' !!}
|
||||
{{-- Avoid formatting issues with unclosed elements --}}
|
||||
|
||||
<div
|
||||
@if (! $trigger->attributes->get('disabled'))
|
||||
@if ($id)
|
||||
x-on:click="$dispatch(@js($openEventName), { id: @js($id) })"
|
||||
@else
|
||||
x-on:click="$el.nextElementSibling.dispatchEvent(new CustomEvent(@js($openEventName)))"
|
||||
@endif
|
||||
@endif
|
||||
{{ $trigger->attributes->except(['disabled'])->class(['fi-modal-trigger']) }}
|
||||
>
|
||||
{{ $trigger }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (filled($teleport))
|
||||
{!! "<template x-teleport=\"{$teleport}\">" !!}
|
||||
{{-- Avoid formatting issues with unclosed elements --}}
|
||||
@endif
|
||||
|
||||
<div
|
||||
@if ($ariaLabelledby)
|
||||
aria-labelledby="{{ $ariaLabelledby }}"
|
||||
@elseif ($heading)
|
||||
aria-labelledby="{{ "{$id}.heading" }}"
|
||||
@endif
|
||||
aria-modal="true"
|
||||
id="{{ $id }}"
|
||||
role="dialog"
|
||||
x-data="filamentModal({
|
||||
id: @js($id),
|
||||
})"
|
||||
@if ($id)
|
||||
data-fi-modal-id="{{ $id }}"
|
||||
x-on:{{ $closeEventName }}.window="if (($event.detail.id === @js($id)) && isOpen) close()"
|
||||
x-on:{{ $closeQuietlyEventName }}.window="if (($event.detail.id === @js($id)) && isOpen) closeQuietly()"
|
||||
x-on:{{ $openEventName }}.window="if (($event.detail.id === @js($id)) && (! isOpen)) open()"
|
||||
@else
|
||||
x-on:{{ $closeEventName }}.stop="if (isOpen) close()"
|
||||
x-on:{{ $closeQuietlyEventName }}.stop="if (isOpen) closeQuietly()"
|
||||
x-on:{{ $openEventName }}.stop="if (! isOpen) open()"
|
||||
@endif
|
||||
x-bind:class="{
|
||||
'fi-modal-open': isOpen,
|
||||
}"
|
||||
x-cloak
|
||||
x-show="isOpen"
|
||||
x-trap.noscroll{{ $autofocus ? '' : '.noautofocus' }}="isOpen"
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-modal',
|
||||
'fi-absolute-positioning-context',
|
||||
'fi-modal-slide-over' => $slideOver,
|
||||
'fi-modal-slide-over-from-start' => $slideOver && $slideOverPosition === SlideOverPosition::Start,
|
||||
'fi-modal-slide-over-from-end' => $slideOver && $slideOverPosition === SlideOverPosition::End,
|
||||
'fi-modal-has-sticky-header' => $stickyHeader,
|
||||
'fi-modal-has-sticky-footer' => $stickyFooter,
|
||||
'fi-width-screen' => $width === Width::Screen,
|
||||
])
|
||||
}}
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
x-show="isOpen"
|
||||
x-transition.duration.300ms.opacity
|
||||
{{
|
||||
($extraModalOverlayAttributeBag ?? new \Illuminate\View\ComponentAttributeBag)->class([
|
||||
'fi-modal-close-overlay',
|
||||
])
|
||||
}}
|
||||
></div>
|
||||
|
||||
<div
|
||||
@if ($closeByClickingAway)
|
||||
x-on:click.self="{{ $closeEventHandler }}"
|
||||
@endif
|
||||
@class([
|
||||
'fi-modal-window-ctn',
|
||||
'fi-clickable' => $closeByClickingAway,
|
||||
])
|
||||
>
|
||||
<{{ filled($wireSubmitHandler) ? 'form' : 'div' }}
|
||||
@if ($closeByEscaping)
|
||||
x-on:keydown.window.escape="if (isTopmost()) {{ $closeEventHandler }}"
|
||||
@endif
|
||||
x-show="isWindowVisible"
|
||||
x-transition:enter="fi-transition-enter"
|
||||
x-transition:leave="fi-transition-leave"
|
||||
@if ($width !== Width::Screen)
|
||||
x-transition:enter-start="fi-transition-enter-start"
|
||||
x-transition:enter-end="fi-transition-enter-end"
|
||||
x-transition:leave-start="fi-transition-leave-start"
|
||||
x-transition:leave-end="fi-transition-leave-end"
|
||||
@endif
|
||||
@if (filled($wireSubmitHandler))
|
||||
wire:submit.prevent="{!! $wireSubmitHandler !!}"
|
||||
@endif
|
||||
@if (filled($id))
|
||||
wire:key="{{ isset($this) ? "{$this->getId()}." : '' }}modal.{{ $id }}.window"
|
||||
@endif
|
||||
{{
|
||||
($extraModalWindowAttributeBag ?? new \Illuminate\View\ComponentAttributeBag)->class([
|
||||
'fi-modal-window',
|
||||
'fi-modal-window-has-close-btn' => $closeButton,
|
||||
'fi-modal-window-has-content' => $hasContent,
|
||||
'fi-modal-window-has-footer' => $hasFooter,
|
||||
'fi-modal-window-has-icon' => $hasIcon,
|
||||
'fi-hidden' => ! $visible,
|
||||
($alignment instanceof Alignment) ? "fi-align-{$alignment->value}" : null,
|
||||
($width instanceof Width) ? "fi-width-{$width->value}" : (is_string($width) ? $width : null),
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if ($heading || $header)
|
||||
<div
|
||||
@if (filled($id))
|
||||
wire:key="{{ isset($this) ? "{$this->getId()}." : '' }}modal.{{ $id }}.header"
|
||||
@endif
|
||||
@class([
|
||||
'fi-modal-header',
|
||||
'fi-vertical-align-center' => $hasIcon && $hasHeading && (! $hasDescription) && in_array($alignment, [Alignment::Start, Alignment::Left]),
|
||||
])
|
||||
>
|
||||
@if ($closeButton)
|
||||
<x-filament::icon-button
|
||||
color="gray"
|
||||
:icon="\Filament\Support\Icons\Heroicon::OutlinedXMark"
|
||||
:icon-alias="\Filament\Support\View\SupportIconAlias::MODAL_CLOSE_BUTTON"
|
||||
icon-size="lg"
|
||||
:label="__('filament::components/modal.actions.close.label')"
|
||||
tabindex="-1"
|
||||
:x-on:click="$closeEventHandler"
|
||||
class="fi-modal-close-btn"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@if ($header)
|
||||
{{ $header }}
|
||||
@else
|
||||
@if ($hasIcon)
|
||||
<div class="fi-modal-icon-ctn">
|
||||
<div
|
||||
{{ (new ComponentAttributeBag)->color(IconComponent::class, $iconColor)->class(['fi-modal-icon-bg']) }}
|
||||
>
|
||||
{{ \Filament\Support\generate_icon_html($icon, $iconAlias, size: \Filament\Support\Enums\IconSize::Large) }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<h2 class="fi-modal-heading">
|
||||
{{ $heading }}
|
||||
</h2>
|
||||
|
||||
@if ($hasDescription)
|
||||
<p class="fi-modal-description">
|
||||
{{ $description }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($hasContent)
|
||||
<div
|
||||
@if (filled($id))
|
||||
wire:key="{{ isset($this) ? "{$this->getId()}." : '' }}modal.{{ $id }}.content"
|
||||
@endif
|
||||
class="fi-modal-content"
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($hasFooter)
|
||||
<div
|
||||
@if (filled($id))
|
||||
wire:key="{{ isset($this) ? "{$this->getId()}." : '' }}modal.{{ $id }}.footer"
|
||||
@endif
|
||||
@class([
|
||||
'fi-modal-footer',
|
||||
($footerActionsAlignment instanceof Alignment) ? "fi-align-{$footerActionsAlignment->value}" : null,
|
||||
])
|
||||
>
|
||||
@if (! \Filament\Support\is_slot_empty($footer))
|
||||
{{ $footer }}
|
||||
@else
|
||||
<div class="fi-modal-footer-actions">
|
||||
@if (is_array($footerActions))
|
||||
@foreach ($footerActions as $action)
|
||||
{{ $action }}
|
||||
@endforeach
|
||||
@else
|
||||
{{ $footerActions }}
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</{{ filled($wireSubmitHandler) ? 'form' : 'div' }}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (filled($teleport))
|
||||
{!! '</template>' !!}
|
||||
{{-- Avoid formatting issues with unclosed elements --}}
|
||||
@endif
|
||||
|
||||
@if ($trigger)
|
||||
{!! '</div>' !!}
|
||||
{{-- Avoid formatting issues with unclosed elements --}}
|
||||
@endif
|
||||
@@ -1,208 +0,0 @@
|
||||
@props([
|
||||
'currentPageOptionProperty' => 'tableRecordsPerPage',
|
||||
'extremeLinks' => false,
|
||||
'paginator',
|
||||
'pageOptions' => [],
|
||||
])
|
||||
|
||||
@php
|
||||
use Illuminate\Contracts\Pagination\CursorPaginator;
|
||||
|
||||
$isRtl = __('filament-panels::layout.direction') === 'rtl';
|
||||
$isSimple = ! $paginator instanceof \Illuminate\Pagination\LengthAwarePaginator;
|
||||
@endphp
|
||||
|
||||
<nav
|
||||
aria-label="{{ __('filament::components/pagination.label') }}"
|
||||
role="navigation"
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-pagination',
|
||||
'fi-simple' => $isSimple,
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if (! $paginator->onFirstPage())
|
||||
@php
|
||||
if ($paginator instanceof CursorPaginator) {
|
||||
$wireClickAction = "setPage('{$paginator->previousCursor()->encode()}', '{$paginator->getCursorName()}')";
|
||||
} else {
|
||||
$wireClickAction = "previousPage('{$paginator->getPageName()}')";
|
||||
}
|
||||
@endphp
|
||||
|
||||
<x-filament::button
|
||||
color="gray"
|
||||
rel="prev"
|
||||
:wire:click="$wireClickAction"
|
||||
:wire:key="$this->getId() . '.pagination.previous'"
|
||||
class="fi-pagination-previous-btn"
|
||||
>
|
||||
{{ __('filament::components/pagination.actions.previous.label') }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if (! $isSimple)
|
||||
<span class="fi-pagination-overview">
|
||||
{{
|
||||
trans_choice(
|
||||
'filament::components/pagination.overview',
|
||||
$paginator->total(),
|
||||
[
|
||||
'first' => \Illuminate\Support\Number::format($paginator->firstItem() ?? 0),
|
||||
'last' => \Illuminate\Support\Number::format($paginator->lastItem() ?? 0),
|
||||
'total' => \Illuminate\Support\Number::format($paginator->total()),
|
||||
],
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@if (count($pageOptions) > 1)
|
||||
<div class="fi-pagination-records-per-page-select-ctn">
|
||||
<label class="fi-pagination-records-per-page-select fi-compact">
|
||||
<x-filament::input.wrapper>
|
||||
<x-filament::input.select
|
||||
:wire:model.live="$currentPageOptionProperty"
|
||||
>
|
||||
@foreach ($pageOptions as $option)
|
||||
<option value="{{ $option }}">
|
||||
{{ $option === 'all' ? __('filament::components/pagination.fields.records_per_page.options.all') : $option }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-filament::input.select>
|
||||
</x-filament::input.wrapper>
|
||||
|
||||
<span class="fi-sr-only">
|
||||
{{ __('filament::components/pagination.fields.records_per_page.label') }}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="fi-pagination-records-per-page-select">
|
||||
<x-filament::input.wrapper
|
||||
:prefix="__('filament::components/pagination.fields.records_per_page.label')"
|
||||
>
|
||||
<x-filament::input.select
|
||||
:wire:model.live="$currentPageOptionProperty"
|
||||
>
|
||||
@foreach ($pageOptions as $option)
|
||||
<option value="{{ $option }}">
|
||||
{{ $option === 'all' ? __('filament::components/pagination.fields.records_per_page.options.all') : $option }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-filament::input.select>
|
||||
</x-filament::input.wrapper>
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($paginator->hasMorePages())
|
||||
@php
|
||||
if ($paginator instanceof CursorPaginator) {
|
||||
$wireClickAction = "setPage('{$paginator->nextCursor()->encode()}', '{$paginator->getCursorName()}')";
|
||||
} else {
|
||||
$wireClickAction = "nextPage('{$paginator->getPageName()}')";
|
||||
}
|
||||
@endphp
|
||||
|
||||
<x-filament::button
|
||||
color="gray"
|
||||
rel="next"
|
||||
:wire:click="$wireClickAction"
|
||||
:wire:key="$this->getId() . '.pagination.next'"
|
||||
class="fi-pagination-next-btn"
|
||||
>
|
||||
{{ __('filament::components/pagination.actions.next.label') }}
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ((! $isSimple) && $paginator->hasPages())
|
||||
<ol class="fi-pagination-items">
|
||||
@if (! $paginator->onFirstPage())
|
||||
@if ($extremeLinks)
|
||||
<x-filament::pagination.item
|
||||
:aria-label="__('filament::components/pagination.actions.first.label')"
|
||||
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::ChevronDoubleRight : \Filament\Support\Icons\Heroicon::ChevronDoubleLeft"
|
||||
:icon-alias="
|
||||
$isRtl
|
||||
? \Filament\Support\View\SupportIconAlias::PAGINATION_FIRST_BUTTON_RTL
|
||||
: \Filament\Support\View\SupportIconAlias::PAGINATION_FIRST_BUTTON
|
||||
"
|
||||
rel="first"
|
||||
:wire:click="'gotoPage(1, \'' . $paginator->getPageName() . '\')'"
|
||||
:wire:key="$this->getId() . '.pagination.first'"
|
||||
/>
|
||||
@endif
|
||||
|
||||
<x-filament::pagination.item
|
||||
:aria-label="__('filament::components/pagination.actions.previous.label')"
|
||||
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::ChevronRight : \Filament\Support\Icons\Heroicon::ChevronLeft"
|
||||
{{-- @deprecated Use `SupportIconAlias::PAGINATION_PREVIOUS_BUTTON_RTL` instead of `SupportIconAlias::PAGINATION_PREVIOUS_BUTTON` for RTL. --}}
|
||||
:icon-alias="
|
||||
$isRtl
|
||||
? [
|
||||
\Filament\Support\View\SupportIconAlias::PAGINATION_PREVIOUS_BUTTON_RTL,
|
||||
\Filament\Support\View\SupportIconAlias::PAGINATION_PREVIOUS_BUTTON,
|
||||
]
|
||||
: \Filament\Support\View\SupportIconAlias::PAGINATION_PREVIOUS_BUTTON
|
||||
"
|
||||
rel="prev"
|
||||
:wire:click="'previousPage(\'' . $paginator->getPageName() . '\')'"
|
||||
:wire:key="$this->getId() . '.pagination.previous'"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@foreach ($paginator->render()->offsetGet('elements') as $element)
|
||||
@if (is_string($element))
|
||||
<x-filament::pagination.item disabled :label="$element" />
|
||||
@endif
|
||||
|
||||
@if (is_array($element))
|
||||
@foreach ($element as $page => $url)
|
||||
<x-filament::pagination.item
|
||||
:active="$page === $paginator->currentPage()"
|
||||
:aria-label="trans_choice('filament::components/pagination.actions.go_to_page.label', $page, ['page' => \Illuminate\Support\Number::format($page)])"
|
||||
:label="\Illuminate\Support\Number::format($page)"
|
||||
:wire:click="'gotoPage(' . $page . ', \'' . $paginator->getPageName() . '\')'"
|
||||
:wire:key="$this->getId() . '.pagination.' . $paginator->getPageName() . '.' . $page"
|
||||
/>
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@if ($paginator->hasMorePages())
|
||||
<x-filament::pagination.item
|
||||
:aria-label="__('filament::components/pagination.actions.next.label')"
|
||||
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::ChevronLeft : \Filament\Support\Icons\Heroicon::ChevronRight"
|
||||
{{-- @deprecated Use `SupportIconAlias::PAGINATION_NEXT_BUTTON_RTL` instead of `SupportIconAlias::PAGINATION_NEXT_BUTTON` for RTL. --}}
|
||||
:icon-alias="
|
||||
$isRtl
|
||||
? [
|
||||
\Filament\Support\View\SupportIconAlias::PAGINATION_NEXT_BUTTON_RTL,
|
||||
\Filament\Support\View\SupportIconAlias::PAGINATION_NEXT_BUTTON,
|
||||
]
|
||||
: \Filament\Support\View\SupportIconAlias::PAGINATION_NEXT_BUTTON
|
||||
"
|
||||
rel="next"
|
||||
:wire:click="'nextPage(\'' . $paginator->getPageName() . '\')'"
|
||||
:wire:key="$this->getId() . '.pagination.next'"
|
||||
/>
|
||||
|
||||
@if ($extremeLinks)
|
||||
<x-filament::pagination.item
|
||||
:aria-label="__('filament::components/pagination.actions.last.label')"
|
||||
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::ChevronDoubleLeft : \Filament\Support\Icons\Heroicon::ChevronDoubleRight"
|
||||
:icon-alias="
|
||||
$isRtl
|
||||
? \Filament\Support\View\SupportIconAlias::PAGINATION_LAST_BUTTON_RTL
|
||||
: \Filament\Support\View\SupportIconAlias::PAGINATION_LAST_BUTTON
|
||||
"
|
||||
rel="last"
|
||||
:wire:click="'gotoPage(' . $paginator->lastPage() . ', \'' . $paginator->getPageName() . '\')'"
|
||||
:wire:key="$this->getId() . '.pagination.last'"
|
||||
/>
|
||||
@endif
|
||||
@endif
|
||||
</ol>
|
||||
@endif
|
||||
</nav>
|
||||
@@ -1,39 +0,0 @@
|
||||
@props([
|
||||
'active' => false,
|
||||
'ariaLabel' => null,
|
||||
'disabled' => false,
|
||||
'icon' => null,
|
||||
'iconAlias' => null,
|
||||
'label' => null,
|
||||
])
|
||||
|
||||
<li
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-pagination-item',
|
||||
'fi-disabled' => $disabled,
|
||||
'fi-active' => $active,
|
||||
])
|
||||
}}
|
||||
>
|
||||
<button
|
||||
aria-label="{{ $ariaLabel }}"
|
||||
@disabled($disabled)
|
||||
type="button"
|
||||
class="fi-pagination-item-btn"
|
||||
>
|
||||
@if (filled($icon))
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, $iconAlias, attributes: (new \Illuminate\View\ComponentAttributeBag)->class([
|
||||
'fi-pagination-item-icon',
|
||||
]))
|
||||
}}
|
||||
@endif
|
||||
|
||||
@if (filled($label))
|
||||
<span class="fi-pagination-item-label">
|
||||
{{ is_numeric($label) ? \Illuminate\Support\Number::format($label) : ($label ?? '...') }}
|
||||
</span>
|
||||
@endif
|
||||
</button>
|
||||
</li>
|
||||
@@ -1,5 +0,0 @@
|
||||
<p
|
||||
{{ $attributes->class(['fi-section-header-description']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</p>
|
||||
@@ -1,5 +0,0 @@
|
||||
<h2
|
||||
{{ $attributes->class(['fi-section-header-heading']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</h2>
|
||||
@@ -1,140 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\View\Components\SectionComponent\IconComponent;
|
||||
|
||||
use function Filament\Support\is_slot_empty;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'afterHeader' => null,
|
||||
'aside' => false,
|
||||
'collapsed' => false,
|
||||
'collapseId' => null,
|
||||
'collapsible' => false,
|
||||
'compact' => false,
|
||||
'contained' => true,
|
||||
'contentBefore' => false,
|
||||
'description' => null,
|
||||
'divided' => false,
|
||||
'footer' => null,
|
||||
'hasContentEl' => true,
|
||||
'heading' => null,
|
||||
'headingTag' => 'h2',
|
||||
'icon' => null,
|
||||
'iconColor' => 'gray',
|
||||
'iconSize' => null,
|
||||
'persistCollapsed' => false,
|
||||
'secondary' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
if (filled($iconSize) && (! $iconSize instanceof IconSize)) {
|
||||
$iconSize = IconSize::tryFrom($iconSize) ?? $iconSize;
|
||||
}
|
||||
|
||||
$hasDescription = filled((string) $description);
|
||||
$hasHeading = filled($heading);
|
||||
$hasIcon = filled($icon);
|
||||
$hasHeader = $hasIcon || $hasHeading || $hasDescription || $collapsible || (! is_slot_empty($afterHeader));
|
||||
@endphp
|
||||
|
||||
<section
|
||||
{{-- TODO: Investigate Livewire bug - https://github.com/filamentphp/filament/pull/8511 --}}
|
||||
x-data="{
|
||||
isCollapsed: @if ($persistCollapsed) $persist(@js($collapsed)).as(`section-${@js($collapseId) ?? $el.id}-isCollapsed`) @else @js($collapsed) @endif,
|
||||
}"
|
||||
@if ($collapsible)
|
||||
x-on:collapse-section.window="if ($event.detail.id == @js($collapseId) ?? $el.id) isCollapsed = true"
|
||||
x-on:expand="isCollapsed = false"
|
||||
x-on:expand-section.window="if ($event.detail.id == @js($collapseId) ?? $el.id) isCollapsed = false"
|
||||
x-on:open-section.window="if ($event.detail.id == @js($collapseId) ?? $el.id) isCollapsed = false"
|
||||
x-on:toggle-section.window="if ($event.detail.id == @js($collapseId) ?? $el.id) isCollapsed = ! isCollapsed"
|
||||
x-bind:class="isCollapsed && 'fi-collapsed'"
|
||||
@endif
|
||||
{{
|
||||
$attributes->class([
|
||||
'fi-section',
|
||||
'fi-section-not-contained' => ! $contained,
|
||||
'fi-section-has-content-before' => $contentBefore,
|
||||
'fi-section-has-header' => $hasHeader,
|
||||
'fi-aside' => $aside,
|
||||
'fi-compact' => $compact,
|
||||
'fi-collapsible' => $collapsible,
|
||||
'fi-divided' => $divided,
|
||||
'fi-secondary' => $secondary,
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if ($hasHeader)
|
||||
<header
|
||||
@if ($collapsible)
|
||||
x-on:click="if (! $event.target.closest('.fi-section-header-after-ctn')) isCollapsed = ! isCollapsed"
|
||||
@endif
|
||||
class="fi-section-header"
|
||||
>
|
||||
{{
|
||||
\Filament\Support\generate_icon_html($icon, attributes: (new \Illuminate\View\ComponentAttributeBag)
|
||||
->color(IconComponent::class, $iconColor), size: $iconSize ?? IconSize::Large)
|
||||
}}
|
||||
|
||||
@if ($hasHeading || $hasDescription)
|
||||
<div class="fi-section-header-text-ctn">
|
||||
@if ($hasHeading)
|
||||
<{{ $headingTag }} class="fi-section-header-heading">
|
||||
{{ $heading }}
|
||||
</{{ $headingTag }}>
|
||||
@endif
|
||||
|
||||
@if ($hasDescription)
|
||||
<p class="fi-section-header-description">
|
||||
{{ $description }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (! is_slot_empty($afterHeader))
|
||||
<div class="fi-section-header-after-ctn">
|
||||
{{ $afterHeader }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($collapsible)
|
||||
<x-filament::icon-button
|
||||
color="gray"
|
||||
:icon="\Filament\Support\Icons\Heroicon::ChevronUp"
|
||||
:icon-alias="\Filament\Support\View\SupportIconAlias::SECTION_COLLAPSE_BUTTON"
|
||||
x-on:click.stop="isCollapsed = ! isCollapsed"
|
||||
class="fi-section-collapse-btn"
|
||||
/>
|
||||
@endif
|
||||
</header>
|
||||
@endif
|
||||
|
||||
@if ((! is_slot_empty($slot)) || (! is_slot_empty($footer)))
|
||||
<div
|
||||
@if ($collapsible)
|
||||
x-bind:aria-expanded="(! isCollapsed).toString()"
|
||||
@if ($collapsed || $persistCollapsed)
|
||||
x-cloak
|
||||
@endif
|
||||
@endif
|
||||
class="fi-section-content-ctn"
|
||||
>
|
||||
@if ($hasContentEl)
|
||||
<div class="fi-section-content">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@else
|
||||
{{ $slot }}
|
||||
@endif
|
||||
|
||||
@if (! is_slot_empty($footer))
|
||||
<footer class="fi-section-footer">
|
||||
{{ $footer }}
|
||||
</footer>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
@@ -1,22 +0,0 @@
|
||||
@props([
|
||||
'contained' => false,
|
||||
'label' => null,
|
||||
'vertical' => false,
|
||||
])
|
||||
|
||||
<nav
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
'aria-label' => $label,
|
||||
'role' => 'tablist',
|
||||
])
|
||||
->class([
|
||||
'fi-tabs',
|
||||
'fi-contained' => $contained,
|
||||
'fi-vertical' => $vertical,
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{ $slot }}
|
||||
</nav>
|
||||
@@ -1,145 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'active' => false,
|
||||
'alpineActive' => null,
|
||||
'alpineDeferredBadgeData' => null,
|
||||
'alpineDeferredBadgeLoading' => null,
|
||||
'badge' => null,
|
||||
'badgeColor' => null,
|
||||
'badgeTooltip' => null,
|
||||
'badgeIcon' => null,
|
||||
'badgeIconPosition' => IconPosition::Before,
|
||||
'href' => null,
|
||||
'icon' => null,
|
||||
'iconColor' => 'gray',
|
||||
'iconPosition' => IconPosition::Before,
|
||||
'spaMode' => null,
|
||||
'tag' => 'button',
|
||||
'target' => null,
|
||||
'type' => 'button',
|
||||
])
|
||||
|
||||
@php
|
||||
if (! $iconPosition instanceof IconPosition) {
|
||||
$iconPosition = filled($iconPosition) ? (IconPosition::tryFrom($iconPosition) ?? $iconPosition) : null;
|
||||
}
|
||||
|
||||
$hasAlpineActiveClasses = filled($alpineActive);
|
||||
$hasDeferredBadge = filled($alpineDeferredBadgeData);
|
||||
@endphp
|
||||
|
||||
<{{ $tag }}
|
||||
@if ($tag === 'button')
|
||||
type="{{ $type }}"
|
||||
@elseif ($tag === 'a')
|
||||
{{ \Filament\Support\generate_href_html($href, $target === '_blank', $spaMode) }}
|
||||
@endif
|
||||
@if ($hasAlpineActiveClasses)
|
||||
x-bind:class="{
|
||||
'fi-active': {{ $alpineActive }},
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
'aria-selected' => $active,
|
||||
'role' => 'tab',
|
||||
])
|
||||
->class([
|
||||
'fi-tabs-item',
|
||||
'fi-active' => (! $hasAlpineActiveClasses) && $active,
|
||||
])
|
||||
}}
|
||||
>
|
||||
@if ($icon && $iconPosition === IconPosition::Before)
|
||||
{{ \Filament\Support\generate_icon_html($icon) }}
|
||||
@endif
|
||||
|
||||
<span class="fi-tabs-item-label">
|
||||
{{ $slot }}
|
||||
</span>
|
||||
|
||||
@if ($icon && $iconPosition === IconPosition::After)
|
||||
{{ \Filament\Support\generate_icon_html($icon) }}
|
||||
@endif
|
||||
|
||||
@if (filled($badge))
|
||||
@if ($badge instanceof \Illuminate\View\ComponentSlot)
|
||||
{{ $badge }}
|
||||
@else
|
||||
<x-filament::badge
|
||||
:color="$badgeColor"
|
||||
:icon="$badgeIcon"
|
||||
:icon-position="$badgeIconPosition"
|
||||
size="sm"
|
||||
:tooltip="$badgeTooltip"
|
||||
>
|
||||
{{ $badge }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
@elseif ($hasDeferredBadge)
|
||||
<span
|
||||
x-show="{{ $alpineDeferredBadgeLoading }}"
|
||||
x-cloak
|
||||
class="fi-tabs-item-badge-placeholder"
|
||||
>
|
||||
{{ \Filament\Support\generate_loading_indicator_html(size: \Filament\Support\Enums\IconSize::Small) }}
|
||||
</span>
|
||||
|
||||
<template
|
||||
x-if="
|
||||
! {{ $alpineDeferredBadgeLoading }} &&
|
||||
{{ $alpineDeferredBadgeData }}?.badge != null
|
||||
"
|
||||
>
|
||||
<span
|
||||
x-bind:class="
|
||||
'fi-badge fi-size-sm ' +
|
||||
({{ $alpineDeferredBadgeData }}?.badgeColorClasses ?? '')
|
||||
"
|
||||
x-bind:style="{{ $alpineDeferredBadgeData }}?.badgeColorStyles ?? ''"
|
||||
x-init="
|
||||
let tooltip = {{ $alpineDeferredBadgeData }}?.badgeTooltip
|
||||
if (tooltip) {
|
||||
window.tippy?.($el, {
|
||||
content: tooltip,
|
||||
theme: $store.theme,
|
||||
})
|
||||
}
|
||||
"
|
||||
>
|
||||
<template
|
||||
x-if="
|
||||
{{ $alpineDeferredBadgeData }}?.badgeIconHtml &&
|
||||
{{ $alpineDeferredBadgeData }}?.badgeIconPosition !== 'after'
|
||||
"
|
||||
>
|
||||
<span
|
||||
x-html="{{ $alpineDeferredBadgeData }}.badgeIconHtml"
|
||||
></span>
|
||||
</template>
|
||||
|
||||
<span class="fi-badge-label-ctn">
|
||||
<span
|
||||
class="fi-badge-label"
|
||||
x-text="{{ $alpineDeferredBadgeData }}?.badge"
|
||||
></span>
|
||||
</span>
|
||||
|
||||
<template
|
||||
x-if="
|
||||
{{ $alpineDeferredBadgeData }}?.badgeIconHtml &&
|
||||
{{ $alpineDeferredBadgeData }}?.badgeIconPosition === 'after'
|
||||
"
|
||||
>
|
||||
<span
|
||||
x-html="{{ $alpineDeferredBadgeData }}.badgeIconHtml"
|
||||
></span>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
@endif
|
||||
</{{ $tag }}>
|
||||
@@ -1,67 +0,0 @@
|
||||
@php
|
||||
use Filament\Support\View\Components\ToggleComponent;
|
||||
use Illuminate\Support\Arr;
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'state',
|
||||
'offColor' => 'gray',
|
||||
'offIcon' => null,
|
||||
'onColor' => 'primary',
|
||||
'onIcon' => null,
|
||||
])
|
||||
|
||||
<button
|
||||
x-data="{ state: {{ $state }} }"
|
||||
x-bind:aria-checked="state?.toString()"
|
||||
x-on:click="state = ! state"
|
||||
x-bind:class="
|
||||
state ? @js(Arr::toCssClasses([
|
||||
'fi-toggle-on',
|
||||
...\Filament\Support\get_component_color_classes(ToggleComponent::class, $onColor),
|
||||
])) : @js(Arr::toCssClasses([
|
||||
'fi-toggle-off',
|
||||
...\Filament\Support\get_component_color_classes(ToggleComponent::class, $offColor),
|
||||
]))
|
||||
"
|
||||
@if ($state)
|
||||
x-cloak
|
||||
@endif
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
'role' => 'switch',
|
||||
'type' => 'button',
|
||||
], escape: false)
|
||||
->class(['fi-toggle'])
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div aria-hidden="true">
|
||||
{{ \Filament\Support\generate_icon_html($offIcon, size: \Filament\Support\Enums\IconSize::ExtraSmall) }}
|
||||
</div>
|
||||
|
||||
<div aria-hidden="true">
|
||||
{{ \Filament\Support\generate_icon_html($onIcon, size: \Filament\Support\Enums\IconSize::ExtraSmall) }}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@if ($state)
|
||||
<div
|
||||
x-cloak="inline-flex"
|
||||
wire:ignore
|
||||
@class([
|
||||
'fi-toggle fi-toggle-on fi-hidden',
|
||||
...\Filament\Support\get_component_color_classes(ToggleComponent::class, $onColor),
|
||||
])
|
||||
>
|
||||
<div>
|
||||
<div aria-hidden="true"></div>
|
||||
|
||||
<div aria-hidden="true">
|
||||
{{ \Filament\Support\generate_icon_html($onIcon, size: \Filament\Support\Enums\IconSize::ExtraSmall) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@@ -1,3 +0,0 @@
|
||||
<div {{ $getExtraAttributeBag() }}>
|
||||
{{ $getState() }}
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{-- Use $getChildSchema() to render the child schema() of this component. --}}
|
||||
</div>
|
||||
@@ -1,8 +0,0 @@
|
||||
<x-dynamic-component
|
||||
:component="$getEntryWrapperView()"
|
||||
:entry="$entry"
|
||||
>
|
||||
<div {{ $getExtraAttributeBag() }}>
|
||||
{{ $getState() }}
|
||||
</div>
|
||||
</x-dynamic-component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<x-dynamic-component
|
||||
:component="$getFieldWrapperView()"
|
||||
:field="$field"
|
||||
>
|
||||
<div
|
||||
x-data="{ state: $wire.$entangle(@js($getStatePath())) }"
|
||||
{{ $getExtraAttributeBag() }}
|
||||
>
|
||||
{{-- Interact with the `state` property in Alpine.js --}}
|
||||
</div>
|
||||
</x-dynamic-component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div>
|
||||
<form wire:submit="{{ submitAction }}">
|
||||
{{ $this->form }}
|
||||
|
||||
<button type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<x-filament-actions::modals />
|
||||
</div>
|
||||
@@ -1,5 +0,0 @@
|
||||
<div>
|
||||
{{ $this->content }}
|
||||
|
||||
<x-filament-actions::modals />
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<x-filament-panels::page>
|
||||
{{-- Page content --}}
|
||||
</x-filament-panels::page>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{-- Custom block preview --}}
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
{{-- Custom block --}}
|
||||
</div>
|
||||
@@ -1,4 +0,0 @@
|
||||
@import '../../../../vendor/filament/filament/resources/css/theme.css';
|
||||
|
||||
@source '../../../../app/Filament/{{ classDirectory }}**/*';
|
||||
@source '../../../../resources/views/filament/{{ viewDirectory }}**/*';
|
||||
@@ -1,5 +0,0 @@
|
||||
<x-filament-widgets::widget>
|
||||
<x-filament::section>
|
||||
{{-- Widget content --}}
|
||||
</x-filament::section>
|
||||
</x-filament-widgets::widget>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\AppMeta;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\AppResource;
|
||||
|
||||
#[Description('A description of what this app resource does.')]
|
||||
#[AppMeta]
|
||||
class {{ class }} extends AppResource
|
||||
{
|
||||
/**
|
||||
* Handle the app resource request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
return Response::view('{{ view }}', [
|
||||
'title' => $this->title(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<x-mcp::app :title="$title">
|
||||
<x-slot:head>
|
||||
<script type="module">
|
||||
createMcpApp(async (app) => {
|
||||
document.getElementById('run-btn').addEventListener('click', async () => {
|
||||
const result = await app.callServerTool('tool-name', {});
|
||||
document.getElementById('output').textContent = result.content[0]?.text ?? '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</x-slot:head>
|
||||
|
||||
<div id="app">
|
||||
<button id="run-btn">Run</button>
|
||||
<p id="output"></p>
|
||||
</div>
|
||||
</x-mcp::app>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\Prompt;
|
||||
use Laravel\Mcp\Server\Prompts\Argument;
|
||||
|
||||
#[Description('A description of what this prompt does.')]
|
||||
class {{ class }} extends Prompt
|
||||
{
|
||||
/**
|
||||
* Handle the prompt request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
//
|
||||
|
||||
return Response::text('The content generated by the prompt.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prompt's arguments.
|
||||
*
|
||||
* @return array<int, Argument>
|
||||
*/
|
||||
public function arguments(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\Resource;
|
||||
|
||||
#[Description('A description of what this resource contains.')]
|
||||
class {{ class }} extends Resource
|
||||
{
|
||||
/**
|
||||
* Handle the resource request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
//
|
||||
|
||||
return Response::text('The resource content.');
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Mcp\Server;
|
||||
use Laravel\Mcp\Server\Attributes\Instructions;
|
||||
use Laravel\Mcp\Server\Attributes\Name;
|
||||
use Laravel\Mcp\Server\Attributes\Version;
|
||||
|
||||
#[Name('{{ serverDisplayName }}')]
|
||||
#[Version('0.0.1')]
|
||||
#[Instructions('Instructions describing how to use the server and its features.')]
|
||||
class {{ class }} extends Server
|
||||
{
|
||||
protected array $tools = [
|
||||
//
|
||||
];
|
||||
|
||||
protected array $resources = [
|
||||
//
|
||||
];
|
||||
|
||||
protected array $prompts = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
|
||||
#[Description('A description of what this tool does.')]
|
||||
class {{ class }} extends Tool
|
||||
{
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
//
|
||||
|
||||
return Response::text('The content generated by the tool.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool's input schema.
|
||||
*
|
||||
* @return array<string, JsonSchema>
|
||||
*/
|
||||
public function schema(JsonSchema $schema): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user