mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
Merge branch '3.x' into extract-theme
This commit is contained in:
@@ -484,7 +484,7 @@ public static function getNavigationLabel(): string
|
||||
|
||||
### Icons
|
||||
|
||||
The `$navigationIcon` property supports the name of any Blade component. By default, the [Blade Heroicons](https://github.com/blade-ui-kit/blade-heroicons) package is installed, so you may use the name of any [Heroicon](https://heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
The `$navigationIcon` property supports the name of any Blade component. By default, the [Blade Heroicons v1](https://github.com/blade-ui-kit/blade-heroicons/tree/1.3.1) package is installed, so you may use the name of any [Heroicons v1](https://v1.heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
|
||||
```php
|
||||
protected static ?string $navigationIcon = 'heroicon-o-user-group';
|
||||
|
||||
@@ -80,7 +80,7 @@ protected function getHeaderActions(): array
|
||||
}
|
||||
```
|
||||
|
||||
Buttons may also have an `icon()`, which is the name of any Blade component. By default, the [Blade Heroicons](https://github.com/blade-ui-kit/blade-heroicons) package is installed, so you may use the name of any [Heroicon](https://heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
Buttons may also have an `icon()`, which is the name of any Blade component. By default, the [Blade Heroicons v1](https://github.com/blade-ui-kit/blade-heroicons/tree/1.3.1) package is installed, so you may use the name of any [Heroicons v1](https://v1.heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
|
||||
```php
|
||||
use Filament\Actions\Action;
|
||||
|
||||
@@ -14,7 +14,7 @@ protected static ?string $navigationLabel = 'Custom Navigation Label';
|
||||
protected static ?int $navigationSort = 3;
|
||||
```
|
||||
|
||||
The `$navigationIcon` supports the name of any Blade component, and passes a set of formatting classes to it. By default, the [Blade Heroicons](https://github.com/blade-ui-kit/blade-heroicons) package is installed, so you may use the name of any [Heroicon](https://heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
The `$navigationIcon` supports the name of any Blade component, and passes a set of formatting classes to it. By default, the [Blade Heroicons v1](https://github.com/blade-ui-kit/blade-heroicons/tree/1.3.1) package is installed, so you may use the name of any [Heroicons v1](https://v1.heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
|
||||
## Navigation item badges
|
||||
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
@import '../../../forms/dist/index.css';
|
||||
@import 'theme.css';
|
||||
|
||||
@import 'tippy.js/dist/tippy.css';
|
||||
@import 'tippy.js/themes/light.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
@layer components {
|
||||
.filament-card-layout {
|
||||
@apply relative bg-no-repeat;
|
||||
background-image: radial-gradient(
|
||||
circle at top,
|
||||
theme('colors.primary.100'),
|
||||
theme('colors.white') 50%
|
||||
);
|
||||
}
|
||||
|
||||
:root {
|
||||
--sidebar-width: 20rem;
|
||||
--collapsed-sidebar-width: 5.4rem;
|
||||
--font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
|
||||
--chart-font-family: var(--font-family);
|
||||
.dark .filament-card-layout {
|
||||
background-image: radial-gradient(
|
||||
circle at top,
|
||||
theme('colors.primary.800'),
|
||||
theme('colors.gray.800'),
|
||||
theme('colors.gray.900') 100%
|
||||
);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
.filament-card-layout .filament-card-layout-card::before {
|
||||
@apply absolute inset-x-0 mx-auto h-px w-2/3 bg-gradient-to-r from-gray-200 via-primary-400 to-gray-200 dark:from-gray-700 dark:via-primary-400 dark:to-gray-700;
|
||||
content: '';
|
||||
z-index: 1;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
[dir='rtl'] select {
|
||||
background-position: left 0.5rem center !important;
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 0.75rem;
|
||||
.filament-sidebar-header::before {
|
||||
@apply pointer-events-none absolute inset-x-0 mx-auto h-px w-full bg-gradient-to-r from-black/5 via-primary-400 to-gray-200 dark:from-gray-700 dark:via-primary-400 dark:to-gray-700;
|
||||
content: '';
|
||||
z-index: 1;
|
||||
bottom: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
@import '../../../forms/dist/index.css';
|
||||
|
||||
@import 'tippy.js/dist/tippy.css';
|
||||
@import 'tippy.js/themes/light.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--sidebar-width: 20rem;
|
||||
--collapsed-sidebar-width: 5.4rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
[dir='rtl'] select {
|
||||
background-position: left 0.5rem center !important;
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@
|
||||
<link href="{{ $font['source'] }}" rel="stylesheet" />
|
||||
@endif
|
||||
|
||||
{{ filament()->getTheme()->getHtml() }}
|
||||
|
||||
@filamentStyles
|
||||
|
||||
<style>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
])
|
||||
|
||||
<div {{ $attributes->class(['filament-page']) }}>
|
||||
{{ filament()->renderHook('page.start') }}
|
||||
|
||||
<div class="space-y-6">
|
||||
@if ($header = $this->getHeader())
|
||||
{{ $header }}
|
||||
@@ -54,4 +56,6 @@
|
||||
@if (! $this instanceof \Filament\Tables\Contracts\HasTable)
|
||||
<x-filament-actions::modals />
|
||||
@endif
|
||||
|
||||
{{ filament()->renderHook('page.end') }}
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,9 @@ use Filament\Pages\Auth\Register;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Resources\RelationManagers\RelationGroup;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Assets\Theme;
|
||||
use Filament\Support\Color;
|
||||
use Filament\Support\Facades\FilamentAsset;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Auth\EloquentUserProvider;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
@@ -111,7 +113,7 @@ class Context
|
||||
|
||||
protected ?string $tenantSlugField = null;
|
||||
|
||||
protected string | Htmlable | null $theme = null;
|
||||
protected string | Htmlable | Theme | null $theme = null;
|
||||
|
||||
protected array $tenantMenuItems = [];
|
||||
|
||||
@@ -336,7 +338,7 @@ class Context
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function theme(string | Htmlable | null $theme): static
|
||||
public function theme(string | Htmlable | Theme $theme): static
|
||||
{
|
||||
$this->theme = $theme;
|
||||
|
||||
@@ -923,9 +925,28 @@ class Context
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getTheme(): string | Htmlable | null
|
||||
public function getTheme(): Theme
|
||||
{
|
||||
return $this->theme;
|
||||
if (blank($this->theme)) {
|
||||
return $this->getDefaultTheme();
|
||||
}
|
||||
|
||||
if ($this->theme instanceof Theme) {
|
||||
return $this->theme;
|
||||
}
|
||||
|
||||
if ($this->theme instanceof Htmlable) {
|
||||
return Theme::make('app')->html($this->theme);
|
||||
}
|
||||
|
||||
$theme = FilamentAsset::getTheme($this->theme);
|
||||
|
||||
return $theme ?? Theme::make('app', path: $this->theme);
|
||||
}
|
||||
|
||||
public function getDefaultTheme(): Theme
|
||||
{
|
||||
return FilamentAsset::getTheme('app');
|
||||
}
|
||||
|
||||
public function getUrl(?Model $tenant = null): ?string
|
||||
@@ -1219,21 +1240,26 @@ class Context
|
||||
$this->queueLivewireComponentForRegistration(GlobalSearch::class);
|
||||
$this->queueLivewireComponentForRegistration(Notifications::class);
|
||||
|
||||
if ($this->hasEmailVerification()) {
|
||||
$this->queueLivewireComponentForRegistration($this->getEmailVerificationPromptRouteAction());
|
||||
if ($this->hasEmailVerification() && is_subclass_of($emailVerificationRouteAction = $this->getEmailVerificationPromptRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($emailVerificationRouteAction);
|
||||
}
|
||||
|
||||
if ($this->hasLogin()) {
|
||||
$this->queueLivewireComponentForRegistration($this->getLoginRouteAction());
|
||||
if ($this->hasLogin() && is_subclass_of($loginRouteAction = $this->getLoginRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($loginRouteAction);
|
||||
}
|
||||
|
||||
if ($this->hasPasswordReset()) {
|
||||
$this->queueLivewireComponentForRegistration($this->getRequestPasswordResetRouteAction());
|
||||
$this->queueLivewireComponentForRegistration($this->getResetPasswordRouteAction());
|
||||
if (is_subclass_of($requestPasswordResetRouteAction = $this->getRequestPasswordResetRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($requestPasswordResetRouteAction);
|
||||
}
|
||||
|
||||
if (is_subclass_of($resetPasswordRouteAction = $this->getResetPasswordRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($resetPasswordRouteAction);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hasRegistration()) {
|
||||
$this->queueLivewireComponentForRegistration($this->getRegistrationRouteAction());
|
||||
if ($this->hasRegistration() && is_subclass_of($registrationRouteAction = $this->getRegistrationRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($registrationRouteAction);
|
||||
}
|
||||
|
||||
foreach ($this->getResources() as $resource) {
|
||||
|
||||
@@ -9,6 +9,7 @@ use Filament\Contracts\Plugin;
|
||||
use Filament\FilamentManager;
|
||||
use Filament\GlobalSearch\Contracts\GlobalSearchProvider;
|
||||
use Filament\Models\Contracts\HasTenants;
|
||||
use Filament\Support\Assets\Theme;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\CanResetPassword;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
@@ -61,7 +62,7 @@ use Illuminate\Support\Facades\Facade;
|
||||
* @method static string getTenantName(Model $tenant)
|
||||
* @method static string | null getTenantRegistrationPage()
|
||||
* @method static string | null getTenantRegistrationUrl()
|
||||
* @method static string | Htmlable | null getTheme()
|
||||
* @method static Theme getTheme()
|
||||
* @method static string | null getUrl(Model | null $tenant = null)
|
||||
* @method static string | null getUserAvatarUrl(Model | Authenticatable $user)
|
||||
* @method static Model | null getUserDefaultTenant(HasTenants | Model | Authenticatable $user)
|
||||
|
||||
@@ -12,6 +12,7 @@ use Filament\Models\Contracts\HasAvatar;
|
||||
use Filament\Models\Contracts\HasDefaultTenant;
|
||||
use Filament\Models\Contracts\HasName;
|
||||
use Filament\Models\Contracts\HasTenants;
|
||||
use Filament\Support\Assets\Theme;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\CanResetPassword;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
@@ -324,7 +325,7 @@ class FilamentManager
|
||||
return $this->getCurrentContext()->getModelResource($model);
|
||||
}
|
||||
|
||||
public function getTheme(): string | Htmlable | null
|
||||
public function getTheme(): Theme
|
||||
{
|
||||
return $this->getCurrentContext()->getTheme();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Filament;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Http\Middleware\MirrorConfigToSubpackages;
|
||||
@@ -17,10 +16,9 @@ use Filament\Http\Responses\Auth\LoginResponse;
|
||||
use Filament\Http\Responses\Auth\LogoutResponse;
|
||||
use Filament\Http\Responses\Auth\PasswordResetResponse;
|
||||
use Filament\Http\Responses\Auth\RegistrationResponse;
|
||||
use Filament\Support\Assets\Css;
|
||||
use Filament\Support\Assets\Js;
|
||||
use Filament\Support\Assets\Theme;
|
||||
use Filament\Support\PluginServiceProvider;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -77,9 +75,9 @@ class FilamentServiceProvider extends PluginServiceProvider
|
||||
protected function getAssets(): array
|
||||
{
|
||||
return [
|
||||
Css::make('app', __DIR__ . '/../dist/index.css')->html(fn (): string | Htmlable | null => Filament::getTheme()),
|
||||
Js::make('app', __DIR__ . '/../dist/index.js')->core(),
|
||||
Js::make('echo', __DIR__ . '/../dist/echo.js')->core(),
|
||||
Theme::make('app', __DIR__ . '/../dist/index.css'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
'min' => (! $isConcealed) ? $getMinValue() : null,
|
||||
'minlength' => (! $isConcealed) ? $getMinLength() : null,
|
||||
'placeholder' => $getPlaceholder(),
|
||||
'readonly' => $isReadOnly(),
|
||||
'required' => $isRequired() && (! $isConcealed),
|
||||
'step' => $getStep(),
|
||||
'type' => $hasMask ? 'text' : $getType(),
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
'maxlength' => (! $isConcealed) ? $getMaxLength() : null,
|
||||
'minlength' => (! $isConcealed) ? $getMinLength() : null,
|
||||
'placeholder' => $getPlaceholder(),
|
||||
'readonly' => $isReadOnly(),
|
||||
'required' => $isRequired() && (! $isConcealed),
|
||||
'rows' => $getRows(),
|
||||
$applyStateBindingModifiers('wire:model') => $statePath,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Components\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait CanBeReadOnly
|
||||
{
|
||||
protected bool | Closure $isReadOnly = false;
|
||||
|
||||
public function readOnly(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isReadOnly = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function readOnlyOn(string | array $contexts): static
|
||||
{
|
||||
$this->readOnly(static function (string $context, HasForms $livewire) use ($contexts): bool {
|
||||
foreach (Arr::wrap($contexts) as $readOnlyContext) {
|
||||
if ($readOnlyContext === $context || $livewire instanceof $readOnlyContext) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isReadOnly(): bool
|
||||
{
|
||||
return $this->evaluate($this->isReadOnly);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class TextInput extends Field implements Contracts\CanBeLengthConstrained, CanHa
|
||||
use Concerns\CanBeAutocapitalized;
|
||||
use Concerns\CanBeAutocompleted;
|
||||
use Concerns\CanBeLengthConstrained;
|
||||
use Concerns\CanBeReadOnly;
|
||||
use Concerns\HasAffixes;
|
||||
use Concerns\HasExtraInputAttributes;
|
||||
use Concerns\HasInputMode;
|
||||
|
||||
@@ -10,6 +10,7 @@ class Textarea extends Field implements Contracts\CanBeLengthConstrained
|
||||
use Concerns\CanBeAutocapitalized;
|
||||
use Concerns\CanBeAutocompleted;
|
||||
use Concerns\CanBeLengthConstrained;
|
||||
use Concerns\CanBeReadOnly;
|
||||
use Concerns\HasExtraInputAttributes;
|
||||
use Concerns\HasPlaceholder;
|
||||
use HasExtraAlpineAttributes;
|
||||
|
||||
@@ -204,6 +204,29 @@ class TestsForms
|
||||
};
|
||||
}
|
||||
|
||||
public function assertFormFieldIsReadOnly(): Closure
|
||||
{
|
||||
return function (string $fieldName, string $formName = 'form'): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertFormFieldExists($fieldName, $formName);
|
||||
|
||||
/** @var ComponentContainer $form */
|
||||
$form = $this->instance()->{$formName};
|
||||
|
||||
/** @var Field $field */
|
||||
$field = $form->getFlatFields(withHidden: true)[$fieldName];
|
||||
|
||||
$livewireClass = $this->instance()::class;
|
||||
|
||||
Assert::assertTrue(
|
||||
$field->isReadOnly(),
|
||||
"Failed asserting that a field with the name [{$fieldName}] is read-only on the form named [{$formName}] on the [{$livewireClass}] component."
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertFormFieldIsHidden(): Closure
|
||||
{
|
||||
return function (string $fieldName, string $formName = 'form'): static {
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Filament\\Notifications\\": "src"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/Testing/Autoload.php"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
|
||||
@@ -19,7 +19,7 @@ class EditPost extends Component
|
||||
public function save(): void
|
||||
{
|
||||
// ...
|
||||
|
||||
|
||||
Notification::make() // [tl! focus:start]
|
||||
->title('Saved successfully')
|
||||
->success()
|
||||
@@ -54,7 +54,7 @@ Markdown text will automatically be rendered if passed to the title.
|
||||
|
||||
## Icon
|
||||
|
||||
Optionally, a notification can have an icon that's displayed in front of its content. You may also set a color for the icon, which defaults to the `secondary` color specified in your `tailwind.config.js` file. The icon can be the name of any Blade component. By default, the [Blade Heroicons](https://github.com/blade-ui-kit/blade-heroicons) package is installed, so you may use the name of any [Heroicon](https://heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
Optionally, a notification can have an icon that's displayed in front of its content. You may also set a color for the icon, which defaults to the `secondary` color specified in your `tailwind.config.js` file. The icon can be the name of any Blade component. By default, the [Blade Heroicons v1](https://github.com/blade-ui-kit/blade-heroicons/tree/1.3.1) package is installed, so you may use the name of any [Heroicons v1](https://v1.heroicons.com) out of the box. However, you may create your own custom icon components or install an alternative library if you wish.
|
||||
|
||||
```php
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
@@ -17,6 +17,22 @@ it('sends a notification', function () {
|
||||
});
|
||||
```
|
||||
|
||||
```php
|
||||
use Filament\Notifications\Notification
|
||||
|
||||
it('sends a notification', function () {
|
||||
Notification::assertNotified();
|
||||
});
|
||||
```
|
||||
|
||||
```php
|
||||
use function Filament\Notifications\Testing\assetNotified;
|
||||
|
||||
it('sends a notification', function () {
|
||||
assertNotified();
|
||||
});
|
||||
```
|
||||
|
||||
You may optionally pass a notification title to test for:
|
||||
|
||||
```php
|
||||
|
||||
@@ -18,8 +18,10 @@ use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\DatabaseNotification as DatabaseNotificationModel;
|
||||
use Illuminate\Notifications\Messages\BroadcastMessage;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
class Notification extends ViewComponent implements Arrayable
|
||||
{
|
||||
@@ -192,4 +194,27 @@ class Notification extends ViewComponent implements Arrayable
|
||||
|
||||
return $static;
|
||||
}
|
||||
|
||||
public static function assertNotified(Notification | string $notification = null): void
|
||||
{
|
||||
$notifications = session()->get('filament.notifications');
|
||||
|
||||
Assert::assertIsArray($notifications);
|
||||
|
||||
$expectedNotification = Arr::last($notifications);
|
||||
|
||||
Assert::assertIsArray($expectedNotification);
|
||||
|
||||
if (blank($notification)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($notification instanceof Notification) {
|
||||
Assert::assertSame($expectedNotification, $notification->toArray());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Assert::assertSame($expectedNotification['title'], $notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Notifications\Testing;
|
||||
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
/**
|
||||
* @return TestCall | TestCase | mixed
|
||||
*/
|
||||
function assertNotified(Notification | string $notification = null)
|
||||
{
|
||||
Notification::assertNotified($notification);
|
||||
|
||||
return test();
|
||||
}
|
||||
@@ -4,10 +4,8 @@ namespace Filament\Notifications\Testing;
|
||||
|
||||
use Closure;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Arr;
|
||||
use Livewire\Component;
|
||||
use Livewire\Testing\TestableLivewire;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* @method Component instance()
|
||||
@@ -19,19 +17,7 @@ class TestsNotifications
|
||||
public function assertNotified(): Closure
|
||||
{
|
||||
return function (Notification | string $notification = null): static {
|
||||
$notifications = session()->get('filament.notifications');
|
||||
|
||||
Assert::assertIsArray($notifications);
|
||||
|
||||
$expectedNotification = Arr::last($notifications);
|
||||
|
||||
Assert::assertIsArray($expectedNotification);
|
||||
|
||||
if ($notification instanceof Notification) {
|
||||
Assert::assertSame($expectedNotification, $notification->toArray());
|
||||
} elseif (filled($notification)) {
|
||||
Assert::assertSame($expectedNotification['title'], $notification);
|
||||
}
|
||||
Notification::assertNotified($notification);
|
||||
|
||||
return $this;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ class AlpineComponent extends Asset
|
||||
|
||||
$path .= 'components';
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
$path .= "{$this->getName()}.js";
|
||||
$path .= "{$this->getId()}.js";
|
||||
|
||||
return public_path("js/filament/{$path}");
|
||||
}
|
||||
|
||||
@@ -4,26 +4,26 @@ namespace Filament\Support\Assets;
|
||||
|
||||
abstract class Asset
|
||||
{
|
||||
protected string $name;
|
||||
protected string $id;
|
||||
|
||||
protected string $path;
|
||||
protected ?string $path = null;
|
||||
|
||||
protected ?string $package = null;
|
||||
|
||||
final public function __construct(string $name, string $path)
|
||||
final public function __construct(string $id, ?string $path = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->id = $id;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
public static function make(string $name, string $path): static
|
||||
public static function make(string $id, ?string $path = null): static
|
||||
{
|
||||
return new static($name, $path);
|
||||
return new static($id, $path);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->name;
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function package(?string $package): static
|
||||
|
||||
@@ -14,6 +14,8 @@ class AssetManager
|
||||
|
||||
protected array $styles = [];
|
||||
|
||||
protected array $themes = [];
|
||||
|
||||
public function register(array $assets, ?string $package = null): void
|
||||
{
|
||||
foreach ($assets as $asset) {
|
||||
@@ -23,6 +25,7 @@ class AssetManager
|
||||
AlpineComponent::class => $this->alpineComponents[$package][] = $asset,
|
||||
Css::class => $this->styles[$package][] = $asset,
|
||||
Js::class => $this->scripts[$package][] = $asset,
|
||||
Theme::class => $this->themes[$asset->getId()] = $asset,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
@@ -100,6 +103,16 @@ class AssetManager
|
||||
])->render();
|
||||
}
|
||||
|
||||
public function getTheme(?string $id): ?Theme
|
||||
{
|
||||
return $this->themes[$id] ?? null;
|
||||
}
|
||||
|
||||
public function getThemes(): array
|
||||
{
|
||||
return $this->themes;
|
||||
}
|
||||
|
||||
protected function getAssets(array $assets, ?array $packages = null): array
|
||||
{
|
||||
if ($packages === null) {
|
||||
|
||||
@@ -32,7 +32,7 @@ class Css extends Asset
|
||||
$href .= "{$package}/";
|
||||
}
|
||||
|
||||
$href .= "{$this->getName()}.css";
|
||||
$href .= "{$this->getId()}.css";
|
||||
|
||||
return asset($href) . '?v=' . InstalledVersions::getVersion('filament/support');
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class Css extends Asset
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$path .= "{$this->getName()}.css";
|
||||
$path .= "{$this->getId()}.css";
|
||||
|
||||
return public_path("css/filament/{$path}");
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class Js extends Asset
|
||||
$src .= "{$package}/";
|
||||
}
|
||||
|
||||
$src .= "{$this->getName()}.js";
|
||||
$src .= "{$this->getId()}.js";
|
||||
|
||||
return asset($src) . '?v=' . InstalledVersions::getVersion('filament/support');
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class Js extends Asset
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$path .= "{$this->getName()}.js";
|
||||
$path .= "{$this->getId()}.js";
|
||||
|
||||
return public_path("js/filament/{$path}");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Assets;
|
||||
|
||||
class Theme extends Css
|
||||
{
|
||||
}
|
||||
@@ -41,6 +41,14 @@ class AssetsCommand extends Command
|
||||
$this->copyAsset($asset->getPath(), $asset->getPublicPath());
|
||||
}
|
||||
|
||||
foreach (FilamentAsset::getThemes() as $asset) {
|
||||
if ($asset->isRemote()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->copyAsset($asset->getPath(), $asset->getPublicPath());
|
||||
}
|
||||
|
||||
$this->components->info('Successfully published assets!');
|
||||
|
||||
return static::SUCCESS;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Filament\Support\Facades;
|
||||
|
||||
use Filament\Support\Assets\AssetManager;
|
||||
use Filament\Support\Assets\Theme;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
/**
|
||||
@@ -10,6 +11,8 @@ use Illuminate\Support\Facades\Facade;
|
||||
* @method static array getScriptData(array | null $packages = null)
|
||||
* @method static array getScripts(array | null $packages = null, bool $withCore = true)
|
||||
* @method static array getStyles(array | null $packages = null)
|
||||
* @method static Theme | null getTheme(string $id)
|
||||
* @method static array getThemes()
|
||||
* @method static void register(array $assets, string | null $package = null)
|
||||
* @method static void registerScriptData(array $data, string | null $package = null)
|
||||
* @method static string renderScripts(array | null $packages = null, bool $withCore = true)
|
||||
|
||||
@@ -82,7 +82,7 @@ IconColumn::make('is_featured')
|
||||
|
||||
### Customizing the boolean icons
|
||||
|
||||
You may customize the icon representing each state. Icons are the name of a Blade component present. By default, [Heroicons](https://heroicons.com) are installed:
|
||||
You may customize the icon representing each state. Icons are the name of a Blade component present. By default, [Heroicons v1](https://v1.heroicons.com) are installed:
|
||||
|
||||
```php
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
x-tooltip="error"
|
||||
x-bind:class="{
|
||||
'border-gray-300 dark:border-gray-600': ! error,
|
||||
'border-danger-600 ring-1 ring-inset ring-danger-600': error,
|
||||
'border-danger-600 ring-1 ring-inset ring-danger-600 dark:border-danger-400 dark:ring-danger-400': error,
|
||||
}"
|
||||
{{
|
||||
$attributes
|
||||
->merge($getExtraAttributes(), escape: false)
|
||||
->merge($getExtraInputAttributes(), escape: false)
|
||||
->class(['ml-0.5 text-gray-900 block transition duration-75 rounded-lg shadow-sm focus:ring-primary-500 focus:ring-1 focus:ring-inset focus:border-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500'])
|
||||
->class(['ml-0.5 text-gray-900 inline-block transition duration-75 rounded-lg shadow-sm focus:ring-primary-500 focus:ring-1 focus:ring-inset focus:border-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500'])
|
||||
}}
|
||||
>
|
||||
@php
|
||||
|
||||
@@ -20,13 +20,21 @@
|
||||
x-tooltip="error"
|
||||
x-bind:class="{
|
||||
'border-gray-300 dark:border-gray-600': ! error,
|
||||
'border-danger-600 ring-1 ring-inset ring-danger-600': error,
|
||||
'border-danger-600 ring-1 ring-inset ring-danger-600 dark:border-danger-400 dark:ring-danger-400': error,
|
||||
}"
|
||||
{{
|
||||
$attributes
|
||||
->merge($getExtraAttributes(), escape: false)
|
||||
->merge($getExtraInputAttributes(), escape: false)
|
||||
->class(['ml-0.5 text-gray-900 block transition duration-75 rounded-lg shadow-sm focus:ring-primary-500 focus:ring-1 focus:ring-inset focus:border-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500'])
|
||||
->class([
|
||||
'ml-0.5 text-gray-900 inline-block transition duration-75 rounded-lg shadow-sm focus:ring-primary-500 focus:ring-1 focus:ring-inset focus:border-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500',
|
||||
match ($getAlignment()) {
|
||||
'center' => 'text-center',
|
||||
'right' => 'text-right',
|
||||
default => 'text-left',
|
||||
},
|
||||
])
|
||||
}}
|
||||
wire:loading.attr="readonly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user