mirror of
https://github.com/filamentphp/filament.git
synced 2026-10-24 23:35:30 +08:00
Merge branch '3.x' into upg3
This commit is contained in:
@@ -41,7 +41,7 @@
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"post-autoload-dump": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
|
||||
Generated
+34
-34
@@ -3032,9 +3032,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
|
||||
"integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -3634,9 +3634,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.21.8",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.8.tgz",
|
||||
"integrity": "sha512-j+7xYe+v+q2Id9qbBeCI8WX5NmZSRe8es1+0xntD/+gaWXznP8tFEkv5IgSaHf5dS1YwVMbX/4W6m937mj+wQw==",
|
||||
"version": "4.21.9",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
|
||||
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3653,8 +3653,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001502",
|
||||
"electron-to-chromium": "^1.4.428",
|
||||
"caniuse-lite": "^1.0.30001503",
|
||||
"electron-to-chromium": "^1.4.431",
|
||||
"node-releases": "^2.0.12",
|
||||
"update-browserslist-db": "^1.0.11"
|
||||
},
|
||||
@@ -3757,9 +3757,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001503",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz",
|
||||
"integrity": "sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==",
|
||||
"version": "1.0.30001505",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001505.tgz",
|
||||
"integrity": "sha512-jaAOR5zVtxHfL0NjZyflVTtXm3D3J9P15zSJ7HmQF8dSKGA6tqzQq+0ZI3xkjyQj46I4/M0K2GbMpcAFOcbr3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4783,9 +4783,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.430",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.430.tgz",
|
||||
"integrity": "sha512-FytjTbGwz///F+ToZ5XSeXbbSaXalsVRXsz2mHityI5gfxft7ieW3HqFLkU5V1aIrY42aflICqbmFoDxW10etg==",
|
||||
"version": "1.4.434",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.434.tgz",
|
||||
"integrity": "sha512-5Gvm09UZTQRaWrimRtWRO5rvaX6Kpk5WHAPKDa7A4Gj6NIPuJ8w8WNpnxCXdd+CJJt6RBU6tUw0KyULoW6XuHw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
@@ -4856,9 +4856,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/envinfo": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz",
|
||||
"integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==",
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.9.0.tgz",
|
||||
"integrity": "sha512-RODB4txU+xImYDemN5DqaKC0CHk05XSVkOX4pq0hK26Qx+1LChkuOyUDlGEjYb3ACr0n9qBhFjg37hQuJvpkRQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"envinfo": "dist/cli.js"
|
||||
@@ -8125,9 +8125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
||||
"integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -8791,9 +8791,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-blade": {
|
||||
"version": "1.5.6",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-blade/-/prettier-plugin-blade-1.5.6.tgz",
|
||||
"integrity": "sha512-2b7dnzvJNyfFJTJUaflZ2S5dbtHkoj+gB6VGWaMJ/zzkSg2EWEEFWOFLgqCel1NZ+XbZyySZyLGx9l4eK7a5Hg==",
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-blade/-/prettier-plugin-blade-1.6.4.tgz",
|
||||
"integrity": "sha512-uJjKjXXdseu4OR8AcFx1np1lrGQVEHL/dNGr/I5wSkjARtAlVuQQiuKCR3GgFF9EZB3UTfPpWLLT7SZS4iW6nQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -9489,9 +9489,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
|
||||
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
|
||||
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
@@ -10324,9 +10324,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.18.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz",
|
||||
"integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==",
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.1.tgz",
|
||||
"integrity": "sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
@@ -10813,9 +10813,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.86.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz",
|
||||
"integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==",
|
||||
"version": "5.87.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.87.0.tgz",
|
||||
"integrity": "sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
@@ -10827,7 +10827,7 @@
|
||||
"acorn-import-assertions": "^1.9.0",
|
||||
"browserslist": "^4.14.5",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.14.1",
|
||||
"enhanced-resolve": "^5.15.0",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
@@ -10837,7 +10837,7 @@
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^3.1.2",
|
||||
"schema-utils": "^3.2.0",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.3.7",
|
||||
"watchpack": "^2.4.0",
|
||||
|
||||
@@ -46,40 +46,21 @@ First, use NPM to install Tailwind CSS and its `forms` and `typography` plugins:
|
||||
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss --save-dev
|
||||
```
|
||||
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `content` path, custom `colors`, and the `plugins` you installed:
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `preset` which configures colors and the plugins you installed:
|
||||
|
||||
```js
|
||||
import colors from 'tailwindcss/colors'
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import preset from './vendor/filament/support/tailwind.config.preset'
|
||||
|
||||
export default {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./resources/**/*.blade.php',
|
||||
'./app/Filament/**/*.php',
|
||||
'./resources/views/filament/**/*.blade.php',
|
||||
'./vendor/filament/**/*.blade.php',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
danger: colors.rose,
|
||||
info: colors.blue,
|
||||
primary: colors.blue,
|
||||
secondary: colors.gray,
|
||||
success: colors.green,
|
||||
warning: colors.yellow,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
forms,
|
||||
typography,
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Of course, you may specify your own custom `danger`, `gray`, `info`, `primary`, `secondary`, `success` and `warning` colors, which will be used instead. But each color needs to be a [Tailwind CSS color](https://tailwindcss.com/docs/customizing-colors#color-palette-reference), or have all 50 - 950 variants specified - a single hex code or RGB value won't work here.
|
||||
|
||||
### Configuring styles
|
||||
|
||||
In `resources/css/app.css`, import Tailwind CSS, and the Filament forms CSS, which is used for action modal forms:
|
||||
@@ -174,10 +155,10 @@ composer update
|
||||
php artisan filament:upgrade
|
||||
```
|
||||
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-update-cmd` to run it automatically:
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-autoload-dump` to run it automatically:
|
||||
|
||||
```json
|
||||
"post-update-cmd": [
|
||||
"post-autoload-dump": [
|
||||
// ...
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
|
||||
@@ -137,21 +137,58 @@ Action::make('approvePost')
|
||||
})
|
||||
```
|
||||
|
||||
## Customizing the modal's heading, subheading, and submit action label
|
||||
## Customizing the modal's heading, description, and submit action label
|
||||
|
||||
You may customize the heading, subheading and label of the submit button in the modal:
|
||||
You may customize the heading, description and label of the submit button in the modal:
|
||||
|
||||
```php
|
||||
Action::make('delete')
|
||||
->action(fn () => $this->record->delete())
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Delete post')
|
||||
->modalSubheading('Are you sure you\'d like to delete this post? This cannot be undone.')
|
||||
->modalDescription('Are you sure you\'d like to delete this post? This cannot be undone.')
|
||||
->modalSubmitActionLabel('Yes, delete it')
|
||||
```
|
||||
|
||||
<AutoScreenshot name="actions/modal/confirmation-custom-text" alt="Confirmation modal with custom text" version="3.x" />
|
||||
|
||||
## Adding an icon inside the modal
|
||||
|
||||
You may add an [icon](https://blade-ui-kit.com/blade-icons?set=1#search) inside the modal using the `modalIcon()` method:
|
||||
|
||||
```php
|
||||
Action::make('delete')
|
||||
->action(fn () => $this->record->delete())
|
||||
->requiresConfirmation()
|
||||
->modalIcon('heroicon-o-trash')
|
||||
```
|
||||
|
||||
By default, the icon will inherit the color of the action button. You may customize the color of the icon using the `modalIconColor()` method:
|
||||
|
||||
```php
|
||||
Action::make('delete')
|
||||
->action(fn () => $this->record->delete())
|
||||
->requiresConfirmation()
|
||||
->color('danger')
|
||||
->modalIcon('heroicon-o-trash')
|
||||
->modalIconColor('warning')
|
||||
```
|
||||
|
||||
## Customizing the alignment of modal content
|
||||
|
||||
By default, modal content will be aligned to the start, or centered if the modal is `xs` or `sm` in [width](#changing-the-modal-width). If you wish to change the alignment of content in a modal, you can use the `modalAlignment()` method and pass it `start` or `center`:
|
||||
|
||||
```php
|
||||
Action::make('updateAuthor')
|
||||
->form([
|
||||
// ...
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
// ...
|
||||
})
|
||||
->modalAlignment('center')
|
||||
```
|
||||
|
||||
## Custom modal content
|
||||
|
||||
You may define custom content to be rendered inside your modal, which you can specify by passing a Blade view into the `modalContent()` method:
|
||||
@@ -179,12 +216,12 @@ Action::make('advance')
|
||||
|
||||
### Adding custom modal content below the form
|
||||
|
||||
By default, the custom content is displayed above the modal form if there is one, but you can add content below using `modalFooter()` if you wish:
|
||||
By default, the custom content is displayed above the modal form if there is one, but you can add content below using `modalContentFooter()` if you wish:
|
||||
|
||||
```php
|
||||
Action::make('advance')
|
||||
->action(fn () => $this->record->advance())
|
||||
->modalFooter(view('filament.pages.actions.advance'))
|
||||
->modalContentFooter(view('filament.pages.actions.advance'))
|
||||
```
|
||||
|
||||
### Adding an action to custom modal content
|
||||
@@ -215,6 +252,7 @@ Now, in the view file, you can render the action button using the name that it w
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
## Using a slide-over instead of a modal
|
||||
|
||||
You can open a "slide-over" dialog instead of a modal by using the `slideOver()` method:
|
||||
@@ -234,6 +272,21 @@ Action::make('updateAuthor')
|
||||
|
||||
Instead of opening in the center of the screen, the modal content will now slide in from the right and consume the entire height of the browser.
|
||||
|
||||
## Making the modal footer sticky
|
||||
|
||||
The footer of a modal is rendered inline after the content by default. Slide-overs, however, have a sticky footer that always shows when scrolling the content. You may enable this for a modal too using `stickyModalFooter()`:
|
||||
|
||||
```php
|
||||
Action::make('updateAuthor')
|
||||
->form([
|
||||
// ...
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
// ...
|
||||
})
|
||||
->stickyModalFooter()
|
||||
```
|
||||
|
||||
## Changing the modal width
|
||||
|
||||
You can change the width of the modal by using the `modalWidth()` method. Options correspond to [Tailwind's max-width scale](https://tailwindcss.com/docs/max-width). The options are `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl`, `5xl`, `6xl`, `7xl`, and `screen`:
|
||||
@@ -272,51 +325,13 @@ use Filament\Forms\Form;
|
||||
Action::make('create')
|
||||
->mountUsing(function (Form $form) {
|
||||
$form->fill();
|
||||
|
||||
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
Please note that the `mountUsing()` method, by default, is used by Filament to initialize the [form](#modal-forms). If you override this method, you will need to call `$form->fill()` to ensure the form is initialized correctly. If you wish to populate the form with data, you can do so by passing an array to the `fill()` method, instead of [using `fillForm()` on the action itself](#filling-the-form-with-existing-data).
|
||||
|
||||
## Centering modal content
|
||||
|
||||
By default, the modal's content will be centered if the modal is `xs` or `sm` in [width](#changing-the-modal-width). If you wish to center the content of a larger modal, you can use the `centerModal()` method:
|
||||
|
||||
```php
|
||||
Action::make('updateAuthor')
|
||||
->form([
|
||||
// ...
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
// ...
|
||||
})
|
||||
->centerModal()
|
||||
```
|
||||
|
||||
## Closing the modal by clicking away
|
||||
|
||||
By default, when you click away from a modal, it will close itself. If you wish to disable this behavior for a specific action, you can use the `closeModalByClickingAway(false)` method:
|
||||
|
||||
```php
|
||||
Action::make('updateAuthor')
|
||||
->form([
|
||||
// ...
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
// ...
|
||||
})
|
||||
->closeModalByClickingAway(false)
|
||||
```
|
||||
|
||||
If you'd like to change the behaviour for all modals in the application, you can do so by calling `Modal::closedByClickingAway()` inside a service provider or middleware:
|
||||
|
||||
```php
|
||||
use Filament\Support\View\Components\Modal;
|
||||
|
||||
Modal::closedByClickingAway(false);
|
||||
```
|
||||
|
||||
## Customizing the action buttons in the footer of the modal
|
||||
|
||||
By default, there are two actions in the footer of a modal. The first is a button to submit, which executes the `action()`. The second button closes the modal and cancels the action.
|
||||
@@ -367,9 +382,55 @@ Action::make('create')
|
||||
])
|
||||
->action(function (array $data, array $arguments): void {
|
||||
// Create
|
||||
|
||||
|
||||
if ($arguments['another'] ?? false) {
|
||||
// Reset the form and don't close the modal
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Closing the modal by clicking away
|
||||
|
||||
By default, when you click away from a modal, it will close itself. If you wish to disable this behavior for a specific action, you can use the `closeModalByClickingAway(false)` method:
|
||||
|
||||
```php
|
||||
Action::make('updateAuthor')
|
||||
->form([
|
||||
// ...
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
// ...
|
||||
})
|
||||
->closeModalByClickingAway(false)
|
||||
```
|
||||
|
||||
If you'd like to change the behaviour for all modals in the application, you can do so by calling `Modal::closedByClickingAway()` inside a service provider or middleware:
|
||||
|
||||
```php
|
||||
use Filament\Support\View\Components\Modal;
|
||||
|
||||
Modal::closedByClickingAway(false);
|
||||
```
|
||||
|
||||
## Hiding the modal close button
|
||||
|
||||
By default, modals have a close button in the top right corner. If you wish to hide the close button, you can use the `modalCloseButton(false)` method:
|
||||
|
||||
```php
|
||||
Action::make('updateAuthor')
|
||||
->form([
|
||||
// ...
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
// ...
|
||||
})
|
||||
->modalCloseButton(false)
|
||||
```
|
||||
|
||||
If you'd like to hide the close button for all modals in the application, you can do so by calling `Modal::closeButton(false)` inside a service provider or middleware:
|
||||
|
||||
```php
|
||||
use Filament\Support\View\Components\Modal;
|
||||
|
||||
Modal::closeButton(false);
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{{
|
||||
$attributes->class([
|
||||
'filament-actions-actions',
|
||||
'flex flex-wrap items-center gap-4' => ! $fullWidth,
|
||||
'flex flex-wrap items-center gap-3' => ! $fullWidth,
|
||||
match ($alignment) {
|
||||
'center' => 'justify-center',
|
||||
'right' => 'flex-row-reverse space-x-reverse',
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
@if ($this instanceof \Filament\Actions\Contracts\HasActions && (! $this->hasActionsModalRendered))
|
||||
<form wire:submit="callMountedAction">
|
||||
<form wire:submit.prevent="callMountedAction">
|
||||
@php
|
||||
$action = $this->getMountedAction();
|
||||
@endphp
|
||||
|
||||
<x-filament::modal
|
||||
:id="$this->getId() . '-action'"
|
||||
:wire:key="$action ? $this->getId() . '.actions.' . $action->getName() . '.modal' : null"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:alignment="$action?->getModalAlignment()"
|
||||
:close-button="$action?->hasModalCloseButton()"
|
||||
:close-by-clicking-away="$action?->isModalClosedByClickingAway()"
|
||||
display-classes="block"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->getId() }}') close()"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->getId() }}') && $wire.mountedActions.length) open()"
|
||||
:footer-actions="$action?->getVisibleModalFooterActions()"
|
||||
:footer-actions-alignment="$action?->getModalFooterActionsAlignment()"
|
||||
:heading="$action?->getModalHeading()"
|
||||
:icon="$action?->getModalIcon()"
|
||||
:icon-color="$action?->getModalIconColor()"
|
||||
:id="$this->id . '-action'"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:sticky-footer="$action?->isModalFooterSticky()"
|
||||
:description="$action?->getModalDescription()"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:wire:key="$action ? $this->id . '.actions.' . $action->getName() . '.modal' : null"
|
||||
x-init="livewire = $wire.__instance"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->id }}') && $wire.mountedActions.length) open()"
|
||||
x-on:modal-closed.stop="
|
||||
const mountedActionShouldOpenModal = {{ \Illuminate\Support\Js::from($action && $this->mountedActionShouldOpenModal()) }}
|
||||
|
||||
@@ -21,42 +30,20 @@
|
||||
return
|
||||
}
|
||||
|
||||
if ($wire.mountedFormComponentActions.length) {
|
||||
if (
|
||||
('mountedFormComponentActions' in livewire?.serverMemo.data) &&
|
||||
livewire.serverMemo.data.mountedFormComponentActions.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
$wire.unmountAction(false)
|
||||
if ('mountedActions' in livewire?.serverMemo.data) {
|
||||
livewire.call('unmountAction', false)
|
||||
}
|
||||
"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->id }}') close()"
|
||||
>
|
||||
@if ($action)
|
||||
@if ($action->isModalCentered())
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-slot name="heading">
|
||||
{{ $heading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-slot name="subheading">
|
||||
{{ $subheading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
@else
|
||||
<x-slot name="header">
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-filament::modal.heading>
|
||||
{{ $heading }}
|
||||
</x-filament::modal.heading>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-filament::modal.subheading>
|
||||
{{ $subheading }}
|
||||
</x-filament::modal.subheading>
|
||||
@endif
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
{{ $action->getModalContent() }}
|
||||
|
||||
@if (count(($infolist = $action->getInfolist())?->getComponents() ?? []))
|
||||
@@ -65,19 +52,7 @@
|
||||
{{ $this->getMountedActionForm() }}
|
||||
@endif
|
||||
|
||||
{{ $action->getModalFooter() }}
|
||||
|
||||
@if (count($modalActions = $action->getVisibleModalActions()))
|
||||
<x-slot name="footer">
|
||||
<x-filament::modal.actions
|
||||
:full-width="$action->isModalCentered()"
|
||||
>
|
||||
@foreach ($modalActions as $modalAction)
|
||||
{{ $modalAction }}
|
||||
@endforeach
|
||||
</x-filament::modal.actions>
|
||||
</x-slot>
|
||||
@endif
|
||||
{{ $action->getModalContentFooter() }}
|
||||
@endif
|
||||
</x-filament::modal>
|
||||
</form>
|
||||
@@ -88,21 +63,30 @@
|
||||
@endif
|
||||
|
||||
@if ($this instanceof \Filament\Infolists\Contracts\HasInfolists && (! $this->hasInfolistsModalRendered))
|
||||
<form wire:submit="callMountedInfolistAction">
|
||||
<form wire:submit.prevent="callMountedInfolistAction">
|
||||
@php
|
||||
$action = $this->getMountedInfolistAction();
|
||||
@endphp
|
||||
|
||||
<x-filament::modal
|
||||
:id="$this->getId() . '-infolist-action'"
|
||||
:wire:key="$action ? $this->getId() . '.infolist.actions.' . $action->getName() . '.modal' : null"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:alignment="$action?->getModalAlignment()"
|
||||
:close-button="$action?->hasModalCloseButton()"
|
||||
:close-by-clicking-away="$action?->isModalClosedByClickingAway()"
|
||||
display-classes="block"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->getId() }}') close()"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->getId() }}') && $wire.mountedInfolistActions.length) open()"
|
||||
:footer-actions="$action?->getVisibleModalFooterActions()"
|
||||
:footer-actions-alignment="$action?->getModalFooterActionsAlignment()"
|
||||
:heading="$action?->getModalHeading()"
|
||||
:icon="$action?->getModalIcon()"
|
||||
:icon-color="$action?->getModalIconColor()"
|
||||
:id="$this->id . '-infolist-action'"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:sticky-footer="$action?->isModalFooterSticky()"
|
||||
:description="$action?->getModalDescription()"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:wire:key="$action ? $this->id . '.infolist.actions.' . $action->getName() . '.modal' : null"
|
||||
x-init="livewire = $wire.__instance"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->id }}') && $wire.mountedInfolistActions.length) open()"
|
||||
x-on:modal-closed.stop="
|
||||
const mountedInfolistActionShouldOpenModal = {{ \Illuminate\Support\Js::from($action && $this->mountedInfolistActionShouldOpenModal()) }}
|
||||
|
||||
@@ -110,42 +94,20 @@
|
||||
return
|
||||
}
|
||||
|
||||
if ($wire.mountedFormComponentActions.length) {
|
||||
if (
|
||||
('mountedFormComponentActions' in livewire?.serverMemo.data) &&
|
||||
livewire.serverMemo.data.mountedFormComponentActions.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
$wire.unmountInfolistAction(false)
|
||||
if ('mountedInfolistActions' in livewire?.serverMemo.data) {
|
||||
livewire.call('unmountInfolistAction', false)
|
||||
}
|
||||
"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->id }}') close()"
|
||||
>
|
||||
@if ($action)
|
||||
@if ($action->isModalCentered())
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-slot name="heading">
|
||||
{{ $heading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-slot name="subheading">
|
||||
{{ $subheading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
@else
|
||||
<x-slot name="header">
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-filament::modal.heading>
|
||||
{{ $heading }}
|
||||
</x-filament::modal.heading>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-filament::modal.subheading>
|
||||
{{ $subheading }}
|
||||
</x-filament::modal.subheading>
|
||||
@endif
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
{{ $action->getModalContent() }}
|
||||
|
||||
@if (count(($infolist = $action->getInfolist())?->getComponents() ?? []))
|
||||
@@ -154,19 +116,7 @@
|
||||
{{ $this->getMountedInfolistActionForm() }}
|
||||
@endif
|
||||
|
||||
{{ $action->getModalFooter() }}
|
||||
|
||||
@if (count($modalActions = $action->getVisibleModalActions()))
|
||||
<x-slot name="footer">
|
||||
<x-filament::modal.actions
|
||||
:full-width="$action->isModalCentered()"
|
||||
>
|
||||
@foreach ($modalActions as $modalAction)
|
||||
{{ $modalAction }}
|
||||
@endforeach
|
||||
</x-filament::modal.actions>
|
||||
</x-slot>
|
||||
@endif
|
||||
{{ $action->getModalContentFooter() }}
|
||||
@endif
|
||||
</x-filament::modal>
|
||||
</form>
|
||||
@@ -177,21 +127,30 @@
|
||||
@endif
|
||||
|
||||
@if ($this instanceof \Filament\Tables\Contracts\HasTable && (! $this->hasTableModalRendered))
|
||||
<form wire:submit="callMountedTableAction">
|
||||
<form wire:submit.prevent="callMountedTableAction">
|
||||
@php
|
||||
$action = $this->getMountedTableAction();
|
||||
@endphp
|
||||
|
||||
<x-filament::modal
|
||||
:id="$this->getId() . '-table-action'"
|
||||
:wire:key="$action ? $this->getId() . '.table.actions.' . $action->getName() . '.modal' : null"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:alignment="$action?->getModalAlignment()"
|
||||
:close-button="$action?->hasModalCloseButton()"
|
||||
:close-by-clicking-away="$action?->isModalClosedByClickingAway()"
|
||||
display-classes="block"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->getId() }}') close()"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->getId() }}') && $wire.mountedTableActions.length) open()"
|
||||
:footer-actions="$action?->getVisibleModalFooterActions()"
|
||||
:footer-actions-alignment="$action?->getModalFooterActionsAlignment()"
|
||||
:heading="$action?->getModalHeading()"
|
||||
:icon="$action?->getModalIcon()"
|
||||
:icon-color="$action?->getModalIconColor()"
|
||||
:id="$this->id . '-table-action'"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:sticky-footer="$action?->isModalFooterSticky()"
|
||||
:description="$action?->getModalDescription()"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:wire:key="$action ? $this->id . '.table.actions.' . $action->getName() . '.modal' : null"
|
||||
x-init="livewire = $wire.__instance"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->id }}') && $wire.mountedTableActions.length) open()"
|
||||
x-on:modal-closed.stop="
|
||||
const mountedTableActionShouldOpenModal = {{ \Illuminate\Support\Js::from($action && $this->mountedTableActionShouldOpenModal()) }}
|
||||
|
||||
@@ -199,42 +158,20 @@
|
||||
return
|
||||
}
|
||||
|
||||
if ($wire.mountedFormComponentActions.length) {
|
||||
if (
|
||||
('mountedFormComponentActions' in livewire?.serverMemo.data) &&
|
||||
livewire.serverMemo.data.mountedFormComponentActions.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
$wire.unmountTableAction(false)
|
||||
if ('mountedTableActions' in livewire?.serverMemo.data) {
|
||||
livewire.call('unmountTableAction', false)
|
||||
}
|
||||
"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->id }}') close()"
|
||||
>
|
||||
@if ($action)
|
||||
@if ($action->isModalCentered())
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-slot name="heading">
|
||||
{{ $heading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-slot name="subheading">
|
||||
{{ $subheading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
@else
|
||||
<x-slot name="header">
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-filament::modal.heading>
|
||||
{{ $heading }}
|
||||
</x-filament::modal.heading>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-filament::modal.subheading>
|
||||
{{ $subheading }}
|
||||
</x-filament::modal.subheading>
|
||||
@endif
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
{{ $action->getModalContent() }}
|
||||
|
||||
@if (count(($infolist = $action->getInfolist())?->getComponents() ?? []))
|
||||
@@ -243,38 +180,35 @@
|
||||
{{ $this->getMountedTableActionForm() }}
|
||||
@endif
|
||||
|
||||
{{ $action->getModalFooter() }}
|
||||
|
||||
@if (count($modalActions = $action->getVisibleModalActions()))
|
||||
<x-slot name="footer">
|
||||
<x-filament::modal.actions
|
||||
:full-width="$action->isModalCentered()"
|
||||
>
|
||||
@foreach ($modalActions as $modalAction)
|
||||
{{ $modalAction }}
|
||||
@endforeach
|
||||
</x-filament::modal.actions>
|
||||
</x-slot>
|
||||
@endif
|
||||
{{ $action->getModalContentFooter() }}
|
||||
@endif
|
||||
</x-filament::modal>
|
||||
</form>
|
||||
|
||||
<form wire:submit="callMountedTableBulkAction">
|
||||
<form wire:submit.prevent="callMountedTableBulkAction">
|
||||
@php
|
||||
$action = $this->getMountedTableBulkAction();
|
||||
@endphp
|
||||
|
||||
<x-filament::modal
|
||||
:id="$this->getId() . '-table-bulk-action'"
|
||||
:wire:key="$action ? $this->getId() . '.table.bulk-actions.' . $action->getName() . '.modal' : null"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:alignment="$action?->getModalAlignment()"
|
||||
:close-button="$action?->hasModalCloseButton()"
|
||||
:close-by-clicking-away="$action?->isModalClosedByClickingAway()"
|
||||
display-classes="block"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->getId() }}') close()"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->getId() }}') && $wire.mountedTableBulkAction) open()"
|
||||
:footer-actions="$action?->getVisibleModalFooterActions()"
|
||||
:footer-actions-alignment="$action?->getModalFooterActionsAlignment()"
|
||||
:heading="$action?->getModalHeading()"
|
||||
:icon="$action?->getModalIcon()"
|
||||
:icon-color="$action?->getModalIconColor()"
|
||||
:id="$this->id . '-table-bulk-action'"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:sticky-footer="$action?->isModalFooterSticky()"
|
||||
:description="$action?->getModalDescription()"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:wire:key="$action ? $this->id . '.table.bulk-actions.' . $action->getName() . '.modal' : null"
|
||||
x-init="livewire = $wire.__instance"
|
||||
x-on:closed-form-component-action-modal.window="if (($event.detail.id === '{{ $this->id }}') && $wire.mountedTableBulkAction) open()"
|
||||
x-on:modal-closed.stop="
|
||||
const mountedTableBulkActionShouldOpenModal = {{ \Illuminate\Support\Js::from($action && $this->mountedTableBulkActionShouldOpenModal()) }}
|
||||
|
||||
@@ -282,42 +216,20 @@
|
||||
return
|
||||
}
|
||||
|
||||
if ($wire.mountedFormComponentActions.length) {
|
||||
if (
|
||||
('mountedFormComponentActions' in livewire?.serverMemo.data) &&
|
||||
livewire.serverMemo.data.mountedFormComponentActions.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
$wire.mountedTableBulkAction = null
|
||||
if ('mountedTableBulkAction' in livewire?.serverMemo.data) {
|
||||
livewire.set('mountedTableBulkAction', null)
|
||||
}
|
||||
"
|
||||
x-on:opened-form-component-action-modal.window="if ($event.detail.id === '{{ $this->id }}') close()"
|
||||
>
|
||||
@if ($action)
|
||||
@if ($action->isModalCentered())
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-slot name="heading">
|
||||
{{ $heading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-slot name="subheading">
|
||||
{{ $subheading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
@else
|
||||
<x-slot name="header">
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-filament::modal.heading>
|
||||
{{ $heading }}
|
||||
</x-filament::modal.heading>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-filament::modal.subheading>
|
||||
{{ $subheading }}
|
||||
</x-filament::modal.subheading>
|
||||
@endif
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
{{ $action->getModalContent() }}
|
||||
|
||||
@if (count(($infolist = $action->getInfolist())?->getComponents() ?? []))
|
||||
@@ -326,19 +238,7 @@
|
||||
{{ $this->getMountedTableBulkActionForm() }}
|
||||
@endif
|
||||
|
||||
{{ $action->getModalFooter() }}
|
||||
|
||||
@if (count($modalActions = $action->getVisibleModalActions()))
|
||||
<x-slot name="footer">
|
||||
<x-filament::modal.actions
|
||||
:full-width="$action->isModalCentered()"
|
||||
>
|
||||
@foreach ($modalActions as $modalAction)
|
||||
{{ $modalAction }}
|
||||
@endforeach
|
||||
</x-filament::modal.actions>
|
||||
</x-slot>
|
||||
@endif
|
||||
{{ $action->getModalContentFooter() }}
|
||||
@endif
|
||||
</x-filament::modal>
|
||||
</form>
|
||||
@@ -353,52 +253,34 @@
|
||||
$action = $this->getMountedFormComponentAction();
|
||||
@endphp
|
||||
|
||||
<form wire:submit="callMountedFormComponentAction">
|
||||
<form wire:submit.prevent="callMountedFormComponentAction">
|
||||
<x-filament::modal
|
||||
:id="$this->getId() . '-form-component-action'"
|
||||
:wire:key="$action ? $this->getId() . '.' . $action->getComponent()->getStatePath() . '.actions.' . $action->getName() . '.modal' : null"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:alignment="$action?->getModalAlignment()"
|
||||
:close-button="$action?->hasModalCloseButton()"
|
||||
:close-by-clicking-away="$action?->isModalClosedByClickingAway()"
|
||||
display-classes="block"
|
||||
:footer-actions="$action?->getVisibleModalFooterActions()"
|
||||
:footer-actions-alignment="$action?->getModalFooterActionsAlignment()"
|
||||
:heading="$action?->getModalHeading()"
|
||||
:icon="$action?->getModalIcon()"
|
||||
:icon-color="$action?->getModalIconColor()"
|
||||
:id="$this->id . '-form-component-action'"
|
||||
:slide-over="$action?->isModalSlideOver()"
|
||||
:sticky-footer="$action?->isModalFooterSticky()"
|
||||
:description="$action?->getModalDescription()"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
:wire:key="$action ? $this->id . '.' . $action->getComponent()->getStatePath() . '.actions.' . $action->getName() . '.modal' : null"
|
||||
x-init="livewire = $wire.__instance"
|
||||
x-on:modal-closed.stop="
|
||||
const mountedFormComponentActionShouldOpenModal = {{ \Illuminate\Support\Js::from($action && $this->mountedFormComponentActionShouldOpenModal()) }}
|
||||
|
||||
if (mountedFormComponentActionShouldOpenModal) {
|
||||
$wire.unmountFormComponentAction(false)
|
||||
if (mountedFormComponentActionShouldOpenModal && 'mountedFormComponentActions' in livewire?.serverMemo.data) {
|
||||
livewire.call('unmountFormComponentAction', false)
|
||||
}
|
||||
"
|
||||
>
|
||||
@if ($action)
|
||||
@if ($action->isModalCentered())
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-slot name="heading">
|
||||
{{ $heading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-slot name="subheading">
|
||||
{{ $subheading }}
|
||||
</x-slot>
|
||||
@endif
|
||||
@else
|
||||
<x-slot name="header">
|
||||
@if ($heading = $action->getModalHeading())
|
||||
<x-filament::modal.heading>
|
||||
{{ $heading }}
|
||||
</x-filament::modal.heading>
|
||||
@endif
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-filament::modal.subheading>
|
||||
{{ $subheading }}
|
||||
</x-filament::modal.subheading>
|
||||
@endif
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
{{ $action->getModalContent() }}
|
||||
|
||||
@if (count(($infolist = $action->getInfolist())?->getComponents() ?? []))
|
||||
@@ -407,19 +289,7 @@
|
||||
{{ $this->getMountedFormComponentActionForm() }}
|
||||
@endif
|
||||
|
||||
{{ $action->getModalFooter() }}
|
||||
|
||||
@if (count($modalActions = $action->getVisibleModalActions()))
|
||||
<x-slot name="footer">
|
||||
<x-filament::modal.actions
|
||||
:full-width="$action->isModalCentered()"
|
||||
>
|
||||
@foreach ($modalActions as $modalAction)
|
||||
{{ $modalAction }}
|
||||
@endforeach
|
||||
</x-filament::modal.actions>
|
||||
</x-slot>
|
||||
@endif
|
||||
{{ $action->getModalContentFooter() }}
|
||||
@endif
|
||||
</x-filament::modal>
|
||||
</form>
|
||||
|
||||
@@ -11,25 +11,44 @@ use Illuminate\Contracts\View\View;
|
||||
|
||||
trait CanOpenModal
|
||||
{
|
||||
/**
|
||||
* @var array<string, StaticAction>
|
||||
*/
|
||||
protected array $cachedExtraModalFooterActions;
|
||||
|
||||
/**
|
||||
* @var array<StaticAction> | Closure
|
||||
*/
|
||||
protected array | Closure $extraModalFooterActions = [];
|
||||
|
||||
protected bool | Closure | null $isModalFooterSticky = null;
|
||||
|
||||
/**
|
||||
* @var array<string, StaticAction>
|
||||
*/
|
||||
protected array $cachedModalActions;
|
||||
|
||||
/**
|
||||
* @var array<StaticAction>
|
||||
*/
|
||||
protected array $modalActions = [];
|
||||
|
||||
protected bool | Closure | null $isModalCentered = null;
|
||||
|
||||
protected bool | Closure $isModalSlideOver = false;
|
||||
|
||||
protected string | Closure | null $modalAlignment = null;
|
||||
|
||||
/**
|
||||
* @var array<string, StaticAction>
|
||||
*/
|
||||
protected array $cachedModalFooterActions;
|
||||
|
||||
/**
|
||||
* @var array<StaticAction> | Closure | null
|
||||
*/
|
||||
protected array | Closure | null $modalFooterActions = null;
|
||||
|
||||
protected string | Closure | null $modalFooterActionsAlignment = null;
|
||||
|
||||
protected StaticAction | bool | Closure | null $modalCancelAction = null;
|
||||
|
||||
protected string | Closure | null $modalCancelActionLabel = null;
|
||||
@@ -40,18 +59,27 @@ trait CanOpenModal
|
||||
|
||||
protected View | Htmlable | Closure | null $modalContent = null;
|
||||
|
||||
protected View | Htmlable | Closure | null $modalFooter = null;
|
||||
protected View | Htmlable | Closure | null $modalContentFooter = null;
|
||||
|
||||
protected string | Htmlable | Closure | null $modalHeading = null;
|
||||
|
||||
protected string | Htmlable | Closure | null $modalSubheading = null;
|
||||
protected string | Htmlable | Closure | null $modalDescription = null;
|
||||
|
||||
protected string | Closure | null $modalWidth = null;
|
||||
|
||||
protected bool | Closure | null $isModalHidden = false;
|
||||
|
||||
protected bool | Closure | null $hasModalCloseButton = null;
|
||||
|
||||
protected bool | Closure | null $isModalClosedByClickingAway = null;
|
||||
|
||||
protected string | Closure | null $modalIcon = null;
|
||||
|
||||
/**
|
||||
* @var string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | Closure | null
|
||||
*/
|
||||
protected string | array | Closure | null $modalIconColor = null;
|
||||
|
||||
public function closeModalByClickingAway(bool | Closure | null $condition = true): static
|
||||
{
|
||||
$this->isModalClosedByClickingAway = $condition;
|
||||
@@ -59,9 +87,45 @@ trait CanOpenModal
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `modalAlignment('center')` instead.
|
||||
*/
|
||||
public function centerModal(bool | Closure | null $condition = true): static
|
||||
{
|
||||
$this->isModalCentered = $condition;
|
||||
if ($this->evaluate($condition)) {
|
||||
$this->modalAlignment('center');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function modalAlignment(string | Closure | null $alignment = null): static
|
||||
{
|
||||
$this->modalAlignment = $alignment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function modalCloseButton(bool | Closure | null $condition = true): static
|
||||
{
|
||||
$this->hasModalCloseButton = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function modalIcon(string | Closure | null $icon = null): static
|
||||
{
|
||||
$this->modalIcon = $icon;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | Closure | null $color
|
||||
*/
|
||||
public function modalIconColor(string | array | Closure | null $color = null): static
|
||||
{
|
||||
$this->modalIconColor = $color;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -90,17 +154,14 @@ trait CanOpenModal
|
||||
*/
|
||||
public function modalFooterActions(array | Closure | null $actions = null): static
|
||||
{
|
||||
if (! is_array($actions)) {
|
||||
$this->modalFooterActions = $actions;
|
||||
$this->modalFooterActions = $actions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->modalFooterActions = [];
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$this->modalFooterActions[$action->getName()] = $action;
|
||||
}
|
||||
public function modalFooterActionsAlignment(string | Closure | null $alignment = null): static
|
||||
{
|
||||
$this->modalFooterActionsAlignment = $alignment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -185,9 +246,17 @@ trait CanOpenModal
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `modalContentFooter()` instead.
|
||||
*/
|
||||
public function modalFooter(View | Htmlable | Closure | null $footer = null): static
|
||||
{
|
||||
$this->modalFooter = $footer;
|
||||
return $this->modalContentFooter($footer);
|
||||
}
|
||||
|
||||
public function modalContentFooter(View | Htmlable | Closure | null $footer = null): static
|
||||
{
|
||||
$this->modalContentFooter = $footer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -199,9 +268,19 @@ trait CanOpenModal
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function modalDescription(string | Htmlable | Closure | null $description = null): static
|
||||
{
|
||||
$this->modalDescription = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `modalDescription()` instead.
|
||||
*/
|
||||
public function modalSubheading(string | Htmlable | Closure | null $subheading = null): static
|
||||
{
|
||||
$this->modalSubheading = $subheading;
|
||||
$this->modalDescription($subheading);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -234,18 +313,18 @@ trait CanOpenModal
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($this->modalFooterActions)) {
|
||||
return $this->modalFooterActions;
|
||||
if (isset($this->cachedModalFooterActions)) {
|
||||
return $this->cachedModalFooterActions;
|
||||
}
|
||||
|
||||
if ($this->modalFooterActions instanceof Closure) {
|
||||
if ($this->modalFooterActions) {
|
||||
$actions = [];
|
||||
|
||||
foreach ($this->evaluate($this->modalFooterActions) as $action) {
|
||||
$actions[$action->getName()] = $this->prepareModalAction($action);
|
||||
}
|
||||
|
||||
return $this->modalFooterActions = $actions;
|
||||
return $this->cachedModalFooterActions = $actions;
|
||||
}
|
||||
|
||||
$actions = [];
|
||||
@@ -263,11 +342,16 @@ trait CanOpenModal
|
||||
$actions['cancel'] = $cancelAction;
|
||||
}
|
||||
|
||||
if ($this->isModalCentered()) {
|
||||
if ($this->getModalFooterActionsAlignment() === 'center') {
|
||||
$actions = array_reverse($actions);
|
||||
}
|
||||
|
||||
return $this->modalFooterActions = $actions;
|
||||
return $this->cachedModalFooterActions = $actions;
|
||||
}
|
||||
|
||||
public function getModalFooterActionsAlignment(): ?string
|
||||
{
|
||||
return $this->evaluate($this->modalFooterActionsAlignment);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,12 +359,8 @@ trait CanOpenModal
|
||||
*/
|
||||
public function getModalActions(): array
|
||||
{
|
||||
if (! count($this->modalActions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! is_numeric(array_key_first($this->modalActions))) {
|
||||
return $this->modalActions;
|
||||
if (isset($this->cachedModalActions)) {
|
||||
return $this->cachedModalActions;
|
||||
}
|
||||
|
||||
$actions = $this->getModalFooterActions();
|
||||
@@ -289,7 +369,7 @@ trait CanOpenModal
|
||||
$actions[$action->getName()] = $this->prepareModalAction($action);
|
||||
}
|
||||
|
||||
return $this->modalActions = $actions;
|
||||
return $this->cachedModalActions = $actions;
|
||||
}
|
||||
|
||||
public function getModalAction(string $name): ?StaticAction
|
||||
@@ -325,7 +405,7 @@ trait CanOpenModal
|
||||
/**
|
||||
* @return array<StaticAction>
|
||||
*/
|
||||
public function getVisibleModalActions(): array
|
||||
public function getVisibleModalFooterActions(): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->getModalFooterActions(),
|
||||
@@ -377,13 +457,22 @@ trait CanOpenModal
|
||||
*/
|
||||
public function getExtraModalFooterActions(): array
|
||||
{
|
||||
if (isset($this->cachedExtraModalFooterActions)) {
|
||||
return $this->cachedExtraModalFooterActions;
|
||||
}
|
||||
|
||||
$actions = [];
|
||||
|
||||
foreach ($this->evaluate($this->extraModalFooterActions) as $action) {
|
||||
$actions[$action->getName()] = $this->prepareModalAction($action);
|
||||
}
|
||||
|
||||
return $this->extraModalFooterActions = $actions;
|
||||
return $this->cachedExtraModalFooterActions = $actions;
|
||||
}
|
||||
|
||||
public function getModalAlignment(): string
|
||||
{
|
||||
return $this->evaluate($this->modalAlignment) ?? (in_array($this->getModalWidth(), ['xs', 'sm']) ? 'center' : 'start');
|
||||
}
|
||||
|
||||
public function getModalSubmitActionLabel(): string
|
||||
@@ -401,9 +490,9 @@ trait CanOpenModal
|
||||
return $this->evaluate($this->modalContent);
|
||||
}
|
||||
|
||||
public function getModalFooter(): View | Htmlable | null
|
||||
public function getModalContentFooter(): View | Htmlable | null
|
||||
{
|
||||
return $this->evaluate($this->modalFooter);
|
||||
return $this->evaluate($this->modalContentFooter);
|
||||
}
|
||||
|
||||
public function getModalHeading(): string | Htmlable
|
||||
@@ -411,9 +500,9 @@ trait CanOpenModal
|
||||
return $this->evaluate($this->modalHeading) ?? $this->getLabel();
|
||||
}
|
||||
|
||||
public function getModalSubheading(): string | Htmlable | null
|
||||
public function getModalDescription(): string | Htmlable | null
|
||||
{
|
||||
return $this->evaluate($this->modalSubheading);
|
||||
return $this->evaluate($this->modalDescription);
|
||||
}
|
||||
|
||||
public function getModalWidth(): string
|
||||
@@ -421,9 +510,9 @@ trait CanOpenModal
|
||||
return $this->evaluate($this->modalWidth) ?? '4xl';
|
||||
}
|
||||
|
||||
public function isModalCentered(): bool
|
||||
public function isModalFooterSticky(): bool
|
||||
{
|
||||
return $this->evaluate($this->isModalCentered) ?? in_array($this->getModalWidth(), ['xs', 'sm']);
|
||||
return $this->evaluate($this->isModalFooterSticky) ?? $this->isModalSlideOver();
|
||||
}
|
||||
|
||||
public function isModalSlideOver(): bool
|
||||
@@ -436,6 +525,11 @@ trait CanOpenModal
|
||||
return $this->evaluate($this->isModalHidden);
|
||||
}
|
||||
|
||||
public function hasModalCloseButton(): bool
|
||||
{
|
||||
return $this->evaluate($this->hasModalCloseButton) ?? Modal::$hasCloseButton;
|
||||
}
|
||||
|
||||
public function isModalClosedByClickingAway(): bool
|
||||
{
|
||||
return $this->evaluate($this->isModalClosedByClickingAway) ?? Modal::$isClosedByClickingAway;
|
||||
@@ -467,4 +561,24 @@ trait CanOpenModal
|
||||
return StaticAction::make($name)
|
||||
->button();
|
||||
}
|
||||
|
||||
public function getModalIcon(): ?string
|
||||
{
|
||||
return $this->evaluate($this->modalIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null
|
||||
*/
|
||||
public function getModalIconColor(): string | array | null
|
||||
{
|
||||
return $this->evaluate($this->modalIconColor) ?? $this->getColor() ?? 'primary';
|
||||
}
|
||||
|
||||
public function stickyModalFooter(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isModalFooterSticky = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ trait CanRequireConfirmation
|
||||
{
|
||||
public function requiresConfirmation(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->modalSubheading(fn (MountableAction $action): ?string => $action->evaluate($condition) ? __('filament-actions::modal.confirmation') : null);
|
||||
$this->modalAlignment(fn (MountableAction $action): ?string => $action->evaluate($condition) ? 'center' : null);
|
||||
$this->modalFooterActionsAlignment(fn (MountableAction $action): ?string => $action->evaluate($condition) ? 'center' : null);
|
||||
$this->modalIcon(fn (MountableAction $action): ?string => $action->evaluate($condition) ? 'heroicon-o-exclamation-triangle' : null);
|
||||
$this->modalDescription(fn (MountableAction $action): ?string => $action->evaluate($condition) ? __('filament-actions::modal.confirmation') : null);
|
||||
$this->modalSubmitActionLabel(fn (MountableAction $action): ?string => $action->evaluate($condition) ? __('filament-actions::modal.actions.confirm.label') : null);
|
||||
$this->modalWidth(fn (MountableAction $action): ?string => $action->evaluate($condition) ? 'sm' : null);
|
||||
$this->centerModal($condition);
|
||||
$this->modalWidth(fn (MountableAction $action): ?string => $action->evaluate($condition) ? 'md' : null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -190,9 +190,9 @@ trait InteractsWithActions
|
||||
return false;
|
||||
}
|
||||
|
||||
return $action->getModalSubheading() ||
|
||||
return $action->getModalDescription() ||
|
||||
$action->getModalContent() ||
|
||||
$action->getModalFooter() ||
|
||||
$action->getModalContentFooter() ||
|
||||
$action->getInfolist() ||
|
||||
$this->mountedActionHasForm();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class DeleteAction extends Action
|
||||
|
||||
$this->requiresConfirmation();
|
||||
|
||||
$this->modalIcon('heroicon-o-trash');
|
||||
|
||||
$this->keyBindings(['mod+d']);
|
||||
|
||||
$this->hidden(static function (Model $record): bool {
|
||||
|
||||
@@ -30,6 +30,8 @@ class ForceDeleteAction extends Action
|
||||
|
||||
$this->requiresConfirmation();
|
||||
|
||||
$this->modalIcon('heroicon-o-trash');
|
||||
|
||||
$this->action(function (): void {
|
||||
$result = $this->process(static fn (Model $record) => $record->forceDelete());
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ class MountableAction extends StaticAction implements HasLivewire
|
||||
use Concerns\HasParentActions;
|
||||
use Concerns\HasWizard;
|
||||
|
||||
public static string $modalActionsAlignment = 'left';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -83,24 +81,4 @@ class MountableAction extends StaticAction implements HasLivewire
|
||||
default => parent::resolveDefaultClosureDependencyForEvaluationByName($parameterName),
|
||||
};
|
||||
}
|
||||
|
||||
public static function alignModalActionsLeft(): void
|
||||
{
|
||||
static::$modalActionsAlignment = 'left';
|
||||
}
|
||||
|
||||
public static function alignModalActionsCenter(): void
|
||||
{
|
||||
static::$modalActionsAlignment = 'center';
|
||||
}
|
||||
|
||||
public static function alignModalActionsRight(): void
|
||||
{
|
||||
static::$modalActionsAlignment = 'right';
|
||||
}
|
||||
|
||||
public static function getModalActionsAlignment(): string
|
||||
{
|
||||
return static::$modalActionsAlignment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class RestoreAction extends Action
|
||||
|
||||
$this->requiresConfirmation();
|
||||
|
||||
$this->modalIcon('heroicon-o-arrow-uturn-left');
|
||||
|
||||
$this->action(function (Model $record): void {
|
||||
if (! method_exists($record, 'restore')) {
|
||||
$this->failure();
|
||||
|
||||
@@ -60,7 +60,7 @@ class TestsActions
|
||||
public function setActionData(): Closure
|
||||
{
|
||||
return function (array $data): static {
|
||||
foreach (Arr::prependKeysWith($data, 'mountedActionsData.' . array_key_last($this->instance()->mountedActionsData) . '.') as $key => $value) {
|
||||
foreach (Arr::dot($data, prepend: 'mountedActionsData.' . array_key_last($this->instance()->mountedActionsData) . '.') as $key => $value) {
|
||||
$this->set($key, $value);
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ class TestsActions
|
||||
public function assertActionDataSet(): Closure
|
||||
{
|
||||
return function (array $data): static {
|
||||
foreach ($data as $key => $value) {
|
||||
$this->assertSet('mountedActionsData.' . array_key_last($this->instance()->mountedActionsData) . '.' . $key, $value);
|
||||
foreach (Arr::dot($data, prepend: 'mountedActionsData.' . array_key_last($this->instance()->mountedActionsData) . '.') as $key => $value) {
|
||||
$this->assertSet($key, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -46,40 +46,21 @@ First, use NPM to install Tailwind CSS and its `forms` and `typography` plugins:
|
||||
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss --save-dev
|
||||
```
|
||||
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `content` path, custom `colors`, and the `plugins` you installed:
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `preset` which configures colors and the plugins you installed:
|
||||
|
||||
```js
|
||||
import colors from 'tailwindcss/colors'
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import preset from './vendor/filament/support/tailwind.config.preset'
|
||||
|
||||
export default {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./resources/**/*.blade.php',
|
||||
'./app/Filament/**/*.php',
|
||||
'./resources/views/filament/**/*.blade.php',
|
||||
'./vendor/filament/**/*.blade.php',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
danger: colors.red,
|
||||
info: colors.blue,
|
||||
primary: colors.amber,
|
||||
secondary: colors.gray,
|
||||
success: colors.green,
|
||||
warning: colors.amber,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
forms,
|
||||
typography,
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Of course, you may specify your own custom `danger`, `gray`, `info`, `primary`, `secondary`, `success` and `warning` colors, which will be used instead. But each color needs to be a [Tailwind CSS color](https://tailwindcss.com/docs/customizing-colors#color-palette-reference), or have all 50 - 950 variants specified - a single hex code or RGB value won't work here.
|
||||
|
||||
### Configuring styles
|
||||
|
||||
In `resources/css/app.css`, import Tailwind CSS, and the form components CSS:
|
||||
@@ -172,10 +153,10 @@ composer update
|
||||
php artisan filament:upgrade
|
||||
```
|
||||
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-update-cmd` to run it automatically:
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-autoload-dump` to run it automatically:
|
||||
|
||||
```json
|
||||
"post-update-cmd": [
|
||||
"post-autoload-dump": [
|
||||
// ...
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
|
||||
@@ -180,6 +180,8 @@ export default function selectFormComponent({
|
||||
|
||||
this.select.clearStore()
|
||||
|
||||
this.refreshPlaceholder()
|
||||
|
||||
this.setChoices(choices)
|
||||
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
|
||||
@@ -58,14 +58,14 @@
|
||||
@if (filled($prefix))
|
||||
<span
|
||||
@class([
|
||||
'filament-input-affix-label -me-px',
|
||||
'filament-input-affix-label -me-px text-sm',
|
||||
$baseAffixClasses,
|
||||
'rounded-s-lg' => ! $prefixIcon,
|
||||
])
|
||||
@if (filled($statePath))
|
||||
x-bind:class="{
|
||||
'text-gray-400': ! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'text-danger-400': (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'text-danger-400': @js($statePath) in $wire.__instance.snapshot.memo.errors,
|
||||
}"
|
||||
@endif
|
||||
>
|
||||
@@ -80,14 +80,14 @@
|
||||
@if (filled($suffix))
|
||||
<span
|
||||
@class([
|
||||
'filament-input-affix-label -ms-px',
|
||||
'filament-input-affix-label -ms-px text-sm',
|
||||
$baseAffixClasses,
|
||||
'rounded-e-lg' => ! $suffixIcon,
|
||||
])
|
||||
@if (filled($statePath))
|
||||
x-bind:class="{
|
||||
'text-gray-400': ! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'text-danger-400': (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'text-danger-400': @js($statePath) in $wire.__instance.snapshot.memo.errors,
|
||||
}"
|
||||
@endif
|
||||
>
|
||||
@@ -104,7 +104,7 @@
|
||||
@if (filled($statePath))
|
||||
x-bind:class="{
|
||||
'text-gray-400': ! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'text-danger-400': (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'text-danger-400': @js($statePath) in $wire.__instance.snapshot.memo.errors,
|
||||
}"
|
||||
@endif
|
||||
>
|
||||
|
||||
@@ -86,7 +86,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => $el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }), 200)
|
||||
setTimeout(
|
||||
() =>
|
||||
$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'start',
|
||||
}),
|
||||
200,
|
||||
)
|
||||
"
|
||||
class="filament-forms-builder-component-item relative rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-800 dark:ring-white/20"
|
||||
>
|
||||
@@ -175,7 +183,10 @@
|
||||
{{ $getAction('collapse') }}
|
||||
</div>
|
||||
|
||||
<div x-show="isCollapsed" x-cloak>
|
||||
<div
|
||||
x-show="isCollapsed"
|
||||
x-cloak
|
||||
>
|
||||
{{ $getAction('expand') }}
|
||||
</div>
|
||||
</li>
|
||||
@@ -185,7 +196,10 @@
|
||||
@endif
|
||||
|
||||
<div
|
||||
x-bind:class="{ 'invisible h-0 !m-0 overflow-y-hidden': isCollapsed, 'p-6': ! isCollapsed}"
|
||||
x-bind:class="{
|
||||
'invisible h-0 !m-0 overflow-y-hidden': isCollapsed,
|
||||
'p-6': ! isCollapsed,
|
||||
}"
|
||||
>
|
||||
{{ $item }}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
x-data="{
|
||||
areAllCheckboxesChecked: false,
|
||||
|
||||
checkboxListOptions: Array.from($root.querySelectorAll('.filament-forms-checkbox-list-component-option-label')),
|
||||
checkboxListOptions: Array.from(
|
||||
$root.querySelectorAll(
|
||||
'.filament-forms-checkbox-list-component-option-label',
|
||||
),
|
||||
),
|
||||
|
||||
search: '',
|
||||
|
||||
@@ -32,7 +36,11 @@
|
||||
},
|
||||
|
||||
checkIfAllCheckboxesAreChecked: function () {
|
||||
this.areAllCheckboxesChecked = this.visibleCheckboxListOptions.length === this.visibleCheckboxListOptions.filter((checkboxLabel) => checkboxLabel.querySelector('input[type=checkbox]:checked')).length
|
||||
this.areAllCheckboxesChecked =
|
||||
this.visibleCheckboxListOptions.length ===
|
||||
this.visibleCheckboxListOptions.filter((checkboxLabel) =>
|
||||
checkboxLabel.querySelector('input[type=checkbox]:checked'),
|
||||
).length
|
||||
},
|
||||
|
||||
toggleAllCheckboxes: function () {
|
||||
@@ -49,10 +57,17 @@
|
||||
},
|
||||
|
||||
updateVisibleCheckboxListOptions: function () {
|
||||
this.visibleCheckboxListOptions = this.checkboxListOptions.filter((checkboxListItem) => {
|
||||
return checkboxListItem.querySelector('.filament-forms-checkbox-list-component-option-label-text').innerText.toLowerCase().includes(this.search.toLowerCase())
|
||||
})
|
||||
}
|
||||
this.visibleCheckboxListOptions = this.checkboxListOptions.filter(
|
||||
(checkboxListItem) => {
|
||||
return checkboxListItem
|
||||
.querySelector(
|
||||
'.filament-forms-checkbox-list-component-option-label-text',
|
||||
)
|
||||
.innerText.toLowerCase()
|
||||
.includes(this.search.toLowerCase())
|
||||
},
|
||||
)
|
||||
},
|
||||
}"
|
||||
>
|
||||
@if (! $isDisabled)
|
||||
@@ -99,10 +114,12 @@
|
||||
:two-xl="$getColumns('2xl')"
|
||||
:direction="$gridDirection ?? 'column'"
|
||||
:x-show="$isSearchable ? 'visibleCheckboxListOptions.length' : null"
|
||||
:attributes="\Filament\Support\prepare_inherited_attributes($attributes->class([
|
||||
'filament-forms-checkbox-list-component gap-1',
|
||||
'space-y-2' => $gridDirection !== 'row',
|
||||
]))"
|
||||
:attributes="
|
||||
\Filament\Support\prepare_inherited_attributes($attributes->class([
|
||||
'filament-forms-checkbox-list-component gap-1',
|
||||
'space-y-2' => $gridDirection !== 'row',
|
||||
]))
|
||||
"
|
||||
>
|
||||
@forelse ($getOptions() as $optionValue => $optionLabel)
|
||||
<div
|
||||
@@ -111,7 +128,11 @@
|
||||
<label
|
||||
class="filament-forms-checkbox-list-component-option-label flex items-center space-x-3 rtl:space-x-reverse"
|
||||
@if ($isSearchable)
|
||||
x-show="$el.querySelector('.filament-forms-checkbox-list-component-option-label-text').innerText.toLowerCase().includes(search.toLowerCase())"
|
||||
x-show="
|
||||
$el.querySelector('.filament-forms-checkbox-list-component-option-label-text')
|
||||
.innerText.toLowerCase()
|
||||
.includes(search.toLowerCase())
|
||||
"
|
||||
@endif
|
||||
>
|
||||
<input
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('color-picker', 'filament/forms') }}"
|
||||
x-data="colorPickerFormComponent({
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
isDisabled: @js($isDisabled),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }}
|
||||
})"
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
isDisabled: @js($isDisabled),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
x-on:keydown.esc="isOpen() && $event.stopPropagation()"
|
||||
{{ $getExtraAlpineAttributeBag()->class(['relative flex-1']) }}
|
||||
>
|
||||
@@ -70,7 +70,10 @@
|
||||
class="pointer-events-none absolute inset-y-0 end-0 flex items-center pe-2"
|
||||
>
|
||||
<span
|
||||
x-bind:style="{ 'background-color': state, ...(state ? { 'background-image': 'none' } : {}) }"
|
||||
x-bind:style="{
|
||||
'background-color': state,
|
||||
...(state ? { 'background-image': 'none' } : {}),
|
||||
}"
|
||||
class="filament-forms-color-picker-component-preview relative h-7 w-7 overflow-hidden rounded-md"
|
||||
></span>
|
||||
</span>
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
@if ($isNative())
|
||||
<input
|
||||
x-data="{}"
|
||||
x-bind:class="{
|
||||
'border-gray-300 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:focus:border-primary-500': ! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-danger-600 ring-danger-600 dark:border-danger-400 dark:ring-danger-400': (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
x-bind:class="
|
||||
'border-gray-300 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:focus:border-primary-500':
|
||||
! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-danger-600 ring-danger-600 dark:border-danger-400 dark:ring-danger-400':
|
||||
@js($statePath) in $wire.__instance.snapshot.memo.errors,
|
||||
}"
|
||||
{{
|
||||
$getExtraInputAttributeBag()
|
||||
@@ -59,13 +61,14 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('date-time-picker', 'filament/forms') }}"
|
||||
x-data="dateTimePickerFormComponent({
|
||||
displayFormat: '{{ convert_date_format($getDisplayFormat())->to('day.js') }}',
|
||||
firstDayOfWeek: {{ $getFirstDayOfWeek() }},
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
locale: @js(app()->getLocale()),
|
||||
shouldCloseOnDateSelection: @js($shouldCloseOnDateSelection()),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
displayFormat:
|
||||
'{{ convert_date_format($getDisplayFormat())->to('day.js') }}',
|
||||
firstDayOfWeek: {{ $getFirstDayOfWeek() }},
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
locale: @js(app()->getLocale()),
|
||||
shouldCloseOnDateSelection: @js($shouldCloseOnDateSelection()),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
x-on:keydown.esc="isOpen() && $event.stopPropagation()"
|
||||
{{
|
||||
$attributes
|
||||
@@ -93,7 +96,11 @@
|
||||
<button
|
||||
x-ref="button"
|
||||
x-on:click="togglePanelVisibility()"
|
||||
x-on:keydown.enter.stop.prevent="if (! $el.disabled) { isOpen() ? selectDate() : togglePanelVisibility() }"
|
||||
x-on:keydown.enter.stop.prevent="
|
||||
if (! $el.disabled) {
|
||||
isOpen() ? selectDate() : togglePanelVisibility()
|
||||
}
|
||||
"
|
||||
x-on:keydown.arrow-left.stop.prevent="if (! $el.disabled) focusPreviousDay()"
|
||||
x-on:keydown.arrow-right.stop.prevent="if (! $el.disabled) focusNextDay()"
|
||||
x-on:keydown.arrow-up.stop.prevent="if (! $el.disabled) focusPreviousWeek()"
|
||||
@@ -167,7 +174,9 @@
|
||||
class="grow cursor-pointer border-0 px-1 py-0 text-lg font-medium text-gray-800 outline-none focus:ring-0 dark:bg-gray-700 dark:text-gray-200"
|
||||
dusk="filament.forms.{{ $statePath }}.focusedMonth"
|
||||
>
|
||||
<template x-for="(month, index) in months">
|
||||
<template
|
||||
x-for="(month, index) in months"
|
||||
>
|
||||
<option
|
||||
x-bind:value="index"
|
||||
x-text="month"
|
||||
@@ -219,8 +228,13 @@
|
||||
x-bind:class="{
|
||||
'text-gray-700 dark:text-gray-300': ! dayIsSelected(day),
|
||||
'cursor-pointer': ! dayIsDisabled(day),
|
||||
'bg-primary-50 dark:bg-primary-100 dark:text-gray-600': dayIsToday(day) && ! dayIsSelected(day) && focusedDate.date() !== day && ! dayIsDisabled(day),
|
||||
'bg-primary-200 dark:text-gray-600': focusedDate.date() === day && ! dayIsSelected(day),
|
||||
'bg-primary-50 dark:bg-primary-100 dark:text-gray-600':
|
||||
dayIsToday(day) &&
|
||||
! dayIsSelected(day) &&
|
||||
focusedDate.date() !== day &&
|
||||
! dayIsDisabled(day),
|
||||
'bg-primary-200 dark:text-gray-600':
|
||||
focusedDate.date() === day && ! dayIsSelected(day),
|
||||
'bg-primary-500 text-white': dayIsSelected(day),
|
||||
'pointer-events-none': dayIsDisabled(day),
|
||||
'opacity-50': focusedDate.date() !== day && dayIsDisabled(day),
|
||||
|
||||
@@ -16,51 +16,56 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('file-upload', 'filament/forms') }}"
|
||||
x-data="fileUploadFormComponent({
|
||||
acceptedFileTypes: @js($getAcceptedFileTypes()),
|
||||
deleteUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.deleteUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
getUploadedFilesUsing: async () => {
|
||||
return await $wire.getFormUploadedFiles(@js($statePath))
|
||||
},
|
||||
imageCropAspectRatio: @js($imageCropAspectRatio),
|
||||
imagePreviewHeight: @js($getImagePreviewHeight()),
|
||||
imageResizeMode: @js($getImageResizeMode()),
|
||||
imageResizeTargetHeight: @js($imageResizeTargetHeight),
|
||||
imageResizeTargetWidth: @js($imageResizeTargetWidth),
|
||||
imageResizeUpscale: @js($getImageResizeUpscale()),
|
||||
isAvatar: {{ $isAvatar ? 'true' : 'false' }},
|
||||
isDownloadable: @js($isDownloadable()),
|
||||
isOpenable: @js($isOpenable()),
|
||||
isPreviewable: @js($isPreviewable()),
|
||||
isReorderable: @js($isReorderable()),
|
||||
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
|
||||
locale: @js(app()->getLocale()),
|
||||
panelAspectRatio: @js($getPanelAspectRatio()),
|
||||
panelLayout: @js($getPanelLayout()),
|
||||
placeholder: @js($getPlaceholder()),
|
||||
maxSize: {{ ($size = $getMaxSize()) ? "'{$size} KB'" : 'null' }},
|
||||
minSize: {{ ($size = $getMinSize()) ? "'{$size} KB'" : 'null' }},
|
||||
removeUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.removeFormUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
|
||||
reorderUploadedFilesUsing: async (files) => {
|
||||
return await $wire.reorderFormUploadedFiles(@js($statePath), files)
|
||||
},
|
||||
shouldAppendFiles: @js($shouldAppendFiles()),
|
||||
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
|
||||
shouldTransformImage: @js($imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
uploadButtonPosition: @js($getUploadButtonPosition()),
|
||||
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
|
||||
uploadUsing: (fileKey, file, success, error, progress) => {
|
||||
// @todo: $wire.upload() / @this.upload() does not exist yet
|
||||
$wire.upload(`{{ $statePath }}.${fileKey}`, file, () => {
|
||||
success(fileKey)
|
||||
}, error, progress)
|
||||
},
|
||||
})"
|
||||
acceptedFileTypes: @js($getAcceptedFileTypes()),
|
||||
deleteUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.deleteUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
getUploadedFilesUsing: async () => {
|
||||
return await $wire.getFormUploadedFiles(@js($statePath))
|
||||
},
|
||||
imageCropAspectRatio: @js($imageCropAspectRatio),
|
||||
imagePreviewHeight: @js($getImagePreviewHeight()),
|
||||
imageResizeMode: @js($getImageResizeMode()),
|
||||
imageResizeTargetHeight: @js($imageResizeTargetHeight),
|
||||
imageResizeTargetWidth: @js($imageResizeTargetWidth),
|
||||
imageResizeUpscale: @js($getImageResizeUpscale()),
|
||||
isAvatar: {{ $isAvatar ? 'true' : 'false' }},
|
||||
isDownloadable: @js($isDownloadable()),
|
||||
isOpenable: @js($isOpenable()),
|
||||
isPreviewable: @js($isPreviewable()),
|
||||
isReorderable: @js($isReorderable()),
|
||||
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
|
||||
locale: @js(app()->getLocale()),
|
||||
panelAspectRatio: @js($getPanelAspectRatio()),
|
||||
panelLayout: @js($getPanelLayout()),
|
||||
placeholder: @js($getPlaceholder()),
|
||||
maxSize: {{ ($size = $getMaxSize()) ? "'{$size} KB'" : 'null' }},
|
||||
minSize: {{ ($size = $getMinSize()) ? "'{$size} KB'" : 'null' }},
|
||||
removeUploadedFileUsing: async (fileKey) => {
|
||||
return await $wire.removeFormUploadedFile(@js($statePath), fileKey)
|
||||
},
|
||||
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
|
||||
reorderUploadedFilesUsing: async (files) => {
|
||||
return await $wire.reorderFormUploadedFiles(@js($statePath), files)
|
||||
},
|
||||
shouldAppendFiles: @js($shouldAppendFiles()),
|
||||
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
|
||||
shouldTransformImage: @js($imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
uploadButtonPosition: @js($getUploadButtonPosition()),
|
||||
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
|
||||
uploadUsing: (fileKey, file, success, error, progress) => {
|
||||
$wire.upload(
|
||||
`{{ $statePath }}.${fileKey}`,
|
||||
file,
|
||||
() => {
|
||||
success(fileKey)
|
||||
},
|
||||
error,
|
||||
progress,
|
||||
)
|
||||
},
|
||||
})"
|
||||
wire:ignore
|
||||
{{
|
||||
$attributes
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('key-value', 'filament/forms') }}"
|
||||
x-data="keyValueFormComponent({
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
wire:ignore
|
||||
{{
|
||||
$attributes
|
||||
@@ -115,7 +115,9 @@
|
||||
@endif
|
||||
|
||||
@if ($deleteAction)
|
||||
<div x-on:click="deleteRow(index)">
|
||||
<div
|
||||
x-on:click="deleteRow(index)"
|
||||
>
|
||||
{{ $deleteAction }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -10,22 +10,24 @@
|
||||
ax-load="visible"
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('markdown-editor', 'filament/forms') }}"
|
||||
x-data="markdownEditorFormComponent({
|
||||
placeholder: @js($getPlaceholder()),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
toolbarButtons: @js($getToolbarButtons()),
|
||||
translations: @js(__('filament-forms::components.markdown_editor')),
|
||||
uploadFileAttachmentUsing: async (file, onSuccess, onError) => {
|
||||
$wire.upload(`componentFileAttachments.{{ $statePath }}`, file, () => {
|
||||
$wire.getFormComponentFileAttachmentUrl('{{ $statePath }}').then((url) => {
|
||||
if (! url) {
|
||||
return onError()
|
||||
}
|
||||
placeholder: @js($getPlaceholder()),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
toolbarButtons: @js($getToolbarButtons()),
|
||||
translations: @js(__('filament-forms::components.markdown_editor')),
|
||||
uploadFileAttachmentUsing: async (file, onSuccess, onError) => {
|
||||
$wire.upload(`componentFileAttachments.{{ $statePath }}`, file, () => {
|
||||
$wire
|
||||
.getFormComponentFileAttachmentUrl('{{ $statePath }}')
|
||||
.then((url) => {
|
||||
if (! url) {
|
||||
return onError()
|
||||
}
|
||||
|
||||
onSuccess(url)
|
||||
})
|
||||
})
|
||||
},
|
||||
})"
|
||||
onSuccess(url)
|
||||
})
|
||||
})
|
||||
},
|
||||
})"
|
||||
wire:ignore
|
||||
{{
|
||||
$attributes
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
:two-xl="$getColumns('2xl')"
|
||||
:is-grid="! $isInline"
|
||||
direction="column"
|
||||
:attributes="\Filament\Support\prepare_inherited_attributes($attributes->merge($getExtraAttributes(), escape: false)->class([
|
||||
'filament-forms-radio-component flex flex-wrap gap-3',
|
||||
'flex-col' => ! $isInline,
|
||||
]))"
|
||||
:attributes="
|
||||
\Filament\Support\prepare_inherited_attributes($attributes->merge($getExtraAttributes(), escape: false)->class([
|
||||
'filament-forms-radio-component flex flex-wrap gap-3',
|
||||
'flex-col' => ! $isInline,
|
||||
]))
|
||||
"
|
||||
>
|
||||
@foreach ($getOptions() as $value => $label)
|
||||
@php
|
||||
|
||||
@@ -80,7 +80,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => $el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }), 200)
|
||||
setTimeout(
|
||||
() =>
|
||||
$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'start',
|
||||
}),
|
||||
200,
|
||||
)
|
||||
"
|
||||
class="filament-forms-repeater-component-item relative rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-800 dark:ring-white/20"
|
||||
>
|
||||
@@ -164,7 +172,10 @@
|
||||
@endif
|
||||
|
||||
<div
|
||||
x-bind:class="{ 'invisible h-0 !m-0 overflow-y-hidden': isCollapsed, 'p-6': ! isCollapsed}"
|
||||
x-bind:class="{
|
||||
'invisible h-0 !m-0 overflow-y-hidden': isCollapsed,
|
||||
'p-6': ! isCollapsed,
|
||||
}"
|
||||
>
|
||||
{{ $item }}
|
||||
</div>
|
||||
|
||||
@@ -9,22 +9,28 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('rich-editor', 'filament/forms') }}"
|
||||
x-data="richEditorFormComponent({
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
x-on:trix-change="state = $event.target.value"
|
||||
x-on:trix-attachment-add="
|
||||
if (! $event.attachment.file) return
|
||||
|
||||
let attachment = $event.attachment
|
||||
|
||||
$wire.upload(`componentFileAttachments.{{ $statePath }}`, attachment.file, () => {
|
||||
$wire.getFormComponentFileAttachmentUrl('{{ $statePath }}').then((url) => {
|
||||
attachment.setAttributes({
|
||||
url: url,
|
||||
href: url,
|
||||
})
|
||||
})
|
||||
})
|
||||
$wire.upload(
|
||||
`componentFileAttachments.{{ $statePath }}`,
|
||||
attachment.file,
|
||||
() => {
|
||||
$wire
|
||||
.getFormComponentFileAttachmentUrl('{{ $statePath }}')
|
||||
.then((url) => {
|
||||
attachment.setAttributes({
|
||||
url: url,
|
||||
href: url,
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
"
|
||||
x-on:trix-file-accept="
|
||||
if ({{ $hasToolbarButton('attachFiles') ? 'true' : 'false' }}) return
|
||||
@@ -116,10 +122,7 @@
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
@class([
|
||||
'h-4',
|
||||
'dark:fill-current' => config('forms.dark_mode'),
|
||||
])
|
||||
class="h-4 dark:fill-current"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fas"
|
||||
@@ -465,8 +468,10 @@
|
||||
dusk="filament.forms.{{ $statePath }}"
|
||||
{{ $getExtraInputAttributeBag()->class(['prose block w-full max-w-none break-words rounded-lg bg-white shadow-sm outline-none transition duration-75 dark:prose-invert focus:ring-1 focus:ring-inset dark:bg-gray-700']) }}
|
||||
x-bind:class="{
|
||||
'border-gray-300 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:focus:border-primary-500': ! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-danger-600 ring-danger-600 dark:border-danger-400 dark:ring-danger-400': (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-gray-300 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:focus:border-primary-500':
|
||||
! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-danger-600 ring-danger-600 dark:border-danger-400 dark:ring-danger-400':
|
||||
@js($statePath) in $wire.__instance.snapshot.memo.errors,
|
||||
}"
|
||||
></trix-editor>
|
||||
@else
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
:disabled="$isDisabled"
|
||||
:id="$getId()"
|
||||
dusk="filament.forms.{{ $statePath }}"
|
||||
:required="$isRequired() && (! ! $isConcealed())"
|
||||
:attributes="\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag()->merge([
|
||||
$applyStateBindingModifiers('wire:model') => $statePath,
|
||||
], escape: false))"
|
||||
:required="$isRequired() && ((bool) $isConcealed())"
|
||||
:attributes="
|
||||
\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag()->merge([
|
||||
$applyStateBindingModifiers('wire:model') => $statePath,
|
||||
], escape: false))
|
||||
"
|
||||
:error="$errors->has($statePath)"
|
||||
:prefix="$hasPrefix"
|
||||
:suffix="$hasSuffix"
|
||||
@@ -70,45 +72,45 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('select', 'filament/forms') }}"
|
||||
x-data="selectFormComponent({
|
||||
canSelectPlaceholder: @js($canSelectPlaceholder()),
|
||||
isHtmlAllowed: @js($isHtmlAllowed()),
|
||||
getOptionLabelUsing: async () => {
|
||||
return await $wire.getFormSelectOptionLabel(@js($statePath))
|
||||
},
|
||||
getOptionLabelsUsing: async () => {
|
||||
return await $wire.getFormSelectOptionLabels(@js($statePath))
|
||||
},
|
||||
getOptionsUsing: async () => {
|
||||
return await $wire.getFormSelectOptions(@js($statePath))
|
||||
},
|
||||
getSearchResultsUsing: async (search) => {
|
||||
return await $wire.getFormSelectSearchResults(@js($statePath), search)
|
||||
},
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
isDisabled: @js($isDisabled),
|
||||
isMultiple: @js($isMultiple()),
|
||||
livewireId: @js($this->getId()),
|
||||
hasDynamicOptions: @js($hasDynamicOptions()),
|
||||
hasDynamicSearchResults: @js($hasDynamicSearchResults()),
|
||||
loadingMessage: @js($getLoadingMessage()),
|
||||
maxItems: @js($getMaxItems()),
|
||||
maxItemsMessage: @js($getMaxItemsMessage()),
|
||||
noSearchResultsMessage: @js($getNoSearchResultsMessage()),
|
||||
options: @js($getOptionsForJs()),
|
||||
optionsLimit: @js($getOptionsLimit()),
|
||||
placeholder: @js($getPlaceholder()),
|
||||
position: @js($getPosition()),
|
||||
searchDebounce: @js($getSearchDebounce()),
|
||||
searchingMessage: @js($getSearchingMessage()),
|
||||
searchPrompt: @js($getSearchPrompt()),
|
||||
searchableOptionFields: @js($getSearchableOptionFields()),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
statePath: @js($statePath),
|
||||
})"
|
||||
canSelectPlaceholder: @js($canSelectPlaceholder()),
|
||||
isHtmlAllowed: @js($isHtmlAllowed()),
|
||||
getOptionLabelUsing: async () => {
|
||||
return await $wire.getFormSelectOptionLabel(@js($statePath))
|
||||
},
|
||||
getOptionLabelsUsing: async () => {
|
||||
return await $wire.getFormSelectOptionLabels(@js($statePath))
|
||||
},
|
||||
getOptionsUsing: async () => {
|
||||
return await $wire.getFormSelectOptions(@js($statePath))
|
||||
},
|
||||
getSearchResultsUsing: async (search) => {
|
||||
return await $wire.getFormSelectSearchResults(@js($statePath), search)
|
||||
},
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
isDisabled: @js($isDisabled),
|
||||
isMultiple: @js($isMultiple()),
|
||||
livewireId: @js($this->id),
|
||||
hasDynamicOptions: @js($hasDynamicOptions()),
|
||||
hasDynamicSearchResults: @js($hasDynamicSearchResults()),
|
||||
loadingMessage: @js($getLoadingMessage()),
|
||||
maxItems: @js($getMaxItems()),
|
||||
maxItemsMessage: @js($getMaxItemsMessage()),
|
||||
noSearchResultsMessage: @js($getNoSearchResultsMessage()),
|
||||
options: @js($getOptionsForJs()),
|
||||
optionsLimit: @js($getOptionsLimit()),
|
||||
placeholder: @js($getPlaceholder()),
|
||||
position: @js($getPosition()),
|
||||
searchDebounce: @js($getSearchDebounce()),
|
||||
searchingMessage: @js($getSearchingMessage()),
|
||||
searchPrompt: @js($getSearchPrompt()),
|
||||
searchableOptionFields: @js($getSearchableOptionFields()),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
statePath: @js($statePath),
|
||||
})"
|
||||
x-on:keydown.esc="select.dropdown.isActive && $event.stopPropagation()"
|
||||
wire:ignore
|
||||
x-bind:class="{
|
||||
'choices--error': (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'choices--error': @js($statePath) in $wire.__instance.snapshot.memo.errors,
|
||||
}"
|
||||
{{
|
||||
$attributes
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<div
|
||||
x-data="{
|
||||
|
||||
tab: null,
|
||||
|
||||
init: function () {
|
||||
@@ -23,7 +22,6 @@
|
||||
|
||||
history.pushState(null, document.title, url.toString())
|
||||
},
|
||||
|
||||
}"
|
||||
x-cloak
|
||||
wire:ignore.self
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
@endphp
|
||||
|
||||
<div
|
||||
x-bind:class="{ 'invisible h-0 p-0 overflow-y-hidden': tab !== '{{ $id }}', 'p-6': tab === '{{ $id }}' }"
|
||||
x-bind:class="{
|
||||
'invisible h-0 p-0 overflow-y-hidden': tab !== '{{ $id }}',
|
||||
'p-6': tab === '{{ $id }}',
|
||||
}"
|
||||
x-on:expand-concealing-component.window="
|
||||
error = $el.querySelector('[data-validation-error]')
|
||||
|
||||
@@ -17,7 +20,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => $el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }), 200)
|
||||
setTimeout(
|
||||
() =>
|
||||
$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'start',
|
||||
}),
|
||||
200,
|
||||
)
|
||||
"
|
||||
{{
|
||||
$attributes
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('tags-input', 'filament/forms') }}"
|
||||
x-data="tagsInputFormComponent({
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
})"
|
||||
{{
|
||||
$attributes
|
||||
->merge($getExtraAttributes(), escape: false)
|
||||
@@ -38,28 +38,30 @@
|
||||
@if ($placeholder = $getPlaceholder()) placeholder="{{ $placeholder }}" @endif
|
||||
type="text"
|
||||
dusk="filament.forms.{{ $statePath }}"
|
||||
x-on:keydown="() => {
|
||||
if (['Enter', ...@js($splitKeys)].includes($event.key)) {
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
x-on:keydown="
|
||||
() => {
|
||||
if (['Enter', ...@js($splitKeys)].includes($event.key)) {
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
|
||||
createTag()
|
||||
createTag()
|
||||
}
|
||||
}
|
||||
}"
|
||||
"
|
||||
x-on:blur="createTag()"
|
||||
x-on:paste="$nextTick(() => {
|
||||
const pattern = @js($splitKeys)
|
||||
.map((key) => key.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'))
|
||||
.join('|')
|
||||
x-on:paste="
|
||||
$nextTick(() => {
|
||||
const pattern = @js($splitKeys).map((key) =>
|
||||
key.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
).join('|')
|
||||
|
||||
newTag
|
||||
.split(new RegExp(pattern, 'g'))
|
||||
.forEach((tag) => {
|
||||
newTag.split(new RegExp(pattern, 'g')).forEach((tag) => {
|
||||
newTag = tag
|
||||
|
||||
createTag()
|
||||
})
|
||||
})"
|
||||
})
|
||||
"
|
||||
x-model="newTag"
|
||||
{{ $getExtraInputAttributeBag()->class(['webkit-calendar-picker-indicator:opacity-0 block w-full border-0 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400 sm:text-sm']) }}
|
||||
/>
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
ax-load
|
||||
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('text-input', 'filament/forms') }}"
|
||||
x-data="textInputFormComponent({
|
||||
getMaskOptionsUsing: (IMask) => ({{ $getJsonMaskConfiguration() }}),
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')", lazilyEntangledModifiers: ['defer']) }},
|
||||
})"
|
||||
getMaskOptionsUsing: (IMask) => {{ $getJsonMaskConfiguration() }},
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')", lazilyEntangledModifiers: ['defer']) }},
|
||||
})"
|
||||
wire:ignore
|
||||
@if ($isDebounced()) x-on:input.debounce.{{ $getDebounce() }}="$wire.$refresh" @endif
|
||||
@if ($isLazy()) x-on:blur="$wire.$refresh" @endif
|
||||
@@ -38,8 +38,10 @@
|
||||
x-data="{}"
|
||||
@endif
|
||||
x-bind:class="{
|
||||
'border-gray-300 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:focus:border-primary-500': ! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-danger-600 ring-danger-600 dark:border-danger-400 dark:ring-danger-400': (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-gray-300 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:focus:border-primary-500':
|
||||
! (@js($statePath) in $wire.__instance.snapshot.memo.errors),
|
||||
'border-danger-600 ring-danger-600 dark:border-danger-400 dark:ring-danger-400':
|
||||
@js($statePath) in $wire.__instance.snapshot.memo.errors,
|
||||
}"
|
||||
{{
|
||||
$getExtraInputAttributeBag()
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
@capture($content)
|
||||
<button
|
||||
x-data="{ state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }} }"
|
||||
x-data="{
|
||||
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
|
||||
}"
|
||||
x-bind:aria-checked="state?.toString()"
|
||||
x-on:click="state = ! state"
|
||||
x-bind:class="
|
||||
@@ -66,10 +68,12 @@
|
||||
<x-filament::icon
|
||||
:name="$getOffIcon()"
|
||||
alias="forms::components.toggle.off"
|
||||
:color="match ($offColor) {
|
||||
'gray' => 'text-gray-400 dark:text-gray-700',
|
||||
default => 'text-custom-600',
|
||||
}"
|
||||
:color="
|
||||
match ($offColor) {
|
||||
'gray' => 'text-gray-400 dark:text-gray-700',
|
||||
default => 'text-custom-600',
|
||||
}
|
||||
"
|
||||
size="h-3 w-3"
|
||||
/>
|
||||
@endif
|
||||
@@ -87,10 +91,12 @@
|
||||
<x-filament::icon
|
||||
:name="$getOnIcon()"
|
||||
alias="forms::components.toggle.on"
|
||||
:color="match ($onColor) {
|
||||
'gray' => 'text-gray-400 dark:text-gray-700',
|
||||
default => 'text-custom-600',
|
||||
}"
|
||||
:color="
|
||||
match ($onColor) {
|
||||
'gray' => 'text-gray-400 dark:text-gray-700',
|
||||
default => 'text-custom-600',
|
||||
}
|
||||
"
|
||||
size="h-3 w-3"
|
||||
x-cloak="x-cloak"
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
|
||||
step: null,
|
||||
|
||||
init: function () {
|
||||
@@ -44,7 +43,11 @@
|
||||
},
|
||||
|
||||
autofocusFields: function () {
|
||||
$nextTick(() => this.$refs[`step-${this.step}`].querySelector('[autofocus]')?.focus())
|
||||
$nextTick(() =>
|
||||
this.$refs[`step-${this.step}`]
|
||||
.querySelector('[autofocus]')
|
||||
?.focus(),
|
||||
)
|
||||
},
|
||||
|
||||
getStepIndex: function (step) {
|
||||
@@ -60,11 +63,11 @@
|
||||
},
|
||||
|
||||
isLastStep: function () {
|
||||
return (this.getStepIndex(this.step) + 1) >= this.getSteps().length
|
||||
return this.getStepIndex(this.step) + 1 >= this.getSteps().length
|
||||
},
|
||||
|
||||
isStepAccessible: function(step, index) {
|
||||
return @js($isSkippable()) || (this.getStepIndex(step) > index)
|
||||
isStepAccessible: function (step, index) {
|
||||
return @js($isSkippable()) || this.getStepIndex(step) > index
|
||||
},
|
||||
|
||||
updateQueryString: function () {
|
||||
@@ -77,7 +80,6 @@
|
||||
|
||||
history.pushState(null, document.title, url.toString())
|
||||
},
|
||||
|
||||
}"
|
||||
x-on:next-wizard-step.window="if ($event.detail.statePath === '{{ $statePath }}') nextStep()"
|
||||
x-cloak
|
||||
@@ -125,7 +127,8 @@
|
||||
<div
|
||||
x-bind:class="{
|
||||
'bg-primary-600': getStepIndex(step) === {{ $loop->index }},
|
||||
'bg-transparent group-hover:bg-gray-200 dark:group-hover:bg-gray-600': getStepIndex(step) > {{ $loop->index }},
|
||||
'bg-transparent group-hover:bg-gray-200 dark:group-hover:bg-gray-600':
|
||||
getStepIndex(step) > {{ $loop->index }},
|
||||
}"
|
||||
class="absolute start-0 top-0 h-full w-1 md:bottom-0 md:top-auto md:h-1 md:w-full"
|
||||
aria-hidden="true"
|
||||
@@ -140,7 +143,8 @@
|
||||
'bg-primary-600': getStepIndex(step) > {{ $loop->index }},
|
||||
'border-2': getStepIndex(step) <= {{ $loop->index }},
|
||||
'border-primary-500': getStepIndex(step) === {{ $loop->index }},
|
||||
'border-gray-300 dark:border-gray-500': getStepIndex(step) < {{ $loop->index }},
|
||||
'border-gray-300 dark:border-gray-500':
|
||||
getStepIndex(step) < {{ $loop->index }},
|
||||
}"
|
||||
class="filament-forms-wizard-component-header-step-icon flex h-10 w-10 items-center justify-center rounded-full"
|
||||
>
|
||||
@@ -169,7 +173,8 @@
|
||||
<span
|
||||
x-show="getStepIndex(step) <= {{ $loop->index }}"
|
||||
x-bind:class="{
|
||||
'text-gray-500 dark:text-gray-400': getStepIndex(step) !== {{ $loop->index }},
|
||||
'text-gray-500 dark:text-gray-400':
|
||||
getStepIndex(step) !== {{ $loop->index }},
|
||||
'text-primary-500': getStepIndex(step) === {{ $loop->index }},
|
||||
}"
|
||||
>
|
||||
@@ -228,7 +233,11 @@
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div x-on:click="previousStep" x-show="! isFirstStep()" x-cloak>
|
||||
<div
|
||||
x-on:click="previousStep"
|
||||
x-show="! isFirstStep()"
|
||||
x-cloak
|
||||
>
|
||||
{{ $getAction('previous') }}
|
||||
</div>
|
||||
|
||||
@@ -239,7 +248,13 @@
|
||||
|
||||
<div>
|
||||
<div
|
||||
x-on:click="$wire.dispatchFormEvent('wizard::nextStep', '{{ $statePath }}', getStepIndex(step))"
|
||||
x-on:click="
|
||||
$wire.dispatchFormEvent(
|
||||
'wizard::nextStep',
|
||||
'{{ $statePath }}',
|
||||
getStepIndex(step),
|
||||
)
|
||||
"
|
||||
x-show="! isLastStep()"
|
||||
x-cloak
|
||||
>
|
||||
|
||||
@@ -22,7 +22,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => $el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }), 200)
|
||||
setTimeout(
|
||||
() =>
|
||||
$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'start',
|
||||
}),
|
||||
200,
|
||||
)
|
||||
"
|
||||
{{
|
||||
$attributes
|
||||
|
||||
@@ -170,9 +170,9 @@ trait HasFormComponentActions
|
||||
return false;
|
||||
}
|
||||
|
||||
return $action->getModalSubheading() ||
|
||||
return $action->getModalDescription() ||
|
||||
$action->getModalContent() ||
|
||||
$action->getModalFooter() ||
|
||||
$action->getModalContentFooter() ||
|
||||
$action->getInfolist() ||
|
||||
$this->mountedFormComponentActionHasForm();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use Filament\Forms\ComponentContainer;
|
||||
use Filament\Forms\Components\Field;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Testing\Assert;
|
||||
use Livewire\Features\SupportUnitTesting\Testable;
|
||||
|
||||
@@ -30,8 +31,8 @@ class TestsForms
|
||||
|
||||
$formStatePath = $form->getStatePath();
|
||||
|
||||
foreach ($state as $key => $value) {
|
||||
$this->set((filled($formStatePath) ? "{$formStatePath}.{$key}" : $key), $value);
|
||||
foreach (Arr::dot($state, prepend: filled($formStatePath) ? "{$formStatePath}." : '') as $key => $value) {
|
||||
$this->set($key, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -51,8 +52,8 @@ class TestsForms
|
||||
|
||||
$formStatePath = $form->getStatePath();
|
||||
|
||||
foreach ($state as $key => $value) {
|
||||
$this->assertSet((filled($formStatePath) ? "{$formStatePath}.{$key}" : $key), $value);
|
||||
foreach (Arr::dot($state, prepend: filled($formStatePath) ? "{$formStatePath}." : '') as $key => $value) {
|
||||
$this->assertSet($key, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -46,40 +46,21 @@ First, use NPM to install Tailwind CSS and its `forms` and `typography` plugins:
|
||||
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss --save-dev
|
||||
```
|
||||
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `content` path, custom `colors`, and the `plugins` you installed:
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `preset` which configures colors and the plugins you installed:
|
||||
|
||||
```js
|
||||
import colors from 'tailwindcss/colors'
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import preset from './vendor/filament/support/tailwind.config.preset'
|
||||
|
||||
export default {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./resources/**/*.blade.php',
|
||||
'./app/Filament/**/*.php',
|
||||
'./resources/views/filament/**/*.blade.php',
|
||||
'./vendor/filament/**/*.blade.php',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
danger: colors.red,
|
||||
info: colors.blue,
|
||||
primary: colors.amber,
|
||||
secondary: colors.gray,
|
||||
success: colors.green,
|
||||
warning: colors.amber,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
forms,
|
||||
typography,
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Of course, you may specify your own custom `danger`, `gray`, `info`, `primary`, `secondary`, `success` and `warning` colors, which will be used instead. But each color needs to be a [Tailwind CSS color](https://tailwindcss.com/docs/customizing-colors#color-palette-reference), or have all 50 - 950 variants specified - a single hex code or RGB value won't work here.
|
||||
|
||||
### Configuring styles
|
||||
|
||||
In `resources/css/app.css`, import Tailwind CSS, and the Filament forms CSS, which is used for action modal forms:
|
||||
@@ -174,10 +155,10 @@ composer update
|
||||
php artisan filament:upgrade
|
||||
```
|
||||
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-update-cmd` to run it automatically:
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-autoload-dump` to run it automatically:
|
||||
|
||||
```json
|
||||
"post-update-cmd": [
|
||||
"post-autoload-dump": [
|
||||
// ...
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
|
||||
@@ -14,14 +14,16 @@
|
||||
:name="$icon"
|
||||
alias="infolists::entries.icon"
|
||||
color="text-custom-500"
|
||||
:size="match ($size = ($getSize($state) ?? 'lg')) {
|
||||
'xs' => 'h-3 w-3 filament-infolists-icon-entry-icon-size-xs',
|
||||
'sm' => 'h-4 w-4 filament-infolists-icon-entry-icon-size-sm',
|
||||
'md' => 'h-5 w-5 filament-infolists-icon-entry-icon-size-md',
|
||||
'lg' => 'h-6 w-6 filament-infolists-icon-entry-icon-size-lg',
|
||||
'xl' => 'h-7 w-7 filament-infolists-icon-entry-icon-size-xl',
|
||||
default => $size,
|
||||
}"
|
||||
:size="
|
||||
match ($size = ($getSize($state) ?? 'lg')) {
|
||||
'xs' => 'h-3 w-3 filament-infolists-icon-entry-icon-size-xs',
|
||||
'sm' => 'h-4 w-4 filament-infolists-icon-entry-icon-size-sm',
|
||||
'md' => 'h-5 w-5 filament-infolists-icon-entry-icon-size-md',
|
||||
'lg' => 'h-6 w-6 filament-infolists-icon-entry-icon-size-lg',
|
||||
'xl' => 'h-7 w-7 filament-infolists-icon-entry-icon-size-xl',
|
||||
default => $size,
|
||||
}
|
||||
"
|
||||
:style="\Filament\Support\get_color_css_variables($getColor($state), shades: [500])"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<div
|
||||
x-data="{
|
||||
|
||||
tab: null,
|
||||
|
||||
init: function () {
|
||||
this.$watch('tab', () => this.updateQueryString())
|
||||
|
||||
this.tab = @js(collect($getChildComponentContainer()->getComponents())
|
||||
->filter(static fn (\Filament\Infolists\Components\Tabs\Tab $tab): bool => $tab->isVisible())
|
||||
->get($getActiveTab() - 1)
|
||||
->getId())
|
||||
->filter(static fn (\Filament\Infolists\Components\Tabs\Tab $tab): bool => $tab->isVisible())
|
||||
->get($getActiveTab() - 1)
|
||||
->getId())
|
||||
},
|
||||
|
||||
updateQueryString: function () {
|
||||
@@ -22,7 +21,6 @@
|
||||
|
||||
history.pushState(null, document.title, url.toString())
|
||||
},
|
||||
|
||||
}"
|
||||
x-cloak
|
||||
{{
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
@endphp
|
||||
|
||||
<div
|
||||
x-bind:class="{ 'invisible h-0 p-0 overflow-y-hidden': tab !== '{{ $id }}', 'p-6': tab === '{{ $id }}' }"
|
||||
x-bind:class="{
|
||||
'invisible h-0 p-0 overflow-y-hidden': tab !== '{{ $id }}',
|
||||
'p-6': tab === '{{ $id }}',
|
||||
}"
|
||||
{{
|
||||
$attributes
|
||||
->merge([
|
||||
|
||||
@@ -81,14 +81,15 @@
|
||||
default => 'text-custom-600',
|
||||
} => ! ($isBadge || $url),
|
||||
($isProse ? match ($size = $getSize($state)) {
|
||||
'sm' => 'prose-sm',
|
||||
'base', 'md', null => 'prose-base',
|
||||
'xs' => 'prose-xs',
|
||||
'sm', null => 'prose-sm',
|
||||
'base', 'md' => 'prose-base',
|
||||
'lg' => 'prose-lg',
|
||||
default => $size,
|
||||
} : match ($size = ($isBadge ? 'sm' : $getSize($state))) {
|
||||
'xs' => 'text-xs',
|
||||
'sm' => 'text-sm',
|
||||
'base', 'md', null => 'text-base',
|
||||
'sm', null => 'text-sm',
|
||||
'base', 'md' => 'text-base',
|
||||
'lg' => 'text-lg',
|
||||
default => $size,
|
||||
}),
|
||||
@@ -137,6 +138,8 @@
|
||||
@endif
|
||||
@class([
|
||||
'inline-block',
|
||||
'[&>*:first-child]:mt-0 [&>*:last-child]:mb-0' => $isProse,
|
||||
'pt-2' => $isProse && (! $isLabelHidden()),
|
||||
'cursor-pointer' => $isCopyable,
|
||||
])
|
||||
>
|
||||
|
||||
@@ -198,9 +198,9 @@ trait InteractsWithInfolists
|
||||
return false;
|
||||
}
|
||||
|
||||
return $action->getModalSubheading() ||
|
||||
return $action->getModalDescription() ||
|
||||
$action->getModalContent() ||
|
||||
$action->getModalFooter() ||
|
||||
$action->getModalContentFooter() ||
|
||||
$action->getInfolist() ||
|
||||
$this->mountedInfolistActionHasForm();
|
||||
}
|
||||
|
||||
@@ -46,40 +46,21 @@ First, use NPM to install Tailwind CSS and its `forms` and `typography` plugins:
|
||||
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss --save-dev
|
||||
```
|
||||
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `content` path, custom `colors`, and the `plugins` you installed:
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `preset` which configures colors and the plugins you installed:
|
||||
|
||||
```js
|
||||
import colors from 'tailwindcss/colors'
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import preset from './vendor/filament/support/tailwind.config.preset'
|
||||
|
||||
export default {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./resources/**/*.blade.php',
|
||||
'./app/Filament/**/*.php',
|
||||
'./resources/views/filament/**/*.blade.php',
|
||||
'./vendor/filament/**/*.blade.php',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
danger: colors.red,
|
||||
info: colors.blue,
|
||||
primary: colors.amber,
|
||||
secondary: colors.gray,
|
||||
success: colors.green,
|
||||
warning: colors.amber,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
forms,
|
||||
typography,
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Of course, you may specify your own custom `danger`, `gray`, `info`, `primary`, `secondary`, `success` and `warning` colors, which will be used instead. But each color needs to be a [Tailwind CSS color](https://tailwindcss.com/docs/customizing-colors#color-palette-reference), or have all 50 - 950 variants specified - a single hex code or RGB value won't work here.
|
||||
|
||||
### Configuring styles
|
||||
|
||||
In `resources/css/app.css`, import Tailwind CSS:
|
||||
@@ -172,10 +153,10 @@ composer update
|
||||
php artisan filament:upgrade
|
||||
```
|
||||
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-update-cmd` to run it automatically:
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-autoload-dump` to run it automatically:
|
||||
|
||||
```json
|
||||
"post-update-cmd": [
|
||||
"post-autoload-dump": [
|
||||
// ...
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
|
||||
@@ -56,7 +56,7 @@ Markdown text will automatically be rendered if passed to the title.
|
||||
|
||||
## Setting an 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 `gray` 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.
|
||||
Optionally, a notification can have an [icon](https://blade-ui-kit.com/blade-icons?set=1#search) that's displayed in front of its content. You may also set a color for the icon, which is gray by default:
|
||||
|
||||
```php
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
x-data="{}"
|
||||
x-init="
|
||||
window.addEventListener('EchoLoaded', () => {
|
||||
window.Echo.private(@js($channel))
|
||||
.listen('.database-notifications.sent', () => {
|
||||
setTimeout(() => $wire.call('$refresh'), 500)
|
||||
})
|
||||
window.Echo.private(@js($channel)).listen('.database-notifications.sent', () => {
|
||||
setTimeout(() => $wire.call('$refresh'), 500)
|
||||
})
|
||||
})
|
||||
"
|
||||
{{ $attributes }}
|
||||
|
||||
+23
-25
@@ -3,37 +3,35 @@
|
||||
'unreadNotificationsCount',
|
||||
])
|
||||
|
||||
<div {{ $attributes }}>
|
||||
@if ($notifications->count())
|
||||
<div class="mt-2 text-sm">
|
||||
@if ($unreadNotificationsCount)
|
||||
<x-filament::link
|
||||
wire:click="markAllNotificationsAsRead"
|
||||
color="gray"
|
||||
tag="button"
|
||||
tabindex="-1"
|
||||
wire:target="markAllNotificationsAsRead"
|
||||
wire:loading.attr="disabled"
|
||||
class="disabled:pointer-events-none disabled:opacity-70"
|
||||
>
|
||||
{{ __('filament-notifications::database.modal.buttons.mark_all_as_read.label') }}
|
||||
</x-filament::link>
|
||||
|
||||
<span>•</span>
|
||||
@endif
|
||||
|
||||
<div {{ $attributes->class('mt-2') }}>
|
||||
<div class="mt-2 text-sm">
|
||||
@if ($unreadNotificationsCount)
|
||||
<x-filament::link
|
||||
wire:click="clearNotifications"
|
||||
x-on:click="close()"
|
||||
wire:click="markAllNotificationsAsRead"
|
||||
color="gray"
|
||||
tag="button"
|
||||
tabindex="-1"
|
||||
wire:target="clearNotifications"
|
||||
wire:target="markAllNotificationsAsRead"
|
||||
wire:loading.attr="disabled"
|
||||
class="disabled:pointer-events-none disabled:opacity-70"
|
||||
>
|
||||
{{ __('filament-notifications::database.modal.buttons.clear.label') }}
|
||||
{{ __('filament-notifications::database.modal.buttons.mark_all_as_read.label') }}
|
||||
</x-filament::link>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<span>•</span>
|
||||
@endif
|
||||
|
||||
<x-filament::link
|
||||
wire:click="clearNotifications"
|
||||
x-on:click="close()"
|
||||
color="gray"
|
||||
tag="button"
|
||||
tabindex="-1"
|
||||
wire:target="clearNotifications"
|
||||
wire:loading.attr="disabled"
|
||||
class="disabled:pointer-events-none disabled:opacity-70"
|
||||
>
|
||||
{{ __('filament-notifications::database.modal.buttons.clear.label') }}
|
||||
</x-filament::link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,19 +11,19 @@
|
||||
>
|
||||
@if ($notifications->count())
|
||||
<x-slot name="header">
|
||||
<x-filament-notifications::database.modal.heading
|
||||
:unread-notifications-count="$unreadNotificationsCount"
|
||||
/>
|
||||
<div>
|
||||
<x-filament-notifications::database.modal.heading
|
||||
:unread-notifications-count="$unreadNotificationsCount"
|
||||
/>
|
||||
|
||||
<x-filament-notifications::database.modal.actions
|
||||
:notifications="$notifications"
|
||||
:unread-notifications-count="$unreadNotificationsCount"
|
||||
/>
|
||||
<x-filament-notifications::database.modal.actions
|
||||
:notifications="$notifications"
|
||||
:unread-notifications-count="$unreadNotificationsCount"
|
||||
/>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div
|
||||
class="-mx-6 mt-[calc(-1rem-1px)] divide-y border-b dark:border-gray-700"
|
||||
>
|
||||
<div class="-mx-6 divide-y border-b dark:border-gray-700">
|
||||
@foreach ($notifications as $notification)
|
||||
<div
|
||||
@class([
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
x-data="{}"
|
||||
x-init="
|
||||
window.addEventListener('EchoLoaded', () => {
|
||||
window.Echo.private(@js($channel))
|
||||
.notification((notification) => {
|
||||
setTimeout(() => $wire.handleBroadcastNotification(notification), 500)
|
||||
})
|
||||
window.Echo.private(@js($channel)).notification((notification) => {
|
||||
setTimeout(() => $wire.handleBroadcastNotification(notification), 500)
|
||||
})
|
||||
})
|
||||
|
||||
if (window.Echo) {
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
:name="$name"
|
||||
alias="notifications::notification"
|
||||
color="text-custom-400"
|
||||
:size="match ($size) {
|
||||
'sm' => 'h-4 w-4',
|
||||
'md' => 'h-5 w-5',
|
||||
'lg' => 'h-6 w-6',
|
||||
default => $size,
|
||||
}"
|
||||
{{-- format-ignore-start --}}
|
||||
{{
|
||||
:size="
|
||||
match ($size) {
|
||||
'sm' => 'h-4 w-4',
|
||||
'md' => 'h-5 w-5',
|
||||
'lg' => 'h-6 w-6',
|
||||
default => $size,
|
||||
}
|
||||
"
|
||||
:attributes="
|
||||
$attributes
|
||||
->class(['filament-notifications-notification-icon'])
|
||||
->style([
|
||||
\Filament\Support\get_color_css_variables($color, shades: [400]),
|
||||
])
|
||||
}}
|
||||
{{-- format-ignore-end --}}
|
||||
"
|
||||
/>
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
<x-filament-notifications::notification
|
||||
:notification="$notification"
|
||||
:x-transition:enter-start="\Illuminate\Support\Arr::toCssClasses([
|
||||
'opacity-0',
|
||||
($this instanceof \Filament\Notifications\Http\Livewire\Notifications)
|
||||
:x-transition:enter-start="
|
||||
\Illuminate\Support\Arr::toCssClasses([
|
||||
'opacity-0',
|
||||
($this instanceof \Filament\Notifications\Http\Livewire\Notifications)
|
||||
? match (static::$horizontalAlignment) {
|
||||
'left' => '-translate-x-12',
|
||||
'right' => 'translate-x-12',
|
||||
@@ -18,7 +19,8 @@
|
||||
},
|
||||
}
|
||||
: null,
|
||||
])"
|
||||
])
|
||||
"
|
||||
x-transition:leave-end="scale-95 opacity-0"
|
||||
@class([
|
||||
'w-full transition duration-300',
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -74,10 +74,10 @@ composer update
|
||||
php artisan filament:upgrade
|
||||
```
|
||||
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-update-cmd` to run it automatically:
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-autoload-dump` to run it automatically:
|
||||
|
||||
```json
|
||||
"post-update-cmd": [
|
||||
"post-autoload-dump": [
|
||||
// ...
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
|
||||
@@ -267,7 +267,7 @@ protected static ?string $navigationIcon = 'heroicon-o-user-group';
|
||||
Alternatively, you may set a dynamic navigation icon in the `getNavigationIcon()` method:
|
||||
|
||||
```php
|
||||
public static function getNavigationIcon(): string
|
||||
public static function getNavigationIcon(): ?string
|
||||
{
|
||||
return 'heroicon-o-user-group';
|
||||
}
|
||||
|
||||
@@ -212,18 +212,6 @@ NavigationItem::make('Analytics')
|
||||
->hidden(! auth()->user()->can('view-analytics')),
|
||||
```
|
||||
|
||||
## Changing the color of a navigation item icon
|
||||
|
||||
By default, navigation item icons are gray. You may change this by calling `iconColor()` on the `NavigationItem` object. The color may be `danger`, `info`, `primary`, `secondary`, `success` or `warning`:
|
||||
|
||||
```php
|
||||
use Filament\Navigation\NavigationItem;
|
||||
|
||||
NavigationItem::make('Analytics')
|
||||
->icon('heroicon-o-presentation-chart-line')
|
||||
->iconColor('primary')
|
||||
```
|
||||
|
||||
## Disabling resource or page navigation items
|
||||
|
||||
To prevent resources or pages from showing up in navigation, you may use:
|
||||
|
||||
@@ -14,13 +14,15 @@ public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
// ...
|
||||
->dangerColor(Color::Rose)
|
||||
->grayColor(Color::Gray)
|
||||
->infoColor(Color::Blue)
|
||||
->primaryColor(Color::Indigo)
|
||||
->secondaryColor(Color::Sky)
|
||||
->successColor(Color::Emerald)
|
||||
->warningColor(Color::Orange);
|
||||
->colors([
|
||||
'danger' => Color::Rose,
|
||||
'gray' => Color::Gray,
|
||||
'info' => Color::Blue,
|
||||
'primary' => Color::Indigo,
|
||||
'secondary' => Color::Sky,
|
||||
'success' => Color::Emerald,
|
||||
'warning' => Color::Orange,
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -29,19 +31,22 @@ The `Filament\Support\Colors\Color` class contains color options for all [Tailwi
|
||||
Alternatively, you may pass your own palette in as an array of RGB values:
|
||||
|
||||
```php
|
||||
$panel->primaryColor([
|
||||
50 => '238, 242, 255',
|
||||
100 => '224, 231, 255',
|
||||
200 => '199, 210, 254',
|
||||
300 => '165, 180, 252',
|
||||
400 => '129, 140, 248',
|
||||
500 => '99, 102, 241',
|
||||
600 => '79, 70, 229',
|
||||
700 => '67, 56, 202',
|
||||
800 => '55, 48, 163',
|
||||
900 => '49, 46, 129',
|
||||
950 => '30, 27, 75',
|
||||
])
|
||||
$panel
|
||||
->colors([
|
||||
'primary' => [
|
||||
50 => '238, 242, 255',
|
||||
100 => '224, 231, 255',
|
||||
200 => '199, 210, 254',
|
||||
300 => '165, 180, 252',
|
||||
400 => '129, 140, 248',
|
||||
500 => '99, 102, 241',
|
||||
600 => '79, 70, 229',
|
||||
700 => '67, 56, 202',
|
||||
800 => '55, 48, 163',
|
||||
900 => '49, 46, 129',
|
||||
950 => '30, 27, 75',
|
||||
],
|
||||
])
|
||||
```
|
||||
|
||||
### Generating a color palette
|
||||
@@ -49,11 +54,15 @@ $panel->primaryColor([
|
||||
If you want us to attempt to generate a palette for you based on a singular hex or RGB value, you can pass that in:
|
||||
|
||||
```php
|
||||
use Filament\Support\Colors\Color;
|
||||
|
||||
$panel->primaryColor('#6366f1')
|
||||
|
||||
$panel->primaryColor('rgb(99, 102, 241)')
|
||||
$panel
|
||||
->colors([
|
||||
'primary' => '#6366f1',
|
||||
])
|
||||
|
||||
$panel
|
||||
->colors([
|
||||
'primary' => 'rgb(99, 102, 241)',
|
||||
])
|
||||
```
|
||||
|
||||
## Changing the font
|
||||
@@ -97,74 +106,23 @@ $panel->font(
|
||||
|
||||
## Creating a custom theme
|
||||
|
||||
Filament allows you to change the fonts and color scheme used in the UI, by compiling a custom stylesheet to replace the default one. This custom stylesheet is called a "theme".
|
||||
Filament allows you to change the CSS used to render the UI, by compiling a custom stylesheet to replace the default one. This custom stylesheet is called a "theme".
|
||||
|
||||
Themes use [Tailwind CSS](https://tailwindcss.com), the Tailwind Forms plugin, and the Tailwind Typography plugin, [Autoprefixer](https://github.com/postcss/autoprefixer), and [Tippy.js](https://atomiks.github.io/tippyjs). You may install these through NPM:
|
||||
Themes use [Tailwind CSS](https://tailwindcss.com), the Tailwind Forms plugin, and the Tailwind Typography plugin, [Autoprefixer](https://github.com/postcss/autoprefixer), and [Tippy.js](https://atomiks.github.io/tippyjs).
|
||||
|
||||
To create a custom theme for a panel, you can use the `php artisan make:filament-theme` command:
|
||||
|
||||
```bash
|
||||
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss autoprefixer tippy.js --save-dev
|
||||
php artisan make:filament-theme
|
||||
```
|
||||
|
||||
To finish installing Tailwind, you must create a new `tailwind.config.js` file in the root of your project. The easiest way to do this is by running `npx tailwindcss init`.
|
||||
If you have more than one panel, you can specify the panel you want to create a theme for:
|
||||
|
||||
In `tailwind.config.js`, require Filament's "preset", which gives you a working Tailwind configuration that you can override however you wish:
|
||||
|
||||
```js
|
||||
import preset from './vendor/filament/filament/tailwind.config.preset'
|
||||
|
||||
export default {
|
||||
presets: [preset],
|
||||
}
|
||||
```bash
|
||||
php artisan make:filament-theme admin
|
||||
```
|
||||
|
||||
If you use Vite to compile assets, in your `vite.config.js` file, register the `filament.css` theme file:
|
||||
|
||||
```js
|
||||
import { defineConfig } from 'vite'
|
||||
import laravel from 'laravel-vite-plugin'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
laravel({
|
||||
input: [
|
||||
// ...
|
||||
'resources/css/filament.css',
|
||||
],
|
||||
// ...
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
And add Tailwind to the `postcss.config.js` file:
|
||||
|
||||
```js
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
In `/resources/css/filament.css`, import Filament's base theme CSS:
|
||||
|
||||
```css
|
||||
@import '../../vendor/filament/filament/resources/css/index.css';
|
||||
```
|
||||
|
||||
Now, you may register the theme file in Filament's [configuration](configuration):
|
||||
|
||||
```php
|
||||
use Filament\Panel;
|
||||
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
// ...
|
||||
->viteTheme('resources/css/filament.css')
|
||||
}
|
||||
```
|
||||
The command will create a CSS file and Tailwind Configuration file in the `/resources/css/filament` directory. You can then customize the theme by editing these files. It will also give you instructions on how to compile the theme and register it in Filament. **Please follow the instructions in the command to complete the setup process.**
|
||||
|
||||
## Non-sticky topbar
|
||||
|
||||
|
||||
@@ -8,18 +8,19 @@
|
||||
<div
|
||||
@if ($this->areFormActionsSticky())
|
||||
x-data="{
|
||||
|
||||
isSticky: false,
|
||||
|
||||
evaluatePageScrollPosition: function() {
|
||||
this.isSticky = (window.scrollY + (window.innerHeight * 2)) <= document.body.scrollHeight
|
||||
evaluatePageScrollPosition: function () {
|
||||
this.isSticky =
|
||||
window.scrollY + window.innerHeight * 2 <=
|
||||
document.body.scrollHeight
|
||||
},
|
||||
|
||||
}"
|
||||
x-init="evaluatePageScrollPosition"
|
||||
x-on:scroll.window="evaluatePageScrollPosition"
|
||||
x-bind:class="{
|
||||
'filament-form-actions-sticky-panel sticky bottom-0 -mx-4 transform bg-white p-4 shadow-lg transition ring-1 ring-gray-950/5 md:-translate-y-4 md:rounded-xl dark:bg-gray-800 dark:ring-white/20': isSticky,
|
||||
'filament-form-actions-sticky-panel sticky bottom-0 -mx-4 transform bg-white p-4 shadow-lg transition ring-1 ring-gray-950/5 md:-translate-y-4 md:rounded-xl dark:bg-gray-800 dark:ring-white/20':
|
||||
isSticky,
|
||||
}"
|
||||
@endif
|
||||
>
|
||||
|
||||
@@ -11,14 +11,10 @@
|
||||
{{
|
||||
$attributes
|
||||
->class([
|
||||
'min-h-4 ms-auto inline-flex items-center justify-center whitespace-normal rounded-xl px-2 py-0.5 text-xs font-medium tracking-tight',
|
||||
match ($active) {
|
||||
true => 'bg-gray-900/10 text-white',
|
||||
false => 'bg-custom-500/10 text-custom-700 dark:text-custom-500',
|
||||
},
|
||||
'inline-flex whitespace-normal rounded-full px-1.5 py-0.5 text-xs font-medium tracking-tighter bg-custom-500/20 text-custom-700 dark:text-custom-500',
|
||||
])
|
||||
->style([
|
||||
\Filament\Support\get_color_css_variables($badgeColor, shades: [500, 700]) => ! $active,
|
||||
\Filament\Support\get_color_css_variables($badgeColor, shades: [500, 700]),
|
||||
])
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,98 +1,89 @@
|
||||
@props([
|
||||
'parentGroup' => null,
|
||||
'collapsible' => true,
|
||||
'hasItemIcons' => false,
|
||||
'icon' => null,
|
||||
'items' => [],
|
||||
'label' => null,
|
||||
])
|
||||
|
||||
<li
|
||||
x-data="{ label: {{ \Illuminate\Support\Js::from((filled($parentGroup) ? "{$parentGroup}." : null) . $label) }} }"
|
||||
class="filament-sidebar-group"
|
||||
@if (filled($parentGroup))
|
||||
x-bind:class="{{ config('filament.layout.sidebar.is_collapsible_on_desktop') ? '$store.sidebar.isOpen' : 'true' }} ? 'ms-11 pe-3 pt-3' : 'hidden'"
|
||||
@endif
|
||||
x-data="{ label: @js($label) }"
|
||||
@class([
|
||||
'filament-sidebar-group grid gap-y-1',
|
||||
])
|
||||
>
|
||||
@if ($label)
|
||||
<button
|
||||
<div
|
||||
@if ($collapsible)
|
||||
x-on:click.prevent="$store.sidebar.toggleCollapsedGroup(label)"
|
||||
x-on:click="$store.sidebar.toggleCollapsedGroup(label)"
|
||||
@endif
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
x-show="$store.sidebar.isOpen"
|
||||
x-transition:enter="lg:transition delay-100"
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@endif
|
||||
class="flex w-full items-center justify-between"
|
||||
@class([
|
||||
'flex items-center gap-x-3 px-3 py-2',
|
||||
'cursor-pointer' => $collapsible,
|
||||
])
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
@if ($icon)
|
||||
<x-filament::icon
|
||||
:name="$icon"
|
||||
alias="panels::sidebar.group"
|
||||
color="text-gray-600 dark:text-gray-300"
|
||||
size="h-3 w-3"
|
||||
class="ms-1 shrink-0"
|
||||
/>
|
||||
@endif
|
||||
|
||||
<p
|
||||
class="flex-1 text-xs font-bold uppercase tracking-wider text-primary-600 dark:text-primary-500"
|
||||
>
|
||||
{{ $label }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@if ($collapsible)
|
||||
@if ($icon)
|
||||
<x-filament::icon
|
||||
name="heroicon-m-chevron-up"
|
||||
alias="panels::sidebar.group.collapse"
|
||||
size="h-5 w-5"
|
||||
class="text-gray-600 transition dark:text-gray-300"
|
||||
x-bind:class="! $store.sidebar.groupIsCollapsed(label) || 'rotate-180'"
|
||||
:name="$icon"
|
||||
alias="panels::sidebar.group"
|
||||
color="text-gray-600 dark:text-gray-400"
|
||||
size="h-6 w-6"
|
||||
/>
|
||||
@endif
|
||||
</button>
|
||||
|
||||
<span
|
||||
class="flex-1 text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ $label }}
|
||||
</span>
|
||||
|
||||
@if ($collapsible)
|
||||
<x-filament::icon-button
|
||||
color="gray"
|
||||
icon="heroicon-m-chevron-down"
|
||||
icon-alias="panels::sidebar.group.collapse"
|
||||
x-on:click.stop="$store.sidebar.toggleCollapsedGroup(label)"
|
||||
x-bind:class="{ 'rotate-180': ! $store.sidebar.groupIsCollapsed(label) }"
|
||||
class="-my-2.5 -me-2.5"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<ul
|
||||
x-show="! ($store.sidebar.groupIsCollapsed(label) && {{ filament()->isSidebarCollapsibleOnDesktop() ? '$store.sidebar.isOpen' : 'true' }})"
|
||||
x-show="! ($store.sidebar.groupIsCollapsed(label) && ($store.sidebar.isOpen || @js(! filament()->isSidebarCollapsibleOnDesktop())))"
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
x-transition:enter="lg:transition delay-100"
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@endif
|
||||
x-collapse.duration.200ms
|
||||
@class([
|
||||
'-mx-3 space-y-1 text-sm',
|
||||
'mt-2' => $label,
|
||||
'grid gap-y-1',
|
||||
])
|
||||
>
|
||||
@foreach ($items as $item)
|
||||
@if ($item instanceof \Filament\Navigation\NavigationItem)
|
||||
@if ($item->isVisible())
|
||||
<x-filament::layouts.app.sidebar.item
|
||||
:active="$item->isActive()"
|
||||
:icon="$item->getIcon()"
|
||||
:iconColor="$item->getIconColor()"
|
||||
:active-icon="$item->getActiveIcon()"
|
||||
:url="$item->getUrl()"
|
||||
:badge="$item->getBadge()"
|
||||
:badgeColor="$item->getBadgeColor()"
|
||||
:shouldOpenUrlInNewTab="$item->shouldOpenUrlInNewTab()"
|
||||
>
|
||||
{{ $item->getLabel() }}
|
||||
</x-filament::layouts.app.sidebar.item>
|
||||
@endif
|
||||
@elseif ($item instanceof \Filament\Navigation\NavigationGroup)
|
||||
<x-filament::layouts.app.sidebar.group
|
||||
:label="$item->getLabel()"
|
||||
@if ($item->isVisible())
|
||||
<x-filament::layouts.app.sidebar.item
|
||||
:active="$item->isActive()"
|
||||
:has-grouped-border="! $hasItemIcons"
|
||||
:icon="$item->getIcon()"
|
||||
:collapsible="$item->isCollapsible()"
|
||||
:items="$item->getItems()"
|
||||
:parentGroup="(filled($parentGroup) ? ('$parentGroup' . '.') : null) . $label"
|
||||
/>
|
||||
:first="$loop->first"
|
||||
:last="$loop->last"
|
||||
:active-icon="$item->getActiveIcon()"
|
||||
:url="$item->getUrl()"
|
||||
:badge="$item->getBadge()"
|
||||
:badge-color="$item->getBadgeColor()"
|
||||
:should-open-url-in-new-tab="$item->shouldOpenUrlInNewTab()"
|
||||
>
|
||||
{{ $item->getLabel() }}
|
||||
</x-filament::layouts.app.sidebar.item>
|
||||
@endif
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
@@ -6,23 +6,31 @@
|
||||
x-data="{}"
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
x-cloak
|
||||
x-bind:class="$store.sidebar.isOpen ? 'filament-sidebar-open translate-x-0 max-w-[20em] lg:max-w-[--sidebar-width]' : '-translate-x-full lg:translate-x-0 lg:max-w-[--collapsed-sidebar-width] rtl:lg:-translate-x-0 rtl:translate-x-full'"
|
||||
x-bind:class="
|
||||
$store.sidebar.isOpen
|
||||
? 'filament-sidebar-open translate-x-0 max-w-[20em] lg:max-w-[--sidebar-width]'
|
||||
: '-translate-x-full lg:translate-x-0 lg:max-w-[--collapsed-sidebar-width] rtl:lg:-translate-x-0 rtl:translate-x-full'
|
||||
"
|
||||
@else
|
||||
@if (filament()->hasTopNavigation() || filament()->isSidebarFullyCollapsibleOnDesktop())
|
||||
x-cloak
|
||||
@else
|
||||
x-cloak="-lg"
|
||||
@endif
|
||||
x-bind:class="$store.sidebar.isOpen ? 'filament-sidebar-open translate-x-0' : '-translate-x-full rtl:translate-x-full'"
|
||||
x-bind:class="
|
||||
$store.sidebar.isOpen
|
||||
? 'filament-sidebar-open translate-x-0'
|
||||
: '-translate-x-full rtl:translate-x-full'
|
||||
"
|
||||
@endif
|
||||
@class([
|
||||
'filament-sidebar fixed inset-y-0 start-0 z-20 flex h-screen w-[--sidebar-width] flex-col bg-white transition-all dark:bg-gray-800 lg:z-0 lg:bg-transparent lg:dark:bg-transparent',
|
||||
'filament-sidebar fixed inset-y-0 start-0 z-20 flex h-screen w-[--sidebar-width] flex-col bg-white transition-all dark:bg-gray-800 lg:z-0 lg:bg-transparent dark:lg:bg-transparent',
|
||||
'lg:translate-x-0 rtl:lg:-translate-x-0' => ! (filament()->isSidebarCollapsibleOnDesktop() || filament()->isSidebarFullyCollapsibleOnDesktop() || filament()->hasTopNavigation()),
|
||||
'lg:-translate-x-full rtl:lg:translate-x-full' => filament()->hasTopNavigation(),
|
||||
])
|
||||
>
|
||||
<header
|
||||
class="filament-sidebar-header relative flex h-[4rem] shrink-0 items-center justify-center border-b bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||
class="filament-sidebar-header relative flex h-[4rem] shrink-0 items-center justify-center bg-white shadow-[0_1px_0_0_theme(colors.gray.950_/_5%)] dark:bg-gray-800 dark:shadow-[0_1px_0_0_theme(colors.white_/_20%)]"
|
||||
>
|
||||
<div
|
||||
@class([
|
||||
@@ -30,7 +38,7 @@
|
||||
'lg:px-4' => filament()->isSidebarCollapsibleOnDesktop() && (! filament()->isSidebarFullyCollapsibleOnDesktop()),
|
||||
])
|
||||
x-show="$store.sidebar.isOpen || @js(! filament()->isSidebarCollapsibleOnDesktop()) || @js(filament()->isSidebarFullyCollapsibleOnDesktop())"
|
||||
x-transition:enter="lg:transition delay-100"
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
>
|
||||
@@ -38,9 +46,13 @@
|
||||
<button
|
||||
type="button"
|
||||
class="filament-sidebar-collapse-button hidden h-10 w-10 shrink-0 items-center justify-center rounded-full text-primary-500 outline-none hover:bg-gray-500/5 focus:bg-primary-500/10 lg:flex"
|
||||
x-bind:aria-label="$store.sidebar.isOpen ? '{{ __('filament::layout.buttons.sidebar.collapse.label') }}' : '{{ __('filament::layout.buttons.sidebar.expand.label') }}'"
|
||||
x-bind:aria-label="
|
||||
$store.sidebar.isOpen
|
||||
? '{{ __('filament::layout.buttons.sidebar.collapse.label') }}'
|
||||
: '{{ __('filament::layout.buttons.sidebar.expand.label') }}'
|
||||
"
|
||||
x-on:click.stop="$store.sidebar.isOpen ? $store.sidebar.close() : $store.sidebar.open()"
|
||||
x-transition:enter="lg:transition delay-100"
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
>
|
||||
@@ -90,10 +102,14 @@
|
||||
<button
|
||||
type="button"
|
||||
class="filament-sidebar-close-button flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-primary-500 outline-none hover:bg-gray-500/5 focus:bg-primary-500/10"
|
||||
x-bind:aria-label="$store.sidebar.isOpen ? '{{ __('filament::layout.buttons.sidebar.collapse.label') }}' : '{{ __('filament::layout.buttons.sidebar.expand.label') }}'"
|
||||
x-bind:aria-label="
|
||||
$store.sidebar.isOpen
|
||||
? '{{ __('filament::layout.buttons.sidebar.collapse.label') }}'
|
||||
: '{{ __('filament::layout.buttons.sidebar.expand.label') }}'
|
||||
"
|
||||
x-on:click.stop="$store.sidebar.isOpen ? $store.sidebar.close() : $store.sidebar.open()"
|
||||
x-show="(! $store.sidebar.isOpen) && @js(! filament()->isSidebarFullyCollapsibleOnDesktop())"
|
||||
x-transition:enter="lg:transition delay-100"
|
||||
x-show="! $store.sidebar.isOpen && @js(! filament()->isSidebarFullyCollapsibleOnDesktop())"
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
>
|
||||
@@ -107,7 +123,7 @@
|
||||
</header>
|
||||
|
||||
<nav
|
||||
class="filament-sidebar-nav flex-1 overflow-y-auto overflow-x-hidden py-6"
|
||||
class="filament-sidebar-nav flex-1 overflow-y-auto overflow-x-hidden py-6 shadow-lg lg:shadow-none"
|
||||
>
|
||||
{{ filament()->renderHook('sidebar.start') }}
|
||||
|
||||
@@ -139,13 +155,14 @@
|
||||
</script>
|
||||
|
||||
@if (filament()->hasNavigation())
|
||||
<ul class="space-y-6 px-6">
|
||||
<ul class="-mx-3 grid gap-y-3 px-6">
|
||||
@foreach ($navigation as $group)
|
||||
<x-filament::layouts.app.sidebar.group
|
||||
:label="$group->getLabel()"
|
||||
:icon="$group->getIcon()"
|
||||
:collapsible="$group->isCollapsible()"
|
||||
:items="$group->getItems()"
|
||||
:has-item-icons="$group->hasItemIcons()"
|
||||
/>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
'activeIcon' => null,
|
||||
'badge' => null,
|
||||
'badgeColor' => null,
|
||||
'icon',
|
||||
'iconColor' => 'gray',
|
||||
'hasGroupedBorder' => false,
|
||||
'last' => false,
|
||||
'first' => false,
|
||||
'icon' => null,
|
||||
'shouldOpenUrlInNewTab' => false,
|
||||
'url',
|
||||
])
|
||||
|
||||
<li
|
||||
@class([
|
||||
'filament-sidebar-item overflow-hidden',
|
||||
'filament-sidebar-item',
|
||||
'filament-sidebar-item-active' => $active,
|
||||
])
|
||||
>
|
||||
@@ -37,32 +39,55 @@
|
||||
x-tooltip.html="tooltip"
|
||||
@endif
|
||||
@class([
|
||||
'flex items-center justify-center gap-3 rounded-lg px-3 py-2 font-medium transition',
|
||||
'hover:bg-gray-500/5 focus:bg-gray-500/5 dark:text-gray-300 dark:hover:bg-gray-700' => ! $active,
|
||||
'bg-primary-500 text-white' => $active,
|
||||
'relative flex items-center justify-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 outline-none transition hover:bg-gray-950/5 focus:bg-gray-950/5 dark:text-gray-300 dark:hover:bg-white/5 dark:focus:bg-white/5',
|
||||
'bg-gray-950/5 text-primary-600 dark:bg-white/5 dark:text-primary-400' => $active,
|
||||
])
|
||||
>
|
||||
<x-filament::icon
|
||||
:name="($active && $activeIcon) ? $activeIcon : $icon"
|
||||
alias="panels::sidebar.item"
|
||||
:color="$active ? null : 'text-custom-600 dark:text-custom-500'"
|
||||
size="h-6 w-6"
|
||||
@style([
|
||||
\Filament\Support\get_color_css_variables($iconColor, shades: [500, 600]) => ! $active,
|
||||
])
|
||||
/>
|
||||
@if ($icon)
|
||||
<x-filament::icon
|
||||
:name="($active && $activeIcon) ? $activeIcon : $icon"
|
||||
alias="panels::sidebar.item"
|
||||
color="text-custom-600 dark:text-custom-400"
|
||||
size="h-6 w-6"
|
||||
:style="\Filament\Support\get_color_css_variables(($active ? 'primary' : 'gray'), shades: [400, 600])"
|
||||
/>
|
||||
@elseif ($hasGroupedBorder)
|
||||
<div
|
||||
class="filament-sidebar-item-grouped-border relative flex h-6 w-6 items-center justify-center"
|
||||
>
|
||||
@if (! $first)
|
||||
<div
|
||||
class="absolute -top-1/2 bottom-1/2 w-px bg-gray-300 dark:bg-gray-600"
|
||||
></div>
|
||||
@endif
|
||||
|
||||
<div
|
||||
@if (! $last)
|
||||
<div
|
||||
class="absolute -bottom-1/2 top-1/2 w-px bg-gray-300 dark:bg-gray-600"
|
||||
></div>
|
||||
@endif
|
||||
|
||||
<div
|
||||
@class([
|
||||
'relative h-1.5 w-1.5 rounded-full',
|
||||
'bg-gray-400' => ! $active,
|
||||
'bg-primary-600 dark:bg-primary-400' => $active,
|
||||
])
|
||||
></div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<span
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
x-show="$store.sidebar.isOpen"
|
||||
x-transition:enter="lg:transition delay-100"
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@endif
|
||||
class="flex flex-1"
|
||||
class="flex-1"
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@if (filled($badge))
|
||||
<x-filament::layouts.app.sidebar.badge
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
])
|
||||
|
||||
<header
|
||||
{{ $attributes->class(['filament-main-topbar sticky top-0 z-10 border-b bg-white dark:border-gray-700 dark:bg-gray-800']) }}
|
||||
{{ $attributes->class(['filament-main-topbar sticky top-0 z-10 bg-white shadow-[0_1px_0_0_theme(colors.gray.950_/_5%)] dark:border-gray-700 dark:bg-gray-800 dark:shadow-[0_1px_0_0_theme(colors.white_/_20%)]']) }}
|
||||
>
|
||||
<div
|
||||
class="-mt-px flex h-16 items-center justify-between px-2 sm:px-4 md:px-6 lg:px-8"
|
||||
class="flex h-16 items-center justify-between px-2 sm:px-4 md:px-6 lg:px-8"
|
||||
>
|
||||
<div class="flex flex-1 items-center">
|
||||
{{ filament()->renderHook('topbar.start') }}
|
||||
@@ -15,7 +15,11 @@
|
||||
<button
|
||||
x-cloak
|
||||
x-data="{}"
|
||||
x-bind:aria-label="$store.sidebar.isOpen ? '{{ __('filament::layout.buttons.sidebar.collapse.label') }}' : '{{ __('filament::layout.buttons.sidebar.expand.label') }}'"
|
||||
x-bind:aria-label="
|
||||
$store.sidebar.isOpen
|
||||
? '{{ __('filament::layout.buttons.sidebar.collapse.label') }}'
|
||||
: '{{ __('filament::layout.buttons.sidebar.expand.label') }}'
|
||||
"
|
||||
x-on:click="$store.sidebar.isOpen ? $store.sidebar.close() : $store.sidebar.open()"
|
||||
@class([
|
||||
'filament-sidebar-open-button flex h-10 w-10 shrink-0 items-center justify-center rounded-full outline-none hover:bg-gray-500/5 focus:bg-primary-500/10',
|
||||
@@ -43,7 +47,7 @@
|
||||
</div>
|
||||
|
||||
@if (filament()->hasNavigation())
|
||||
<ul class="hidden flex-wrap items-center gap-3 lg:flex">
|
||||
<ul class="hidden flex-wrap items-center gap-x-1 lg:flex">
|
||||
@foreach ($navigation as $group)
|
||||
@if ($groupLabel = $group->getLabel())
|
||||
<x-filament::dropdown placement="bottom-start">
|
||||
|
||||
@@ -8,26 +8,35 @@
|
||||
'url' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$tag = $url ? 'a' : 'button';
|
||||
@endphp
|
||||
|
||||
<li
|
||||
@class(['filament-topbar-item overflow-hidden', 'filament-topbar-item-active' => $active])
|
||||
@class([
|
||||
'filament-topbar-item overflow-hidden',
|
||||
'filament-topbar-item-active' => $active,
|
||||
])
|
||||
>
|
||||
<a
|
||||
<{{ $tag }}
|
||||
@if ($url)
|
||||
href="{{ $url }}"
|
||||
@if ($shouldOpenUrlInNewTab) target="_blank" @endif
|
||||
@else
|
||||
type="button"
|
||||
@endif
|
||||
@class([
|
||||
'flex items-center justify-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition',
|
||||
'hover:bg-gray-500/5 focus:bg-gray-500/5 dark:text-gray-300 dark:hover:bg-gray-700' => ! $active,
|
||||
'bg-primary-500 text-white' => $active,
|
||||
'flex items-center justify-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 outline-none transition hover:bg-gray-950/5 focus:bg-gray-950/5 dark:text-gray-300 dark:hover:bg-white/5 dark:focus:bg-white/5',
|
||||
'bg-gray-950/5 text-primary-600 dark:bg-white/5 dark:text-primary-400' => $active,
|
||||
])
|
||||
>
|
||||
@if ($icon || $activeIcon)
|
||||
<x-filament::icon
|
||||
:name="($active && $activeIcon) ? $activeIcon : $icon"
|
||||
alias="panels::topbar.item"
|
||||
:color="(! $active) ? 'text-gray-600 dark:text-gray-500' : null"
|
||||
size="h-5 w-5"
|
||||
color="text-custom-600 dark:text-custom-400"
|
||||
size="h-6 w-6"
|
||||
:style="\Filament\Support\get_color_css_variables(($active ? 'primary' : 'gray'), shades: [400, 600])"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@@ -42,5 +51,5 @@
|
||||
:active="$active"
|
||||
/>
|
||||
@endif
|
||||
</a>
|
||||
</{{ $tag }}>
|
||||
</li>
|
||||
|
||||
@@ -24,14 +24,25 @@
|
||||
|
||||
{{ filament()->renderHook('user-menu.account.before') }}
|
||||
|
||||
<x-filament::dropdown.header
|
||||
:color="$accountItem?->getColor() ?? 'gray'"
|
||||
:icon="$accountItem?->getIcon() ?? 'heroicon-m-user-circle'"
|
||||
:href="$accountItemUrl"
|
||||
:tag="filled($accountItemUrl) ? 'a' : 'div'"
|
||||
>
|
||||
{{ $accountItem?->getLabel() ?? filament()->getUserName($user) }}
|
||||
</x-filament::dropdown.header>
|
||||
@if (filled($accountItemUrl))
|
||||
<x-filament::dropdown.list>
|
||||
<x-filament::dropdown.list.item
|
||||
:color="$accountItem->getColor() ?? 'gray'"
|
||||
:icon="$accountItem->getIcon() ?? 'heroicon-m-user-circle'"
|
||||
:href="$accountItemUrl"
|
||||
tag="a"
|
||||
>
|
||||
{{ $accountItem->getLabel() ?? filament()->getUserName($user) }}
|
||||
</x-filament::dropdown.list.item>
|
||||
</x-filament::dropdown.list>
|
||||
@else
|
||||
<x-filament::dropdown.header
|
||||
:color="$accountItem?->getColor() ?? 'gray'"
|
||||
:icon="$accountItem?->getIcon() ?? 'heroicon-m-user-circle'"
|
||||
>
|
||||
{{ $accountItem?->getLabel() ?? filament()->getUserName($user) }}
|
||||
</x-filament::dropdown.header>
|
||||
@endif
|
||||
|
||||
{{ filament()->renderHook('user-menu.account.after') }}
|
||||
|
||||
@@ -43,29 +54,49 @@
|
||||
init: function () {
|
||||
this.theme = localStorage.getItem('theme') || 'system'
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
|
||||
if (this.theme !== 'system') {
|
||||
return
|
||||
}
|
||||
window
|
||||
.matchMedia('(prefers-color-scheme: dark)')
|
||||
.addEventListener('change', (event) => {
|
||||
if (this.theme !== 'system') {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.matches && (! document.documentElement.classList.contains('dark'))) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if ((! event.matches) && document.documentElement.classList.contains('dark')) {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
})
|
||||
if (
|
||||
event.matches &&
|
||||
! document.documentElement.classList.contains('dark')
|
||||
) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if (
|
||||
! event.matches &&
|
||||
document.documentElement.classList.contains('dark')
|
||||
) {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
})
|
||||
|
||||
$watch('theme', () => {
|
||||
localStorage.setItem('theme', this.theme)
|
||||
|
||||
if (this.theme === 'dark' && (! document.documentElement.classList.contains('dark'))) {
|
||||
if (
|
||||
this.theme === 'dark' &&
|
||||
! document.documentElement.classList.contains('dark')
|
||||
) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if (this.theme === 'light' && document.documentElement.classList.contains('dark')) {
|
||||
} else if (
|
||||
this.theme === 'light' &&
|
||||
document.documentElement.classList.contains('dark')
|
||||
) {
|
||||
document.documentElement.classList.remove('dark')
|
||||
} else if (this.theme === 'system') {
|
||||
if (this.isSystemDark() && (! document.documentElement.classList.contains('dark'))) {
|
||||
if (
|
||||
this.isSystemDark() &&
|
||||
! document.documentElement.classList.contains('dark')
|
||||
) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if ((! this.isSystemDark()) && document.documentElement.classList.contains('dark')) {
|
||||
} else if (
|
||||
! this.isSystemDark() &&
|
||||
document.documentElement.classList.contains('dark')
|
||||
) {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Commands\Aliases;
|
||||
|
||||
use Filament\Commands;
|
||||
|
||||
class CompileThemeCommand extends Commands\CompileThemeCommand
|
||||
{
|
||||
protected $hidden = true;
|
||||
|
||||
protected $signature = 'filament:theme {panel?} {--W|watch}';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Commands\Aliases;
|
||||
|
||||
use Filament\Commands;
|
||||
|
||||
class MakeThemeCommand extends Commands\MakeThemeCommand
|
||||
{
|
||||
protected $hidden = true;
|
||||
|
||||
protected $signature = 'filament:theme {panel?} {--F|force}';
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Commands;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Panel;
|
||||
use Filament\Support\Commands\Concerns\CanManipulateFiles;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CompileThemeCommand extends Command
|
||||
{
|
||||
use CanManipulateFiles;
|
||||
|
||||
protected $description = 'Compiles a Filament theme.';
|
||||
|
||||
protected $signature = 'filament:compile-theme {panel?} {--W|watch}';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
exec('npm -v', $npmVersion, $npmVersionExistCode);
|
||||
|
||||
if ($npmVersionExistCode !== 0) {
|
||||
$this->error('Node.js is not installed. Please install before continuing.');
|
||||
|
||||
return static::FAILURE;
|
||||
}
|
||||
|
||||
$this->info("Using Node.js v{$npmVersion[0]}");
|
||||
|
||||
$panelId = $this->argument('panel');
|
||||
|
||||
if (! $panelId) {
|
||||
$panelIds = collect(Filament::getPanels())
|
||||
->map(fn (Panel $panel): string => $panel->getId())
|
||||
->filter(fn (string $panelId): bool => (
|
||||
file_exists(resource_path("css/filament/{$panelId}/theme.css")) &&
|
||||
file_exists(resource_path("css/filament/{$panelId}/tailwind.config.js"))
|
||||
))
|
||||
->all();
|
||||
|
||||
if (empty($panelIds)) {
|
||||
$this->error('No themes found. Please create a theme using `php artisan make:filament-theme` before continuing.');
|
||||
|
||||
return static::FAILURE;
|
||||
}
|
||||
|
||||
$defaultPanelId = in_array(Filament::getDefaultPanel()->getId(), $panelIds) ?
|
||||
Filament::getDefaultPanel()->getId() :
|
||||
Arr::first($panelIds);
|
||||
|
||||
$panelId = (count($panelIds) > 1) ? $this->choice(
|
||||
'Which panel\'s theme would you like to compile?',
|
||||
$panelIds,
|
||||
$defaultPanelId,
|
||||
) : Arr::first($panelIds);
|
||||
}
|
||||
|
||||
exec('npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss autoprefixer tippy.js --save-dev');
|
||||
exec("npx tailwindcss --input ./resources/css/filament/{$panelId}/theme.css --output ./public/css/filament/{$panelId}/theme.css --config ./resources/css/filament/{$panelId}/tailwind.config.js --minify" . ($this->option('watch') ? ' --watch' : ''));
|
||||
|
||||
$this->components->info("Compiled resources/css/filament/{$panelId}/theme.css!");
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,18 @@ class MakeThemeCommand extends Command
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
exec('npm -v', $npmVersion, $npmVersionExistCode);
|
||||
|
||||
if ($npmVersionExistCode !== 0) {
|
||||
$this->error('Node.js is not installed. Please install before continuing.');
|
||||
|
||||
return static::FAILURE;
|
||||
}
|
||||
|
||||
$this->info("Using Node.js v{$npmVersion[0]}");
|
||||
|
||||
exec('npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss autoprefixer tippy.js --save-dev');
|
||||
|
||||
$panel = $this->argument('panel');
|
||||
|
||||
if ($panel) {
|
||||
@@ -68,11 +80,34 @@ class MakeThemeCommand extends Command
|
||||
'viewPathPrefix' => $viewPathPrefix,
|
||||
]);
|
||||
|
||||
$this->call('filament:compile-theme', ['panel' => $panelId]);
|
||||
|
||||
$this->components->info("Successfully created resources/css/filament/{$panelId}/theme.css and resources/css/filament/{$panelId}/tailwind.config.js!");
|
||||
|
||||
$this->components->info("Make sure to register the theme in the {$panelId} panel provider using `theme(asset('css/filament/{$panelId}/theme.css'))`");
|
||||
if (! file_exists(base_path('vite.config.js'))) {
|
||||
$this->components->warn('Action is required to complete the theme setup:');
|
||||
$this->components->bulletList([
|
||||
"It looks like you don't have Vite installed. Please use your asset bundling system of choice to compile `resources/css/filament/{$panelId}/theme.css` into `public/css/filament/{$panelId}/theme.css`.",
|
||||
"If you're not currently using a bundler, we recommend using Vite. Alternatively, you can use the Tailwind CLI with the following command:",
|
||||
"npx tailwindcss --input ./resources/css/filament/{$panelId}/theme.css --output ./public/css/filament/{$panelId}/theme.css --config ./resources/css/filament/{$panelId}/tailwind.config.js --minify",
|
||||
"Make sure to register the theme in the {$panelId} panel provider using `->theme(asset('css/filament/{$panelId}/theme.css'))`",
|
||||
]);
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
|
||||
$postcssConfigPath = base_path('postcss.config.js');
|
||||
|
||||
if (! file_exists($postcssConfigPath)) {
|
||||
$this->copyStubToApp('ThemePostcssConfig', $postcssConfigPath);
|
||||
|
||||
$this->components->info('Successfully created postcss.config.js!');
|
||||
}
|
||||
|
||||
$this->components->warn('Action is required to complete the theme setup:');
|
||||
$this->components->bulletList([
|
||||
"First, add a new item to the `input` array of `vite.config.js`: `resources/css/filament/{$panelId}/theme.css`.",
|
||||
"Next, register the theme in the {$panelId} panel provider using `->viteTheme('resources/css/filament/{$panelId}/theme.css')`",
|
||||
'Finally, run `npm run build` to compile the theme.',
|
||||
]);
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ class FilamentServiceProvider extends PackageServiceProvider
|
||||
protected function getCommands(): array
|
||||
{
|
||||
$commands = [
|
||||
Commands\CompileThemeCommand::class,
|
||||
Commands\MakePageCommand::class,
|
||||
Commands\MakePanelCommand::class,
|
||||
Commands\MakeRelationManagerCommand::class,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Filament\Navigation;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
class NavigationGroup
|
||||
@@ -13,7 +14,7 @@ class NavigationGroup
|
||||
protected ?string $icon = null;
|
||||
|
||||
/**
|
||||
* @var array<NavigationItem | NavigationGroup> | Arrayable
|
||||
* @var array<NavigationItem> | Arrayable
|
||||
*/
|
||||
protected array | Arrayable $items = [];
|
||||
|
||||
@@ -53,10 +54,18 @@ class NavigationGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<NavigationItem | NavigationGroup> | Arrayable $items
|
||||
* @param array<NavigationItem> | Arrayable $items
|
||||
*/
|
||||
public function items(array | Arrayable $items): static
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
if ($item instanceof NavigationItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Exception("Navigation group [{$this->getLabel()}] has a nested group, which is not supported.");
|
||||
}
|
||||
|
||||
$this->items = $items;
|
||||
|
||||
return $this;
|
||||
@@ -71,11 +80,15 @@ class NavigationGroup
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
if (filled($this->icon) && $this->hasItemIcons()) {
|
||||
throw new Exception("Navigation group [{$this->getLabel()}] has an icon but one or more of its items also have icons. Either the group or its items can have icons, but not both. This is to ensure a proper user experience.");
|
||||
}
|
||||
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<NavigationItem | NavigationGroup> | Arrayable
|
||||
* @return array<NavigationItem> | Arrayable
|
||||
*/
|
||||
public function getItems(): array | Arrayable
|
||||
{
|
||||
@@ -109,4 +122,30 @@ class NavigationGroup
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasItemIcons(): bool
|
||||
{
|
||||
$hasIconCount = 0;
|
||||
$hasNoIconCount = 0;
|
||||
|
||||
foreach ($this->getItems() as $item) {
|
||||
if (! $item instanceof NavigationItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blank($item->getIcon())) {
|
||||
$hasNoIconCount++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasIconCount++;
|
||||
}
|
||||
|
||||
if (($hasIconCount > 0) && ($hasNoIconCount > 0) && filled($label = $this->getLabel())) {
|
||||
throw new Exception("Navigation group [{$label}] has items with and without icons. All items must have icons or none of them can have icons. This is to ensure a proper user experience.");
|
||||
}
|
||||
|
||||
return $hasIconCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,6 @@ class NavigationItem
|
||||
|
||||
protected ?string $activeIcon = null;
|
||||
|
||||
/**
|
||||
* @var string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null
|
||||
*/
|
||||
protected string | array | null $iconColor = null;
|
||||
|
||||
protected string $label;
|
||||
|
||||
protected ?string $badge = null;
|
||||
@@ -89,23 +84,13 @@ class NavigationItem
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function activeIcon(string $activeIcon): static
|
||||
public function activeIcon(?string $activeIcon): static
|
||||
{
|
||||
$this->activeIcon = $activeIcon;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null $color
|
||||
*/
|
||||
public function iconColor(string | array | null $color): static
|
||||
{
|
||||
$this->iconColor = $color;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isActiveWhen(Closure $callback): static
|
||||
{
|
||||
$this->isActiveWhen = $callback;
|
||||
@@ -184,14 +169,6 @@ class NavigationItem
|
||||
return $this->activeIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null
|
||||
*/
|
||||
public function getIconColor(): string | array | null
|
||||
{
|
||||
return $this->iconColor;
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->label;
|
||||
|
||||
@@ -24,7 +24,7 @@ class Dashboard extends Page
|
||||
__('filament::pages/dashboard.title');
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): string
|
||||
public static function getNavigationIcon(): ?string
|
||||
{
|
||||
return static::$navigationIcon ??
|
||||
FilamentIcon::resolve('panels::pages.dashboard.navigation')?->name ??
|
||||
|
||||
@@ -92,15 +92,14 @@ abstract class Page extends BasePage
|
||||
return static::$navigationGroup;
|
||||
}
|
||||
|
||||
public static function getActiveNavigationIcon(): string
|
||||
public static function getActiveNavigationIcon(): ?string
|
||||
{
|
||||
return static::$activeNavigationIcon ?? static::getNavigationIcon();
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): ?string
|
||||
{
|
||||
return static::$navigationIcon ??
|
||||
(filament()->hasTopNavigation() ? null : 'heroicon-o-document-text');
|
||||
return static::$navigationIcon;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Filament\Panel\Concerns;
|
||||
|
||||
trait HasSidebar
|
||||
{
|
||||
protected string $sidebarWidth = '18rem';
|
||||
protected string $sidebarWidth = '20rem';
|
||||
|
||||
protected string $collapsedSidebarWidth = '5.4rem';
|
||||
|
||||
|
||||
@@ -609,8 +609,7 @@ abstract class Resource
|
||||
|
||||
public static function getNavigationIcon(): ?string
|
||||
{
|
||||
return static::$navigationIcon ??
|
||||
(filament()->hasTopNavigation() ? null : 'heroicon-o-rectangle-stack');
|
||||
return static::$navigationIcon;
|
||||
}
|
||||
|
||||
public static function navigationIcon(?string $icon): void
|
||||
@@ -618,7 +617,7 @@ abstract class Resource
|
||||
static::$navigationIcon = $icon;
|
||||
}
|
||||
|
||||
public static function getActiveNavigationIcon(): string
|
||||
public static function getActiveNavigationIcon(): ?string
|
||||
{
|
||||
return static::$activeNavigationIcon ?? static::getNavigationIcon();
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Testing;
|
||||
|
||||
use Closure;
|
||||
use Filament\Pages\Page;
|
||||
use Livewire\Features\SupportUnitTesting\Testable;
|
||||
|
||||
/**
|
||||
* @method Page instance()
|
||||
*
|
||||
* @mixin Testable
|
||||
*/
|
||||
class TestsPages
|
||||
{
|
||||
public function fillForm(): Closure
|
||||
{
|
||||
return function (array $state = []): static {
|
||||
$this->set('data', $state);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertFormSet(): Closure
|
||||
{
|
||||
return function (array $state): static {
|
||||
foreach ($state as $key => $value) {
|
||||
$this->assertSet("data.{$key}", $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertHasFormErrors(): Closure
|
||||
{
|
||||
return function (array $keys = []): static {
|
||||
$this->assertHasErrors(
|
||||
collect($keys)
|
||||
->mapWithKeys(function ($value, $key): array {
|
||||
if (is_int($key)) {
|
||||
return [$key => "data.{$value}"];
|
||||
}
|
||||
|
||||
return ["data.{$key}" => $value];
|
||||
})
|
||||
->all(),
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertHasNoFormErrors(): Closure
|
||||
{
|
||||
return function (array $keys = []): static {
|
||||
$this->assertHasNoErrors(
|
||||
collect($keys)
|
||||
->mapWithKeys(function ($value, $key): array {
|
||||
if (is_int($key)) {
|
||||
return [$key => "data.{$value}"];
|
||||
}
|
||||
|
||||
return ["data.{$key}" => $value];
|
||||
})
|
||||
->all(),
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
@import '../../../../vendor/filament/filament/resources/css/theme.css';
|
||||
|
||||
@config './tailwind.config.js';
|
||||
|
||||
@@ -1,122 +1,8 @@
|
||||
import defaultPreset from '../support/tailwind.config.preset.js'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
|
||||
export default {
|
||||
content: ['./vendor/filament/**/*.blade.php'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
custom: {
|
||||
50: 'rgba(var(--c-50), <alpha-value>)',
|
||||
100: 'rgba(var(--c-100), <alpha-value>)',
|
||||
200: 'rgba(var(--c-200), <alpha-value>)',
|
||||
300: 'rgba(var(--c-300), <alpha-value>)',
|
||||
400: 'rgba(var(--c-400), <alpha-value>)',
|
||||
500: 'rgba(var(--c-500), <alpha-value>)',
|
||||
600: 'rgba(var(--c-600), <alpha-value>)',
|
||||
700: 'rgba(var(--c-700), <alpha-value>)',
|
||||
800: 'rgba(var(--c-800), <alpha-value>)',
|
||||
900: 'rgba(var(--c-900), <alpha-value>)',
|
||||
950: 'rgba(var(--c-950), <alpha-value>)',
|
||||
},
|
||||
danger: {
|
||||
50: 'rgba(var(--danger-50), <alpha-value>)',
|
||||
100: 'rgba(var(--danger-100), <alpha-value>)',
|
||||
200: 'rgba(var(--danger-200), <alpha-value>)',
|
||||
300: 'rgba(var(--danger-300), <alpha-value>)',
|
||||
400: 'rgba(var(--danger-400), <alpha-value>)',
|
||||
500: 'rgba(var(--danger-500), <alpha-value>)',
|
||||
600: 'rgba(var(--danger-600), <alpha-value>)',
|
||||
700: 'rgba(var(--danger-700), <alpha-value>)',
|
||||
800: 'rgba(var(--danger-800), <alpha-value>)',
|
||||
900: 'rgba(var(--danger-900), <alpha-value>)',
|
||||
950: 'rgba(var(--danger-950), <alpha-value>)',
|
||||
},
|
||||
gray: {
|
||||
50: 'rgba(var(--gray-50), <alpha-value>)',
|
||||
100: 'rgba(var(--gray-100), <alpha-value>)',
|
||||
200: 'rgba(var(--gray-200), <alpha-value>)',
|
||||
300: 'rgba(var(--gray-300), <alpha-value>)',
|
||||
400: 'rgba(var(--gray-400), <alpha-value>)',
|
||||
500: 'rgba(var(--gray-500), <alpha-value>)',
|
||||
600: 'rgba(var(--gray-600), <alpha-value>)',
|
||||
700: 'rgba(var(--gray-700), <alpha-value>)',
|
||||
800: 'rgba(var(--gray-800), <alpha-value>)',
|
||||
900: 'rgba(var(--gray-900), <alpha-value>)',
|
||||
950: 'rgba(var(--gray-950), <alpha-value>)',
|
||||
},
|
||||
info: {
|
||||
50: 'rgba(var(--info-50), <alpha-value>)',
|
||||
100: 'rgba(var(--info-100), <alpha-value>)',
|
||||
200: 'rgba(var(--info-200), <alpha-value>)',
|
||||
300: 'rgba(var(--info-300), <alpha-value>)',
|
||||
400: 'rgba(var(--info-400), <alpha-value>)',
|
||||
500: 'rgba(var(--info-500), <alpha-value>)',
|
||||
600: 'rgba(var(--info-600), <alpha-value>)',
|
||||
700: 'rgba(var(--info-700), <alpha-value>)',
|
||||
800: 'rgba(var(--info-800), <alpha-value>)',
|
||||
900: 'rgba(var(--info-900), <alpha-value>)',
|
||||
950: 'rgba(var(--info-950), <alpha-value>)',
|
||||
},
|
||||
primary: {
|
||||
50: 'rgba(var(--primary-50), <alpha-value>)',
|
||||
100: 'rgba(var(--primary-100), <alpha-value>)',
|
||||
200: 'rgba(var(--primary-200), <alpha-value>)',
|
||||
300: 'rgba(var(--primary-300), <alpha-value>)',
|
||||
400: 'rgba(var(--primary-400), <alpha-value>)',
|
||||
500: 'rgba(var(--primary-500), <alpha-value>)',
|
||||
600: 'rgba(var(--primary-600), <alpha-value>)',
|
||||
700: 'rgba(var(--primary-700), <alpha-value>)',
|
||||
800: 'rgba(var(--primary-800), <alpha-value>)',
|
||||
900: 'rgba(var(--primary-900), <alpha-value>)',
|
||||
950: 'rgba(var(--primary-950), <alpha-value>)',
|
||||
},
|
||||
secondary: {
|
||||
50: 'rgba(var(--secondary-50), <alpha-value>)',
|
||||
100: 'rgba(var(--secondary-100), <alpha-value>)',
|
||||
200: 'rgba(var(--secondary-200), <alpha-value>)',
|
||||
300: 'rgba(var(--secondary-300), <alpha-value>)',
|
||||
400: 'rgba(var(--secondary-400), <alpha-value>)',
|
||||
500: 'rgba(var(--secondary-500), <alpha-value>)',
|
||||
600: 'rgba(var(--secondary-600), <alpha-value>)',
|
||||
700: 'rgba(var(--secondary-700), <alpha-value>)',
|
||||
800: 'rgba(var(--secondary-800), <alpha-value>)',
|
||||
900: 'rgba(var(--secondary-900), <alpha-value>)',
|
||||
950: 'rgba(var(--secondary-950), <alpha-value>)',
|
||||
},
|
||||
success: {
|
||||
50: 'rgba(var(--success-50), <alpha-value>)',
|
||||
100: 'rgba(var(--success-100), <alpha-value>)',
|
||||
200: 'rgba(var(--success-200), <alpha-value>)',
|
||||
300: 'rgba(var(--success-300), <alpha-value>)',
|
||||
400: 'rgba(var(--success-400), <alpha-value>)',
|
||||
500: 'rgba(var(--success-500), <alpha-value>)',
|
||||
600: 'rgba(var(--success-600), <alpha-value>)',
|
||||
700: 'rgba(var(--success-700), <alpha-value>)',
|
||||
800: 'rgba(var(--success-800), <alpha-value>)',
|
||||
900: 'rgba(var(--success-900), <alpha-value>)',
|
||||
950: 'rgba(var(--success-950), <alpha-value>)',
|
||||
},
|
||||
warning: {
|
||||
50: 'rgba(var(--warning-50), <alpha-value>)',
|
||||
100: 'rgba(var(--warning-100), <alpha-value>)',
|
||||
200: 'rgba(var(--warning-200), <alpha-value>)',
|
||||
300: 'rgba(var(--warning-300), <alpha-value>)',
|
||||
400: 'rgba(var(--warning-400), <alpha-value>)',
|
||||
500: 'rgba(var(--warning-500), <alpha-value>)',
|
||||
600: 'rgba(var(--warning-600), <alpha-value>)',
|
||||
700: 'rgba(var(--warning-700), <alpha-value>)',
|
||||
800: 'rgba(var(--warning-800), <alpha-value>)',
|
||||
900: 'rgba(var(--warning-900), <alpha-value>)',
|
||||
950: 'rgba(var(--warning-950), <alpha-value>)',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['var(--font-family)', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [forms, typography],
|
||||
defaultPreset.theme.extend.fontFamily = {
|
||||
sans: ['var(--font-family)', ...defaultTheme.fontFamily.sans],
|
||||
}
|
||||
|
||||
export default defaultPreset
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Colors
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Filament uses CSS variables to define its color palette. These CSS variables are mapped to Tailwind classes in the preset file that you load when installing Filament.
|
||||
|
||||
## Customizing the default colors
|
||||
|
||||
From a service provider's `boot()` method, or middleware, you can call the `FilamentColor::register()` method, which you can use to customize which colors Filament uses for UI elements.
|
||||
|
||||
There are 7 default colors that are used throughout Filament that you are able to customize:
|
||||
|
||||
```php
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Support\Facades\FilamentColor;
|
||||
|
||||
FilamentColor::register([
|
||||
'danger' => Color::Red,
|
||||
'gray' => Color::Gray,
|
||||
'info' => Color::Blue,
|
||||
'primary' => Color::Amber,
|
||||
'secondary' => Color::Gray,
|
||||
'success' => Color::Green,
|
||||
'warning' => Color::Amber,
|
||||
]);
|
||||
```
|
||||
|
||||
The `Color` class contains every [Tailwind CSS color](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) to choose from.
|
||||
|
||||
## Using a non-Tailwind color
|
||||
|
||||
You can use custom colors that are not included in the [Tailwind CSS color](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) palette by passing an array of color shades from `50` to `900` in RGB format:
|
||||
|
||||
```php
|
||||
use Filament\Support\Facades\FilamentColor;
|
||||
|
||||
FilamentColor::register([
|
||||
'danger' => [
|
||||
50 => '254, 242, 242',
|
||||
100 => '254, 226, 226',
|
||||
200 => '254, 202, 202',
|
||||
300 => '252, 165, 165',
|
||||
400 => '248, 113, 113',
|
||||
500 => '239, 68, 68',
|
||||
600 => '220, 38, 38',
|
||||
700 => '185, 28, 28',
|
||||
800 => '153, 27, 27',
|
||||
900 => '127, 29, 29',
|
||||
950 => '69, 10, 10',
|
||||
];
|
||||
]);
|
||||
```
|
||||
|
||||
### Generating a custom color from a hex code
|
||||
|
||||
You can use the `Color::hex()` method to generate a custom color palette from a hex code:
|
||||
|
||||
```php
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Support\Facades\FilamentColor;
|
||||
|
||||
FilamentColor::register([
|
||||
'danger' => Color::hex('#ff0000'),
|
||||
]);
|
||||
```
|
||||
|
||||
### Generating a custom color from an RGB value
|
||||
|
||||
You can use the `Color::rgb()` method to generate a custom color palette from an RGB value:
|
||||
|
||||
```php
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Support\Facades\FilamentColor;
|
||||
|
||||
FilamentColor::register([
|
||||
'danger' => Color::rgb('rgb(255, 0, 0)'),
|
||||
]);
|
||||
```
|
||||
|
||||
## Registering extra colors
|
||||
|
||||
You can register extra colors that you can use throughout Filament:
|
||||
|
||||
```php
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Support\Facades\FilamentColor;
|
||||
|
||||
FilamentColor::register([
|
||||
'indigo' => Color::Indigo,
|
||||
]);
|
||||
```
|
||||
|
||||
Now, you can use this color anywhere you would normally add `primary`, `secondary`, `danger`, etc.
|
||||
@@ -20,14 +20,14 @@
|
||||
@php
|
||||
$buttonClasses = \Illuminate\Support\Arr::toCssClasses([
|
||||
...[
|
||||
'filament-button relative grid-flow-col items-center justify-center rounded-lg border font-medium outline-none transition-colors focus:ring-2 disabled:pointer-events-none disabled:opacity-70',
|
||||
"filament-button filament-button-size-{$size} relative grid-flow-col items-center justify-center rounded-lg font-medium outline-none transition focus:ring-2 disabled:pointer-events-none disabled:opacity-70",
|
||||
is_string($color) ? "filament-button-color-{$color}" : null,
|
||||
match ($size) {
|
||||
'xs' => 'filament-button-size-xs gap-1.5 px-[calc(theme(spacing.3)-1px)] py-[calc(theme(spacing.2)-1px)] text-xs',
|
||||
'sm' => 'filament-button-size-sm gap-1.5 px-[calc(theme(spacing.[3.5])-1px)] py-[calc(theme(spacing.2)-1px)] text-sm',
|
||||
'md' => 'filament-button-size-md gap-2 px-[calc(theme(spacing.4)-1px)] py-[calc(theme(spacing[2.5])-1px)] text-sm',
|
||||
'lg' => 'filament-button-size-lg gap-2 px-[calc(theme(spacing.5)-1px)] py-[calc(theme(spacing.3)-1px)] text-sm',
|
||||
'xl' => 'filament-button-size-xl gap-2 px-[calc(theme(spacing.6)-1px)] py-[calc(theme(spacing.3)-1px)] text-base',
|
||||
'xs' => 'gap-1 px-2 py-1.5 text-xs',
|
||||
'sm' => 'gap-1 px-2.5 py-1.5 text-sm',
|
||||
'md' => 'gap-1.5 px-3 py-2 text-sm',
|
||||
'lg' => 'gap-1.5 px-3.5 py-2.5 text-sm',
|
||||
'xl' => 'gap-1.5 px-4 py-3 text-sm',
|
||||
},
|
||||
'hidden' => $labeledFrom,
|
||||
match ($labeledFrom) {
|
||||
@@ -42,17 +42,17 @@
|
||||
...(
|
||||
$outlined
|
||||
? [
|
||||
'filament-button-outlined',
|
||||
'filament-button-outlined ring-1 ',
|
||||
match ($color) {
|
||||
'gray' => 'border-gray-300 text-gray-700 hover:bg-gray-500/10 focus:bg-gray-500/10 focus:ring-primary-500/50 dark:border-gray-600 dark:text-gray-200',
|
||||
default => 'border-custom-600 text-custom-600 hover:bg-custom-500/10 focus:bg-custom-500/10 focus:ring-custom-500/50 dark:border-custom-400 dark:text-custom-400',
|
||||
'gray' => 'ring-gray-600 text-gray-700 hover:bg-gray-500/10 focus:bg-gray-500/10 focus:ring-gray-500/50 dark:ring-gray-300/70 dark:text-gray-200',
|
||||
default => 'ring-custom-600 text-custom-600 hover:bg-custom-500/10 focus:bg-custom-500/10 focus:ring-custom-500/50 dark:ring-custom-400 dark:text-custom-400 dark:focus:ring-custom-300/70',
|
||||
},
|
||||
]
|
||||
: [
|
||||
'shadow',
|
||||
match ($color) {
|
||||
'gray' => 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus:border-transparent focus:bg-gray-50 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:focus:bg-gray-700',
|
||||
default => 'border-transparent bg-custom-600 text-white hover:bg-custom-500 focus:bg-custom-500 focus:ring-custom-500/50',
|
||||
'gray' => 'ring-gray-950/10 ring-1 bg-white text-gray-700 hover:bg-gray-50 focus:bg-gray-50 focus:ring-2 dark:ring-white/20 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:focus:bg-gray-700 dark:hover:ring-white/30 dark:focus:ring-white/30',
|
||||
default => 'bg-custom-600 text-white hover:bg-custom-500 focus:bg-custom-500 focus:ring-custom-500/50',
|
||||
},
|
||||
]
|
||||
),
|
||||
@@ -61,13 +61,12 @@
|
||||
$buttonStyles = \Illuminate\Support\Arr::toCssStyles([
|
||||
\Filament\Support\get_color_css_variables(
|
||||
$color,
|
||||
shades: $outlined ? [400, 500, 600] : [500, 600],
|
||||
shades: $outlined ? [300, 400, 500, 600] : [500, 600],
|
||||
) => $color !== 'gray',
|
||||
]);
|
||||
|
||||
$iconSize ??= match ($size) {
|
||||
'xs', 'sm' => 'sm',
|
||||
'md', 'lg', 'xl' => 'md',
|
||||
default => 'md',
|
||||
};
|
||||
|
||||
@@ -78,28 +77,7 @@
|
||||
default => $iconSize,
|
||||
};
|
||||
|
||||
$iconClasses = \Illuminate\Support\Arr::toCssClasses([
|
||||
'filament-button-icon',
|
||||
match ($iconPosition) {
|
||||
'before' => match ($size) {
|
||||
'xs' => '-ms-0.5',
|
||||
'sm' => '-ms-0.5',
|
||||
'md' => '-ms-1',
|
||||
'lg' => '-ms-1',
|
||||
'xl' => '-ms-1',
|
||||
default => null,
|
||||
},
|
||||
'after' => match ($size) {
|
||||
'xs' => '-me-0.5',
|
||||
'sm' => '-me-0.5',
|
||||
'md' => '-me-1',
|
||||
'lg' => '-me-1',
|
||||
'xl' => '-me-1',
|
||||
default => null,
|
||||
},
|
||||
default => null,
|
||||
} => ! $labelSrOnly,
|
||||
]);
|
||||
$iconClasses = 'filament-button-icon';
|
||||
|
||||
$indicatorClasses = 'filament-button-indicator absolute -end-1 -top-1 inline-flex h-4 w-4 items-center justify-center rounded-full bg-custom-600 text-[0.5rem] font-medium text-white';
|
||||
|
||||
@@ -135,14 +113,16 @@
|
||||
:tag="$tag"
|
||||
:tooltip="$tooltip"
|
||||
:type="$type"
|
||||
:class="match ($labeledFrom) {
|
||||
'sm' => 'sm:hidden',
|
||||
'md' => 'md:hidden',
|
||||
'lg' => 'lg:hidden',
|
||||
'xl' => 'xl:hidden',
|
||||
'2xl' => '2xl:hidden',
|
||||
default => 'hidden',
|
||||
}"
|
||||
:class="
|
||||
match ($labeledFrom) {
|
||||
'sm' => 'sm:hidden',
|
||||
'md' => 'md:hidden',
|
||||
'lg' => 'lg:hidden',
|
||||
'xl' => 'xl:hidden',
|
||||
'2xl' => '2xl:hidden',
|
||||
default => 'hidden',
|
||||
}
|
||||
"
|
||||
:attributes="\Filament\Support\prepare_inherited_attributes($attributes)"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<p
|
||||
{{ $attributes->class(['filament-card-description mt-1 text-sm text-gray-500 dark:text-gray-400']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</p>
|
||||
@@ -1,5 +1,5 @@
|
||||
<h2
|
||||
{{ $attributes->class(['filament-card-heading text-xl font-medium tracking-tight']) }}
|
||||
<h3
|
||||
{{ $attributes->class(['filament-card-heading text-base font-semibold leading-6']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</h2>
|
||||
</h3>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<h3
|
||||
{{ $attributes->class(['filament-card-subheading text-sm text-gray-600 dark:text-gray-400']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</h3>
|
||||
@@ -33,8 +33,8 @@
|
||||
x-ref="panel"
|
||||
x-float{{ $placement ? ".placement.{$placement}" : '' }}.flip{{ $shift ? '.shift' : '' }}{{ $teleport ? '.teleport' : '' }}{{ $offset ? '.offset' : '' }}="{ offset: {{ $offset }} }"
|
||||
x-cloak
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
x-transition:enter-start="scale-95 opacity-0"
|
||||
x-transition:leave-end="scale-95 opacity-0"
|
||||
@if ($attributes->has('wire:key'))
|
||||
wire:ignore.self
|
||||
wire:key="{{ $attributes->get('wire:key') }}.panel"
|
||||
|
||||
@@ -18,24 +18,31 @@
|
||||
$iconSize ??= $size;
|
||||
|
||||
$linkClasses = \Illuminate\Support\Arr::toCssClasses([
|
||||
'filament-link relative inline-flex items-center justify-center gap-0.5 font-medium outline-none hover:underline focus:underline disabled:pointer-events-none disabled:opacity-70',
|
||||
"filament-link filament-link-size-{$size} relative inline-flex items-center justify-center font-medium outline-none hover:underline focus:underline disabled:pointer-events-none disabled:opacity-70",
|
||||
'pe-4' => $indicator,
|
||||
'pointer-events-none opacity-70' => $disabled,
|
||||
match ($size) {
|
||||
'xs' => 'gap-1 text-xs',
|
||||
'sm' => 'gap-1 text-sm',
|
||||
'md' => 'gap-1.5 text-sm',
|
||||
'lg' => 'gap-1.5 text-sm',
|
||||
'xl' => 'gap-1.5 text-sm',
|
||||
},
|
||||
match ($color) {
|
||||
'gray' => 'text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200',
|
||||
default => 'text-custom-600 hover:text-custom-500 dark:text-custom-500 dark:hover:text-custom-400',
|
||||
},
|
||||
match ($size) {
|
||||
'sm' => 'text-sm',
|
||||
'md' => 'text-sm',
|
||||
'lg' => 'text-base',
|
||||
},
|
||||
]);
|
||||
|
||||
$linkStyles = \Illuminate\Support\Arr::toCssStyles([
|
||||
\Filament\Support\get_color_css_variables($color, shades: [400, 500, 600]) => $color !== 'gray',
|
||||
]);
|
||||
|
||||
$iconSize ??= match ($size) {
|
||||
'xs', 'sm' => 'sm',
|
||||
default => 'md',
|
||||
};
|
||||
|
||||
$iconSize = match ($iconSize) {
|
||||
'sm' => 'h-4 w-4',
|
||||
'md' => 'h-5 w-5',
|
||||
@@ -43,11 +50,7 @@
|
||||
default => $iconSize,
|
||||
};
|
||||
|
||||
$iconClasses = \Illuminate\Support\Arr::toCssClasses([
|
||||
'filament-link-icon',
|
||||
'me-1' => $iconPosition === 'before',
|
||||
'ms-1' => $iconPosition === 'after',
|
||||
]);
|
||||
$iconClasses = 'filament-link-icon';
|
||||
|
||||
$indicatorClasses = 'filament-link-indicator absolute -end-1 -top-1 inline-flex h-4 w-4 items-center justify-center rounded-full bg-custom-600 text-[0.5rem] font-medium text-white';
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
@props([
|
||||
'alignment' => \Filament\Actions\MountableAction::getModalActionsAlignment(),
|
||||
'fullWidth' => false,
|
||||
])
|
||||
|
||||
<div
|
||||
{{
|
||||
$attributes->class([
|
||||
'filament-modal-actions',
|
||||
'flex flex-wrap items-center gap-4 rtl:space-x-reverse' => ! $fullWidth,
|
||||
'flex-row-reverse space-x-reverse' => (! $fullWidth) && ($alignment === 'right'),
|
||||
'justify-center' => (! $fullWidth) && ($alignment === 'center'),
|
||||
'grid grid-cols-[repeat(auto-fit,minmax(0,1fr))] gap-2' => $fullWidth,
|
||||
])
|
||||
}}
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<h2
|
||||
{{ $attributes->class(['filament-modal-heading text-xl font-medium tracking-tight']) }}
|
||||
{{ $attributes->class(['filament-modal-heading text-base font-semibold leading-6']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</h2>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
@props([
|
||||
'actions' => null,
|
||||
'alignment' => 'start',
|
||||
'ariaLabelledby' => null,
|
||||
'closeButton' => true,
|
||||
'closeButton' => \Filament\Support\View\Components\Modal::$hasCloseButton,
|
||||
'closeByClickingAway' => \Filament\Support\View\Components\Modal::$isClosedByClickingAway,
|
||||
'closeEventName' => 'close-modal',
|
||||
'displayClasses' => 'inline-block',
|
||||
'footer' => null,
|
||||
'footerActions' => [],
|
||||
'footerActionsAlignment' => 'start',
|
||||
'header' => null,
|
||||
'heading' => null,
|
||||
'headingComponent' => 'filament::modal.heading',
|
||||
'hrComponent' => 'filament::hr',
|
||||
'icon' => null,
|
||||
'iconColor' => 'primary',
|
||||
'id' => null,
|
||||
'openEventName' => 'open-modal',
|
||||
'slideOver' => false,
|
||||
'subheading' => null,
|
||||
'subheadingComponent' => 'filament::modal.subheading',
|
||||
'stickyFooter' => false,
|
||||
'description' => null,
|
||||
'trigger' => null,
|
||||
'visible' => true,
|
||||
'width' => 'sm',
|
||||
@@ -22,7 +25,6 @@
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
|
||||
isOpen: false,
|
||||
|
||||
livewire: null,
|
||||
@@ -30,15 +32,18 @@
|
||||
close: function () {
|
||||
this.isOpen = false
|
||||
|
||||
this.$refs.modalContainer.dispatchEvent(new CustomEvent('modal-closed', { id: '{{ $id }}' }))
|
||||
this.$refs.modalContainer.dispatchEvent(
|
||||
new CustomEvent('modal-closed', { id: '{{ $id }}' }),
|
||||
)
|
||||
},
|
||||
|
||||
open: function () {
|
||||
this.isOpen = true
|
||||
|
||||
this.$refs.modalContainer.dispatchEvent(new CustomEvent('modal-opened', { id: '{{ $id }}' }))
|
||||
this.$refs.modalContainer.dispatchEvent(
|
||||
new CustomEvent('modal-opened', { id: '{{ $id }}' }),
|
||||
)
|
||||
},
|
||||
|
||||
}"
|
||||
x-trap.noscroll="isOpen"
|
||||
@if ($id)
|
||||
@@ -52,8 +57,8 @@
|
||||
@endif
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
class="filament-modal {{ $displayClasses }}"
|
||||
wire:ignore.self
|
||||
class="filament-modal {{ $displayClasses }}"
|
||||
>
|
||||
{{ $trigger }}
|
||||
|
||||
@@ -94,10 +99,12 @@
|
||||
>
|
||||
<div
|
||||
x-data="{ isShown: false }"
|
||||
x-init="$nextTick(() => {
|
||||
isShown = isOpen
|
||||
$watch('isOpen', () => isShown = isOpen)
|
||||
})"
|
||||
x-init="
|
||||
$nextTick(() => {
|
||||
isShown = isOpen
|
||||
$watch('isOpen', () => (isShown = isOpen))
|
||||
})
|
||||
"
|
||||
x-show="isShown"
|
||||
x-cloak
|
||||
@if (filled($id))
|
||||
@@ -119,7 +126,7 @@
|
||||
x-transition:leave-end="translate-y-8"
|
||||
@endif
|
||||
@class([
|
||||
'filament-modal-window pointer-events-auto w-full cursor-default bg-white py-2 dark:bg-gray-800',
|
||||
'filament-modal-window pointer-events-auto w-full cursor-default bg-white dark:bg-gray-800',
|
||||
'relative' => $width !== 'screen',
|
||||
'ms-auto h-screen overflow-y-auto' => $slideOver,
|
||||
'mx-auto rounded-xl' => ! ($slideOver || ($width === 'screen')),
|
||||
@@ -138,106 +145,134 @@
|
||||
'fixed inset-0' => $width === 'screen',
|
||||
])
|
||||
>
|
||||
@if ($closeButton)
|
||||
<button
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
class="absolute end-2 top-2"
|
||||
@if (filled($id))
|
||||
x-on:click="$dispatch('{{ $closeEventName }}', { id: '{{ $id }}' })"
|
||||
@else
|
||||
x-on:click="close()"
|
||||
@endif
|
||||
>
|
||||
<x-filament::icon
|
||||
name="heroicon-m-x-mark"
|
||||
alias="support::modal.close-button"
|
||||
color="text-gray-400"
|
||||
size="h-5 w-5"
|
||||
class="filament-modal-close-button cursor-pointer"
|
||||
:title="__('filament-support::components/modal.actions.close.label')"
|
||||
x-on:click="close()"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
||||
<span class="sr-only">
|
||||
{{ __('filament-support::components/modal.actions.close.label') }}
|
||||
</span>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<div
|
||||
@class([
|
||||
'flex h-full flex-col' => ($width === 'screen') || $slideOver,
|
||||
'relative flex h-full flex-col' => ($width === 'screen') || $slideOver,
|
||||
])
|
||||
>
|
||||
<div class="space-y-2">
|
||||
@if ($header)
|
||||
<div class="filament-modal-header px-6 py-2">
|
||||
@if ($heading || $header)
|
||||
<div
|
||||
@class([
|
||||
'filament-modal-header flex px-6 pt-6',
|
||||
'mb-6' => $slot->isEmpty(),
|
||||
match ($alignment) {
|
||||
'left', 'start' => 'gap-x-5',
|
||||
'center' => 'flex-col',
|
||||
},
|
||||
])
|
||||
>
|
||||
@if ($header)
|
||||
{{ $header }}
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
@if ($icon)
|
||||
<div
|
||||
@class([
|
||||
'mb-5 flex items-center justify-center' => $alignment === 'center',
|
||||
])
|
||||
>
|
||||
<div
|
||||
@class([
|
||||
'rounded-full bg-custom-100 dark:bg-custom-500/20',
|
||||
match ($alignment) {
|
||||
'left', 'start' => 'p-2',
|
||||
'center' => 'p-3',
|
||||
},
|
||||
])
|
||||
style="{{ \Filament\Support\get_color_css_variables($iconColor, shades: [100, 500]) }}"
|
||||
>
|
||||
<x-filament::icon
|
||||
alias="filament::modal"
|
||||
color="text-custom-600 dark:text-custom-400"
|
||||
:name="$icon"
|
||||
size="h-6 w-6"
|
||||
:style="\Filament\Support\get_color_css_variables($iconColor, shades: [400, 600])"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($header && ($actions || $heading || $slot->isNotEmpty() || $subheading))
|
||||
<x-dynamic-component
|
||||
:component="$hrComponent"
|
||||
class="px-2"
|
||||
<div
|
||||
@class([
|
||||
'text-center' => $alignment === 'center',
|
||||
])
|
||||
>
|
||||
<x-filament::modal.heading>
|
||||
{{ $heading }}
|
||||
</x-filament::modal.heading>
|
||||
|
||||
@if ($description)
|
||||
<p
|
||||
class="filament-modal-description mt-2 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{{ $description }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($closeButton)
|
||||
<div
|
||||
@class([
|
||||
'absolute',
|
||||
'end-6 top-6' => $slideOver,
|
||||
'end-4 top-4' => ! $slideOver,
|
||||
])
|
||||
>
|
||||
<x-filament::icon-button
|
||||
color="gray"
|
||||
icon="heroicon-o-x-mark"
|
||||
icon-alias="support::modal.close-button"
|
||||
icon-size="lg"
|
||||
:label="__('filament-support::components/modal.actions.close.label')"
|
||||
tabindex="-1"
|
||||
:x-on:click="filled($id) ? '$dispatch(' . \Illuminate\Support\Js::from($closeEventName) . ', { id: ' . \Illuminate\Support\Js::from($id) . ' })' : 'close()'"
|
||||
class="filament-modal-close-button -m-2"
|
||||
/>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($slot->isNotEmpty())
|
||||
<div
|
||||
@class([
|
||||
'filament-modal-content flex flex-col gap-y-4 py-6',
|
||||
'flex-1' => ($width === 'screen') || $slideOver,
|
||||
'pe-6 ps-[5.25rem]' => $icon && ($alignment === 'start'),
|
||||
'px-6' => ! ($icon && ($alignment === 'start')),
|
||||
])
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div
|
||||
@class([
|
||||
'filament-modal-content space-y-2 p-2',
|
||||
'flex-1 overflow-y-auto' => ($width === 'screen') || $slideOver,
|
||||
'filament-modal-footer w-full',
|
||||
'pe-6 ps-[5.25rem]' => $icon && ($alignment === 'start') && ($footerActionsAlignment !== 'center') && (! $stickyFooter),
|
||||
'px-6' => ! ($icon && ($alignment === 'start') && ($footerActionsAlignment !== 'center') && (! $stickyFooter)),
|
||||
'sticky bottom-0 rounded-b-xl border-t border-gray-200 bg-white py-5 dark:border-gray-700 dark:bg-gray-800' => $stickyFooter,
|
||||
'pb-6' => ! $stickyFooter,
|
||||
'mt-6' => (! $stickyFooter) && $slot->isEmpty() && (! $slideOver),
|
||||
'mt-auto' => $slideOver,
|
||||
])
|
||||
>
|
||||
@if ($heading || $subheading)
|
||||
@if ($footer)
|
||||
{{ $footer }}
|
||||
@elseif (count($footerActions))
|
||||
<div
|
||||
@class([
|
||||
'space-y-2 p-4 dark:text-white',
|
||||
'text-center' => ! $slideOver,
|
||||
'filament-modal-footer-actions gap-3',
|
||||
match ($footerActionsAlignment) {
|
||||
'center' => 'flex flex-col-reverse sm:grid sm:grid-cols-[repeat(auto-fit,minmax(0,1fr))]',
|
||||
'end', 'right' => 'flex flex-row-reverse flex-wrap items-center',
|
||||
'left', 'start' => 'flex flex-wrap items-center',
|
||||
},
|
||||
])
|
||||
>
|
||||
@if ($heading)
|
||||
<x-dynamic-component
|
||||
:component="$headingComponent"
|
||||
:id="$id . '.heading'"
|
||||
>
|
||||
{{ $heading }}
|
||||
</x-dynamic-component>
|
||||
@endif
|
||||
|
||||
@if ($subheading)
|
||||
<x-dynamic-component
|
||||
:component="$subheadingComponent"
|
||||
>
|
||||
{{ $subheading }}
|
||||
</x-dynamic-component>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($slot->isNotEmpty())
|
||||
<div class="space-y-4 px-4 py-2">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ $actions }}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
@if ($footer && ($actions || $heading || $slot->isNotEmpty() || $subheading))
|
||||
<x-dynamic-component
|
||||
:component="$hrComponent"
|
||||
class="px-2"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@if ($footer)
|
||||
<div class="filament-modal-footer px-6 py-2">
|
||||
{{ $footer }}
|
||||
@foreach ($footerActions as $action)
|
||||
{{ $action }}
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<p {{ $attributes->class(['filament-modal-subheading text-gray-500']) }}>
|
||||
{{ $slot }}
|
||||
</p>
|
||||
@@ -32,7 +32,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => $el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }), 200)
|
||||
setTimeout(
|
||||
() =>
|
||||
$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'start',
|
||||
}),
|
||||
200,
|
||||
)
|
||||
"
|
||||
@endif
|
||||
{{
|
||||
@@ -58,7 +66,7 @@
|
||||
} => ! $aside,
|
||||
])
|
||||
>
|
||||
<div class="filament-section-component-header grid flex-1 gap-y-1">
|
||||
<div class="filament-section-component-header flex-1">
|
||||
<div
|
||||
class="filament-section-component-header-heading-wrapper flex items-center gap-x-2"
|
||||
>
|
||||
@@ -67,12 +75,14 @@
|
||||
:name="$icon"
|
||||
alias="support::section.icon"
|
||||
color="text-custom-400"
|
||||
:size="match ($iconSize) {
|
||||
'sm' => 'h-4 w-4',
|
||||
'md' => 'h-5 w-5',
|
||||
'lg' => 'h-6 w-6',
|
||||
default => $iconSize,
|
||||
}"
|
||||
:size="
|
||||
match ($iconSize) {
|
||||
'sm' => 'h-4 w-4',
|
||||
'md' => 'h-5 w-5',
|
||||
'lg' => 'h-6 w-6',
|
||||
default => $iconSize,
|
||||
}
|
||||
"
|
||||
class="filament-section-component-header-icon"
|
||||
:style="\Filament\Support\get_color_css_variables($iconColor, shades: [400])"
|
||||
/>
|
||||
@@ -87,7 +97,7 @@
|
||||
|
||||
@if ($description?->isNotEmpty())
|
||||
<p
|
||||
class="filament-section-component-header-description text-sm text-gray-500 dark:text-gray-400"
|
||||
class="filament-section-component-header-description mt-1 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{{ $description }}
|
||||
</p>
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
@endif
|
||||
@if ($alpineActive)
|
||||
x-bind:class="{
|
||||
'hover:text-gray-800 focus:text-primary-600 dark:text-gray-400 dark:hover:text-gray-300 dark:focus:text-gray-400': ! ({{ $alpineActive }}),
|
||||
'text-primary-600 shadow bg-white dark:text-white dark:bg-primary-600': {{ $alpineActive }},
|
||||
'hover:text-gray-800 focus:text-primary-600 dark:text-gray-400 dark:hover:text-gray-300 dark:focus:text-gray-400':
|
||||
! {{ $alpineActive }},
|
||||
'text-primary-600 shadow bg-white dark:text-white dark:bg-primary-600':
|
||||
{{ $alpineActive }},
|
||||
}"
|
||||
@endif
|
||||
{{
|
||||
@@ -76,7 +78,7 @@
|
||||
<span
|
||||
@if ($alpineActive)
|
||||
x-bind:class="{
|
||||
'bg-gray-200 dark:bg-gray-600': ! ({{ $alpineActive }}),
|
||||
'bg-gray-200 dark:bg-gray-600': ! {{ $alpineActive }},
|
||||
'bg-white text-primary-600 font-medium': {{ $alpineActive }},
|
||||
}"
|
||||
@endif
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Filament\Support\Colors;
|
||||
|
||||
use Spatie\Color\Hex;
|
||||
|
||||
class ColorManager
|
||||
{
|
||||
/**
|
||||
@@ -35,6 +37,24 @@ class ColorManager
|
||||
return Color::rgb($color);
|
||||
}
|
||||
|
||||
if (is_array($color)) {
|
||||
return array_map(function (string $color): string {
|
||||
if (str_starts_with($color, '#')) {
|
||||
$color = Hex::fromString($color)->toRgb();
|
||||
|
||||
return "{$color->red()}, {$color->green()}, {$color->blue()}";
|
||||
}
|
||||
|
||||
if (str_starts_with($color, 'rgb')) {
|
||||
return (string) str($color)
|
||||
->after('rgb(')
|
||||
->before(')');
|
||||
}
|
||||
|
||||
return $color;
|
||||
}, $color);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class InstallCommand extends Command
|
||||
{
|
||||
use CanManipulateFiles;
|
||||
|
||||
protected $signature = 'filament:install {--scaffold} {--actions} {--forms} {--notifications} {--panels} {--tables} {--widgets} {--F|force}';
|
||||
protected $signature = 'filament:install {--scaffold} {--actions} {--forms} {--infolists} {--notifications} {--panels} {--tables} {--widgets} {--F|force}';
|
||||
|
||||
protected $description = 'Install Filament.';
|
||||
|
||||
@@ -80,6 +80,7 @@ class InstallCommand extends Command
|
||||
if (
|
||||
(! $this->option('actions')) &&
|
||||
(! $this->option('forms')) &&
|
||||
(! $this->option('infolists')) &&
|
||||
(! $this->option('tables'))
|
||||
) {
|
||||
$css = $filesystem->get(resource_path('css/app.css'));
|
||||
@@ -159,12 +160,12 @@ class InstallCommand extends Command
|
||||
|
||||
$command = '@php artisan filament:upgrade';
|
||||
|
||||
if (in_array($command, $configuration['scripts']['post-update-cmd'] ?? [])) {
|
||||
if (in_array($command, $configuration['scripts']['post-autoload-dump'] ?? [])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configuration['scripts']['post-update-cmd'] ??= [];
|
||||
$configuration['scripts']['post-update-cmd'][] = $command;
|
||||
$configuration['scripts']['post-autoload-dump'] ??= [];
|
||||
$configuration['scripts']['post-autoload-dump'][] = $command;
|
||||
|
||||
file_put_contents(
|
||||
$path,
|
||||
|
||||
@@ -4,8 +4,15 @@ namespace Filament\Support\View\Components;
|
||||
|
||||
class Modal
|
||||
{
|
||||
public static bool $hasCloseButton = true;
|
||||
|
||||
public static bool $isClosedByClickingAway = true;
|
||||
|
||||
public static function closeButton(bool $condition = true): void
|
||||
{
|
||||
static::$hasCloseButton = $condition;
|
||||
}
|
||||
|
||||
public static function closedByClickingAway(bool $condition = true): void
|
||||
{
|
||||
static::$isClosedByClickingAway = $condition;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,21 +1,10 @@
|
||||
import colors from 'tailwindcss/colors'
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import preset from './vendor/filament/support/tailwind.config.preset'
|
||||
|
||||
export default {
|
||||
content: ['./resources/**/*.blade.php', './vendor/filament/**/*.blade.php'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
danger: colors.red,
|
||||
info: colors.blue,
|
||||
primary: colors.amber,
|
||||
secondary: colors.gray,
|
||||
success: colors.green,
|
||||
warning: colors.amber,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [forms, typography],
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./app/Filament/**/*.php',
|
||||
'./resources/views/**/*.blade.php',
|
||||
'./vendor/filament/**/*.blade.php',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
|
||||
export default {
|
||||
content: ['./vendor/filament/**/*.blade.php'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
custom: {
|
||||
50: 'rgba(var(--c-50), <alpha-value>)',
|
||||
100: 'rgba(var(--c-100), <alpha-value>)',
|
||||
200: 'rgba(var(--c-200), <alpha-value>)',
|
||||
300: 'rgba(var(--c-300), <alpha-value>)',
|
||||
400: 'rgba(var(--c-400), <alpha-value>)',
|
||||
500: 'rgba(var(--c-500), <alpha-value>)',
|
||||
600: 'rgba(var(--c-600), <alpha-value>)',
|
||||
700: 'rgba(var(--c-700), <alpha-value>)',
|
||||
800: 'rgba(var(--c-800), <alpha-value>)',
|
||||
900: 'rgba(var(--c-900), <alpha-value>)',
|
||||
950: 'rgba(var(--c-950), <alpha-value>)',
|
||||
},
|
||||
danger: {
|
||||
50: 'rgba(var(--danger-50), <alpha-value>)',
|
||||
100: 'rgba(var(--danger-100), <alpha-value>)',
|
||||
200: 'rgba(var(--danger-200), <alpha-value>)',
|
||||
300: 'rgba(var(--danger-300), <alpha-value>)',
|
||||
400: 'rgba(var(--danger-400), <alpha-value>)',
|
||||
500: 'rgba(var(--danger-500), <alpha-value>)',
|
||||
600: 'rgba(var(--danger-600), <alpha-value>)',
|
||||
700: 'rgba(var(--danger-700), <alpha-value>)',
|
||||
800: 'rgba(var(--danger-800), <alpha-value>)',
|
||||
900: 'rgba(var(--danger-900), <alpha-value>)',
|
||||
950: 'rgba(var(--danger-950), <alpha-value>)',
|
||||
},
|
||||
gray: {
|
||||
50: 'rgba(var(--gray-50), <alpha-value>)',
|
||||
100: 'rgba(var(--gray-100), <alpha-value>)',
|
||||
200: 'rgba(var(--gray-200), <alpha-value>)',
|
||||
300: 'rgba(var(--gray-300), <alpha-value>)',
|
||||
400: 'rgba(var(--gray-400), <alpha-value>)',
|
||||
500: 'rgba(var(--gray-500), <alpha-value>)',
|
||||
600: 'rgba(var(--gray-600), <alpha-value>)',
|
||||
700: 'rgba(var(--gray-700), <alpha-value>)',
|
||||
800: 'rgba(var(--gray-800), <alpha-value>)',
|
||||
900: 'rgba(var(--gray-900), <alpha-value>)',
|
||||
950: 'rgba(var(--gray-950), <alpha-value>)',
|
||||
},
|
||||
info: {
|
||||
50: 'rgba(var(--info-50), <alpha-value>)',
|
||||
100: 'rgba(var(--info-100), <alpha-value>)',
|
||||
200: 'rgba(var(--info-200), <alpha-value>)',
|
||||
300: 'rgba(var(--info-300), <alpha-value>)',
|
||||
400: 'rgba(var(--info-400), <alpha-value>)',
|
||||
500: 'rgba(var(--info-500), <alpha-value>)',
|
||||
600: 'rgba(var(--info-600), <alpha-value>)',
|
||||
700: 'rgba(var(--info-700), <alpha-value>)',
|
||||
800: 'rgba(var(--info-800), <alpha-value>)',
|
||||
900: 'rgba(var(--info-900), <alpha-value>)',
|
||||
950: 'rgba(var(--info-950), <alpha-value>)',
|
||||
},
|
||||
primary: {
|
||||
50: 'rgba(var(--primary-50), <alpha-value>)',
|
||||
100: 'rgba(var(--primary-100), <alpha-value>)',
|
||||
200: 'rgba(var(--primary-200), <alpha-value>)',
|
||||
300: 'rgba(var(--primary-300), <alpha-value>)',
|
||||
400: 'rgba(var(--primary-400), <alpha-value>)',
|
||||
500: 'rgba(var(--primary-500), <alpha-value>)',
|
||||
600: 'rgba(var(--primary-600), <alpha-value>)',
|
||||
700: 'rgba(var(--primary-700), <alpha-value>)',
|
||||
800: 'rgba(var(--primary-800), <alpha-value>)',
|
||||
900: 'rgba(var(--primary-900), <alpha-value>)',
|
||||
950: 'rgba(var(--primary-950), <alpha-value>)',
|
||||
},
|
||||
secondary: {
|
||||
50: 'rgba(var(--secondary-50), <alpha-value>)',
|
||||
100: 'rgba(var(--secondary-100), <alpha-value>)',
|
||||
200: 'rgba(var(--secondary-200), <alpha-value>)',
|
||||
300: 'rgba(var(--secondary-300), <alpha-value>)',
|
||||
400: 'rgba(var(--secondary-400), <alpha-value>)',
|
||||
500: 'rgba(var(--secondary-500), <alpha-value>)',
|
||||
600: 'rgba(var(--secondary-600), <alpha-value>)',
|
||||
700: 'rgba(var(--secondary-700), <alpha-value>)',
|
||||
800: 'rgba(var(--secondary-800), <alpha-value>)',
|
||||
900: 'rgba(var(--secondary-900), <alpha-value>)',
|
||||
950: 'rgba(var(--secondary-950), <alpha-value>)',
|
||||
},
|
||||
success: {
|
||||
50: 'rgba(var(--success-50), <alpha-value>)',
|
||||
100: 'rgba(var(--success-100), <alpha-value>)',
|
||||
200: 'rgba(var(--success-200), <alpha-value>)',
|
||||
300: 'rgba(var(--success-300), <alpha-value>)',
|
||||
400: 'rgba(var(--success-400), <alpha-value>)',
|
||||
500: 'rgba(var(--success-500), <alpha-value>)',
|
||||
600: 'rgba(var(--success-600), <alpha-value>)',
|
||||
700: 'rgba(var(--success-700), <alpha-value>)',
|
||||
800: 'rgba(var(--success-800), <alpha-value>)',
|
||||
900: 'rgba(var(--success-900), <alpha-value>)',
|
||||
950: 'rgba(var(--success-950), <alpha-value>)',
|
||||
},
|
||||
warning: {
|
||||
50: 'rgba(var(--warning-50), <alpha-value>)',
|
||||
100: 'rgba(var(--warning-100), <alpha-value>)',
|
||||
200: 'rgba(var(--warning-200), <alpha-value>)',
|
||||
300: 'rgba(var(--warning-300), <alpha-value>)',
|
||||
400: 'rgba(var(--warning-400), <alpha-value>)',
|
||||
500: 'rgba(var(--warning-500), <alpha-value>)',
|
||||
600: 'rgba(var(--warning-600), <alpha-value>)',
|
||||
700: 'rgba(var(--warning-700), <alpha-value>)',
|
||||
800: 'rgba(var(--warning-800), <alpha-value>)',
|
||||
900: 'rgba(var(--warning-900), <alpha-value>)',
|
||||
950: 'rgba(var(--warning-950), <alpha-value>)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [forms, typography],
|
||||
}
|
||||
@@ -46,40 +46,21 @@ First, use NPM to install Tailwind CSS and its `forms` and `typography` plugins:
|
||||
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss --save-dev
|
||||
```
|
||||
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `content` path, custom `colors`, and the `plugins` you installed:
|
||||
Create a new `tailwind.config.js` file. Ensure that you add Filament's `preset` which configures colors and the plugins you installed:
|
||||
|
||||
```js
|
||||
import colors from 'tailwindcss/colors'
|
||||
import forms from '@tailwindcss/forms'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import preset from './vendor/filament/support/tailwind.config.preset'
|
||||
|
||||
export default {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./resources/**/*.blade.php',
|
||||
'./app/Filament/**/*.php',
|
||||
'./resources/views/filament/**/*.blade.php',
|
||||
'./vendor/filament/**/*.blade.php',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
danger: colors.red,
|
||||
info: colors.blue,
|
||||
primary: colors.amber,
|
||||
secondary: colors.gray,
|
||||
success: colors.green,
|
||||
warning: colors.amber,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
forms,
|
||||
typography,
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Of course, you may specify your own custom `danger`, `gray`, `info`, `primary`, `secondary`, `success` and `warning` colors, which will be used instead. But each color needs to be a [Tailwind CSS color](https://tailwindcss.com/docs/customizing-colors#color-palette-reference), or have all 50 - 950 variants specified - a single hex code or RGB value won't work here.
|
||||
|
||||
### Configuring styles
|
||||
|
||||
In `resources/css/app.css`, import Tailwind CSS, and the Filament forms CSS, which is used for action modal forms:
|
||||
@@ -174,10 +155,10 @@ composer update
|
||||
php artisan filament:upgrade
|
||||
```
|
||||
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-update-cmd` to run it automatically:
|
||||
We recommend adding the `filament:upgrade` command to your `composer.json`'s `post-autoload-dump` to run it automatically:
|
||||
|
||||
```json
|
||||
"post-update-cmd": [
|
||||
"post-autoload-dump": [
|
||||
// ...
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user