├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── UPGRADE.md ├── bootstrap └── app.php ├── canvas.yaml ├── composer.json ├── config └── filament-navigation.php ├── database ├── factories │ └── ModelFactory.php └── migrations │ ├── 2022_04_18_132920_create_navigations_table.php │ └── 2022_04_21_203420_make_items_longtext_on_navigations_table.php ├── mix-manifest.json ├── package-lock.json ├── package.json ├── resources ├── css │ └── plugin.css ├── dist │ ├── plugin.css │ ├── plugin.js │ └── plugin.js.LICENSE.txt ├── js │ └── plugin.js ├── lang │ ├── ar │ │ └── filament-navigation.php │ ├── cs │ │ └── filament-navigation.php │ ├── de │ │ └── filament-navigation.php │ ├── en │ │ └── filament-navigation.php │ ├── es │ │ └── filament-navigation.php │ ├── fr │ │ └── filament-navigation.php │ ├── it │ │ └── filament-navigation.php │ ├── nl │ │ └── filament-navigation.php │ ├── pt_BR │ │ └── filament-navigation.php │ ├── pt_PT │ │ └── filament-navigation.php │ └── ru │ │ └── filament-navigation.php └── views │ ├── card-divider.blade.php │ ├── components │ └── nav-item.blade.php │ ├── hidden-action.blade.php │ └── navigation-builder.blade.php ├── src ├── Filament │ ├── Fields │ │ └── NavigationSelect.php │ └── Resources │ │ ├── NavigationResource.php │ │ └── NavigationResource │ │ └── Pages │ │ ├── Concerns │ │ └── HandlesNavigationBuilder.php │ │ ├── CreateNavigation.php │ │ ├── EditNavigation.php │ │ └── ListNavigations.php ├── FilamentNavigation.php ├── FilamentNavigationServiceProvider.php └── Models │ └── Navigation.php ├── tailwind.config.js └── webpack.mix.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filament-navigation` will be documented in this file. 4 | 5 | ## v1.0.0-beta6 - 2025-02-24 6 | 7 | ### What's Changed 8 | 9 | * Stop name overriding handle on edit page by @ralphmorris in https://github.com/ryangjchandler/filament-navigation/pull/108 10 | 11 | ### New Contributors 12 | 13 | * @ralphmorris made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/108 14 | 15 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v1.0.0-beta5...v1.0.0-beta6 16 | 17 | ## v1.0.0-beta5 - 2024-03-13 18 | 19 | * Add support for Laravel 11.x 20 | 21 | ## v1.0.0-beta4 - 2023-09-01 22 | 23 | ### What's Changed 24 | 25 | - Spanish translation by @neurotools in https://github.com/ryangjchandler/filament-navigation/pull/92 26 | 27 | ### New Contributors 28 | 29 | - @neurotools made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/92 30 | 31 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v1.0.0-beta3...v1.0.0-beta4 32 | 33 | ## v1.0.0-beta3 - 2023-08-07 34 | 35 | ### What's Changed 36 | 37 | - Replace redundant config with filament plugin method by @howdu in https://github.com/ryangjchandler/filament-navigation/pull/86 38 | - fix: remove conditions on dark-mode classes by @mrfade in https://github.com/ryangjchandler/filament-navigation/pull/85 39 | 40 | ### New Contributors 41 | 42 | - @mrfade made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/85 43 | 44 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v1.0.0-beta2...v1.0.0-beta3 45 | 46 | ## v1.0.0-beta2 - 2023-08-04 47 | 48 | ### What's Changed 49 | 50 | - switch icon component to svg by @atmonshi in https://github.com/ryangjchandler/filament-navigation/pull/84 51 | 52 | ### New Contributors 53 | 54 | - @atmonshi made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/84 55 | 56 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v1.0.0-beta1...v1.0.0-beta2 57 | 58 | ## v1.0.0-beta1 - 2023-08-04 59 | 60 | This release marks the start of the `v1.0.0` series of releases. 61 | 62 | It introduces support for Filament v3.x and removes support for v2.x. 63 | 64 | To upgrade your application and this package, please consult the [`UPGRADE`](UPGRADE.md) guide. 65 | 66 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.5.0...v1.0.0-beta1 67 | 68 | ## v0.5.0 - 2023-02-15 69 | 70 | ### What's Changed 71 | 72 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/ryangjchandler/filament-navigation/pull/61 73 | - Laravel 10 compatibility by @howdu in https://github.com/ryangjchandler/filament-navigation/pull/64 74 | 75 | ### New Contributors 76 | 77 | - @howdu made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/64 78 | 79 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.4.2...v0.5.0 80 | 81 | ## v0.4.2 - 2022-12-08 82 | 83 | ### What's Changed 84 | 85 | - Fixed navigation model class handling. by @danielbehrendt in https://github.com/ryangjchandler/filament-navigation/pull/56 86 | 87 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.4.1...v0.4.2 88 | 89 | ## v0.4.1 - 2022-11-29 90 | 91 | ### What's Changed 92 | 93 | - Fix typo in it translation by @digitall-it in https://github.com/ryangjchandler/filament-navigation/pull/55 94 | 95 | ### New Contributors 96 | 97 | - @digitall-it made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/55 98 | 99 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.4.0...v0.4.1 100 | 101 | ## v0.4.0 - 2022-11-17 102 | 103 | ### What's Changed 104 | 105 | - Bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 by @dependabot in https://github.com/ryangjchandler/filament-navigation/pull/49 106 | - Fix actions by @Kristories in https://github.com/ryangjchandler/filament-navigation/pull/48 107 | - feature: drag & drop navigation by @ryangjchandler in https://github.com/ryangjchandler/filament-navigation/pull/51 108 | - Enabled model and resource class configuration. by @danielbehrendt in https://github.com/ryangjchandler/filament-navigation/pull/53 109 | 110 | ### New Contributors 111 | 112 | - @Kristories made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/48 113 | - @danielbehrendt made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/53 114 | 115 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.3.2...v0.4.0 116 | 117 | ## v0.3.2 - 2022-10-27 118 | 119 | ### What's Changed 120 | 121 | - Add german translations by @bumbummen99 in https://github.com/ryangjchandler/filament-navigation/pull/35 122 | - Bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 by @dependabot in https://github.com/ryangjchandler/filament-navigation/pull/42 123 | - Added French translations by @FDT2k in https://github.com/ryangjchandler/filament-navigation/pull/47 124 | - Italian tranlation; fixed label 'Type' in modal by @mynamespace in https://github.com/ryangjchandler/filament-navigation/pull/46 125 | 126 | ### New Contributors 127 | 128 | - @FDT2k made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/47 129 | - @mynamespace made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/46 130 | 131 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.3.1...v0.3.2 132 | 133 | ## v0.3.1 - 2022-09-11 134 | 135 | ### What's Changed 136 | 137 | - [Fix] Load support ServiceProvider by @bumbummen99 in https://github.com/ryangjchandler/filament-navigation/pull/20 138 | - Fix typo in README by @happytodev in https://github.com/ryangjchandler/filament-navigation/pull/28 139 | - Bump dependabot/fetch-metadata from 1.3.1 to 1.3.3 by @dependabot in https://github.com/ryangjchandler/filament-navigation/pull/30 140 | - A Minor updated on the README to reflect need to Run Migration by @blackmunk in https://github.com/ryangjchandler/filament-navigation/pull/32 141 | - Created translations for plugin by @koupisbean in https://github.com/ryangjchandler/filament-navigation/pull/25 142 | - [feat] Add pt_BR and pt_PT translations by @devmatheus in https://github.com/ryangjchandler/filament-navigation/pull/36 143 | 144 | ### New Contributors 145 | 146 | - @bumbummen99 made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/20 147 | - @happytodev made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/28 148 | - @blackmunk made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/32 149 | - @koupisbean made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/25 150 | - @devmatheus made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/36 151 | 152 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.3.0...v0.3.1 153 | 154 | ## v0.3.0 - 2022-06-16 155 | 156 | ### What's Changed 157 | 158 | - fix: entangle-based components not working in item modal by @ryangjchandler in https://github.com/ryangjchandler/filament-navigation/pull/18 159 | 160 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.2.4...v0.3.0 161 | 162 | ## v0.2.4 - 2022-06-13 163 | 164 | ### What's Changed 165 | 166 | - Add type hint for property by @flxsource in https://github.com/ryangjchandler/filament-navigation/pull/16 167 | 168 | ### New Contributors 169 | 170 | - @flxsource made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/16 171 | 172 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.2.3...v0.2.4 173 | 174 | ## v0.2.3 - 2022-05-05 175 | 176 | ## What's Changed 177 | 178 | - Avoid overlapping of tooltips by @justRau in https://github.com/ryangjchandler/filament-navigation/pull/11 179 | 180 | ## New Contributors 181 | 182 | - @justRau made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/11 183 | 184 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.2.2...v0.2.3 185 | 186 | ## v0.2.2 - 2022-04-25 187 | 188 | ## What's Changed 189 | 190 | - Bump dependabot/fetch-metadata from 1.3.0 to 1.3.1 by @dependabot in https://github.com/ryangjchandler/filament-navigation/pull/10 191 | - [FIX] showing the navigation_type name by @lordjoo in https://github.com/ryangjchandler/filament-navigation/pull/6 192 | 193 | ## New Contributors 194 | 195 | - @dependabot made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/10 196 | - @lordjoo made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/6 197 | 198 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.2.1...v0.2.2 199 | 200 | ## v0.2.1 - 2022-04-21 201 | 202 | ## What's Changed 203 | 204 | - Fixes Tailwind config to use class based dark mode. by @awcodes in https://github.com/ryangjchandler/filament-navigation/pull/5 205 | 206 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.2.0...v0.2.1 207 | 208 | ## v0.2.0 - 2022-04-21 209 | 210 | ## What's Changed 211 | 212 | - fix: use longText instead of json column for MySQL 5.7 by @ryangjchandler in https://github.com/ryangjchandler/filament-navigation/pull/4 213 | - Fixes item input styling for dark mode. by @awcodes in https://github.com/ryangjchandler/filament-navigation/pull/2 214 | 215 | ## Upgrade 216 | 217 | When upgrading to this version, please run the following commands: 218 | 219 | ```sh 220 | composer update 221 | php artisan view:clear 222 | php artisan vendor:publish --tag="filament-navigation-assets" 223 | php artisan migrate 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | ``` 243 | ## New Contributors 244 | 245 | - @ryangjchandler made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/4 246 | - @awcodes made their first contribution in https://github.com/ryangjchandler/filament-navigation/pull/2 247 | 248 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/compare/v0.1.0...v0.2.0 249 | 250 | ## v0.1.0 - 2022-04-19 251 | 252 | **Full Changelog**: https://github.com/ryangjchandler/filament-navigation/commits/v0.1.0 253 | 254 | ## 1.0.0 - 202X-XX-XX 255 | 256 | - initial release 257 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) ryangjchandler 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build structured navigation menus in Filament. 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/ryangjchandler/filament-navigation.svg?style=flat-square)](https://packagist.org/packages/ryangjchandler/filament-navigation) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/ryangjchandler/filament-navigation/run-tests?label=tests)](https://github.com/ryangjchandler/filament-navigation/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/ryangjchandler/filament-navigation/Check%20&%20fix%20styling?label=code%20style)](https://github.com/ryangjchandler/filament-navigation/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/ryangjchandler/filament-navigation.svg?style=flat-square)](https://packagist.org/packages/ryangjchandler/filament-navigation) 7 | 8 | This plugin for Filament provides a `Navigation` resource that lets you build structural navigation menus with a clean drag-and-drop UI. 9 | 10 | ## Installation 11 | 12 | Begin by installing this package via Composer: 13 | 14 | ```sh 15 | composer require ryangjchandler/filament-navigation 16 | ``` 17 | 18 | Run migrations. 19 | 20 | ```sh 21 | php artisan migrate 22 | ``` 23 | 24 | Publish the package's assets: 25 | 26 | ```sh 27 | php artisan filament:assets 28 | ``` 29 | 30 | ## Usage 31 | 32 | You first need to register the plugin with Filament. This can be done inside of your `PanelProvider`, e.g. `AdminPanelProvider`. 33 | 34 | ```php 35 | use RyanChandler\FilamentNavigation\FilamentNavigation; 36 | 37 | return $panel 38 | ->plugin(FilamentNavigation::make()); 39 | ``` 40 | 41 | If you wish to customise the navigation group, sort or icon, you can use the `NavigationResource::navigationGroup()`, `NavigationResource::navigationSort()` and `NavigationResource::navigationIcon()` methods. 42 | 43 | ### Data structure 44 | 45 | Each navigation menu is required to have a `name` and `handle`. The `handle` should be unique and used to retrieve the menu. 46 | 47 | Items are stored inside of a JSON column called `items`. This is a recursive data structure that looks like: 48 | 49 | ```json 50 | [ 51 | { 52 | "label": "Home", 53 | "type": "external-link", 54 | "data": { 55 | "url": "/", 56 | "target": "_blank", 57 | }, 58 | "children": [ 59 | // ... 60 | ], 61 | } 62 | ] 63 | ``` 64 | 65 | The recursive structure makes it really simple to render nested menus / dropdowns: 66 | 67 | ```blade 68 | 81 | ``` 82 | 83 | ### Retrieving a navigation object 84 | 85 | To retrieve a navigation object, provide the handle to the `RyanChandler\FilamentNavigation\Models\Navigation::fromHandle()` method. 86 | 87 | ```php 88 | use RyanChandler\FilamentNavigation\Models\Navigation; 89 | 90 | $menu = Navigation::fromHandle('main-menu'); 91 | ``` 92 | 93 | ### Custom item types 94 | 95 | Out of the box, this plugin comes with a single "item type" called "External link". This item type expects a URL to be provided and an optional "target" (same tab or new tab). 96 | 97 | It's possible to extend the plugin with custom item types. Custom item types have a name and an array of Filament field objects (or a `Closure` that produces an array) that will be displayed inside of the "Item" modal. 98 | 99 | This API allows you to deeply integrate navigation menus with your application's own entities and models. 100 | 101 | ```php 102 | return $panel 103 | ->plugin( 104 | FilamentNavigation::make() 105 | ->itemType('post', [ 106 | Select::make('post_id') 107 | ->//... 108 | ]) 109 | ); 110 | ``` 111 | 112 | All custom fields for the item type can be found inside of the `data` property on the item. 113 | 114 | ### Global custom fields 115 | 116 | There might be cases where you want all item types to have an additional set of fields. This is useful for classes, custom IDs and more. 117 | 118 | To register global custom fields, use the `withExtraFields()` method on the plugin object. 119 | 120 | ```php 121 | return $panel 122 | ->plugin( 123 | FilamentNavigation::make() 124 | ->withExtraFields([ 125 | TextInput::make('classes'), 126 | ]), 127 | ); 128 | ``` 129 | 130 | ### The `Navigation` field type 131 | 132 | This plugin also provides a custom Filament field that can be used to search and select a navigation menu inside other forms and resources. 133 | 134 | ```php 135 | use RyanChandler\FilamentNavigation\Filament\Fields\NavigationSelect; 136 | 137 | ->schema([ 138 | NavigationSelect::make('navigation_id'), 139 | ]) 140 | ``` 141 | 142 | By default, this field will not be searchable and the value for each option will be the menu `id`. 143 | 144 | To make the field searchable, call the `->searchable()` method. 145 | 146 | ```php 147 | ->schema([ 148 | NavigationSelect::make('navigation_id') 149 | ->searchable(), 150 | ]) 151 | ``` 152 | 153 | If you wish to change the value for each option, call the `->optionValue()` method. 154 | 155 | ```php 156 | ->schema([ 157 | NavigationSelect::make('navigation_id') 158 | ->optionValue('handle'), 159 | ]) 160 | ``` 161 | 162 | ## Testing 163 | 164 | ```bash 165 | composer test 166 | ``` 167 | 168 | ## Changelog 169 | 170 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 171 | 172 | ## Contributing 173 | 174 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 175 | 176 | ## Security Vulnerabilities 177 | 178 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 179 | 180 | ## Credits 181 | 182 | - [Ryan Chandler](https://github.com/ryangjchandler) 183 | - [All Contributors](../../contributors) 184 | 185 | ## License 186 | 187 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 188 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | ## Upgrading from v0.x to v1.0 4 | 5 | Starting with version v1.0, this package now only supports Filament v3.x. 6 | 7 | Follow these steps to update the package for Filament v3.x. 8 | 9 | 1. Update the package version in your `composer.json`. 10 | 2. Run `composer update`. 11 | 3. Register the plugin inside of your project's `PanelProvider`. 12 | 13 | ```php 14 | use RyanChandler\FilamentNavigation\FilamentNavigation; 15 | 16 | public function panel(Panel $panel): Panel 17 | { 18 | return $panel 19 | ->plugin(FilamentNavigation::make()) 20 | // ... 21 | } 22 | ``` 23 | 24 | 4. Publish the plugin assets. 25 | 26 | ```sh 27 | php artisan filament:assets 28 | ``` 29 | 30 | 5. Open your panel and check that the resource has been registered and existing navigation menus are showing. 31 | 32 | If you have registered custom navigation item types, the `addItemType()` method no longer exists. 33 | 34 | Instead, register the item types on the `FilamentNavigation` plugin object directly. 35 | 36 | ```php 37 | return $panel 38 | ->plugin( 39 | FilamentNavigation::make() 40 | ->itemType('post', [ 41 | Select::make('post_id') 42 | ->//... 43 | ]) 44 | ) 45 | // ... 46 | ``` 47 | 48 | If you previously used the configuration file to change the `navigation_model` or `navigation_resource` values, those no longer exist and need to be updated to method calls on the plugin object. 49 | 50 | ```php 51 | return $panel 52 | ->plugin( 53 | FilamentNavigation::make() 54 | ->usingResource(MyNavigationResource::class) 55 | ->usingModel(MyNavigationModel::class) 56 | ) 57 | // ... 58 | ``` 59 | 60 | If you have any issues with the upgrade, please open an issue and provide details. Reproduction repositories are much appreciated. 61 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | configure([ 16 | 'enables_package_discoveries' => true, 17 | ]) 18 | ->createApplication(); 19 | 20 | $app->register(LivewireServiceProvider::class); 21 | $app->register(FormsServiceProvider::class); 22 | $app->register(TablesServiceProvider::class); 23 | $app->register(FilamentServiceProvider::class); 24 | 25 | return $app; 26 | -------------------------------------------------------------------------------- /canvas.yaml: -------------------------------------------------------------------------------- 1 | preset: package 2 | 3 | namespace: RyanChandler\FilamentNavigation 4 | user-auth-provider: App\User 5 | 6 | paths: 7 | src: src 8 | resource: resources 9 | 10 | factory: 11 | path: database/factories 12 | 13 | migration: 14 | path: database/migrations 15 | prefix: '' 16 | 17 | console: 18 | namespace: RyanChandler\FilamentNavigation\Console 19 | 20 | model: 21 | namespace: RyanChandler\FilamentNavigation 22 | 23 | provider: 24 | namespace: RyanChandler\FilamentNavigation\Providers 25 | 26 | testing: 27 | namespace: RyanChandler\FilamentNavigation\Tests 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ryangjchandler/filament-navigation", 3 | "description": "Build structured navigation menus in Filament.", 4 | "keywords": [ 5 | "ryangjchandler", 6 | "laravel", 7 | "filament-navigation" 8 | ], 9 | "homepage": "https://github.com/ryangjchandler/filament-navigation", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Ryan Chandler", 14 | "email": "support@ryangjchandler.co.uk", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1", 20 | "doctrine/dbal": "^3.5.1", 21 | "illuminate/contracts": "^10.0|^11.0", 22 | "filament/filament": "^3.0", 23 | "spatie/laravel-package-tools": "^1.9.2" 24 | }, 25 | "require-dev": { 26 | "friendsofphp/php-cs-fixer": "^3.8", 27 | "nunomaduro/collision": "^7.0|^8.0", 28 | "nunomaduro/larastan": "^2.0.1", 29 | "orchestra/canvas": "^8.0", 30 | "orchestra/testbench": "^8.0|^9.0", 31 | "pestphp/pest": "^2.0", 32 | "pestphp/pest-plugin-laravel": "^2.0", 33 | "phpstan/extension-installer": "^1.1", 34 | "phpstan/phpstan-deprecation-rules": "^1.0", 35 | "phpstan/phpstan-phpunit": "^1.0", 36 | "phpunit/phpunit": "^9.5|^10.0", 37 | "spatie/laravel-ray": "^1.26" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "RyanChandler\\FilamentNavigation\\": "src", 42 | "RyanChandler\\FilamentNavigation\\Database\\Factories\\": "database/factories" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "RyanChandler\\FilamentNavigation\\Tests\\": "tests" 48 | } 49 | }, 50 | "scripts": { 51 | "analyse": "vendor/bin/phpstan analyse", 52 | "test": "vendor/bin/pest", 53 | "test-coverage": "vendor/bin/pest coverage", 54 | "format": "vendor/bin/php-cs-fixer fix --config=.php_cs.dist.php --allow-risky=yes" 55 | }, 56 | "config": { 57 | "sort-packages": true, 58 | "allow-plugins": { 59 | "pestphp/pest-plugin": true, 60 | "phpstan/extension-installer": true 61 | } 62 | }, 63 | "extra": { 64 | "laravel": { 65 | "providers": [ 66 | "RyanChandler\\FilamentNavigation\\FilamentNavigationServiceProvider" 67 | ] 68 | } 69 | }, 70 | "minimum-stability": "dev", 71 | "prefer-stable": true 72 | } 73 | -------------------------------------------------------------------------------- /config/filament-navigation.php: -------------------------------------------------------------------------------- 1 | \RyanChandler\FilamentNavigation\Models\Navigation::class, 6 | 7 | 'navigation_resource' => \RyanChandler\FilamentNavigation\Filament\Resources\NavigationResource::class, 8 | ]; 9 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('handle')->unique(); 20 | $table->json('items')->nullable(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('navigations'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2022_04_21_203420_make_items_longtext_on_navigations_table.php: -------------------------------------------------------------------------------- 1 | longText('items')->change(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::dropIfExists('navigations'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/resources/dist/plugin.js": "/resources/dist/plugin.js", 3 | "/resources/dist/plugin.css": "/resources/dist/plugin.css" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "laravel-mix": "^6.0.49", 5 | "sortablejs": "^1.15.0", 6 | "tailwindcss": "^3.0.24" 7 | }, 8 | "scripts": { 9 | "dev": "mix", 10 | "watch": "mix watch", 11 | "prod": "mix --production" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /resources/css/plugin.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .sortable-container-maybe-active { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /resources/dist/plugin.css: -------------------------------------------------------------------------------- 1 | *,:after,:before{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.filament-navigation .absolute{position:absolute}.filament-navigation .relative{position:relative}.filament-navigation .top-0{top:0}.filament-navigation .right-0{right:0}.filament-navigation .-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.filament-navigation .-mr-2{margin-right:-.5rem}.filament-navigation .ml-6{margin-left:1.5rem}.filament-navigation .flex{display:flex}.filament-navigation .hidden{display:none}.filament-navigation .h-4{height:1rem}.filament-navigation .h-3\.5{height:.875rem}.filament-navigation .h-3{height:.75rem}.filament-navigation .h-6{height:1.5rem}.filament-navigation .w-full{width:100%}.filament-navigation .w-4{width:1rem}.filament-navigation .w-3\.5{width:.875rem}.filament-navigation .w-3{width:.75rem}.filament-navigation .-rotate-90{--tw-rotate:-90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.filament-navigation .appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.filament-navigation .items-center{align-items:center}.filament-navigation .justify-end{justify-content:flex-end}.filament-navigation .space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.filament-navigation .divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;border-left-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-right-width:calc(1px*var(--tw-divide-x-reverse))}.filament-navigation .overflow-hidden{overflow:hidden}.filament-navigation .rounded-lg{border-radius:.5rem}.filament-navigation .rounded-l-lg{border-top-left-radius:.5rem}.filament-navigation .rounded-bl-lg,.filament-navigation .rounded-l-lg{border-bottom-left-radius:.5rem}.filament-navigation .rounded-tr-lg{border-top-right-radius:.5rem}.filament-navigation .border{border-width:1px}.filament-navigation .border-r{border-right-width:1px}.filament-navigation .border-b{border-bottom-width:1px}.filament-navigation .border-l{border-left-width:1px}.filament-navigation .border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.filament-navigation .bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.filament-navigation .bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.filament-navigation .p-1{padding:.25rem}.filament-navigation .px-3{padding-left:.75rem;padding-right:.75rem}.filament-navigation .py-2{padding-bottom:.5rem;padding-top:.5rem}.filament-navigation .px-px{padding-left:1px;padding-right:1px}.filament-navigation .text-left{text-align:left}.filament-navigation .text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.filament-navigation .text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.filament-navigation .opacity-0{opacity:0}.filament-navigation .transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.filament-navigation .duration-200{transition-duration:.2s}.filament-navigation .ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.filament-navigation .hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.filament-navigation .group:hover .group-hover\:flex{display:flex}.filament-navigation .group:hover .group-hover\:opacity-100{opacity:1}.filament-navigation [dir=rtl] .rtl\:right-auto{right:auto}.filament-navigation [dir=rtl] .rtl\:left-0{left:0}.filament-navigation [dir=rtl] .rtl\:rounded-bl-none{border-bottom-left-radius:0}.filament-navigation [dir=rtl] .rtl\:rounded-br-lg{border-bottom-right-radius:.5rem}.filament-navigation [dir=rtl] .rtl\:rounded-tr-none{border-top-right-radius:0}.filament-navigation [dir=rtl] .rtl\:rounded-tl-lg{border-top-left-radius:.5rem}.filament-navigation [dir=rtl] .rtl\:border-l-0{border-left-width:0}.filament-navigation [dir=rtl] .rtl\:border-r{border-right-width:1px}.filament-navigation .dark .dark\:divide-gray-600>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(75 85 99/var(--tw-divide-opacity))}.filament-navigation .dark .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.filament-navigation .dark .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.filament-navigation .dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))} 2 | -------------------------------------------------------------------------------- /resources/dist/plugin.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see plugin.js.LICENSE.txt */ 2 | (()=>{"use strict";var t,e={512:()=>{function t(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function e(e){for(var n=1;n=0||(i[n]=t[n]);return i}(t,e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(i[n]=t[n])}return i}function a(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}var l=a(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),s=a(/Edge/i),c=a(/firefox/i),u=a(/safari/i)&&!a(/chrome/i)&&!a(/android/i),d=a(/iP(ad|od|hone)/i),h=a(/chrome/i)&&a(/android/i),f={capture:!1,passive:!1};function p(t,e,n){t.addEventListener(e,n,!l&&f)}function g(t,e,n){t.removeEventListener(e,n,!l&&f)}function v(t,e){if(e){if(">"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return!1}return!1}}function m(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function b(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"===e[0]?t.parentNode===n&&v(t,e):v(t,e))||o&&t===n)return t;if(t===n)break}while(t=m(t))}return null}var y,w=/\s+/g;function E(t,e,n){if(t&&e)if(t.classList)t.classList[n?"add":"remove"](e);else{var o=(" "+t.className+" ").replace(w," ").replace(" "+e+" "," ");t.className=(o+(n?" "+e:"")).replace(w," ")}}function S(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in o||-1!==e.indexOf("webkit")||(e="-webkit-"+e),o[e]=n+("string"==typeof n?"":"px")}}function D(t,e){var n="";if("string"==typeof t)n=t;else do{var o=S(t,"transform");o&&"none"!==o&&(n=o+" "+n)}while(!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function _(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=r:i<=r))return o;if(o===T())break;o=M(o,!1)}return!1}function x(t,e,n,o){for(var i=0,r=0,a=t.children;r2&&void 0!==arguments[2]?arguments[2]:{},i=o.evt,a=r(o,W);H.pluginEvent.bind(Yt)(t,n,e({dragEl:U,parentEl:V,ghostEl:q,rootEl:$,nextEl:Z,lastDownEl:K,cloneEl:Q,cloneHidden:J,dragStarted:ht,putSortable:rt,activeSortable:Yt.active,originalEvent:i,oldIndex:tt,oldDraggableIndex:nt,newIndex:et,newDraggableIndex:ot,hideGhostForTarget:Mt,unhideGhostForTarget:It,cloneNowHidden:function(){J=!0},cloneNowShown:function(){J=!1},dispatchSortableEvent:function(t){G({sortable:n,name:t,originalEvent:i})}},a))};function G(t){L(e({putSortable:rt,cloneEl:Q,targetEl:U,rootEl:$,oldIndex:tt,oldDraggableIndex:nt,newIndex:et,newDraggableIndex:ot},t))}var U,V,q,$,Z,K,Q,J,tt,et,nt,ot,it,rt,at,lt,st,ct,ut,dt,ht,ft,pt,gt,vt,mt=!1,bt=!1,yt=[],wt=!1,Et=!1,St=[],Dt=!1,_t=[],Tt="undefined"!=typeof document,Ct=d,Ot=s||l?"cssFloat":"float",xt=Tt&&!h&&!d&&"draggable"in document.createElement("div"),Nt=function(){if(Tt){if(l)return!1;var t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents}}(),Pt=function(t,e){var n=S(t),o=parseInt(n.width)-parseInt(n.paddingLeft)-parseInt(n.paddingRight)-parseInt(n.borderLeftWidth)-parseInt(n.borderRightWidth),i=x(t,0,e),r=x(t,1,e),a=i&&S(i),l=r&&S(r),s=a&&parseInt(a.marginLeft)+parseInt(a.marginRight)+C(i).width,c=l&&parseInt(l.marginLeft)+parseInt(l.marginRight)+C(r).width;if("flex"===n.display)return"column"===n.flexDirection||"column-reverse"===n.flexDirection?"vertical":"horizontal";if("grid"===n.display)return n.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(i&&a.float&&"none"!==a.float){var u="left"===a.float?"left":"right";return!r||"both"!==l.clear&&l.clear!==u?"horizontal":"vertical"}return i&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||s>=o&&"none"===n[Ot]||r&&"none"===n[Ot]&&s+c>o)?"vertical":"horizontal"},At=function(t){function e(t,n){return function(o,i,r,a){var l=o.options.group.name&&i.options.group.name&&o.options.group.name===i.options.group.name;if(null==t&&(n||l))return!0;if(null==t||!1===t)return!1;if(n&&"clone"===t)return t;if("function"==typeof t)return e(t(o,i,r,a),n)(o,i,r,a);var s=(n?o:i).options.group.name;return!0===t||"string"==typeof t&&t===s||t.join&&t.indexOf(s)>-1}}var o={},i=t.group;i&&"object"==n(i)||(i={name:i}),o.name=i.name,o.checkPull=e(i.pull,!0),o.checkPut=e(i.put),o.revertClone=i.revertClone,t.group=o},Mt=function(){!Nt&&q&&S(q,"display","none")},It=function(){!Nt&&q&&S(q,"display","")};Tt&&!h&&document.addEventListener("click",(function(t){if(bt)return t.preventDefault(),t.stopPropagation&&t.stopPropagation(),t.stopImmediatePropagation&&t.stopImmediatePropagation(),bt=!1,!1}),!0);var kt=function(t){if(U){t=t.touches?t.touches[0]:t;var e=(i=t.clientX,r=t.clientY,yt.some((function(t){var e=t[R].options.emptyInsertThreshold;if(e&&!N(t)){var n=C(t),o=i>=n.left-e&&i<=n.right+e,l=r>=n.top-e&&r<=n.bottom+e;return o&&l?a=t:void 0}})),a);if(e){var n={};for(var o in t)t.hasOwnProperty(o)&&(n[o]=t[o]);n.target=n.rootEl=e,n.preventDefault=void 0,n.stopPropagation=void 0,e[R]._onDragOver(n)}}var i,r,a},Xt=function(t){U&&U.parentNode[R]._isOutsideThisEl(t.target)};function Yt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=i({},e),t[R]=this;var n={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Pt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Yt.supportPointer&&"PointerEvent"in window&&!u,emptyInsertThreshold:5};for(var o in H.initializePlugins(this,t,n),n)!(o in e)&&(e[o]=n[o]);for(var r in At(e),this)"_"===r.charAt(0)&&"function"==typeof this[r]&&(this[r]=this[r].bind(this));this.nativeDraggable=!e.forceFallback&&xt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?p(t,"pointerdown",this._onTapStart):(p(t,"mousedown",this._onTapStart),p(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(p(t,"dragover",this),p(t,"dragenter",this)),yt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),i(this,B())}function Rt(t,e,n,o,i,r,a,c){var u,d,h=t[R],f=h.options.onMove;return!window.CustomEvent||l||s?(u=document.createEvent("Event")).initEvent("move",!0,!0):u=new CustomEvent("move",{bubbles:!0,cancelable:!0}),u.to=e,u.from=t,u.dragged=n,u.draggedRect=o,u.related=i||e,u.relatedRect=r||C(e),u.willInsertAfter=c,u.originalEvent=a,t.dispatchEvent(u),f&&(d=f.call(h,u,a)),d}function Bt(t){t.draggable=!1}function Ft(){Dt=!1}function jt(t){for(var e=t.tagName+t.className+t.src+t.href+t.textContent,n=e.length,o=0;n--;)o+=e.charCodeAt(n);return o.toString(36)}function Ht(t){return setTimeout(t,0)}function Lt(t){return clearTimeout(t)}Yt.prototype={constructor:Yt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(ft=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,U):this.options.direction},_onTapStart:function(t){if(t.cancelable){var e=this,n=this.el,o=this.options,i=o.preventOnFilter,r=t.type,a=t.touches&&t.touches[0]||t.pointerType&&"touch"===t.pointerType&&t,l=(a||t).target,s=t.target.shadowRoot&&(t.path&&t.path[0]||t.composedPath&&t.composedPath()[0])||l,c=o.filter;if(function(t){_t.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&_t.push(o)}}(n),!U&&!(/mousedown|pointerdown/.test(r)&&0!==t.button||o.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=b(l,o.draggable,n,!1))&&l.animated||K===l)){if(tt=P(l),nt=P(l,o.draggable),"function"==typeof c){if(c.call(this,t,l,this))return G({sortable:e,rootEl:s,name:"filter",targetEl:l,toEl:n,fromEl:n}),z("filter",e,{evt:t}),void(i&&t.cancelable&&t.preventDefault())}else if(c&&(c=c.split(",").some((function(o){if(o=b(s,o.trim(),n,!1))return G({sortable:e,rootEl:o,name:"filter",targetEl:l,fromEl:n,toEl:n}),z("filter",e,{evt:t}),!0}))))return void(i&&t.cancelable&&t.preventDefault());o.handle&&!b(s,o.handle,n,!1)||this._prepareDragStart(t,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,u=r.ownerDocument;if(n&&!U&&n.parentNode===r){var d=C(n);if($=r,V=(U=n).parentNode,Z=U.nextSibling,K=n,it=a.group,Yt.dragged=U,at={target:U,clientX:(e||t).clientX,clientY:(e||t).clientY},ut=at.clientX-d.left,dt=at.clientY-d.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,U.style["will-change"]="all",o=function(){z("delayEnded",i,{evt:t}),Yt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!c&&i.nativeDraggable&&(U.draggable=!0),i._triggerDragStart(t,e),G({sortable:i,name:"choose",originalEvent:t}),E(U,a.chosenClass,!0))},a.ignore.split(",").forEach((function(t){_(U,t.trim(),Bt)})),p(u,"dragover",kt),p(u,"mousemove",kt),p(u,"touchmove",kt),p(u,"mouseup",i._onDrop),p(u,"touchend",i._onDrop),p(u,"touchcancel",i._onDrop),c&&this.nativeDraggable&&(this.options.touchStartThreshold=4,U.draggable=!0),z("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(s||l))o();else{if(Yt.eventCanceled)return void this._onDrop();p(u,"mouseup",i._disableDelayedDrag),p(u,"touchend",i._disableDelayedDrag),p(u,"touchcancel",i._disableDelayedDrag),p(u,"mousemove",i._delayedDragTouchMoveHandler),p(u,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&p(u,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)}}},_delayedDragTouchMoveHandler:function(t){var e=t.touches?t.touches[0]:t;Math.max(Math.abs(e.clientX-this._lastX),Math.abs(e.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){U&&Bt(U),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;g(t,"mouseup",this._disableDelayedDrag),g(t,"touchend",this._disableDelayedDrag),g(t,"touchcancel",this._disableDelayedDrag),g(t,"mousemove",this._delayedDragTouchMoveHandler),g(t,"touchmove",this._delayedDragTouchMoveHandler),g(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?p(document,"pointermove",this._onTouchMove):p(document,e?"touchmove":"mousemove",this._onTouchMove):(p(U,"dragend",this),p($,"dragstart",this._onDragStart));try{document.selection?Ht((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){if(mt=!1,$&&U){z("dragStarted",this,{evt:e}),this.nativeDraggable&&p(document,"dragover",Xt);var n=this.options;!t&&E(U,n.dragClass,!1),E(U,n.ghostClass,!0),Yt.active=this,t&&this._appendGhost(),G({sortable:this,name:"start",originalEvent:e})}else this._nulling()},_emulateDragOver:function(){if(lt){this._lastX=lt.clientX,this._lastY=lt.clientY,Mt();for(var t=document.elementFromPoint(lt.clientX,lt.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(lt.clientX,lt.clientY))!==e;)e=t;if(U.parentNode[R]._isOutsideThisEl(t),e)do{if(e[R]){if(e[R]._onDragOver({clientX:lt.clientX,clientY:lt.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}t=e}while(e=e.parentNode);It()}},_onTouchMove:function(t){if(at){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=q&&D(q,!0),a=q&&r&&r.a,l=q&&r&&r.d,s=Ct&&vt&&A(vt),c=(i.clientX-at.clientX+o.x)/(a||1)+(s?s[0]-St[0]:0)/(a||1),u=(i.clientY-at.clientY+o.y)/(l||1)+(s?s[1]-St[1]:0)/(l||1);if(!Yt.active&&!mt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))o.right+i||t.clientX<=o.right&&t.clientY>o.bottom&&t.clientX>=o.left:t.clientX>o.right&&t.clientY>o.top||t.clientX<=o.right&&t.clientY>o.bottom+i}(t,r,this)&&!v.animated){if(v===U)return L(!1);if(v&&a===t.target&&(l=v),l&&(o=C(l)),!1!==Rt($,a,U,n,l,o,t,!!l))return H(),v&&v.nextSibling?a.insertBefore(U,v.nextSibling):a.appendChild(U),V=a,W(),L(!0)}else if(v&&function(t,e,n){var o=C(x(n.el,0,n.options,!0)),i=10;return e?t.clientXu+c*r/2:sd-gt)return-pt}else if(s>u+c*(1-i)/2&&sd-c*r/2))return s>u+c/2?1:-1;return 0}(t,l,o,r,T?1:s.swapThreshold,null==s.invertedSwapThreshold?s.swapThreshold:s.invertedSwapThreshold,Et,ft===l),0!==y){var k=P(U);do{k-=y,D=V.children[k]}while(D&&("none"===S(D,"display")||D===q))}if(0===y||D===l)return L(!1);ft=l,pt=y;var Y=l.nextElementSibling,B=!1,F=Rt($,a,U,n,l,o,t,B=1===y);if(!1!==F)return 1!==F&&-1!==F||(B=1===F),Dt=!0,setTimeout(Ft,30),H(),B&&!Y?a.appendChild(U):l.parentNode.insertBefore(U,B?Y:l),M&&X(M,0,I-M.scrollTop),V=U.parentNode,void 0===w||Et||(gt=Math.abs(w-C(l)[A])),W(),L(!0)}if(a.contains(U))return L(!1)}return!1}function j(s,c){z(s,p,e({evt:t,isOwner:d,axis:r?"vertical":"horizontal",revert:i,dragRect:n,targetRect:o,canSort:h,fromSortable:f,target:l,completed:L,onMove:function(e,o){return Rt($,a,U,n,e,C(e),t,o)},changed:W},c))}function H(){j("dragOverAnimationCapture"),p.captureAnimationState(),p!==f&&f.captureAnimationState()}function L(e){return j("dragOverCompleted",{insertion:e}),e&&(d?u._hideClone():u._showClone(p),p!==f&&(E(U,rt?rt.options.ghostClass:u.options.ghostClass,!1),E(U,s.ghostClass,!0)),rt!==p&&p!==Yt.active?rt=p:p===Yt.active&&rt&&(rt=null),f===p&&(p._ignoreWhileAnimating=l),p.animateAll((function(){j("dragOverAnimationComplete"),p._ignoreWhileAnimating=null})),p!==f&&(f.animateAll(),f._ignoreWhileAnimating=null)),(l===U&&!U.animated||l===a&&!l.animated)&&(ft=null),s.dragoverBubble||t.rootEl||l===document||(U.parentNode[R]._isOutsideThisEl(t.target),!e&&kt(t)),!s.dragoverBubble&&t.stopPropagation&&t.stopPropagation(),g=!0}function W(){et=P(U),ot=P(U,s.draggable),G({sortable:p,name:"change",toEl:a,newIndex:et,newDraggableIndex:ot,originalEvent:t})}},_ignoreWhileAnimating:null,_offMoveEvents:function(){g(document,"mousemove",this._onTouchMove),g(document,"touchmove",this._onTouchMove),g(document,"pointermove",this._onTouchMove),g(document,"dragover",kt),g(document,"mousemove",kt),g(document,"touchmove",kt)},_offUpEvents:function(){var t=this.el.ownerDocument;g(t,"mouseup",this._onDrop),g(t,"touchend",this._onDrop),g(t,"pointerup",this._onDrop),g(t,"touchcancel",this._onDrop),g(document,"selectstart",this)},_onDrop:function(t){var e=this.el,n=this.options;et=P(U),ot=P(U,n.draggable),z("drop",this,{evt:t}),V=U&&U.parentNode,et=P(U),ot=P(U,n.draggable),Yt.eventCanceled||(mt=!1,Et=!1,wt=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),Lt(this.cloneId),Lt(this._dragStartId),this.nativeDraggable&&(g(document,"drop",this),g(e,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),u&&S(document.body,"user-select",""),S(U,"transform",""),t&&(ht&&(t.cancelable&&t.preventDefault(),!n.dropBubble&&t.stopPropagation()),q&&q.parentNode&&q.parentNode.removeChild(q),($===V||rt&&"clone"!==rt.lastPutMode)&&Q&&Q.parentNode&&Q.parentNode.removeChild(Q),U&&(this.nativeDraggable&&g(U,"dragend",this),Bt(U),U.style["will-change"]="",ht&&!mt&&E(U,rt?rt.options.ghostClass:this.options.ghostClass,!1),E(U,this.options.chosenClass,!1),G({sortable:this,name:"unchoose",toEl:V,newIndex:null,newDraggableIndex:null,originalEvent:t}),$!==V?(et>=0&&(G({rootEl:V,name:"add",toEl:V,fromEl:$,originalEvent:t}),G({sortable:this,name:"remove",toEl:V,originalEvent:t}),G({rootEl:V,name:"sort",toEl:V,fromEl:$,originalEvent:t}),G({sortable:this,name:"sort",toEl:V,originalEvent:t})),rt&&rt.save()):et!==tt&&et>=0&&(G({sortable:this,name:"update",toEl:V,originalEvent:t}),G({sortable:this,name:"sort",toEl:V,originalEvent:t})),Yt.active&&(null!=et&&-1!==et||(et=tt,ot=nt),G({sortable:this,name:"end",toEl:V,originalEvent:t}),this.save())))),this._nulling()},_nulling:function(){z("nulling",this),$=U=V=q=Z=Q=K=J=at=lt=ht=et=ot=tt=nt=ft=pt=rt=it=Yt.dragged=Yt.ghost=Yt.clone=Yt.active=null,_t.forEach((function(t){t.checked=!0})),_t.length=st=ct=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragenter":case"dragover":U&&(this._onDragOver(t),function(t){t.dataTransfer&&(t.dataTransfer.dropEffect="move");t.cancelable&&t.preventDefault()}(t));break;case"selectstart":t.preventDefault()}},toArray:function(){for(var t,e=[],n=this.el.children,o=0,i=n.length,r=this.options;o{}},n={};function o(t){var i=n[t];if(void 0!==i)return i.exports;var r=n[t]={exports:{}};return e[t](r,r.exports,o),r.exports}o.m=e,t=[],o.O=(e,n,i,r)=>{if(!n){var a=1/0;for(u=0;u=r)&&Object.keys(o.O).every((t=>o.O[t](n[s])))?n.splice(s--,1):(l=!1,r0&&t[u-1][2]>r;u--)t[u]=t[u-1];t[u]=[n,i,r]},o.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{var t={680:0,823:0};o.O.j=e=>0===t[e];var e=(e,n)=>{var i,r,[a,l,s]=n,c=0;if(a.some((e=>0!==t[e]))){for(i in l)o.o(l,i)&&(o.m[i]=l[i]);if(s)var u=s(o)}for(e&&e(n);co(512)));var i=o.O(void 0,[823],(()=>o(265)));i=o.O(i)})(); -------------------------------------------------------------------------------- /resources/dist/plugin.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /**! 2 | * Sortable 1.15.0 3 | * @author RubaXa 4 | * @author owenm 5 | * @license MIT 6 | */ 7 | -------------------------------------------------------------------------------- /resources/js/plugin.js: -------------------------------------------------------------------------------- 1 | import Sortable from 'sortablejs' 2 | 3 | document.addEventListener('alpine:initializing', () => { 4 | window.Alpine.data('navigationSortableContainer', ({ statePath }) => ({ 5 | statePath, 6 | sortable: null, 7 | init() { 8 | this.sortable = new Sortable(this.$el, { 9 | group: 'nested', 10 | animation: 150, 11 | fallbackOnBody: true, 12 | swapThreshold: 0.50, 13 | draggable: '[data-sortable-item]', 14 | handle: '[data-sortable-handle]', 15 | onSort: () => { 16 | this.sorted() 17 | } 18 | }) 19 | }, 20 | sorted() { 21 | this.$wire.sortNavigation(this.statePath, this.sortable.toArray()) 22 | } 23 | })) 24 | }) 25 | 26 | -------------------------------------------------------------------------------- /resources/lang/ar/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'رابط خارجي', 6 | 'url' => 'الرابط', 7 | 'target' => 'الهدف', 8 | 'name' => 'الاسم', 9 | 'items' => 'العناصر', 10 | 'handle' => 'المقبض', 11 | 'created_at' => 'تم الإنشاء في', 12 | 'updated_at' => 'تم التحديث في', 13 | ], 14 | 15 | 'select-options' => [ 16 | 'same-tab' => 'نفس علامة التبويب', 17 | 'new-tab' => 'علامة تبويب جديدة' 18 | ], 19 | 20 | 'items' => [ 21 | 'empty' => 'لا يوجد عناصر.', 22 | 'add-item' => 'إضافة عنصر', 23 | 'add-child' => 'إضافة فرع', 24 | 'move-up' => 'تحريك للأعلى', 25 | 'move-down' => 'تحريك للأسفل', 26 | 'indent' => 'إزاحة للداخل', 27 | 'dedent' => 'إزاحة للخارج', 28 | 'remove' => 'إزالة' 29 | ], 30 | 31 | 'items-modal' => [ 32 | 'title' => 'العنوان', 33 | 'label' => 'التسمية', 34 | 'type' => 'النوع', 35 | 'btn' => 'الزر', 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /resources/lang/cs/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Odkaz', 6 | 'url' => 'URL/Adresa', 7 | 'target' => 'Cíl', 8 | 'name' => 'Název', 9 | 'items' => 'Položky', 10 | 'handle' => 'Identifikátor', 11 | 'created_at' => 'Vytvořeno', 12 | 'updated_at' => 'Upraveno', 13 | ], 14 | 15 | 'select-options' => [ 16 | 'same-tab' => 'Ve stejném okně', 17 | 'new-tab' => 'V novém okně' 18 | ], 19 | 20 | 'items' => [ 21 | 'empty' => 'Žádné položky', 22 | 'add-item' => 'Přidat položku', 23 | 'add-child' => 'Přidat pod-položku', 24 | 'move-up' => 'Posunout nahoru', 25 | 'move-down' => 'Posunout dolu', 26 | 'indent' => 'Přidružit', 27 | 'dedent' => 'Oddělit', 28 | 'remove' => 'Odstranit' 29 | ], 30 | 31 | 'items-modal' => [ 32 | 'title' => 'Nová položka', 33 | 'label' => 'Název', 34 | 'type' => 'Typ', 35 | 'btn' => 'Uložit', 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /resources/lang/de/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Externer Link', 6 | 'url' => 'URL', 7 | 'target' => 'Ziel', 8 | 'name' => 'Name', 9 | 'items' => 'Einträge', 10 | 'handle' => 'Handle', 11 | 'created_at' => 'Erstellt', 12 | 'updated_at' => 'Geändert', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Gleicher Tab', 18 | 'new-tab' => 'Neuer Tab' 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'Keine Einträge.', 23 | 'add-item' => 'Einträge hinzufügen', 24 | 'add-child' => 'Kind hinzufügen', 25 | 'move-up' => 'Nach oben verschieben', 26 | 'move-down' => 'Nach unten verschieben', 27 | 'indent' => 'Einziehen', 28 | 'dedent' => 'Ausziehen', 29 | 'remove' => 'Entfernen' 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Eintrag', 34 | 'label' => 'Name', 35 | 'type' => 'Typ', 36 | 'btn' => 'Speichern', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/en/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'External link', 6 | 'url' => 'URL', 7 | 'target' => 'Target', 8 | 'name' => 'Name', 9 | 'items' => 'Items', 10 | 'handle' => 'Handle', 11 | 'created_at' => 'Created at', 12 | 'updated_at' => 'Updated at', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Same tab', 18 | 'new-tab' => 'New tab' 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'No items.', 23 | 'add-item' => 'Add item', 24 | 'add-child' => 'Add child', 25 | 'move-up' => 'Move up', 26 | 'move-down' => 'Move down', 27 | 'indent' => 'Indent', 28 | 'dedent' => 'Dedent', 29 | 'remove' => 'Remove' 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Item', 34 | 'label' => 'Label', 35 | 'type' => 'Type', 36 | 'btn' => 'Save', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/es/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Vínculo externo', 6 | 'url' => 'URL', 7 | 'target' => 'Destino', 8 | 'name' => 'Nombre', 9 | 'items' => 'Ítems', 10 | 'handle' => 'Referencia', 11 | 'created_at' => 'Creado', 12 | 'updated_at' => 'Actualizado', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Misma pestaña', 18 | 'new-tab' => 'Nueva pestaña' 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'No hay ítems.', 23 | 'add-item' => 'Agregar ítem', 24 | 'add-child' => 'Agregar hijo', 25 | 'move-up' => 'Mover arriba', 26 | 'move-down' => 'Mover abajo', 27 | 'indent' => 'Indentar', 28 | 'dedent' => 'Desindentar', 29 | 'remove' => 'Remover' 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Ítem', 34 | 'label' => 'Etiqueta', 35 | 'type' => 'Tipo', 36 | 'btn' => 'Guardar', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/fr/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Lien externe', 6 | 'url' => 'URL', 7 | 'target' => 'Cible', 8 | 'name' => 'Nom', 9 | 'items' => 'Éléments', 10 | 'handle' => 'Clé', 11 | 'created_at' => 'Créé le', 12 | 'updated_at' => 'Màj le', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Même fenêtre', 18 | 'new-tab' => 'Nouvelle fenêtre' 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'pas d\'élément', 23 | 'add-item' => 'Ajouter un élément', 24 | 'add-child' => 'Ajouter un enfant', 25 | 'move-up' => 'Déplacer vers le haut', 26 | 'move-down' => 'Déplacer vers le bas', 27 | 'indent' => 'Indenter', 28 | 'dedent' => 'Désindenter', 29 | 'remove' => 'Supprimer' 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Élément', 34 | 'label' => 'Label', 35 | 'type' => 'Type', 36 | 'btn' => 'Enregistrer', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/it/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Link esterno', 6 | 'url' => 'URL', 7 | 'target' => 'Apri in', 8 | 'name' => 'Nome', 9 | 'items' => 'Voci di menù', 10 | 'handle' => 'Riferimento', 11 | 'created_at' => 'Data di creazione', 12 | 'updated_at' => 'Data di aggiornamento', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Stessa scheda', 18 | 'new-tab' => 'Nuova scheda' 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'Nessuna voce di menù.', 23 | 'add-item' => 'Aggiungi voce di menù', 24 | 'add-child' => 'Aggiungi sotto-voce di menù', 25 | 'move-up' => 'Sposta su', 26 | 'move-down' => 'Sposta giù', 27 | 'indent' => 'Aumenta rientro', 28 | 'dedent' => 'Riduci rientro', 29 | 'remove' => 'Rimuovi' 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Voce di menù', 34 | 'label' => 'Etichetta', 35 | 'type' => 'Tipo', 36 | 'btn' => 'Salva', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/nl/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Externe link', 6 | 'url' => 'URL', 7 | 'target' => 'Doel', 8 | 'name' => 'Naam', 9 | 'items' => 'Elementen', 10 | 'handle' => 'Sleutel', 11 | 'created_at' => 'Aangemaakt op', 12 | 'updated_at' => 'Aangepast op', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Dezelfde tab', 18 | 'new-tab' => 'Nieuwe tab' 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'Geen elementen.', 23 | 'add-item' => 'Nieuw element', 24 | 'add-child' => 'Voeg kind toe', 25 | 'move-up' => 'Verplaats omhoog', 26 | 'move-down' => 'Verplaats omlaag', 27 | 'indent' => 'Inspringen', 28 | 'dedent' => 'Terugspringen', 29 | 'remove' => 'Verwijderen' 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Element', 34 | 'label' => 'Label', 35 | 'type' => 'Type', 36 | 'btn' => 'Bewaar', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/pt_BR/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Link externo', 6 | 'url' => 'URL', 7 | 'target' => 'Abrir em', 8 | 'name' => 'Nome', 9 | 'items' => 'Itens', 10 | 'handle' => 'Referência', 11 | 'created_at' => 'Criado em', 12 | 'updated_at' => 'Editado em', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Mesma tab', 18 | 'new-tab' => 'Nova tab', 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'Nenhum item', 23 | 'add-item' => 'Adicionar item', 24 | 'add-child' => 'Adicionar subitem', 25 | 'move-up' => 'Mover para cima', 26 | 'move-down' => 'Mover para baixo', 27 | 'indent' => 'Avançar', 28 | 'dedent' => 'Recuar', 29 | 'remove' => 'Remover', 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Item', 34 | 'label' => 'Nome', 35 | 'type' => 'Tipo', 36 | 'btn' => 'Salvar', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/pt_PT/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Link externo', 6 | 'url' => 'URL', 7 | 'target' => 'Abrir em', 8 | 'name' => 'Nome', 9 | 'items' => 'Itens', 10 | 'handle' => 'Referência', 11 | 'created_at' => 'Criado em', 12 | 'updated_at' => 'Editado em', 13 | 14 | ], 15 | 16 | 'select-options' => [ 17 | 'same-tab' => 'Mesmo separador', 18 | 'new-tab' => 'Novo separador', 19 | ], 20 | 21 | 'items' => [ 22 | 'empty' => 'Nenhum item', 23 | 'add-item' => 'Adicionar item', 24 | 'add-child' => 'Adicionar subitem', 25 | 'move-up' => 'Mover para cima', 26 | 'move-down' => 'Mover para baixo', 27 | 'indent' => 'Avançar', 28 | 'dedent' => 'Recuar', 29 | 'remove' => 'Remover', 30 | ], 31 | 32 | 'items-modal' => [ 33 | 'title' => 'Item', 34 | 'label' => 'Nome', 35 | 'type' => 'Tipo', 36 | 'btn' => 'Guardar', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/ru/filament-navigation.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'external-link' => 'Внешняя ссылка', 6 | 'url' => 'URL', 7 | 'target' => 'Открыть в', 8 | 'name' => 'Название', 9 | 'items' => 'Элементы', 10 | 'handle' => 'Идентификатор', 11 | 'created_at' => 'Создано в', 12 | 'updated_at' => 'Изменено в', 13 | ], 14 | 15 | 'select-options' => [ 16 | 'same-tab' => 'Этой вкладке', 17 | 'new-tab' => 'Новой вкладке' 18 | ], 19 | 20 | 'items' => [ 21 | 'empty' => 'Нет элементов', 22 | 'add-item' => 'Добавить элемент', 23 | 'add-child' => 'Добавить дочерний', 24 | 'move-up' => 'Переместить выше', 25 | 'move-down' => 'Переместить ниже', 26 | 'indent' => 'Indent', 27 | 'dedent' => 'Dedent', 28 | 'remove' => 'Удалить' 29 | ], 30 | 31 | 'items-modal' => [ 32 | 'title' => 'Элемент', 33 | 'label' => 'Название', 34 | 'type' => 'Тип', 35 | 'btn' => 'Сохранить', 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /resources/views/card-divider.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /resources/views/components/nav-item.blade.php: -------------------------------------------------------------------------------- 1 | @props(['item', 'statePath']) 2 | 3 |
10 |
11 |
15 | 22 | 23 | 30 | 31 | @if(count($item['children']) > 0) 32 | 37 | @endif 38 |
39 | 40 | 66 |
67 | 68 |
69 |
76 | @foreach ($item['children'] as $uuid => $child) 77 | 78 | @endforeach 79 |
80 |
81 |
82 | -------------------------------------------------------------------------------- /resources/views/hidden-action.blade.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryangjchandler/filament-navigation/8cff8d07fbd5b2593829b2e9de4c0c5fe0c12d87/resources/views/hidden-action.blade.php -------------------------------------------------------------------------------- /resources/views/navigation-builder.blade.php: -------------------------------------------------------------------------------- 1 | 12 |
13 |
20 | @forelse($getState() as $uuid => $item) 21 | 22 | @empty 23 |
27 | {{__('filament-navigation::filament-navigation.items.empty')}} 28 |
29 | @endforelse 30 |
31 |
32 | 33 |
34 | 35 | {{__('filament-navigation::filament-navigation.items.add-item')}} 36 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /src/Filament/Fields/NavigationSelect.php: -------------------------------------------------------------------------------- 1 | options(function (NavigationSelect $component) { 17 | return Navigation::pluck('name', $component->getOptionValueProperty()); 18 | }); 19 | } 20 | 21 | public function getOptionValueProperty(): string 22 | { 23 | return $this->optionValueProperty; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Filament/Resources/NavigationResource.php: -------------------------------------------------------------------------------- 1 | schema([ 44 | Section::make('')->schema([ 45 | TextInput::make('name') 46 | ->label(__('filament-navigation::filament-navigation.attributes.name')) 47 | ->reactive() 48 | ->debounce() 49 | ->afterStateUpdated(function (?string $state, Set $set, string $context) { 50 | if (! $state) { 51 | return; 52 | } 53 | 54 | if ($context === 'create') { 55 | $set('handle', Str::slug($state)); 56 | } 57 | }) 58 | ->required(), 59 | ViewField::make('items') 60 | ->label(__('filament-navigation::filament-navigation.attributes.items')) 61 | ->default([]) 62 | ->view('filament-navigation::navigation-builder'), 63 | ]) 64 | ->columnSpan([ 65 | 12, 66 | 'lg' => 8, 67 | ]), 68 | Group::make([ 69 | Section::make('')->schema([ 70 | TextInput::make('handle') 71 | ->label(__('filament-navigation::filament-navigation.attributes.handle')) 72 | ->required() 73 | ->unique(column: 'handle', ignoreRecord: true), 74 | View::make('filament-navigation::card-divider') 75 | ->visible(static::$showTimestamps), 76 | Placeholder::make('created_at') 77 | ->label(__('filament-navigation::filament-navigation.attributes.created_at')) 78 | ->visible(static::$showTimestamps) 79 | ->content(fn (?Navigation $record) => $record ? $record->created_at->translatedFormat(Table::$defaultDateTimeDisplayFormat) : new HtmlString('—')), 80 | Placeholder::make('updated_at') 81 | ->label(__('filament-navigation::filament-navigation.attributes.updated_at')) 82 | ->visible(static::$showTimestamps) 83 | ->content(fn (?Navigation $record) => $record ? $record->updated_at->translatedFormat(Table::$defaultDateTimeDisplayFormat) : new HtmlString('—')), 84 | ]), 85 | ]) 86 | ->columnSpan([ 87 | 12, 88 | 'lg' => 4, 89 | ]), 90 | ]) 91 | ->columns(12); 92 | } 93 | 94 | public static function navigationLabel(?string $string): void 95 | { 96 | self::$workNavigationLabel = $string; 97 | } 98 | 99 | public static function pluralLabel(?string $string): void 100 | { 101 | self::$workPluralLabel = $string; 102 | } 103 | 104 | public static function label(?string $string): void 105 | { 106 | self::$workLabel = $string; 107 | } 108 | 109 | public static function getNavigationLabel(): string 110 | { 111 | return self::$workNavigationLabel ?? parent::getNavigationLabel(); 112 | } 113 | 114 | public static function getModelLabel(): string 115 | { 116 | return self::$workLabel ?? parent::getModelLabel(); 117 | } 118 | 119 | public static function getPluralModelLabel(): string 120 | { 121 | return self::$workPluralLabel ?? parent::getPluralModelLabel(); 122 | } 123 | 124 | public static function table(Table $table): Table 125 | { 126 | return $table 127 | ->columns([ 128 | TextColumn::make('name') 129 | ->label(__('filament-navigation::filament-navigation.attributes.name')) 130 | ->searchable(), 131 | TextColumn::make('handle') 132 | ->label(__('filament-navigation::filament-navigation.attributes.handle')) 133 | ->searchable(), 134 | TextColumn::make('created_at') 135 | ->label(__('filament-navigation::filament-navigation.attributes.created_at')) 136 | ->dateTime() 137 | ->sortable(), 138 | TextColumn::make('updated_at') 139 | ->label(__('filament-navigation::filament-navigation.attributes.updated_at')) 140 | ->dateTime() 141 | ->sortable(), 142 | ]) 143 | ->actions([ 144 | EditAction::make() 145 | ->icon(null), 146 | DeleteAction::make() 147 | ->icon(null), 148 | ]) 149 | ->filters([ 150 | 151 | ]); 152 | } 153 | 154 | public static function getPages(): array 155 | { 156 | return [ 157 | 'index' => NavigationResource\Pages\ListNavigations::route('/'), 158 | 'create' => NavigationResource\Pages\CreateNavigation::route('/create'), 159 | 'edit' => NavigationResource\Pages\EditNavigation::route('/{record}'), 160 | ]; 161 | } 162 | 163 | public static function getModel(): string 164 | { 165 | return FilamentNavigation::get()->getModel(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Filament/Resources/NavigationResource/Pages/Concerns/HandlesNavigationBuilder.php: -------------------------------------------------------------------------------- 1 | mountedChildTarget = $statePath; 41 | 42 | $this->mountAction('item'); 43 | } 44 | 45 | public function removeItem(string $statePath) 46 | { 47 | $uuid = Str::afterLast($statePath, '.'); 48 | 49 | $parentPath = Str::beforeLast($statePath, '.'); 50 | $parent = data_get($this, $parentPath); 51 | 52 | data_set($this, $parentPath, Arr::except($parent, $uuid)); 53 | } 54 | 55 | public function editItem(string $statePath) 56 | { 57 | $this->mountedItem = $statePath; 58 | $this->mountedItemData = Arr::except(data_get($this, $statePath), 'children'); 59 | 60 | $this->mountAction('item'); 61 | } 62 | 63 | public function createItem() 64 | { 65 | $this->mountedItem = null; 66 | $this->mountedItemData = []; 67 | $this->mountedActionData = []; 68 | 69 | $this->mountAction('item'); 70 | } 71 | 72 | protected function getActions(): array 73 | { 74 | return [ 75 | Action::make('item') 76 | ->mountUsing(function (ComponentContainer $form) { 77 | if (! $this->mountedItem) { 78 | return; 79 | } 80 | 81 | $form->fill($this->mountedItemData); 82 | }) 83 | ->view('filament-navigation::hidden-action') 84 | ->form([ 85 | TextInput::make('label') 86 | ->label(__('filament-navigation::filament-navigation.items-modal.label')) 87 | ->required(), 88 | Select::make('type') 89 | ->label(__('filament-navigation::filament-navigation.items-modal.type')) 90 | ->options(function () { 91 | $types = FilamentNavigation::get()->getItemTypes(); 92 | 93 | return array_combine(array_keys($types), Arr::pluck($types, 'name')); 94 | }) 95 | ->afterStateUpdated(function ($state, Select $component): void { 96 | if (! $state) { 97 | return; 98 | } 99 | 100 | // NOTE: This chunk of code is a workaround for Livewire not letting 101 | // you entangle to non-existent array keys, which wire:model 102 | // would normally let you do. 103 | $component 104 | ->getContainer() 105 | ->getComponent(fn (Component $component) => $component instanceof Group) 106 | ->getChildComponentContainer() 107 | ->fill(); 108 | }) 109 | ->reactive(), 110 | Group::make() 111 | ->statePath('data') 112 | ->whenTruthy('type') 113 | ->schema(function (Get $get) { 114 | $type = $get('type'); 115 | 116 | return FilamentNavigation::get()->getItemTypes()[$type]['fields'] ?? []; 117 | }), 118 | Group::make() 119 | ->statePath('data') 120 | ->visible(fn (Component $component) => $component->evaluate(FilamentNavigation::get()->getExtraFields()) !== []) 121 | ->schema(function (Component $component) { 122 | return FilamentNavigation::get()->getExtraFields(); 123 | }), 124 | ]) 125 | ->modalWidth('md') 126 | ->action(function (array $data) { 127 | if ($this->mountedItem) { 128 | data_set($this, $this->mountedItem, array_merge(data_get($this, $this->mountedItem), $data)); 129 | 130 | $this->mountedItem = null; 131 | $this->mountedItemData = []; 132 | } elseif ($this->mountedChildTarget) { 133 | $children = data_get($this, $this->mountedChildTarget . '.children', []); 134 | 135 | $children[(string) Str::uuid()] = [ 136 | ...$data, 137 | ...['children' => []], 138 | ]; 139 | 140 | data_set($this, $this->mountedChildTarget . '.children', $children); 141 | 142 | $this->mountedChildTarget = null; 143 | } else { 144 | $this->data['items'][(string) Str::uuid()] = [ 145 | ...$data, 146 | ...['children' => []], 147 | ]; 148 | } 149 | 150 | $this->mountedActionData = []; 151 | }) 152 | ->modalButton(__('filament-navigation::filament-navigation.items-modal.btn')) 153 | ->label(__('filament-navigation::filament-navigation.items-modal.title')), 154 | ]; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Filament/Resources/NavigationResource/Pages/CreateNavigation.php: -------------------------------------------------------------------------------- 1 | getResource(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Filament/Resources/NavigationResource/Pages/EditNavigation.php: -------------------------------------------------------------------------------- 1 | getResource(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Filament/Resources/NavigationResource/Pages/ListNavigations.php: -------------------------------------------------------------------------------- 1 | getResource(); 14 | } 15 | 16 | protected function getActions(): array 17 | { 18 | return [ 19 | CreateAction::make('create'), 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/FilamentNavigation.php: -------------------------------------------------------------------------------- 1 | $resource */ 30 | public function usingResource(string $resource): static 31 | { 32 | $this->resource = $resource; 33 | 34 | return $this; 35 | } 36 | 37 | /** @param class-string<\Illuminate\Database\Eloquent\Model> $model */ 38 | public function usingModel(string $model): static 39 | { 40 | $this->model = $model; 41 | 42 | return $this; 43 | } 44 | 45 | public function itemType(string $name, array | Closure $fields, ?string $slug = null): static 46 | { 47 | $this->itemTypes[$slug ?? Str::slug($name)] = [ 48 | 'name' => $name, 49 | 'fields' => $fields, 50 | ]; 51 | 52 | return $this; 53 | } 54 | 55 | public function withExtraFields(array | Closure $schema): static 56 | { 57 | $this->extraFields = $schema; 58 | 59 | return $this; 60 | } 61 | 62 | public function register(Panel $panel): void 63 | { 64 | $panel 65 | ->resources([$this->getResource()]); 66 | } 67 | 68 | public function boot(Panel $panel): void 69 | { 70 | // 71 | } 72 | 73 | public static function make(): static 74 | { 75 | return new static(); 76 | } 77 | 78 | public static function get(): static 79 | { 80 | return filament('navigation'); 81 | } 82 | 83 | public function getModel(): string 84 | { 85 | return $this->model; 86 | } 87 | 88 | public function getResource(): string 89 | { 90 | return $this->resource; 91 | } 92 | 93 | public function getExtraFields(): array | Closure 94 | { 95 | return $this->extraFields; 96 | } 97 | 98 | public function getItemTypes(): array 99 | { 100 | return array_merge( 101 | [ 102 | 'external-link' => [ 103 | 'name' => __('filament-navigation::filament-navigation.attributes.external-link'), 104 | 'fields' => [ 105 | TextInput::make('url') 106 | ->label(__('filament-navigation::filament-navigation.attributes.url')) 107 | ->required(), 108 | Select::make('target') 109 | ->label(__('filament-navigation::filament-navigation.attributes.target')) 110 | ->options([ 111 | '' => __('filament-navigation::filament-navigation.select-options.same-tab'), 112 | '_blank' => __('filament-navigation::filament-navigation.select-options.new-tab'), 113 | ]) 114 | ->default('') 115 | ->selectablePlaceholder(false), 116 | ], 117 | ], 118 | ], 119 | $this->itemTypes 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/FilamentNavigationServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom([ 15 | __DIR__ . '/../database/migrations', 16 | ]); 17 | 18 | $this->loadViewsFrom([ 19 | __DIR__ . '/../resources/views', 20 | ], 'filament-navigation'); 21 | 22 | $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'filament-navigation'); 23 | 24 | FilamentAsset::register([ 25 | Css::make('filament-navigation-styles', __DIR__ . '/../resources/dist/plugin.css'), 26 | Js::make('filament-navigation-scripts', __DIR__ . '/../resources/dist/plugin.js'), 27 | ], 'filament-navigation'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Models/Navigation.php: -------------------------------------------------------------------------------- 1 | 'json', 22 | ]; 23 | 24 | public static function fromHandle(string $handle): ?static 25 | { 26 | return static::query()->firstWhere('handle', $handle); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./resources/views/**/*.blade.php"], 3 | darkMode: "class", 4 | important: ".filament-navigation", 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | corePlugins: { 10 | preflight: false, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix') 2 | 3 | mix 4 | .postCss('resources/css/plugin.css', 'resources/dist/plugin.css', [ 5 | require('tailwindcss') 6 | ]) 7 | .js('resources/js/plugin.js', 'resources/dist/plugin.js'); 8 | --------------------------------------------------------------------------------