├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── dist
├── css
│ └── filter.css
├── js
│ └── filter.js
└── mix-manifest.json
├── nova.mix.js
├── package.json
├── resources
├── css
│ └── filter.css
└── js
│ ├── components
│ └── Filter.vue
│ └── filter.js
├── screenshots
├── dark.png
└── light.png
├── src
├── FilterServiceProvider.php
└── SliderFilter.php
├── tailwind.config.js
├── webpack.mix.js
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: milewski
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /vendor
3 | /node_modules
4 | package-lock.json
5 | composer.phar
6 | composer.lock
7 | phpunit.xml
8 | .phpunit.result.cache
9 | .DS_Store
10 | Thumbs.db
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Digital Creative
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nova Slider Filter
2 |
3 | [](https://packagist.org/packages/digital-creative/nova-slider-filter)
4 | [](https://packagist.org/packages/digital-creative/nova-slider-filter)
5 | [](https://github.com/dcasia/nova-slider-filter/blob/main/LICENSE)
6 |
7 | A Laravel Nova filter for picking range between a min/max value.
8 |
9 |
10 |
11 |
12 |
13 |
14 | # Installation
15 |
16 | You can install the package via composer:
17 |
18 | ```shell
19 | composer require digital-creative/nova-slider-filter
20 | ```
21 |
22 | ## Basic Usage
23 |
24 | Create a filter as usual and extend the `DigitalCreative\SliderFilter\SliderFilter` class
25 |
26 | ```php
27 | use DigitalCreative\SliderFilter\SliderFilter;
28 |
29 | class MyFilter extends SliderFilter {
30 |
31 | public function apply(NovaRequest $request, $query, $values)
32 | {
33 | // $values will be an array when using ->range() and int when using ->single()
34 | }
35 |
36 | }
37 | ```
38 |
39 | and use it as usual on the filters methods within your resource class:
40 |
41 | ```php
42 | class ExampleNovaResource extends Resource {
43 |
44 | public function filters(NovaRequest $request): array
45 | {
46 | return [
47 | MyFilter::make()->single(min: 0, max: 100),
48 | ];
49 | }
50 |
51 | }
52 | ```
53 |
54 | Calling the `->range()` method will render a slider with two (or more) handles, while calling the `single()` method will render a slider with a single handle.
55 |
56 | ```php
57 | class ExampleNovaResource extends Resource {
58 |
59 | public function filters(NovaRequest $request): array
60 | {
61 | return [
62 | MyFilter::make()
63 | ->range(0, 500, 300)
64 | ->label('${value}')
65 |
66 | MyFilter::make()
67 | ->single(min: 0, max: 500)
68 | ->label('${value}')
69 | ];
70 | }
71 |
72 | }
73 | ```
74 |
75 | You can also set marks on the slider by using the `->marks()` method. The method accepts an array of key/value pairs where the key is the value of the mark and the value is the label to be displayed.
76 |
77 | ```php
78 | class ExampleNovaResource extends Resource {
79 |
80 | public function filters(NovaRequest $request): array
81 | {
82 | return [
83 | MyFilter::make()
84 | ->single(0, 100)
85 | ->marks([
86 | '0' => '🌑',
87 | '50' => '🌓',
88 | '100' => '🌕'
89 | ])
90 | ];
91 | }
92 |
93 | }
94 | ```
95 |
96 | ## ⭐️ Show Your Support
97 |
98 | Please give a ⭐️ if this project helped you!
99 |
100 | ### Other Packages You Might Like
101 |
102 | - [Nova Dashboard](https://github.com/dcasia/nova-dashboard) - The missing dashboard for Laravel Nova!
103 | - [Nova Welcome Card](https://github.com/dcasia/nova-welcome-card) - A configurable version of the `Help card` that comes with Nova.
104 | - [Icon Action Toolbar](https://github.com/dcasia/icon-action-toolbar) - Replaces the default boring action menu with an inline row of icon-based actions.
105 | - [Expandable Table Row](https://github.com/dcasia/expandable-table-row) - Provides an easy way to append extra data to each row of your resource tables.
106 | - [Collapsible Resource Manager](https://github.com/dcasia/collapsible-resource-manager) - Provides an easy way to order and group your resources on the sidebar.
107 | - [Resource Navigation Tab](https://github.com/dcasia/resource-navigation-tab) - Organize your resource fields into tabs.
108 | - [Resource Navigation Link](https://github.com/dcasia/resource-navigation-link) - Create links to internal or external resources.
109 | - [Nova Mega Filter](https://github.com/dcasia/nova-mega-filter) - Display all your filters in a card instead of a tiny dropdown!
110 | - [Nova Pill Filter](https://github.com/dcasia/nova-pill-filter) - A Laravel Nova filter that renders into clickable pills.
111 | - [Nova Slider Filter](https://github.com/dcasia/nova-slider-filter) - A Laravel Nova filter for picking range between a min/max value.
112 | - [Nova Range Input Filter](https://github.com/dcasia/nova-range-input-filter) - A Laravel Nova range input filter.
113 | - [Nova FilePond](https://github.com/dcasia/nova-filepond) - A Nova field for uploading File, Image and Video using Filepond.
114 | - [Custom Relationship Field](https://github.com/dcasia/custom-relationship-field) - Emulate HasMany relationship without having a real relationship set between resources.
115 | - [Column Toggler](https://github.com/dcasia/column-toggler) - A Laravel Nova package that allows you to hide/show columns in the index view.
116 | - [Batch Edit Toolbar](https://github.com/dcasia/batch-edit-toolbar) - Allows you to update a single column of a resource all at once directly from the index page.
117 |
118 | ## License
119 |
120 | The MIT License (MIT). Please see [License File](https://raw.githubusercontent.com/dcasia/nova-slider-filter/master/LICENSE) for more information.
121 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "digital-creative/nova-slider-filter",
3 | "description": "A Laravel Nova filter for picking range between a min/max value.",
4 | "keywords": [
5 | "laravel",
6 | "nova",
7 | "slider",
8 | "filter",
9 | "range"
10 | ],
11 | "authors": [
12 | {
13 | "name": "Rafael Milewski"
14 | }
15 | ],
16 | "license": "MIT",
17 | "require": {
18 | "php": ">=8.0",
19 | "laravel/nova": "^4.0"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "DigitalCreative\\NovaRangeFilter\\": "src/"
24 | }
25 | },
26 | "extra": {
27 | "laravel": {
28 | "providers": [
29 | "DigitalCreative\\NovaRangeFilter\\FilterServiceProvider"
30 | ]
31 | }
32 | },
33 | "config": {
34 | "sort-packages": true
35 | },
36 | "minimum-stability": "dev",
37 | "prefer-stable": true
38 | }
39 |
--------------------------------------------------------------------------------
/dist/css/filter.css:
--------------------------------------------------------------------------------
1 | .nova-slider-filter :is(.ml-2){margin-left:.5rem}.nova-slider-filter :is(.mr-2){margin-right:.5rem}.nova-slider-filter :is(.flex){display:flex}.nova-slider-filter :is(.flex-shrink-0){flex-shrink:0}.nova-slider-filter :is(.flex-row){flex-direction:row}.nova-slider-filter :is(.items-center){align-items:center}.nova-slider-filter :is(.justify-center){justify-content:center}.nova-slider-filter :is(.p-2){padding:.5rem}.nova-slider-filter :is(.pb-6){padding-bottom:1.5rem}.nova-slider-filter :is(.pt-6){padding-top:1.5rem}.nova-slider-filter :is(.text-xs){font-size:.75rem;line-height:1rem}.nova-slider-filter :is(.\!filter){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.nova-slider-filter :is(.filter){filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}
2 |
--------------------------------------------------------------------------------
/dist/js/filter.js:
--------------------------------------------------------------------------------
1 | (()=>{var t,e={514:(t,e,r)=>{"use strict";var n=r(311);var i=r(454);function o(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,i,o,a,s=[],l=!0,u=!1;try{if(o=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;l=!1}else for(;!(l=(n=o.call(r)).done)&&(s.push(n.value),s.length!==e);l=!0);}catch(t){u=!0,i=t}finally{try{if(!l&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(u)throw i}}return s}}(t,e)||s(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(t){return function(t){if(Array.isArray(t))return l(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||s(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function s(t,e){if(t){if("string"==typeof t)return l(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?l(t,e):void 0}}function l(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r{"use strict";r.d(e,{Z:()=>o});var n=r(645),i=r.n(n)()((function(t){return t[1]}));i.push([t.id,'.vue-slider-disabled{cursor:not-allowed;opacity:.5}.vue-slider-rail{background-color:#ccc;border-radius:15px}.vue-slider-process{background-color:#3498db;border-radius:15px}.vue-slider-mark{z-index:4}.vue-slider-mark:first-child .vue-slider-mark-step,.vue-slider-mark:last-child .vue-slider-mark-step{display:none}.vue-slider-mark-step{background-color:rgba(0,0,0,.16);border-radius:50%;height:100%;width:100%}.vue-slider-mark-label{font-size:14px;white-space:nowrap}.vue-slider-dot-handle{background-color:#fff;border-radius:50%;box-shadow:.5px .5px 2px 1px rgba(0,0,0,.32);box-sizing:border-box;cursor:pointer;height:100%;width:100%}.vue-slider-dot-handle-focus{box-shadow:0 0 1px 2px rgba(52,152,219,.36)}.vue-slider-dot-handle-disabled{background-color:#ccc;cursor:not-allowed}.vue-slider-dot-tooltip-inner{background-color:#3498db;border-color:#3498db;border-radius:5px;box-sizing:content-box;color:#fff;font-size:14px;min-width:20px;padding:2px 5px;text-align:center;white-space:nowrap}.vue-slider-dot-tooltip-inner:after{content:"";position:absolute}.vue-slider-dot-tooltip-inner-top:after{border:0 solid transparent;border-top-color:inherit;height:0;left:50%;top:100%;transform:translate(-50%);width:0}.vue-slider-dot-tooltip-inner-bottom:after{border:0 solid transparent;border-bottom-color:inherit;bottom:100%;height:0;left:50%;transform:translate(-50%);width:0}.vue-slider-dot-tooltip-inner-left:after{border:0 solid transparent;border-left-color:inherit;height:0;left:100%;top:50%;transform:translateY(-50%);width:0}.vue-slider-dot-tooltip-inner-right:after{border:0 solid transparent;border-right-color:inherit;height:0;right:100%;top:50%;transform:translateY(-50%);width:0}.vue-slider-dot-tooltip-wrapper{opacity:0;transition:all .3s}.vue-slider-dot-tooltip-wrapper-show{opacity:1}.dark .nova-slider-filter .vue-slider-rail{background-color:rgba(var(--colors-gray-700))}.dark .nova-slider-filter .vue-slider-process{background-color:rgba(var(--colors-primary-500))}.dark .nova-slider-filter .vue-slider-dot-tooltip-inner{background:rgba(var(--colors-gray-800));color:rgba(var(--colors-gray-400))}.nova-slider-filter .vue-slider-dot-tooltip-inner{background-color:rgba(var(--colors-gray-200));border-radius:.25rem;color:rgba(var(--colors-gray-500));font-size:12px;font-weight:700;margin-bottom:-5px;padding:2px 5px}',""]);const o=i},645:t=>{"use strict";t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var r=t(e);return e[2]?"@media ".concat(e[2]," {").concat(r,"}"):r})).join("")},e.i=function(t,r,n){"string"==typeof t&&(t=[[null,t,""]]);var i={};if(n)for(var o=0;o{},379:(t,e,r)=>{"use strict";var n,i=function(){return void 0===n&&(n=Boolean(window&&document&&document.all&&!window.atob)),n},o=function(){var t={};return function(e){if(void 0===t[e]){var r=document.querySelector(e);if(window.HTMLIFrameElement&&r instanceof window.HTMLIFrameElement)try{r=r.contentDocument.head}catch(t){r=null}t[e]=r}return t[e]}}(),a=[];function s(t){for(var e=-1,r=0;r{"use strict";e.Z=(t,e)=>{const r=t.__vccOpts||t;for(const[t,n]of e)r[t]=n;return r}},454:function(t,e,r){var n;"undefined"!=typeof self&&self,t.exports=(n=r(311),function(){var t={388:function(t,e){var r,n,i,o;"undefined"!=typeof self&&self,o=function(){function t(){var e=Object.getOwnPropertyDescriptor(document,"currentScript");if(!e&&"currentScript"in document&&document.currentScript)return document.currentScript;if(e&&e.get!==t&&document.currentScript)return document.currentScript;try{throw new Error}catch(t){var r,n,i,o=/@([^@]*):(\d+):(\d+)\s*$/gi,a=/.*at [^(]*\((.*):(.+):(.+)\)$/gi.exec(t.stack)||o.exec(t.stack),s=a&&a[1]||!1,l=a&&a[2]||!1,u=document.location.href.replace(document.location.hash,""),c=document.getElementsByTagName("script");s===u&&(r=document.documentElement.outerHTML,n=new RegExp("(?:[^\\n]+?\\n){0,"+(l-2)+"}[^<]*
136 |
137 |
175 |
--------------------------------------------------------------------------------
/resources/js/filter.js:
--------------------------------------------------------------------------------
1 | import SliderFilter from './components/Filter.vue'
2 |
3 | Nova.booting((app, store) => {
4 | app.component('nova-slider-filter', SliderFilter)
5 | })
6 |
--------------------------------------------------------------------------------
/screenshots/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcasia/nova-slider-filter/81004eb53126ff51ee6ad6388939f9a3405cbf49/screenshots/dark.png
--------------------------------------------------------------------------------
/screenshots/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dcasia/nova-slider-filter/81004eb53126ff51ee6ad6388939f9a3405cbf49/screenshots/light.png
--------------------------------------------------------------------------------
/src/FilterServiceProvider.php:
--------------------------------------------------------------------------------
1 | withMeta([ 'mode' => 'range', 'min' => min($values), 'max' => max($values), 'values' => $values ]);
18 | }
19 |
20 | public function single(int $min, int $max): self
21 | {
22 | return $this->withMeta([ 'mode' => 'single', 'min' => $min, 'max' => $max ]);
23 | }
24 |
25 | public function interval(int $value): self
26 | {
27 | return $this->withMeta([ 'interval' => $value ]);
28 | }
29 |
30 | public function minRange(int $min): self
31 | {
32 | return $this->withMeta([ 'minRange' => $min ]);
33 | }
34 |
35 | public function maxRange(int $max): self
36 | {
37 | return $this->withMeta([ 'maxRange' => $max ]);
38 | }
39 |
40 | /**
41 | * Possible values: none, hover, always, active
42 | */
43 | public function tooltip(string $type = 'always'): self
44 | {
45 | return $this->withMeta([ 'tooltip' => $type ]);
46 | }
47 |
48 | /**
49 | * @see https://nightcatsama.github.io/vue-slider-component/#/basics/marks
50 | */
51 | public function marks(array|bool $marks): self
52 | {
53 | return $this->withMeta([ 'marks' => $marks ]);
54 | }
55 |
56 | public function enableCross(): self
57 | {
58 | return $this->withMeta([ 'enableCross' => true ]);
59 | }
60 |
61 | public function disableMinMaxLabels(): self
62 | {
63 | return $this->withMeta([ 'disableMinMaxLabels' => true ]);
64 | }
65 |
66 | public function label(string $label): self
67 | {
68 | return $this->withMeta([ 'label' => $label ]);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | content: [ './resources/**/*.vue' ],
4 | important: '.nova-slider-filter'
5 | }
6 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix')
2 |
3 | require('./nova.mix')
4 | require('mix-tailwindcss')
5 |
6 | mix
7 | .setPublicPath('dist')
8 | .js('resources/js/filter.js', 'js')
9 | .vue({ version: 3 })
10 | .postCss('resources/css/filter.css', 'css')
11 | .tailwind()
12 | .nova('digital-creative/nova-slider-filter')
13 |
--------------------------------------------------------------------------------