diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f5b502f..165e4b733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,22 @@ All notable changes to `laravel-livewire-tables` will be documented in this file +## [v3.3.1] - 2024-07-16 +### Bug Fixes +- Fix NumberRangeFilter initial state when loaded by querystring by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1774 +- Fix SnakeCase issue with snake-cased relations for AggregateColumn types (AvgColumn, CountColumn, SumColumn) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1777 + +### New Features +- Set a Prefix for NumberRangeFilter by @RenoLooijmans in https://github.com/rappasoft/laravel-livewire-tables/pull/1773 +- Add Separator customisation for Array Filters for FilterPills by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1772 +- Add bulk actions button/dropdown customisations by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1771 + ## [v3.3.0] - 2024-07-11 ### New Features - Add new columns (ArrayColumn, AvgColumn, CountColumn, SumColumn) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1761 - Add new column WireLinkColumn by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1763 - Add Option to Retain Selected when Searching/Filtering by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1762 +- Add Option to set locale and default date to DateRangeFilter by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1766 ## [v3.2.8] - 2024-07-03 ### Bug Fixes diff --git a/config/livewire-tables.php b/config/livewire-tables.php index 1e0b0b04c..4cc3dccab 100644 --- a/config/livewire-tables.php +++ b/config/livewire-tables.php @@ -72,6 +72,7 @@ 'dateFormat' => 'Y-m-d', // Date format that will be received by the filter 'earliestDate' => null, // The earliest acceptable date 'latestDate' => null, // The latest acceptable date + 'locale' => 'en', // The default locale ], ], @@ -87,6 +88,7 @@ 'minRange' => 0, // The minimum possible value 'maxRange' => 100, // The maximum possible value 'suffix' => '', // A suffix to append to the values when displayed + 'prefix' => '', // A prefix to prepend to the values when displayed ], ], /** @@ -105,7 +107,7 @@ ], /** - * Configuration options for MultiSelectFilter + * Configuration options for MultiSelectDropdownFilter */ 'multiSelectDropdownFilter' => [ 'defaultOptions' => [], diff --git a/docs/bulk-actions/available-methods.md b/docs/bulk-actions/available-methods.md index 512394c23..112b83189 100644 --- a/docs/bulk-actions/available-methods.md +++ b/docs/bulk-actions/available-methods.md @@ -184,61 +184,6 @@ public function configure(): void } ``` -## setBulkActionsThAttributes - -You may pass an array to this method, which allows you to pass Custom Attributes into the table header - -```php -public function configure(): void -{ - $this->setBulkActionsThAttributes([ - 'class' => 'bg-red-500', - 'default' => false - ]); -} -``` - -## setBulkActionsThCheckboxAttributes - -You may pass an array to this method, which allows you to pass Custom Attributes into the Select All/None checkbox in the Table Header - -```php -public function configure(): void -{ - $this->setBulkActionsThCheckboxAttributes([ - 'class' => 'bg-blue-500', - 'default' => false - ]); -} -``` - -## setBulkActionsTdAttributes - -You may pass an array to this method, which allows you to pass Custom Attributes into the td containing the Bulk Actions Checkbox for the row - -```php -public function configure(): void -{ - $this->setBulkActionsTdAttributes([ - 'class' => 'bg-green-500', - 'default' => true - ]); -} -``` - -## setBulkActionsTdCheckboxAttributes - -You may pass an array to this method, which allows you to pass Custom Attributes into the Bulk Actions Checkbox for the row - -```php -public function configure(): void -{ - $this->setBulkActionsTdCheckboxAttributes([ - 'class' => 'bg-green-500', - 'default' => true - ]); -} -``` ## setShouldAlwaysHideBulkActionsDropdownOption @@ -345,4 +290,4 @@ public function configure(): void { $this->setClearSelectedOnFilterDisabled(); } -``` +``` \ No newline at end of file diff --git a/docs/bulk-actions/customisations.md b/docs/bulk-actions/customisations.md new file mode 100644 index 000000000..dfe64e2e8 --- /dev/null +++ b/docs/bulk-actions/customisations.md @@ -0,0 +1,106 @@ +--- +title: Customising Style +weight: 5 +--- + +## setBulkActionsThAttributes + +You may pass an array to this method, which allows you to pass Custom Attributes into the table header + +```php +public function configure(): void +{ + $this->setBulkActionsThAttributes([ + 'class' => 'bg-red-500', + 'default' => false + ]); +} +``` + +## setBulkActionsThCheckboxAttributes + +You may pass an array to this method, which allows you to pass Custom Attributes into the Select All/None checkbox in the Table Header + +```php +public function configure(): void +{ + $this->setBulkActionsThCheckboxAttributes([ + 'class' => 'bg-blue-500', + 'default' => false + ]); +} +``` + +## setBulkActionsTdAttributes + +You may pass an array to this method, which allows you to pass Custom Attributes into the td containing the Bulk Actions Checkbox for the row + +```php +public function configure(): void +{ + $this->setBulkActionsTdAttributes([ + 'class' => 'bg-green-500', + 'default' => true + ]); +} +``` + +## setBulkActionsTdCheckboxAttributes + +You may pass an array to this method, which allows you to pass Custom Attributes into the Bulk Actions Checkbox for the row + +```php +public function configure(): void +{ + $this->setBulkActionsTdCheckboxAttributes([ + 'class' => 'bg-green-500', + 'default' => true + ]); +} +``` + +## setBulkActionsButtonAttributes + +You may pass an array to this method, which allows you to pass Custom Attributes into the Bulk Actions Button in the Toolbar + +```php +public function configure(): void +{ + $this->setBulkActionsButtonAttributes([ + 'class' => 'bg-green-500', + 'default-colors' => true, + 'default-styling' => true, + ]); +} +``` + +## setBulkActionsMenuAttributes + +You may pass an array to this method, which allows you to pass Custom Attributes into the Bulk Actions Menu + +```php +public function configure(): void +{ + $this->setBulkActionsMenuAttributes([ + 'class' => 'bg-green-500', + 'default-colors' => true, + 'default-styling' => true, + ]); +} +``` + + +## setBulkActionsMenuItemAttributes + +You may pass an array to this method, which allows you to pass Custom Attributes into Items on the Bulk Actions Menu + +```php +public function configure(): void +{ + $this->setBulkActionsMenuItemAttributes([ + 'class' => 'bg-green-500', + 'default-colors' => true, + 'default-styling' => true, + ]); +} +``` diff --git a/docs/column-types/standard_column.md b/docs/column-types/standard_column.md index 6f7844982..7d5b4ede3 100644 --- a/docs/column-types/standard_column.md +++ b/docs/column-types/standard_column.md @@ -28,4 +28,4 @@ A standard Column has a multitude of different options, making it simple for you Anonymous Columns - + \ No newline at end of file diff --git a/docs/filter-types/filters-date.md b/docs/filter-types/filters-date.md index 488cda84f..d29a900df 100644 --- a/docs/filter-types/filters-date.md +++ b/docs/filter-types/filters-date.md @@ -3,8 +3,6 @@ title: Date Filters weight: 2 --- -## Date Filters - Date filters are HTML date elements. ```php diff --git a/docs/filter-types/filters-daterange.md b/docs/filter-types/filters-daterange.md index 37d0ea552..e496b31ed 100644 --- a/docs/filter-types/filters-daterange.md +++ b/docs/filter-types/filters-daterange.md @@ -3,8 +3,6 @@ title: DateRange Filters weight: 3 --- -## DateRange Filters - DateRange filters are Flatpickr based components, and simply filtering by a date range. If you would like to more smoothly filter your query by a start and end date, you can use the DateRangeFilter: ```php @@ -33,6 +31,7 @@ public function filters(): array 'earliestDate' => '2020-01-01', // The earliest acceptable date 'latestDate' => '2023-08-01', // The latest acceptable date 'placeholder' => 'Enter Date Range', // A placeholder value + 'locale' => 'en', ]) ->setFilterPillValues([0 => 'minDate', 1 => 'maxDate']) // The values that will be displayed for the Min/Max Date Values ->filter(function (Builder $builder, array $dateRange) { // Expects an array. @@ -44,31 +43,85 @@ public function filters(): array } ``` +## Configuration +By default, this filter will inject the Flatpickr JS Library and CSS. However, you can customise this behaviour using the configuration file. + +### Option 1 - The default behaviour: +``` + 'inject_third_party_assets_enabled' => true, +``` +### Option 2 - Bundled +If you choose to bundle the Tables JS/CSS (recommended) by adding the following to your build process: -## Configuration -By default, this filter will use a CDN to include the Flatpickr JS Library and CSS. However, you can customise this behaviour using the configuration file. +``` +'vendor/rappasoft/laravel-livewire-tables/resources/js/laravel-livewire-tables-thirdparty.min.js'; +``` + +or in your app.js + +``` +import '../../vendor/rappasoft/livewire-tables/resources/js/laravel-livewire-tables-thirdparty.min.js'; +``` -### Option 1 - The default CDN behaviour: +Then you should disable injection to avoid conflicts: + +``` + 'inject_third_party_assets_enabled' => false, +``` + +### Option 3 - CDN +You must ensure that Flatpickr is present PRIOR to the tables loading. For example, to add Flatpickr with the Spanish locale, ensure that the following is present in your Layout head section. Noting the "async" presence to ensure that the script is present before a page renders. + +It is typically recommended not to utilise the CDN approach, as changes to core code may impact behaviour, and you may need to implement changes to your CSP if present. + +If using the CDN approach, ensure the following config matches: ``` - 'published_third_party_assets' => false, - 'remote_third_party_assets' => true, + 'inject_third_party_assets_enabled' => false, ``` -### Option 2 - Publish included version -You may publish the included version of Flatpickr. To do so, run: +Then include the following in your layout: ``` -php artisan vendor:publish --tag=livewire-tables-public +// Flatpickr Core Script + + +// Flatpickr Core CSS + ``` -This will publish the tested version of Flatpickr to your public directory. You should then set + +### Option 4 - Locally Installed +If you have a locally installed version of Flatpickr already, you can set injection to false, and your local version will be used instead. ``` - 'published_third_party_assets' => true, - 'remote_third_party_assets' => false, + 'inject_third_party_assets_enabled' => false, ``` -### Option 3 - Locally Installed -If you have a locally installed version of Flatpickr already, you can set both options to false, and your local version will be used instead. +## Localisation (BETA) +The default installation includes only the English (en) locale. + +### Bundling +Should you wish to localise, you must include the Flatpickr locale files in your build pipeline. This applies to only the specific locales that you require in your app.js (requires adding the flatpickr library to your package.json by executing "npm i flatpickr --save") ``` - 'published_third_party_assets' => false, - 'remote_third_party_assets' => false, +import { Arabic } from "../imports/flatpickr/l10n/ar.js"; +import { Catalan } from "../imports/flatpickr/l10n/cat.js"; +import { Danish } from "../imports/flatpickr/l10n/da.js"; +import { German } from "../imports/flatpickr/l10n/de.js"; +import { Spanish } from "../imports/flatpickr/l10n/es.js"; +import { French } from "../imports/flatpickr/l10n/fr.js"; +import { Indonesian } from "../imports/flatpickr/l10n/id.js"; +import { Italian } from "../imports/flatpickr/l10n/it.js"; +import { Malaysian } from "../imports/flatpickr/l10n/ms.js"; +import { Dutch } from "../imports/flatpickr/l10n/nl.js"; +import { Portuguese } from "../imports/flatpickr/l10n/pt.js"; +import { Russian } from "../imports/flatpickr/l10n/ru.js" +import { Thai } from "../imports/flatpickr/l10n/th.js" +import { Turkish } from "../imports/flatpickr/l10n/tr.js" +import { Ukrainian } from "../imports/flatpickr/l10n/uk.js" +``` + +### CDN +You can also add locales using the Flatpickr CDN, ensuring that these are loaded before the page renders. + +For example to add German (de), ensure that the following is in the "head" section of your layout, ideally before your app.js ``` + +``` \ No newline at end of file diff --git a/docs/filter-types/filters-datetime.md b/docs/filter-types/filters-datetime.md index 288d90b4b..64c14eca4 100644 --- a/docs/filter-types/filters-datetime.md +++ b/docs/filter-types/filters-datetime.md @@ -3,8 +3,6 @@ title: DateTime Filters weight: 4 --- -## DateTime Filters - DateTime filters are HTML datetime-local elements and act the same as date filters. ```php diff --git a/docs/filter-types/filters-livewire-component.md b/docs/filter-types/filters-livewire-component.md index d11d337f9..5ffb6dac6 100644 --- a/docs/filter-types/filters-livewire-component.md +++ b/docs/filter-types/filters-livewire-component.md @@ -3,8 +3,6 @@ title: Livewire Custom Filter (Beta) weight: 11 --- -## Livewire Custom Filter - **IN BETA** This feature is currently in beta, and use in production is not recommended. diff --git a/docs/filter-types/filters-multiselect-dropdown.md b/docs/filter-types/filters-multiselect-dropdown.md index d81c14570..2f3ed9c01 100644 --- a/docs/filter-types/filters-multiselect-dropdown.md +++ b/docs/filter-types/filters-multiselect-dropdown.md @@ -3,9 +3,6 @@ title: Multi-Select Dropdown Filters weight: 5 --- - -## Multi-select dropdown Filters - Multi-select dropdown filters are a simple dropdown list. The user can select multiple options from the list. There is also an 'All' option that will select all values ```php @@ -27,3 +24,26 @@ public function filters(): array ]; } ``` + +## Filter Pills Separator + +As this filter returns one or more values, you have the option to utilise a custom separator for the values displayed in the Filter Pills section at the top of the table. The default is ", ", but you may use any HTML string to separate the selected values + +```php +public function filters(): array +{ + return [ + MultiSelectFilter::make('Tags') + ->options( + Tag::query() + ->orderBy('name') + ->get() + ->keyBy('id') + ->map(fn($tag) => $tag->name) + ->toArray() + ) + ->setPillsSeparator('
'), + ]; +} + +``` \ No newline at end of file diff --git a/docs/filter-types/filters-multiselect.md b/docs/filter-types/filters-multiselect.md index f6c6bfa37..f91a65912 100644 --- a/docs/filter-types/filters-multiselect.md +++ b/docs/filter-types/filters-multiselect.md @@ -3,8 +3,6 @@ title: Multi-Select Filters weight: 6 --- -## Multi-select Filters - Multi-select filters are a list of checkboxes. The user can select multiple options from the list. There is also an 'All' option that will select all values. ```php @@ -25,3 +23,26 @@ public function filters(): array ]; } ``` + +## Filter Pills Separator + +As this filter returns one or more values, you have the option to utilise a custom separator for the values displayed in the Filter Pills section at the top of the table. The default is ", ", but you may use any HTML string to separate the selected values + +```php +public function filters(): array +{ + return [ + MultiSelectFilter::make('Tags') + ->options( + Tag::query() + ->orderBy('name') + ->get() + ->keyBy('id') + ->map(fn($tag) => $tag->name) + ->toArray() + ) + ->setPillsSeparator('
'), + ]; +} + +``` \ No newline at end of file diff --git a/docs/filter-types/filters-number-range.md b/docs/filter-types/filters-number-range.md index 0969e5c33..938900ef9 100644 --- a/docs/filter-types/filters-number-range.md +++ b/docs/filter-types/filters-number-range.md @@ -3,8 +3,6 @@ title: NumberRange Filters weight: 7 --- -## NumberRange Filters - NumberRange filters allow for a minimum and maximum value to be input on a single slider. ```php @@ -22,6 +20,7 @@ public function filters(): array 'minRange' => 0, 'maxRange' => 100, 'suffix' => '%', + 'prefix' => '$', ]) ->filter(function (Builder $builder, array $values) { $builder->where('users.success_rate', '>=', intval($values['min'])) @@ -34,4 +33,6 @@ public function filters(): array The default values should be set in the options() method. -You may also specify a minimum and maximum range in the config() options, and should you wish to use real values instead of a percentage, you can change the "suffix" to a metric of your choosing. +You may also specify a minimum and maximum range in the config() options, and should you wish to use real values instead of a percentage. +You can change the "suffix" to a metric of your choosing. +You can change the "prefix" to an item of your choosing (e.g $/£/€) diff --git a/docs/filter-types/filters-number.md b/docs/filter-types/filters-number.md index 3b629dd0a..e1423ae9c 100644 --- a/docs/filter-types/filters-number.md +++ b/docs/filter-types/filters-number.md @@ -3,8 +3,6 @@ title: Number Filters weight: 8 --- -## Number Filters - Number filters are just HTML number inputs. ```php diff --git a/docs/filter-types/filters-select.md b/docs/filter-types/filters-select.md index d28cfba94..568522f1b 100644 --- a/docs/filter-types/filters-select.md +++ b/docs/filter-types/filters-select.md @@ -3,8 +3,6 @@ title: Select Filters weight: 9 --- -## Select Filters - Select filters are a simple dropdown list. The user selects one choice from the list. ```php diff --git a/docs/filter-types/filters-text.md b/docs/filter-types/filters-text.md index 649384915..22255dfd4 100644 --- a/docs/filter-types/filters-text.md +++ b/docs/filter-types/filters-text.md @@ -3,8 +3,6 @@ title: Text Filters weight: 10 --- -## Text Filters - Text filters are just HTML text fields. ```php diff --git a/docs/filter-types/introduction.md b/docs/filter-types/introduction.md index d509cdc39..c553de5f9 100644 --- a/docs/filter-types/introduction.md +++ b/docs/filter-types/introduction.md @@ -4,3 +4,36 @@ weight: 1 --- There are several Filter types available for use, offering a range of capabilities to filter your data. + + \ No newline at end of file diff --git a/resources/css/laravel-livewire-tables.css b/resources/css/laravel-livewire-tables.css index 77c564573..2e8ffef5f 100644 --- a/resources/css/laravel-livewire-tables.css +++ b/resources/css/laravel-livewire-tables.css @@ -137,13 +137,11 @@ .range-slider::before { --before: 1; - --at-edge: var(--thumb-close-to-min); counter-reset: x var(--min); left: var(--offset); } .range-slider::after { - --at-edge: var(--thumb-close-to-max); counter-reset: x var(--max); right: var(--offset); } diff --git a/resources/css/laravel-livewire-tables.min.css b/resources/css/laravel-livewire-tables.min.css index bf77392cc..750fbd378 100644 --- a/resources/css/laravel-livewire-tables.min.css +++ b/resources/css/laravel-livewire-tables.min.css @@ -4,4 +4,4 @@ var(--value, 0), var(--max));--value-b:var(--value, 0);--text-value-a:var(--text-value, "");--completed-a:calc((var(--value-a) - var(--min)) / (var(--max) - var(--min)) * 100);--completed-b:calc((var(--value-b) - var(--min)) / (var(--max) - var(--min)) * 100);--ca:Min(var(--completed-a), var(--completed-b));--cb:Max(var(--completed-a), var(--completed-b));--thumbs-too-close:Clamp(-1, 1000 * (Min(1, Max(var(--cb) - var(--ca) - 5, -1)) + 0.001), 1);--thumb-close-to-min:Min(1, Max(var(--ca) - 2, - 0));--thumb-close-to-max:Min(1, Max(98 - var(--cb), 0));display:inline-block;height:max(var(--track-height),var(--thumb-size));background:linear-gradient(to right,var(--ticks-color,silver) var(--ticks-thickness),transparent 1px) repeat-x;background-size:var(--tickIntervalPerc) var(--ticks-height);background-position-x:calc(var(--thumb-size)/ 2 - var(--ticks-thickness)/ 2);background-position-y:var(--flip-y,bottom);padding-bottom:var(--flip-y,var(--ticks-gap));padding-top:calc(var(--flip-y) * var(--ticks-gap));position:relative;z-index:1}.range-slider::after,.range-slider::before{--offset:calc(var(--thumb-size) / 2);content:counter(x);display:var(--show-min-max,block);font:var(--min-max-font, 12px Arial);position:absolute;bottom:var(--flip-y,-2.5ch);top:calc(-2.5ch * var(--flip-y));opacity:clamp(0, var(--at-edge), var(--min-max-opacity));transform:translateX(calc(var(--min-max-x-offset) * var(--before,-1) * -1)) scale(var(--at-edge));pointer-events:none}.dark .range-slider::after,.dark .range-slider::before,.dark .range-slider>input+output::after,.dark .range-slider>input:first-of-type+output::after{color:#fff}.range-slider::before{--before:1;--at-edge:var(--thumb-close-to-min);counter-reset:x var(--min);left:var(--offset)}.range-slider::after{--at-edge:var(--thumb-close-to-max);counter-reset:x var(--max);right:var(--offset)}.range-slider__progress::after,.range-slider__progress::before{content:"";top:0;right:0;bottom:0;border-radius:inherit;left:0}.range-slider__values{position:relative;top:50%;line-height:0;text-align:justify;width:100%;pointer-events:none;margin:0 auto;z-index:5}.range-slider__values::after{content:"";width:100%;display:inline-block;height:0;background:red}.range-slider__progress{--start-end:calc(var(--thumb-size) / 2);--clip-end:calc(100% - (var(--cb)) * 1%);--clip-start:calc(var(--ca) * 1%);--clip:inset(-20px var(--clip-end) -20px var(--clip-start));position:absolute;left:var(--start-end);right:var(--start-end);top:calc(var(--ticks-gap) * var(--flip-y,0) + var(--thumb-size)/ 2 - var(--track-height)/ 2);height:calc(var(--track-height));background:var(--progress-background,#eee);pointer-events:none;z-index:-1;border-radius:var(--progress-radius)}.range-slider__progress::before{position:absolute;-webkit-clip-path:var(--clip);clip-path:var(--clip);background:var(--fill-color,#0366d6);box-shadow:var(--progress-flll-shadow);z-index:1}.range-slider__progress::after{position:absolute;box-shadow:var(--progress-shadow);pointer-events:none}.range-slider>input{-webkit-appearance:none;width:100%;height:var(--thumb-size);margin:0;position:absolute;left:0;top:calc(50% - Max(var(--track-height),var(--thumb-size))/ 2 + calc(var(--ticks-gap)/ 2 * var(--flip-y,-1)));cursor:-webkit-grab;cursor:grab;outline:0;background:0 0}.range-slider>input:not(:only-of-type){pointer-events:none}.range-slider>input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-webkit-transition:.1s;transition:.1s}.range-slider>input::-moz-range-thumb{-moz-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-moz-transition:.1s;transition:.1s}.range-slider>input::-ms-thumb{appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-ms-transition:.1s;transition:.1s}.range-slider>input:hover{--thumb-shadow:var(--thumb-shadow-hover)}.range-slider>input:hover+output{--value-background:var(--value-background-hover, #0366d6);--y-offset:-5px;color:var(--value-active-color,#fff);box-shadow:0 0 0 3px var(--value-background)}.range-slider>input:active{--thumb-shadow:var(--thumb-shadow-active);cursor:-webkit-grabbing;cursor:grabbing;z-index:2}.range-slider>input:active+output{transition:none}.range-slider>input:first-of-type{--is-left-most:Clamp(0, (var(--value-a) - var(--value-b)) * 99999, 1)}.range-slider>input:first-of-type+output{--value:var(--value-a);--x-offset:calc(var(--completed-a) * -1%)}.range-slider>input:first-of-type+output:not(:only-of-type){--flip:calc(var(--thumbs-too-close) * -1)}.range-slider>input:first-of-type+output::after{content:var(--prefix, "") var(--text-value-a) var(--suffix, "")}.range-slider>input:nth-of-type(2){--is-left-most:Clamp(0, (var(--value-b) - var(--value-a)) * 99999, 1)}.range-slider>input:nth-of-type(2)+output{--value:var(--value-b)}.range-slider>input:only-of-type~.range-slider__progress{--clip-start:0}.range-slider>input+output{--flip:-1;--x-offset:calc(var(--completed-b) * -1%);--pos:calc(((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%);pointer-events:none;position:absolute;z-index:5;background:var(--value-background);border-radius:10px;padding:2px 4px;left:var(--pos);transform:translate(var(--x-offset),calc(150% * var(--flip) - (var(--y-offset,0px) + var(--value-offset-y)) * var(--flip)));transition:.12s ease-out,left}.range-slider>input+output::after{content:var(--prefix, "") var(--text-value-b) var(--suffix, "");font:var(--value-font)}body>.range-slider,label[dir=rtl] .range-slider{width:clamp(300px,50vw,800px);min-width:200px}.superhide{display:none}.lds-hourglass{display:inline-block;position:relative;width:80px;height:80px}.lds-hourglass:after{content:" ";display:block;border-radius:50%;width:0;height:0;margin:8px;box-sizing:border-box;border:32px solid #000;border-color:#000 transparent #fff;animation:1.2s infinite lds-hourglass}.dark .lds-hourglass:after{border:32px solid #fff;border-color:#fff transparent #000}@keyframes lds-hourglass{0%{transform:rotate(0);animation-timing-function:cubic-bezier(0.55,0.055,0.675,0.19)}50%{transform:rotate(900deg);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}100%{transform:rotate(1800deg)}} \ No newline at end of file + 0));--thumb-close-to-max:Min(1, Max(98 - var(--cb), 0));display:inline-block;height:max(var(--track-height),var(--thumb-size));background:linear-gradient(to right,var(--ticks-color,silver) var(--ticks-thickness),transparent 1px) repeat-x;background-size:var(--tickIntervalPerc) var(--ticks-height);background-position-x:calc(var(--thumb-size)/ 2 - var(--ticks-thickness)/ 2);background-position-y:var(--flip-y,bottom);padding-bottom:var(--flip-y,var(--ticks-gap));padding-top:calc(var(--flip-y) * var(--ticks-gap));position:relative;z-index:1}.range-slider::after,.range-slider::before{--offset:calc(var(--thumb-size) / 2);content:counter(x);display:var(--show-min-max,block);font:var(--min-max-font, 12px Arial);position:absolute;bottom:var(--flip-y,-2.5ch);top:calc(-2.5ch * var(--flip-y));opacity:clamp(0, var(--at-edge), var(--min-max-opacity));transform:translateX(calc(var(--min-max-x-offset) * var(--before,-1) * -1)) scale(var(--at-edge));pointer-events:none}.dark .range-slider::after,.dark .range-slider::before,.dark .range-slider>input+output::after,.dark .range-slider>input:first-of-type+output::after{color:#fff}.range-slider::before{--before:1;counter-reset:x var(--min);left:var(--offset)}.range-slider::after{counter-reset:x var(--max);right:var(--offset)}.range-slider__progress::after,.range-slider__progress::before{content:"";top:0;right:0;bottom:0;border-radius:inherit;left:0}.range-slider__values{position:relative;top:50%;line-height:0;text-align:justify;width:100%;pointer-events:none;margin:0 auto;z-index:5}.range-slider__values::after{content:"";width:100%;display:inline-block;height:0;background:red}.range-slider__progress{--start-end:calc(var(--thumb-size) / 2);--clip-end:calc(100% - (var(--cb)) * 1%);--clip-start:calc(var(--ca) * 1%);--clip:inset(-20px var(--clip-end) -20px var(--clip-start));position:absolute;left:var(--start-end);right:var(--start-end);top:calc(var(--ticks-gap) * var(--flip-y,0) + var(--thumb-size)/ 2 - var(--track-height)/ 2);height:calc(var(--track-height));background:var(--progress-background,#eee);pointer-events:none;z-index:-1;border-radius:var(--progress-radius)}.range-slider__progress::before{position:absolute;-webkit-clip-path:var(--clip);clip-path:var(--clip);background:var(--fill-color,#0366d6);box-shadow:var(--progress-flll-shadow);z-index:1}.range-slider__progress::after{position:absolute;box-shadow:var(--progress-shadow);pointer-events:none}.range-slider>input{-webkit-appearance:none;width:100%;height:var(--thumb-size);margin:0;position:absolute;left:0;top:calc(50% - Max(var(--track-height),var(--thumb-size))/ 2 + calc(var(--ticks-gap)/ 2 * var(--flip-y,-1)));cursor:-webkit-grab;cursor:grab;outline:0;background:0 0}.range-slider>input:not(:only-of-type){pointer-events:none}.range-slider>input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-webkit-transition:.1s;transition:.1s}.range-slider>input::-moz-range-thumb{-moz-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-moz-transition:.1s;transition:.1s}.range-slider>input::-ms-thumb{appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-ms-transition:.1s;transition:.1s}.range-slider>input:hover{--thumb-shadow:var(--thumb-shadow-hover)}.range-slider>input:hover+output{--value-background:var(--value-background-hover, #0366d6);--y-offset:-5px;color:var(--value-active-color,#fff);box-shadow:0 0 0 3px var(--value-background)}.range-slider>input:active{--thumb-shadow:var(--thumb-shadow-active);cursor:-webkit-grabbing;cursor:grabbing;z-index:2}.range-slider>input:active+output{transition:none}.range-slider>input:first-of-type{--is-left-most:Clamp(0, (var(--value-a) - var(--value-b)) * 99999, 1)}.range-slider>input:first-of-type+output{--value:var(--value-a);--x-offset:calc(var(--completed-a) * -1%)}.range-slider>input:first-of-type+output:not(:only-of-type){--flip:calc(var(--thumbs-too-close) * -1)}.range-slider>input:first-of-type+output::after{content:var(--prefix, "") var(--text-value-a) var(--suffix, "")}.range-slider>input:nth-of-type(2){--is-left-most:Clamp(0, (var(--value-b) - var(--value-a)) * 99999, 1)}.range-slider>input:nth-of-type(2)+output{--value:var(--value-b)}.range-slider>input:only-of-type~.range-slider__progress{--clip-start:0}.range-slider>input+output{--flip:-1;--x-offset:calc(var(--completed-b) * -1%);--pos:calc(((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%);pointer-events:none;position:absolute;z-index:5;background:var(--value-background);border-radius:10px;padding:2px 4px;left:var(--pos);transform:translate(var(--x-offset),calc(150% * var(--flip) - (var(--y-offset,0px) + var(--value-offset-y)) * var(--flip)));transition:.12s ease-out,left}.range-slider>input+output::after{content:var(--prefix, "") var(--text-value-b) var(--suffix, "");font:var(--value-font)}body>.range-slider,label[dir=rtl] .range-slider{width:clamp(300px,50vw,800px);min-width:200px}.superhide{display:none}.lds-hourglass{display:inline-block;position:relative;width:80px;height:80px}.lds-hourglass:after{content:" ";display:block;border-radius:50%;width:0;height:0;margin:8px;box-sizing:border-box;border:32px solid #000;border-color:#fff transparent;animation:1.2s infinite lds-hourglass}.dark .lds-hourglass:after{border:32px solid #fff}@keyframes lds-hourglass{0%{transform:rotate(0);animation-timing-function:cubic-bezier(0.55,0.055,0.675,0.19)}50%{transform:rotate(900deg);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}100%{transform:rotate(1800deg)}} \ No newline at end of file diff --git a/resources/js/laravel-livewire-tables.js b/resources/js/laravel-livewire-tables.js index 0d068dac1..c9afc8331 100644 --- a/resources/js/laravel-livewire-tables.js +++ b/resources/js/laravel-livewire-tables.js @@ -60,14 +60,20 @@ document.addEventListener('alpine:init', () => { defaultMin: filterConfig['minRange'], defaultMax: filterConfig['maxRange'], restrictUpdates: false, - updateStyles() { + initialiseStyles() + { let numRangeFilterContainer = document.getElementById(parentElementPath); - let currentFilterMin = document.getElementById(childElementRoot + "-min"); - let currentFilterMax = document.getElementById(childElementRoot + "-max"); - numRangeFilterContainer.style.setProperty('--value-a', currentFilterMin.value); - numRangeFilterContainer.style.setProperty('--text-value-a', JSON.stringify(currentFilterMin.value)); - numRangeFilterContainer.style.setProperty('--value-b', currentFilterMax.value); - numRangeFilterContainer.style.setProperty('--text-value-b', JSON.stringify(currentFilterMax.value)); + numRangeFilterContainer.style.setProperty('--value-a', this.wireValues['min'] ?? this.filterMin); + numRangeFilterContainer.style.setProperty('--text-value-a', JSON.stringify(this.wireValues['min'] ?? this.filterMin)); + numRangeFilterContainer.style.setProperty('--value-b', this.wireValues['max'] ?? this.filterMax); + numRangeFilterContainer.style.setProperty('--text-value-b', JSON.stringify(this.wireValues['max'] ?? this.filterMax)); + }, + updateStyles(filterMin, filterMax) { + let numRangeFilterContainer = document.getElementById(parentElementPath); + numRangeFilterContainer.style.setProperty('--value-a', filterMin); + numRangeFilterContainer.style.setProperty('--text-value-a', JSON.stringify(filterMin)); + numRangeFilterContainer.style.setProperty('--value-b', filterMax); + numRangeFilterContainer.style.setProperty('--text-value-b', JSON.stringify(filterMax)); }, setupWire() { if (this.wireValues !== undefined) { @@ -77,7 +83,7 @@ document.addEventListener('alpine:init', () => { this.filterMin = this.originalMin = this.defaultMin; this.filterMax = this.originalMax = this.defaultMax; } - this.updateStyles(); + this.updateStyles(this.filterMin, this.filterMax); }, allowUpdates() { this.updateWire(); @@ -95,7 +101,7 @@ document.addEventListener('alpine:init', () => { this.originalMin = tmpFilterMin; this.originalMax = tmpFilterMax; } - this.updateStyles(); + this.updateStyles(this.filterMin,this.filterMax); }, updateWireable() { if (this.hasUpdate) { @@ -103,9 +109,9 @@ document.addEventListener('alpine:init', () => { this.wireValues = { 'min': this.filterMin, 'max': this.filterMax }; wire.set('filterComponents.' + filterKey, this.wireValues); } - }, init() { + this.initialiseStyles(); this.setupWire(); this.$watch('allFilters', value => this.setupWire()); }, diff --git a/resources/js/laravel-livewire-tables.min.js b/resources/js/laravel-livewire-tables.min.js index 3b5991c4a..740ebca80 100644 --- a/resources/js/laravel-livewire-tables.min.js +++ b/resources/js/laravel-livewire-tables.min.js @@ -1 +1 @@ -document.addEventListener("alpine:init",()=>{Alpine.data("tableWrapper",(e,t)=>({childElementOpen:!1,filtersOpen:e.entangle("filterSlideDownDefaultVisible"),paginationCurrentCount:e.entangle("paginationCurrentCount"),paginationTotalItemCount:e.entangle("paginationTotalItemCount"),paginationCurrentItems:e.entangle("paginationCurrentItems"),selectedItems:e.entangle("selected"),hideBulkActionsWhenEmpty:e.entangle("hideBulkActionsWhenEmpty"),toggleSelectAll(){t&&(this.paginationTotalItemCount===this.selectedItems.length?this.clearSelected():this.setAllSelected())},setAllSelected(){t&&e.setAllSelected()},clearSelected(){t&&e.clearSelected()},selectAllOnPage(){if(!t)return;let e=this.selectedItems,i=this.paginationCurrentItems.values();for(let l of i)e.push(l.toString());this.selectedItems=[...new Set(e)]}})),Alpine.data("numberRangeFilter",(e,t,i,l,a)=>({allFilters:e.entangle("filterComponents",!1),originalMin:0,originalMax:100,filterMin:0,filterMax:100,currentMin:0,currentMax:100,hasUpdate:!1,wireValues:e.entangle("filterComponents."+t,!1),defaultMin:l.minRange,defaultMax:l.maxRange,restrictUpdates:!1,updateStyles(){let e=document.getElementById(i),t=document.getElementById(a+"-min"),l=document.getElementById(a+"-max");e.style.setProperty("--value-a",t.value),e.style.setProperty("--text-value-a",JSON.stringify(t.value)),e.style.setProperty("--value-b",l.value),e.style.setProperty("--text-value-b",JSON.stringify(l.value))},setupWire(){void 0!==this.wireValues?(this.filterMin=this.originalMin=void 0!==this.wireValues.min?this.wireValues.min:this.defaultMin,this.filterMax=this.originalMax=void 0!==this.wireValues.max?this.wireValues.max:this.defaultMax):(this.filterMin=this.originalMin=this.defaultMin,this.filterMax=this.originalMax=this.defaultMax),this.updateStyles()},allowUpdates(){this.updateWire()},updateWire(){let e=parseInt(this.filterMin),t=parseInt(this.filterMax);(e!=this.originalMin||t!=this.originalMax)&&(tthis.setupWire())}})),Alpine.data("flatpickrFilter",(e,t,i,l,a)=>({wireValues:e.entangle("filterComponents."+t),flatpickrInstance:flatpickr(l,{mode:"range",clickOpens:!0,allowInvalidPreload:!0,defaultDate:[],ariaDateFormat:i.ariaDateFormat,allowInput:i.allowInput,altFormat:i.altFormat,altInput:i.altInput,dateFormat:i.dateFormat,locale:"en",minDate:i.earliestDate,maxDate:i.latestDate,onOpen:function(){window.childElementOpen=!0},onChange:function(i,l,a){if(i.length>1){var s=l.split(" ")[0],r=l.split(" ")[2],n={};window.childElementOpen=!1,window.filterPopoverOpen=!1,n={minDate:s,maxDate:r},e.set("filterComponents."+t,n)}}}),setupWire(){if(void 0!==this.wireValues){if(void 0!==this.wireValues.minDate&&void 0!==this.wireValues.maxDate){let e=[this.wireValues.minDate,this.wireValues.maxDate];this.flatpickrInstance.setDate(e)}else this.flatpickrInstance.setDate([])}else this.flatpickrInstance.setDate([])},init(){this.setupWire(),this.$watch("wireValues",e=>this.setupWire())}})),Alpine.data("reorderFunction",(e,t,i)=>({dragging:!1,reorderEnabled:!1,sourceID:"",targetID:"",evenRowClasses:"",oddRowClasses:"",currentlyHighlightedElement:"",evenRowClassArray:{},oddRowClassArray:{},evenNotInOdd:{},oddNotInEven:{},orderedRows:[],defaultReorderColumn:e.get("defaultReorderColumn"),reorderStatus:e.get("reorderStatus"),currentlyReorderingStatus:e.entangle("currentlyReorderingStatus"),hideReorderColumnUnlessReorderingStatus:e.entangle("hideReorderColumnUnlessReorderingStatus"),reorderDisplayColumn:e.entangle("reorderDisplayColumn"),dragStart(e){this.sourceID=e.target.id,e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",e.target.id),e.target.classList.add("laravel-livewire-tables-dragging")},dragOverEvent(e){"object"==typeof this.currentlyHighlightedElement&&this.currentlyHighlightedElement.classList.remove("laravel-livewire-tables-highlight-bottom","laravel-livewire-tables-highlight-top");let t=e.target.closest("tr");this.currentlyHighlightedElement=t,e.offsetYi.getBoundingClientRect().height/2?l.insertBefore(a,i.nextSibling):l.insertBefore(a,i),r!l.includes(e)),this.oddNotInEven=l.filter(e=>!i.includes(e)),i=[],l=[])}},init(){this.$watch("currentlyReorderingStatus",e=>this.setupEvenOddClasses())}}))}); \ No newline at end of file +document.addEventListener("alpine:init",()=>{Alpine.data("tableWrapper",(e,t)=>({childElementOpen:!1,filtersOpen:e.entangle("filterSlideDownDefaultVisible"),paginationCurrentCount:e.entangle("paginationCurrentCount"),paginationTotalItemCount:e.entangle("paginationTotalItemCount"),paginationCurrentItems:e.entangle("paginationCurrentItems"),selectedItems:e.entangle("selected"),hideBulkActionsWhenEmpty:e.entangle("hideBulkActionsWhenEmpty"),toggleSelectAll(){t&&(this.paginationTotalItemCount===this.selectedItems.length?this.clearSelected():this.setAllSelected())},setAllSelected(){t&&e.setAllSelected()},clearSelected(){t&&e.clearSelected()},selectAllOnPage(){if(!t)return;let e=this.selectedItems,i=this.paginationCurrentItems.values();for(let l of i)e.push(l.toString());this.selectedItems=[...new Set(e)]}})),Alpine.data("numberRangeFilter",(e,t,i,l,s)=>({allFilters:e.entangle("filterComponents",!1),originalMin:0,originalMax:100,filterMin:0,filterMax:100,currentMin:0,currentMax:100,hasUpdate:!1,wireValues:e.entangle("filterComponents."+t,!1),defaultMin:l.minRange,defaultMax:l.maxRange,restrictUpdates:!1,initialiseStyles(){let e=document.getElementById(i);e.style.setProperty("--value-a",this.wireValues.min??this.filterMin),e.style.setProperty("--text-value-a",JSON.stringify(this.wireValues.min??this.filterMin)),e.style.setProperty("--value-b",this.wireValues.max??this.filterMax),e.style.setProperty("--text-value-b",JSON.stringify(this.wireValues.max??this.filterMax))},updateStyles(e,t){let l=document.getElementById(i);l.style.setProperty("--value-a",e),l.style.setProperty("--text-value-a",JSON.stringify(e)),l.style.setProperty("--value-b",t),l.style.setProperty("--text-value-b",JSON.stringify(t))},setupWire(){void 0!==this.wireValues?(this.filterMin=this.originalMin=void 0!==this.wireValues.min?this.wireValues.min:this.defaultMin,this.filterMax=this.originalMax=void 0!==this.wireValues.max?this.wireValues.max:this.defaultMax):(this.filterMin=this.originalMin=this.defaultMin,this.filterMax=this.originalMax=this.defaultMax),this.updateStyles(this.filterMin,this.filterMax)},allowUpdates(){this.updateWire()},updateWire(){let e=parseInt(this.filterMin),t=parseInt(this.filterMax);(e!=this.originalMin||t!=this.originalMax)&&(tthis.setupWire())}})),Alpine.data("flatpickrFilter",(e,t,i,l,s)=>({wireValues:e.entangle("filterComponents."+t),flatpickrInstance:flatpickr(l,{mode:"range",clickOpens:!0,allowInvalidPreload:!0,defaultDate:[],ariaDateFormat:i.ariaDateFormat,allowInput:i.allowInput,altFormat:i.altFormat,altInput:i.altInput,dateFormat:i.dateFormat,locale:"en",minDate:i.earliestDate,maxDate:i.latestDate,onOpen:function(){window.childElementOpen=!0},onChange:function(i,l,s){if(i.length>1){var a=l.split(" ")[0],r=l.split(" ")[2],n={};window.childElementOpen=!1,window.filterPopoverOpen=!1,n={minDate:a,maxDate:r},e.set("filterComponents."+t,n)}}}),setupWire(){if(void 0!==this.wireValues){if(void 0!==this.wireValues.minDate&&void 0!==this.wireValues.maxDate){let e=[this.wireValues.minDate,this.wireValues.maxDate];this.flatpickrInstance.setDate(e)}else this.flatpickrInstance.setDate([])}else this.flatpickrInstance.setDate([])},init(){this.setupWire(),this.$watch("wireValues",e=>this.setupWire())}})),Alpine.data("reorderFunction",(e,t,i)=>({dragging:!1,reorderEnabled:!1,sourceID:"",targetID:"",evenRowClasses:"",oddRowClasses:"",currentlyHighlightedElement:"",evenRowClassArray:{},oddRowClassArray:{},evenNotInOdd:{},oddNotInEven:{},orderedRows:[],defaultReorderColumn:e.get("defaultReorderColumn"),reorderStatus:e.get("reorderStatus"),currentlyReorderingStatus:e.entangle("currentlyReorderingStatus"),hideReorderColumnUnlessReorderingStatus:e.entangle("hideReorderColumnUnlessReorderingStatus"),reorderDisplayColumn:e.entangle("reorderDisplayColumn"),dragStart(e){this.sourceID=e.target.id,e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",e.target.id),e.target.classList.add("laravel-livewire-tables-dragging")},dragOverEvent(e){"object"==typeof this.currentlyHighlightedElement&&this.currentlyHighlightedElement.classList.remove("laravel-livewire-tables-highlight-bottom","laravel-livewire-tables-highlight-top");let t=e.target.closest("tr");this.currentlyHighlightedElement=t,e.offsetYi.getBoundingClientRect().height/2?l.insertBefore(s,i.nextSibling):l.insertBefore(s,i),r!l.includes(e)),this.oddNotInEven=l.filter(e=>!i.includes(e)),i=[],l=[])}},init(){this.$watch("currentlyReorderingStatus",e=>this.setupEvenOddClasses())}}))}); \ No newline at end of file diff --git a/resources/views/components/table/th.blade.php b/resources/views/components/table/th.blade.php index ee6333ee6..184f71cd0 100644 --- a/resources/views/components/table/th.blade.php +++ b/resources/views/components/table/th.blade.php @@ -70,11 +70,11 @@ class="d-flex align-items-center" @if ($direction === 'asc') - + @elseif ($direction === 'desc') - + @else - + @endif diff --git a/resources/views/components/tools/filter-pills.blade.php b/resources/views/components/tools/filter-pills.blade.php index afb9568a7..5079909d7 100644 --- a/resources/views/components/tools/filter-pills.blade.php +++ b/resources/views/components/tools/filter-pills.blade.php @@ -30,7 +30,17 @@ 'badge rounded-pill bg-info d-inline-flex align-items-center' => $component->isBootstrap5(), ]) > - {{ $filter->getFilterPillTitle() }}: {{ $filter->getFilterPillValue($value) }} + {{ $filter->getFilterPillTitle() }}: + @php( $filterPillValue = $filter->getFilterPillValue($value)) + @php( $separator = method_exists($filter, 'getPillsSeparator') ? $filter->getPillsSeparator() : ', ') + + @if(is_array($filterPillValue) && !empty($filterPillValue)) + @foreach($filterPillValue as $filterPillArrayValue) + {{ $filterPillArrayValue }}{!! $separator !!} + @endforeach + @else + {{ $filterPillValue }} + @endif @if ($component->isTailwind()) @@ -68,10 +90,14 @@ class="block w-full px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 @else
$component->isBootstrap4(), - 'dropdown-menu dropdown-menu-end w-100' => $component->isBootstrap5(), - ]) + {{ + $attributes->merge($this->getBulkActionsMenuAttributes) + ->class([ + 'dropdown-menu dropdown-menu-right w-100' => $component->isBootstrap4() && $this->getBulkActionsMenuAttributes['default-styling'] ?? true, + 'dropdown-menu dropdown-menu-end w-100' => $component->isBootstrap5() && $this->getBulkActionsMenuAttributes['default-styling'] ?? true, + ]) + ->except('default') + }} aria-labelledby="{{ $tableName }}-bulkActionsDropdown" > @foreach ($component->getBulkActions() as $action => $title) @@ -82,9 +108,13 @@ class="block w-full px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 @endif wire:click="{{ $action }}" wire:key="{{ $tableName }}-bulk-action-{{ $action }}" - @class([ - 'dropdown-item' => $component->isBootstrap(), - ]) + {{ + $attributes->merge($this->getBulkActionsMenuItemAttributes) + ->class([ + 'dropdown-item' => $component->isBootstrap() && $this->getBulkActionsMenuItemAttributes['default-styling'] ?? true, + ]) + ->except('default') + }} > {{ $title }} diff --git a/src/Traits/Configuration/BulkActionsConfiguration.php b/src/Traits/Configuration/BulkActionsConfiguration.php index 6af318f0e..f46cb712e 100644 --- a/src/Traits/Configuration/BulkActionsConfiguration.php +++ b/src/Traits/Configuration/BulkActionsConfiguration.php @@ -213,4 +213,25 @@ public function setClearSelectedOnFilterDisabled(): self return $this; } + + public function setBulkActionsButtonAttributes(array $bulkActionsButtonAttributes): self + { + $this->bulkActionsButtonAttributes = [...$this->bulkActionsButtonAttributes, ...$bulkActionsButtonAttributes]; + + return $this; + } + + public function setBulkActionsMenuAttributes(array $bulkActionsMenuAttributes): self + { + $this->bulkActionsMenuAttributes = [...$this->bulkActionsMenuAttributes, ...$bulkActionsMenuAttributes]; + + return $this; + } + + public function setBulkActionsMenuItemAttributes(array $bulkActionsMenuItemAttributes): self + { + $this->bulkActionsMenuItemAttributes = [...$this->bulkActionsMenuItemAttributes, ...$bulkActionsMenuItemAttributes]; + + return $this; + } } diff --git a/src/Traits/Helpers/BulkActionsHelpers.php b/src/Traits/Helpers/BulkActionsHelpers.php index 77a263635..542e31bb4 100644 --- a/src/Traits/Helpers/BulkActionsHelpers.php +++ b/src/Traits/Helpers/BulkActionsHelpers.php @@ -230,4 +230,23 @@ public function getClearSelectedOnFilter(): bool { return $this->clearSelectedOnFilter ?? true; } + + #[Computed] + public function getBulkActionsButtonAttributes(): array + { + return array_merge(['default-colors' => true, 'default-styling' => true], $this->bulkActionsButtonAttributes); + + } + + #[Computed] + public function getBulkActionsMenuAttributes(): array + { + return array_merge(['default-colors' => true, 'default-styling' => true], $this->bulkActionsMenuAttributes); + } + + #[Computed] + public function getBulkActionsMenuItemAttributes(): array + { + return array_merge(['default-colors' => true, 'default-styling' => true], $this->bulkActionsMenuItemAttributes); + } } diff --git a/src/Traits/WithBulkActions.php b/src/Traits/WithBulkActions.php index 37a5cd111..13a516974 100644 --- a/src/Traits/WithBulkActions.php +++ b/src/Traits/WithBulkActions.php @@ -40,6 +40,12 @@ trait WithBulkActions public bool $clearSelectedOnFilter = true; + protected array $bulkActionsButtonAttributes = ['default-colors' => true, 'default-styling' => true]; + + protected array $bulkActionsMenuAttributes = ['default-colors' => true, 'default-styling' => true]; + + protected array $bulkActionsMenuItemAttributes = ['default-colors' => true, 'default-styling' => true]; + public function bulkActions(): array { return property_exists($this, 'bulkActions') ? $this->bulkActions : []; diff --git a/src/Views/Filters/DateFilter.php b/src/Views/Filters/DateFilter.php index 99d6a7ba0..6679a86aa 100644 --- a/src/Views/Filters/DateFilter.php +++ b/src/Views/Filters/DateFilter.php @@ -28,7 +28,7 @@ public function validate(string $value): string|bool return $value; } - public function getFilterPillValue($value): ?string + public function getFilterPillValue($value): string|array|null { if ($this->validate($value)) { return DateTime::createFromFormat('Y-m-d', $value)->format($this->getConfig('pillFormat')); diff --git a/src/Views/Filters/DateRangeFilter.php b/src/Views/Filters/DateRangeFilter.php index e08c4bfe5..9e95265b1 100644 --- a/src/Views/Filters/DateRangeFilter.php +++ b/src/Views/Filters/DateRangeFilter.php @@ -108,7 +108,7 @@ public function getDefaultValue(): array return []; } - public function getFilterPillValue($value): ?string + public function getFilterPillValue($value): string|array|null { $validatedValue = $this->validate($value); diff --git a/src/Views/Filters/DateTimeFilter.php b/src/Views/Filters/DateTimeFilter.php index 1d266549e..f46b77f20 100644 --- a/src/Views/Filters/DateTimeFilter.php +++ b/src/Views/Filters/DateTimeFilter.php @@ -28,7 +28,7 @@ public function validate(string $value): string|bool return $value; } - public function getFilterPillValue($value): ?string + public function getFilterPillValue($value): string|array|null { if ($this->validate($value)) { return DateTime::createFromFormat('Y-m-d\TH:i', $value)->format($this->getConfig('pillFormat')); diff --git a/src/Views/Filters/MultiSelectDropdownFilter.php b/src/Views/Filters/MultiSelectDropdownFilter.php index 3022ddba5..027767735 100644 --- a/src/Views/Filters/MultiSelectDropdownFilter.php +++ b/src/Views/Filters/MultiSelectDropdownFilter.php @@ -37,7 +37,7 @@ public function validate(int|string|array $value): array|int|string|bool return (is_string($value) || is_numeric($value)) ? $value : false; } - public function getFilterPillValue($value): ?string + public function getFilterPillValue($value): string|array|null { $values = []; @@ -52,7 +52,7 @@ public function getFilterPillValue($value): ?string } } - return implode(', ', $values); + return $values; } public function isEmpty(mixed $value): bool diff --git a/src/Views/Filters/MultiSelectFilter.php b/src/Views/Filters/MultiSelectFilter.php index f43b37302..614f615c7 100644 --- a/src/Views/Filters/MultiSelectFilter.php +++ b/src/Views/Filters/MultiSelectFilter.php @@ -34,7 +34,7 @@ public function validate(int|string|array $value): array|int|string|bool return $value; } - public function getFilterPillValue($value): ?string + public function getFilterPillValue($value): string|array|null { $values = []; @@ -46,6 +46,6 @@ public function getFilterPillValue($value): ?string } } - return implode(', ', $values); + return $values; } } diff --git a/src/Views/Filters/NumberRangeFilter.php b/src/Views/Filters/NumberRangeFilter.php index a95be42c3..ed7ab5911 100644 --- a/src/Views/Filters/NumberRangeFilter.php +++ b/src/Views/Filters/NumberRangeFilter.php @@ -86,7 +86,7 @@ public function getDefaultValue(): array|string return []; } - public function getFilterPillValue($values): ?string + public function getFilterPillValue($values): string|array|null { if ($this->validate($values)) { return __('Min:').$values['min'].', '.__('Max:').$values['max']; diff --git a/src/Views/Filters/SelectFilter.php b/src/Views/Filters/SelectFilter.php index 0f247ada4..9940d7150 100644 --- a/src/Views/Filters/SelectFilter.php +++ b/src/Views/Filters/SelectFilter.php @@ -41,7 +41,7 @@ public function validate(string $value): array|string|bool return $value; } - public function getFilterPillValue($value): ?string + public function getFilterPillValue($value): string|array|null { return $this->getCustomFilterPillValue($value) diff --git a/src/Views/Traits/Configuration/AggregateColumnConfiguration.php b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php index 2f181ed2f..9e12611dd 100644 --- a/src/Views/Traits/Configuration/AggregateColumnConfiguration.php +++ b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php @@ -3,6 +3,7 @@ namespace Rappasoft\LaravelLivewireTables\Views\Traits\Configuration; use Illuminate\Database\Eloquent\{Builder, Model}; +use Illuminate\Support\Str; use Rappasoft\LaravelLivewireTables\Views\Column; trait AggregateColumnConfiguration @@ -40,10 +41,10 @@ public function setDefaultLabel(): void { $this->label(function ($row, Column $column) { if ($this->hasForeignColumn()) { - return $row->{$this->getDataSource().'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn()}; + return $row->{Str::snake($this->getDataSource()).'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn()}; } - return $row->{$this->getDataSource().'_'.$this->getAggregateMethod()}; + return $row->{Str::snake($this->getDataSource()).'_'.$this->getAggregateMethod()}; }); } diff --git a/src/Views/Traits/Filters/IsArrayFilter.php b/src/Views/Traits/Filters/IsArrayFilter.php index 5812fe35c..5238d71d3 100644 --- a/src/Views/Traits/Filters/IsArrayFilter.php +++ b/src/Views/Traits/Filters/IsArrayFilter.php @@ -8,6 +8,8 @@ trait IsArrayFilter { + public string $pillsSeparator = ', '; + /** * Get the filter default options. */ @@ -32,4 +34,16 @@ public function isEmpty(mixed $value): bool return empty($value); } + + public function getPillsSeparator(): string + { + return $this->pillsSeparator ?? ', '; + } + + public function setPillsSeparator(string $pillsSeparator): self + { + $this->pillsSeparator = $pillsSeparator; + + return $this; + } } diff --git a/src/Views/Traits/Helpers/FilterHelpers.php b/src/Views/Traits/Helpers/FilterHelpers.php index 5e4f1ae7f..672353184 100644 --- a/src/Views/Traits/Helpers/FilterHelpers.php +++ b/src/Views/Traits/Helpers/FilterHelpers.php @@ -70,7 +70,7 @@ public function getFilterPillTitle(): string /** * @param mixed $value */ - public function getFilterPillValue($value): ?string + public function getFilterPillValue($value): string|array|null { return $value; } diff --git a/tests/Http/Livewire/PetsTableCustomFilters.php b/tests/Http/Livewire/PetsTableCustomFilters.php new file mode 100644 index 000000000..ef717c99d --- /dev/null +++ b/tests/Http/Livewire/PetsTableCustomFilters.php @@ -0,0 +1,138 @@ +setPerPageAccepted([1, 3, 5, 10, 15, 25, 50])->setPerPage(3); + $this->setPaginationMethod($type); + $this->setDisplayPaginationDetailsEnabled(); + + } + + public function disableDetailedPagination(string $type = 'standard') + { + $this->setPerPageAccepted([1, 3, 5, 10, 15, 25, 50])->setPerPage(3); + $this->setPaginationMethod($type); + $this->setDisplayPaginationDetailsDisabled(); + } + + public function setPaginationTest(string $type) + { + $this->paginationTest = $type; + } + + public function configure(): void + { + $this->setPrimaryKey('id'); + } + + public function columns(): array + { + return [ + Column::make('ID', 'id') + ->sortable() + ->setSortingPillTitle('Key') + ->setSortingPillDirections('0-9', '9-0'), + Column::make('Sort') + ->sortable() + ->excludeFromColumnSelect(), + Column::make('Name') + ->sortable() + ->secondaryHeader($this->getFilterByKey('pet_name_filter')) + ->footerFilter('pet_name_filter') + ->searchable(), + + Column::make('Age'), + + Column::make('Breed', 'breed.name') + ->secondaryHeaderFilter('breed') + ->footer($this->getFilterByKey('breed')) + ->sortable( + fn (Builder $query, string $direction) => $query->orderBy('pets.id', $direction) + ) + ->searchable( + fn (Builder $query, $searchTerm) => $query->orWhere('breed.name', $searchTerm) + ), + + Column::make('Other') + ->label(function ($row, Column $column) { + return 'Other'; + }) + ->footer(function ($rows) { + return 'Count: '.$rows->count(); + }), + + LinkColumn::make('Link') + ->title(fn ($row) => 'Edit') + ->location(fn ($row) => 'http://www.google.com') + ->attributes(fn ($row) => [ + 'class' => 'rounded-full', + 'alt' => $row->name.' Avatar', + ]), + ImageColumn::make('RowImg') + ->location(fn ($row) => 'test'.$row->id) + ->attributes(fn ($row) => [ + 'class' => 'rounded-full', + 'alt' => $row->name.' Avatar', + ]), + Column::make('Last Visit', 'last_visit') + ->sortable() + ->deselected(), + ]; + } + + public function filters(): array + { + return [ + MultiSelectFilter::make('Breed') + ->options( + Breed::query() + ->orderBy('name') + ->get() + ->keyBy('id') + ->map(fn ($breed) => $breed->name) + ->toArray() + ) + ->filter(function (Builder $builder, array $values) { + return $builder->whereIn('breed_id', $values); + }), + MultiSelectDropdownFilter::make('Species') + ->options( + Species::query() + ->orderBy('name') + ->get() + ->keyBy('id') + ->map(fn ($species) => $species->name) + ->toArray() + ) + ->filter(function (Builder $builder, array $values) { + return $builder->whereIn('species_id', $values); + }) + ->setPillsSeparator('
'), + + ]; + } +} diff --git a/tests/Traits/Configuration/BulkActionsConfigurationTest.php b/tests/Traits/Configuration/BulkActionsConfigurationTest.php index b2f24a499..7cf864bf8 100644 --- a/tests/Traits/Configuration/BulkActionsConfigurationTest.php +++ b/tests/Traits/Configuration/BulkActionsConfigurationTest.php @@ -193,4 +193,53 @@ public function test_can_set_bulk_actions_th_checkbox_attributes(): void $this->assertSame(['default' => false, 'class' => 'bg-green-500'], $this->basicTable->getBulkActionsThCheckboxAttributes()); } + + public function test_can_set_bulk_actions_button_attributes(): void + { + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsButtonAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuAttributes()); + + $this->basicTable->setBulkActionsButtonAttributes(['default-colors' => false, 'class' => 'bg-green-500']); + + $this->assertSame(['default-colors' => false, 'default-styling' => true, 'class' => 'bg-green-500'], $this->basicTable->getBulkActionsButtonAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuAttributes()); + + $this->basicTable->setBulkActionsButtonAttributes(['class' => 'bg-green-500', 'default-colors' => false]); + + $this->assertSame(['default-colors' => false, 'default-styling' => true, 'class' => 'bg-green-500'], $this->basicTable->getBulkActionsButtonAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuAttributes()); + } + + public function test_can_set_bulk_actions_menu_attributes(): void + { + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsButtonAttributes()); + + $this->basicTable->setBulkActionsMenuAttributes(['class' => 'bg-blue-500']); + + $this->assertSame(['default-colors' => true, 'default-styling' => true, 'class' => 'bg-blue-500'], $this->basicTable->getBulkActionsMenuAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsButtonAttributes()); + + $this->basicTable->setBulkActionsMenuAttributes(['class' => 'bg-blue-500', 'default-colors' => false]); + + $this->assertSame(['default-colors' => false, 'default-styling' => true, 'class' => 'bg-blue-500'], $this->basicTable->getBulkActionsMenuAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsButtonAttributes()); + } + + public function test_can_set_bulk_actions_menu_item_attributes(): void + { + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuItemAttributes()); + + $this->basicTable->setBulkActionsMenuItemAttributes(['class' => 'bg-red-500']); + + $this->assertSame(['default-colors' => true, 'default-styling' => true, 'class' => 'bg-red-500'], $this->basicTable->getBulkActionsMenuItemAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuAttributes()); + + $this->basicTable->setBulkActionsMenuItemAttributes(['class' => 'bg-amber-500', 'default-colors' => false]); + + $this->assertSame(['default-colors' => false, 'default-styling' => true, 'class' => 'bg-amber-500'], $this->basicTable->getBulkActionsMenuItemAttributes()); + $this->assertSame(['default-colors' => true, 'default-styling' => true], $this->basicTable->getBulkActionsMenuAttributes()); + + } } diff --git a/tests/Traits/Visuals/FilterVisualsTest.php b/tests/Traits/Visuals/FilterVisualsTest.php index d73c317ad..25a877c8b 100644 --- a/tests/Traits/Visuals/FilterVisualsTest.php +++ b/tests/Traits/Visuals/FilterVisualsTest.php @@ -87,6 +87,63 @@ public function test_filters_with_invalid_key_dont_error(): void ->assertDontSee('Applied Filters'); } + public function test_filters_pills_separator_is_customisable(): void + { + Livewire::test(new class extends PetsTable + { + public function configure(): void + { + $this->setPrimaryKey('id'); + } + + public function filters(): array + { + return [ + \Rappasoft\LaravelLivewireTables\Views\Filters\MultiSelectFilter::make('Breed') + ->options( + \Rappasoft\LaravelLivewireTables\Tests\Models\Breed::query() + ->orderBy('name') + ->get() + ->keyBy('id') + ->map(fn ($breed) => $breed->name) + ->toArray() + ) + ->filter(function (\Illuminate\Database\Eloquent\Builder $builder, array $values) { + return $builder->whereIn('pets.breed_id', $values); + }), + \Rappasoft\LaravelLivewireTables\Views\Filters\MultiSelectDropdownFilter::make('Species') + ->options( + \Rappasoft\LaravelLivewireTables\Tests\Models\Species::query() + ->orderBy('name') + ->get() + ->keyBy('id') + ->map(fn ($species) => $species->name) + ->toArray() + ) + ->filter(function (\Illuminate\Database\Eloquent\Builder $builder, array $values) { + return $builder->whereIn('pets.species_id', $values); + }) + ->setPillsSeparator('
'), + + ]; + } + }) + ->set('filterComponents.species', [1, 2]) + ->assertSeeHtmlInOrder([ + 'wire:key="table-filter-pill-species"', + 'Cat', + '
', + 'Dog', + '
', + ]) + ->set('filterComponents.breed', [1, 2]) + ->assertSeeHtmlInOrder([ + 'wire:key="table-filter-pill-breed"', + 'American Shorthair,', + 'Maine Coon', + ]); + } + /*public function test_filter_events_apply_correctly(): void { Livewire::test(PetsTable::class) diff --git a/tests/Views/Filters/DateRangeFilterTest.php b/tests/Views/Filters/DateRangeFilterTest.php index 312579f82..bef64b240 100644 --- a/tests/Views/Filters/DateRangeFilterTest.php +++ b/tests/Views/Filters/DateRangeFilterTest.php @@ -32,6 +32,7 @@ public function test_can_get_filter_configs(): void 'dateFormat' => 'Y-m-d', 'earliestDate' => null, 'latestDate' => null, + 'locale' => 'en', ]; $this->assertSame($defaultConfig, $filter->getConfigs()); @@ -52,6 +53,7 @@ public function test_get_a_single_filter_config(): void 'dateFormat' => 'Y-m-d', 'earliestDate' => null, 'latestDate' => null, + 'locale' => 'en', ], $filter->getConfigs()); $filter->config(['foo' => 'bar']); @@ -64,10 +66,40 @@ public function test_get_a_single_filter_config(): void 'dateFormat' => 'Y-m-d', 'earliestDate' => null, 'latestDate' => null, + 'locale' => 'en', 'foo' => 'bar'], $filter->getConfigs()); } + public function test_can_change_locale(): void + { + $filter = DateRangeFilter::make('Active'); + + $this->assertSame([ + 'allowInput' => true, + 'altFormat' => 'F j, Y', + 'ariaDateFormat' => 'F j, Y', + 'dateFormat' => 'Y-m-d', + 'earliestDate' => null, + 'latestDate' => null, + 'locale' => 'en', + ], $filter->getConfigs()); + + $filter->config(['locale' => 'fr']); + + $this->assertSame([ + 'allowInput' => true, + 'altFormat' => 'F j, Y', + 'ariaDateFormat' => 'F j, Y', + 'dateFormat' => 'Y-m-d', + 'earliestDate' => null, + 'latestDate' => null, + 'locale' => 'fr', + ], + $filter->getConfigs() + ); + } + public function test_can_get_filter_options(): void { $filter = DateRangeFilter::make('Active'); diff --git a/tests/Views/Filters/MultiSelectDropdownFilterTest.php b/tests/Views/Filters/MultiSelectDropdownFilterTest.php index b0a31441c..8f06e0209 100644 --- a/tests/Views/Filters/MultiSelectDropdownFilterTest.php +++ b/tests/Views/Filters/MultiSelectDropdownFilterTest.php @@ -90,8 +90,8 @@ public function test_can_get_filter_pill_title(): void public function test_can_get_filter_pill_value(array $optionsArray): void { $filter = MultiSelectDropdownFilter::make('Active')->options($optionsArray); - $this->assertSame($optionsArray[1], $filter->getFilterPillValue(['1'])); - $this->assertSame($optionsArray[1].', '.$optionsArray[2], $filter->getFilterPillValue(['1', '2'])); + $this->assertSame($optionsArray[1], $filter->getFilterPillValue(['1'])[0]); + $this->assertSame([$optionsArray[1], $optionsArray[2]], $filter->getFilterPillValue(['1', '2'])); } public function test_can_check_if_filter_has_configs(): void @@ -259,4 +259,13 @@ public function test_can_set_select_filter_wireable_live(array $optionsArray): v $this->assertSame('wire:model.live.debounce.500ms=filterComponents.active', $filter->getWireMethod('filterComponents.'.$filter->getKey())); } + + #[Depends('test_array_setup')] + public function test_can_set_separator(array $optionsArray): void + { + $filter = MultiSelectDropdownFilter::make('Active')->options($optionsArray); + $this->assertSame(', ', $filter->getPillsSeparator()); + $filter->setPillsSeparator('
'); + $this->assertSame('
', $filter->getPillsSeparator()); + } } diff --git a/tests/Views/Filters/MultiSelectFilterTest.php b/tests/Views/Filters/MultiSelectFilterTest.php index cf3ebdeab..9d52f88bd 100644 --- a/tests/Views/Filters/MultiSelectFilterTest.php +++ b/tests/Views/Filters/MultiSelectFilterTest.php @@ -224,6 +224,13 @@ public function test_can_set_select_filter_wireable_live(): void $this->assertSame('live.debounce.500ms', $filter->getWireableMethod()); $this->assertSame('wire:model.live.debounce.500ms=filterComponents.active', $filter->getWireMethod('filterComponents.'.$filter->getKey())); + } + public function test_can_set_separator(): void + { + $filter = MultiSelectFilter::make('Active'); + $this->assertSame(', ', $filter->getPillsSeparator()); + $filter->setPillsSeparator('
'); + $this->assertSame('
', $filter->getPillsSeparator()); } }