├── .gitignore ├── .prettierrc ├── LICENSE.md ├── README.md ├── composer.json ├── config └── tab-layout-plugin.php ├── package.json ├── phpstan-baseline.neon ├── phpstan.neon.dist ├── phpunit.xml.dist ├── pint.json ├── postcss.config.js ├── resources ├── css │ ├── .gitkeep │ └── plugin.css ├── dist │ └── .gitkeep ├── js │ └── plugin.js ├── lang │ └── en │ │ └── .gitkeep └── views │ ├── .gitkeep │ ├── components │ ├── component-container.blade.php │ ├── tabs.blade.php │ └── tabs │ │ └── tab.blade.php │ ├── tabs │ └── component-wrapper.blade.php │ └── widgets │ └── tabs-widget.blade.php ├── src ├── Commands │ ├── MakeTabComponent.php │ └── MakeTabWidgetCommand.php ├── Components │ ├── ComponentContainer.php │ ├── FilamentComponent.php │ ├── Tabs.php │ └── Tabs │ │ ├── ComponentWrapper.php │ │ ├── Tab.php │ │ ├── TabContainer.php │ │ └── TabLayoutComponent.php ├── Concerns │ ├── Components │ │ ├── BelongsToContainer.php │ │ ├── BelongsToParentComponent.php │ │ ├── CanBeHidden.php │ │ ├── CanSpanColumns.php │ │ ├── HasBadge.php │ │ ├── HasChildComponents.php │ │ ├── HasColumns.php │ │ ├── HasComponent.php │ │ ├── HasComponentData.php │ │ ├── HasComponents.php │ │ ├── HasExtraAttributes.php │ │ ├── HasIcon.php │ │ ├── HasId.php │ │ ├── HasLabel.php │ │ └── HasMaxWidth.php │ └── Layouts │ │ └── InteractsWithTab.php ├── TabLayoutPluginServiceProvider.php └── Widgets │ └── TabsWidget.php ├── stubs ├── .gitkeep ├── TabComponent.stub └── TabsWidget.stub └── tailwind.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .phpunit.result.cache 3 | build 4 | composer.lock 5 | coverage 6 | phpunit.xml 7 | phpstan.neon 8 | testbench.yaml 9 | vendor 10 | node_modules 11 | /build/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Solution Forest 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 | > [!IMPORTANT] 2 | > We will archive this project since filament3 supports tabs now. 3 | > https://beta.filamentphp.com/docs/3.x/infolists/layout/tabs 4 | 5 | 6 | # Tab Layout Plugin 7 | 8 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/solution-forest/tab-layout-plugin.svg?style=flat-square)](https://packagist.org/packages/solution-forest/tab-layout-plugin) 9 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/solution-forest/tab-layout-plugin/run-tests?label=tests)](https://github.com/solution-forest/tab-layout-plugin/actions?query=workflow%3Arun-tests+branch%3Amain) 10 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/solution-forest/tab-layout-plugin/Check%20&%20fix%20styling?label=code%20style)](https://github.com/solution-forest/tab-layout-plugin/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 11 | [![Total Downloads](https://img.shields.io/packagist/dt/solution-forest/tab-layout-plugin.svg?style=flat-square)](https://packagist.org/packages/solution-forest/tab-layout-plugin) 12 | 13 | This plugin creates widgets with tab layout for Filament Admin. 14 | 15 | ![filament-tab-1](https://github.com/solutionforest/filament-tab-plugin/assets/68525320/0dd61497-1c22-474c-b74a-75700df51292) 16 | 17 | Demo site : https://filament-cms-website-demo.solutionforest.net/admin 18 | 19 | Demo username : demo@solutionforest.net 20 | 21 | Demo password : 12345678 Auto Reset every hour. 22 | ## Installation 23 | 24 | You can install the package via composer: 25 | 26 | ```bash 27 | composer require solution-forest/tab-layout-plugin 28 | ``` 29 | 30 | Optionally, you can publish the views using 31 | 32 | ```bash 33 | php artisan vendor:publish --tag="tab-layout-plugin-views" 34 | ``` 35 | 36 | ## Usage 37 | 38 | To build `Tab` widget: 39 | ```php 40 | php artisan make:filament-tab-widget DummyTabs 41 | ``` 42 | 43 | You will then define the child component 'schema()' to display inside: 44 | ```php 45 | use SolutionForest\TabLayoutPlugin\Components\Tabs\Tab as TabLayoutTab; 46 | use SolutionForest\TabLayoutPlugin\Components\Tabs\TabContainer; 47 | use SolutionForest\TabLayoutPlugin\Widgets\TabsWidget as BaseWidget; 48 | 49 | class DummyTabs extends BaseWidget 50 | { 51 | protected function schema(): array 52 | { 53 | return [ 54 | TabLayoutTab::make('Label 1') 55 | ->icon('heroicon-o-bell') 56 | ->badge('39') 57 | ->schema([ 58 | TabContainer::make(\Filament\Widgets\AccountWidget::class) 59 | ]), 60 | TabLayoutTab::make('Label 2') 61 | ->schema([ 62 | TabContainer::make(\Filament\Widgets\AccountWidget::class) 63 | ->columnSpan(1), 64 | TabContainer::make(\Filament\Widgets\AccountWidget::class) 65 | ->columnSpan(1), 66 | ]) 67 | ->columns(2), 68 | TabLayoutTab::make('Go To Filamentphp (Link)')->url("https://filamentphp.com/", true), 69 | ]; 70 | } 71 | } 72 | ``` 73 | 74 | Tabs may have an icon and badge, which you can set using the `icon()` and `badge()` methods: 75 | ```php 76 | Tab::make('Label 1') 77 | ->icon('heroicon-o-bell') 78 | ->badge('39') 79 | ->schema([ 80 | // ... 81 | ]), 82 | ``` 83 | 84 | ## Assign parameters to component 85 | Additionally, you have the option to pass an array of data to your component. 86 | ```php 87 | protected function schema(): array 88 | { 89 | return [ 90 | TabLayoutTab::make('Label 1') 91 | ->icon('heroicon-o-bell') 92 | ->badge('39') 93 | ->schema([ 94 | TabContainer::make(\Filament\Widgets\AccountWidget::class) 95 | TabContainer::make(ViewProductCategory::class) //TARGET COMPONENT 96 | ->data(['record' => 1]), // TARGET COMPONENT'S DATA 97 | ]), 98 | TabLayoutTab::make('Label 2') 99 | ->schema([ 100 | TabContainer::make(\Filament\Widgets\FilamentInfoWidget::class), 101 | ]), 102 | ]; 103 | } 104 | ``` 105 | ![tab-example-1](https://github.com/solutionforest/filament-tab-plugin/assets/68525320/1061acbb-cfdf-422f-8c2f-1c0f709ecf7f) 106 | ![tab-example-2](https://github.com/solutionforest/filament-tab-plugin/assets/68525320/23898112-9d25-4260-bed1-081e679b8b68) 107 | 108 | 109 | In addition to using the `TabContainer` component, you can create your own custom tab layout components by extending the `TabLayoutComponent` class or using command `php artisan tab-layout:component`. 110 | 111 | For example, the following PHP code defines a FilamentInfoWidget class that extends TabLayoutComponent and specifies a `ComponentTabComponent` as the tab component to use. The **getData** method can be used to populate the component with data. 112 | ```php 113 | schema([ 145 | App\Filament\Tabs\Components\FilamentInfoWidget::make() 146 | // ->data([]), // Also can assign data here 147 | ]), 148 | ]; 149 | } 150 | ``` 151 | 152 | 153 | ## Changelog 154 | 155 | Please see [CHANGELOG](../../releases) for more information on what has changed recently. 156 | 157 | 158 | ## Security Vulnerabilities 159 | 160 | If you discover any security related issues, please email info+package@solutionforest.net instead of using the issue tracker. 161 | 162 | 163 | ## License 164 | 165 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 166 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solution-forest/tab-layout-plugin", 3 | "description": "This is a layout plugin for Filament Admin", 4 | "keywords": [ 5 | "Solution Forest", 6 | "laravel", 7 | "tab-layout-plugin" 8 | ], 9 | "homepage": "https://github.com/solution-forest/tab-layout-plugin", 10 | "support": { 11 | "issues": "https://github.com/solution-forest/tab-layout-plugin/issues", 12 | "source": "https://github.com/solution-forest/tab-layout-plugin" 13 | }, 14 | "license": "MIT", 15 | "authors": [{ 16 | "name": "Carly", 17 | "email": "info@solutionforest.net", 18 | "role": "Developer" 19 | }], 20 | "require": { 21 | "php": "^8.0", 22 | "filament/filament": "^2.0", 23 | "spatie/laravel-package-tools": "^1.13.5" 24 | }, 25 | "require-dev": { 26 | "laravel/pint": "^1.0", 27 | "nunomaduro/collision": "^6.0", 28 | "nunomaduro/larastan": "^2.0.1", 29 | "orchestra/testbench": "^7.0", 30 | "pestphp/pest": "^1.21", 31 | "pestphp/pest-plugin-laravel": "^1.1", 32 | "pestphp/pest-plugin-livewire": "^1.0", 33 | "pestphp/pest-plugin-parallel": "^0.3", 34 | "phpstan/extension-installer": "^1.1", 35 | "phpstan/phpstan-deprecation-rules": "^1.0", 36 | "phpstan/phpstan-phpunit": "^1.0", 37 | "phpunit/phpunit": "^9.5", 38 | "spatie/laravel-ray": "^1.26" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "SolutionForest\\TabLayoutPlugin\\": "src" 43 | } 44 | }, 45 | "scripts": { 46 | "pint": "vendor/bin/pint" 47 | }, 48 | "config": { 49 | "sort-packages": true, 50 | "allow-plugins": { 51 | "composer/package-versions-deprecated": true, 52 | "pestphp/pest-plugin": true, 53 | "phpstan/extension-installer": true 54 | } 55 | }, 56 | "extra": { 57 | "laravel": { 58 | "providers": [ 59 | "SolutionForest\\TabLayoutPlugin\\TabLayoutPluginServiceProvider" 60 | ] 61 | } 62 | }, 63 | "minimum-stability": "dev", 64 | "prefer-stable": true 65 | } 66 | -------------------------------------------------------------------------------- /config/tab-layout-plugin.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'namespace' => 'App\\Filament\\Tabs\\Components', 6 | 'path' => app_path('Filament/Tabs/Components'), 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev:styles": "npx tailwindcss -i resources/css/plugin.css -o resources/dist/tab-layout-plugin.css --postcss --watch", 5 | "dev:scripts": "esbuild resources/js/plugin.js --bundle --sourcemap=inline --outfile=resources/dist/tab-layout-plugin.js --watch", 6 | "build:styles": "npx tailwindcss -i resources/css/plugin.css -o resources/dist/tab-layout-plugin.css --postcss --minify && npm run purge", 7 | "build:scripts": "esbuild resources/js/plugin.js --bundle --minify --outfile=resources/dist/tab-layout-plugin.js", 8 | "purge": "filament-purge -i resources/dist/tab-layout-plugin.css -o resources/dist/tab-layout-plugin.css", 9 | "dev": "npm-run-all --parallel dev:*", 10 | "build": "npm-run-all build:*" 11 | }, 12 | "devDependencies": { 13 | "@awcodes/filament-plugin-purge": "^1.0.2", 14 | "autoprefixer": "^10.4.7", 15 | "esbuild": "^0.8.57", 16 | "npm-run-all": "^4.1.5", 17 | "postcss": "^8.4.14", 18 | "postcss-import": "^15.0.0", 19 | "prettier": "^2.7.1", 20 | "prettier-plugin-tailwindcss": "^0.1.13", 21 | "tailwindcss": "^3.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutionforest/filament-tab-plugin/c1388074fd81d21ef510a1d4ed29bff7b46b64e8/phpstan-baseline.neon -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 8 6 | paths: 7 | - config 8 | - resources 9 | - src 10 | tmpDir: build/phpstan 11 | checkOctaneCompatibility: true 12 | checkModelProperties: true 13 | checkMissingIterableValueType: false 14 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | 10 | ./src 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel" 3 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-import": {}, 4 | "tailwindcss/nesting": {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /resources/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutionforest/filament-tab-plugin/c1388074fd81d21ef510a1d4ed29bff7b46b64e8/resources/css/.gitkeep -------------------------------------------------------------------------------- /resources/css/plugin.css: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | -------------------------------------------------------------------------------- /resources/dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutionforest/filament-tab-plugin/c1388074fd81d21ef510a1d4ed29bff7b46b64e8/resources/dist/.gitkeep -------------------------------------------------------------------------------- /resources/js/plugin.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutionforest/filament-tab-plugin/c1388074fd81d21ef510a1d4ed29bff7b46b64e8/resources/js/plugin.js -------------------------------------------------------------------------------- /resources/lang/en/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutionforest/filament-tab-plugin/c1388074fd81d21ef510a1d4ed29bff7b46b64e8/resources/lang/en/.gitkeep -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solutionforest/filament-tab-plugin/c1388074fd81d21ef510a1d4ed29bff7b46b64e8/resources/views/.gitkeep -------------------------------------------------------------------------------- /resources/views/components/component-container.blade.php: -------------------------------------------------------------------------------- 1 | 10 | @foreach ($getComponents(withHidden: false) as $tabContainer) 11 | @php 12 | $columns = $tabContainer->getColumnSpan() ?? []; 13 | 14 | $tabComponent = $tabContainer->getComponent(); 15 | $data = $tabContainer->getData() ?? []; 16 | 17 | @endphp 18 | 19 | 41 | 42 | @if ($tabComponent) 43 | @if ($tabComponent == \Livewire\Livewire::getAlias(\SolutionForest\TabLayoutPlugin\Components\Tabs\ComponentWrapper::class)) 44 | @livewire($tabComponent, $data) 45 | @else 46 | 47 | @livewire(\Livewire\Livewire::getAlias($tabComponent), $data) 48 | @endif 49 | @endif 50 | 51 | 52 | @endforeach 53 | 54 | -------------------------------------------------------------------------------- /resources/views/components/tabs.blade.php: -------------------------------------------------------------------------------- 1 |
merge($getExtraAttributes())->class([ 32 | 'filament-tabs-component rounded-xl shadow-sm border border-gray-300 bg-white', 33 | 'dark:bg-gray-800 dark:border-gray-700' => config('filament.dark_mode'), 34 | ]) }} 35 | {{ $getExtraAlpineAttributeBag() }} 36 | wire:key="{{ $this->id }}.{{ \SolutionForest\TabLayoutPlugin\Components\Tabs::class }}.container" 37 | > 38 | 49 | 50 |
config('filament.dark_mode'), 56 | ]) 57 | > 58 | @foreach ($getChildComponentContainer()->getComponents() as $tab) 59 | @php 60 | $tabUrl = $tab->getUrl(); 61 | @endphp 62 | 63 | 101 | @endforeach 102 |
103 | 104 | @foreach ($getChildComponentContainer()->getComponents() as $tab) 105 | {{ $tab }} 106 | @endforeach 107 |
108 | -------------------------------------------------------------------------------- /resources/views/components/tabs/tab.blade.php: -------------------------------------------------------------------------------- 1 |
merge($getExtraAttributes())->class(['filament-tabs-component-tab outline-none']) }} 23 | wire:key="{{ $this->id }}.{{ \SolutionForest\TabLayoutPlugin\Components\Tabs\Tab::class }}.tabs.{{ $getId() }}" 24 | > 25 | {{ $getChildComponentContainer() }} 26 |
27 | -------------------------------------------------------------------------------- /resources/views/tabs/component-wrapper.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $rawComponent = $this->getRawComponent() ?? null; 3 | @endphp 4 | @if ($rawComponent) 5 | {{ $rawComponent }} 6 | @endif -------------------------------------------------------------------------------- /resources/views/widgets/tabs-widget.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{ $this->tabs }} 3 | 4 | -------------------------------------------------------------------------------- /src/Commands/MakeTabComponent.php: -------------------------------------------------------------------------------- 1 | argument('name') ?? $this->askRequired('Name (e.g. `EditProductCategoryPage`)', 'name'))) 25 | ->studly() 26 | ->trim('/') 27 | ->trim('\\') 28 | ->trim(' ') 29 | ->replace('/', '\\'); 30 | 31 | $path = (string) Str::of($name) 32 | ->prepend('/') 33 | ->replace('\\', '/') 34 | ->replace('//', '/') 35 | ->prepend($path) 36 | ->append('.php'); 37 | 38 | $class = (string) Str::of($name) 39 | ->prepend('\\') 40 | ->prepend($namespace); 41 | 42 | $classNamespace = Str::beforeLast($class, '\\'); 43 | $className = Str::afterLast($class, '\\'); 44 | 45 | $component = (string) Str::of(strval($this->argument('component') ?? $this->askRequired('Component (e.g. `App\Filament\Resources\ProductCategoryResource\Pages\EditProductCategory`)', 'component'))) 46 | ->replace('/', '\\'); 47 | 48 | if (! $this->option('force') && $this->checkForCollision([$path])) { 49 | return static::INVALID; 50 | } 51 | 52 | $componentName = Str::afterLast($component, '\\'); 53 | 54 | $this->copyStubToApp('TabComponent', $path, [ 55 | 'class' => $className, 56 | 'namespace' => $classNamespace, 57 | 'componentClass' => $componentName == $className ? 'ComponentTabComponent' : $componentName, 58 | 'component' => $componentName == $className ? "{$component} as ComponentTabComponent" : $component, 59 | ]); 60 | 61 | $this->info("Successfully created {$className} ! "); 62 | 63 | $this->info("Make sure to register the component in `schema()` of any SolutionForest\\TabLayoutPlugin\\Components\\Tabs\\Tab."); 64 | return static::SUCCESS; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Commands/MakeTabWidgetCommand.php: -------------------------------------------------------------------------------- 1 | argument('name') ?? $this->askRequired('Name (e.g. `MemberDetails`)', 'name'))) 26 | ->trim('/') 27 | ->trim('\\') 28 | ->trim(' ') 29 | ->replace('/', '\\'); 30 | $widgetClass = (string) Str::of($widget)->afterLast('\\'); 31 | $widgetNamespace = Str::of($widget)->contains('\\') ? 32 | (string) Str::of($widget)->beforeLast('\\') : 33 | ''; 34 | 35 | $path = (string) Str::of($widget) 36 | ->prepend('/') 37 | ->prepend($path) 38 | ->replace('\\', '/') 39 | ->replace('//', '/') 40 | ->append('.php'); 41 | 42 | 43 | if (! $this->option('force') && $this->checkForCollision([$path])) { 44 | return static::INVALID; 45 | } 46 | 47 | $this->copyStubToApp('TabsWidget', $path, [ 48 | 'class' => $widgetClass, 49 | 'namespace' => $namespace . ($widgetNamespace !== '' ? "\\{$widgetNamespace}" : ''), 50 | ]); 51 | 52 | $this->info("Successfully created {$widget}!"); 53 | 54 | return static::SUCCESS; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Components/ComponentContainer.php: -------------------------------------------------------------------------------- 1 | id(uniqid()); 22 | } 23 | 24 | public static function make(): static 25 | { 26 | $static = app(static::class); 27 | $static->configure(); 28 | 29 | return $static; 30 | } 31 | 32 | public function tabs(array | Closure $tabs): static 33 | { 34 | $this->childComponents($tabs); 35 | 36 | return $this; 37 | } 38 | 39 | public function activeTab(int | Closure $activeTab): static 40 | { 41 | $this->activeTab = $activeTab; 42 | 43 | return $this; 44 | } 45 | 46 | public function getActiveTab(): int 47 | { 48 | if ($this->isTabPersistedInQueryString()) { 49 | $queryStringTab = request()->query($this->getTabQueryStringKey()); 50 | 51 | foreach ($this->getChildComponentContainer()->getComponents() as $index => $tab) { 52 | if ($tab->getId() !== $queryStringTab) { 53 | continue; 54 | } 55 | 56 | return $index + 1; 57 | } 58 | } 59 | 60 | return $this->evaluate($this->activeTab); 61 | } 62 | 63 | public function getTabQueryStringKey(): ?string 64 | { 65 | return $this->evaluate($this->tabQueryStringKey); 66 | } 67 | 68 | public function isTabPersistedInQueryString(): bool 69 | { 70 | return filled($this->getTabQueryStringKey()); 71 | } 72 | 73 | public function persistTabInQueryString(string | Closure | null $key = 'tab'): static 74 | { 75 | $this->tabQueryStringKey = $key; 76 | 77 | return $this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Components/Tabs/ComponentWrapper.php: -------------------------------------------------------------------------------- 1 | rawComponent = $rawComponent; 32 | 33 | return $this; 34 | } 35 | 36 | public function getRawComponent() 37 | { 38 | return $this->rawComponent; 39 | } 40 | 41 | public function render() 42 | { 43 | return view('tab-layout-plugin::tabs.component-wrapper'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Components/Tabs/Tab.php: -------------------------------------------------------------------------------- 1 | label($label); 24 | $this->id(Str::slug($id ?: $label)); 25 | } 26 | 27 | public static function make(string $label, string $id = null): static 28 | { 29 | $static = app(static::class, ['label' => $label, 'id' => $id]); 30 | $static->configure(); 31 | 32 | return $static; 33 | } 34 | 35 | public function url(string | Closure | null $url, bool $shouldOpenInNewTab = false): static 36 | { 37 | $this->shouldOpenUrlInNewTab = $shouldOpenInNewTab; 38 | $this->url = $url; 39 | 40 | return $this; 41 | } 42 | 43 | public function openUrlInNewTab(bool $condition = true): static 44 | { 45 | $this->shouldOpenUrlInNewTab = $condition; 46 | 47 | return $this; 48 | } 49 | 50 | public function getId(): string 51 | { 52 | return $this->getContainer()->getParentComponent()->getId() . '-' . parent::getId() . '-tab'; 53 | } 54 | 55 | public function getColumnsConfig(): array 56 | { 57 | return $this->columns ?? $this->getContainer()->getColumnsConfig(); 58 | } 59 | 60 | public function getUrl(): ?string 61 | { 62 | return value($this->url); 63 | } 64 | 65 | public function shouldOpenUrlInNewTab(): bool 66 | { 67 | return $this->shouldOpenUrlInNewTab; 68 | } 69 | 70 | public function canConcealComponents(): bool 71 | { 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Components/Tabs/TabContainer.php: -------------------------------------------------------------------------------- 1 | component($component); 22 | } 23 | 24 | public static function make(string $component): static 25 | { 26 | $static = app(static::class, ['component' => $component]); 27 | 28 | return $static; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Components/Tabs/TabLayoutComponent.php: -------------------------------------------------------------------------------- 1 | container = $container; 14 | 15 | return $this; 16 | } 17 | 18 | public function getContainer(): ComponentContainer 19 | { 20 | return $this->container; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Concerns/Components/BelongsToParentComponent.php: -------------------------------------------------------------------------------- 1 | parentComponent = $component; 14 | 15 | return $this; 16 | } 17 | 18 | public function getParentComponent(): ?FilamentComponent 19 | { 20 | return $this->parentComponent; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Concerns/Components/CanBeHidden.php: -------------------------------------------------------------------------------- 1 | isHidden = $condition; 17 | 18 | return $this; 19 | } 20 | 21 | public function when(bool | Closure $condition = true): static 22 | { 23 | $this->visible($condition); 24 | 25 | return $this; 26 | } 27 | 28 | public function visible(bool | Closure $condition = true): static 29 | { 30 | $this->isVisible = $condition; 31 | 32 | return $this; 33 | } 34 | 35 | public function isHidden(): bool 36 | { 37 | if ($this->evaluate($this->isHidden)) { 38 | return true; 39 | } 40 | 41 | return ! $this->evaluate($this->isVisible); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Concerns/Components/CanSpanColumns.php: -------------------------------------------------------------------------------- 1 | 1, 11 | 'sm' => null, 12 | 'md' => null, 13 | 'lg' => null, 14 | 'xl' => null, 15 | '2xl' => null, 16 | ]; 17 | 18 | public function columnSpan(array | int | string | Closure | null $span): static 19 | { 20 | if (! is_array($span)) { 21 | $span = [ 22 | 'default' => $span, 23 | ]; 24 | } 25 | 26 | $this->columnSpan = array_merge($this->columnSpan, $span); 27 | 28 | return $this; 29 | } 30 | 31 | public function columnSpanFull(): static 32 | { 33 | $this->columnSpan('full'); 34 | 35 | return $this; 36 | } 37 | 38 | public function getColumnSpan(int | string | null $breakpoint = null): array | int | string | null 39 | { 40 | $span = $this->columnSpan; 41 | 42 | if ($breakpoint !== null) { 43 | return $this->evaluate($span[$breakpoint] ?? null); 44 | } 45 | 46 | return array_map( 47 | fn (array | int | string | Closure | null $value): array | int | string | null => $this->evaluate($value), 48 | $span, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasBadge.php: -------------------------------------------------------------------------------- 1 | badge = $badge; 14 | 15 | return $this; 16 | } 17 | 18 | public function getBadge(): ?string 19 | { 20 | return $this->evaluate($this->badge); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasChildComponents.php: -------------------------------------------------------------------------------- 1 | childComponents = $components; 17 | 18 | return $this; 19 | } 20 | 21 | public function schema(array | Closure $components): static 22 | { 23 | $this->childComponents($components); 24 | 25 | return $this; 26 | } 27 | 28 | /** 29 | * @deprecated Since version 1.0.0 30 | */ 31 | public function schemaComponentData(array | Closure $schema): static 32 | { 33 | $this->childComponentsData = $schema; 34 | 35 | return $this; 36 | } 37 | 38 | /** 39 | * Components' parameters (Need same array key with components) 40 | */ 41 | public function getChildComponents(): array 42 | { 43 | return $this->evaluate($this->childComponents); 44 | } 45 | 46 | /** 47 | * @deprecated Since version 1.0.0 48 | */ 49 | public function getChildComponentsData(): array 50 | { 51 | return $this->evaluate($this->childComponentsData); 52 | } 53 | 54 | public function getChildComponentContainer($key = null): ComponentContainer 55 | { 56 | if (filled($key)) { 57 | return $this->getChildComponentContainers()[$key]; 58 | } 59 | 60 | return ComponentContainer::make() 61 | ->parentComponent($this) 62 | ->components($this->getChildComponents()); 63 | } 64 | 65 | public function getChildComponentContainers(bool $withHidden = false): array 66 | { 67 | if (! $this->hasChildComponentContainer($withHidden)) { 68 | return []; 69 | } 70 | 71 | return [$this->getChildComponentContainer()]; 72 | } 73 | 74 | public function hasChildComponentContainer(bool $withHidden = false): bool 75 | { 76 | if ((! $withHidden) && $this->isHidden()) { 77 | return false; 78 | } 79 | 80 | if ($this->getChildComponents() === []) { 81 | return false; 82 | } 83 | 84 | return true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasColumns.php: -------------------------------------------------------------------------------- 1 | $columns, 16 | ]; 17 | } 18 | 19 | $this->columns = array_merge($this->columns ?? [], $columns); 20 | 21 | return $this; 22 | } 23 | 24 | public function getColumns($breakpoint = null): array | int | null 25 | { 26 | $columns = $this->getColumnsConfig(); 27 | 28 | if ($breakpoint !== null) { 29 | return $columns[$breakpoint] ?? null; 30 | } 31 | 32 | return $columns; 33 | } 34 | 35 | public function getColumnsConfig(): array 36 | { 37 | if ($this instanceof ComponentContainer && $this->getParentComponent()) { 38 | return $this->getParentComponent()->getColumnsConfig(); 39 | } 40 | 41 | return $this->columns ?? [ 42 | 'default' => 1, 43 | 'sm' => null, 44 | 'md' => null, 45 | 'lg' => null, 46 | 'xl' => null, 47 | '2xl' => null, 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasComponent.php: -------------------------------------------------------------------------------- 1 | component = $component; 12 | 13 | return $this; 14 | } 15 | 16 | public function getComponent(): ?string 17 | { 18 | return $this->component; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasComponentData.php: -------------------------------------------------------------------------------- 1 | data = $data; 12 | 13 | return $this; 14 | } 15 | 16 | public function getData(): array 17 | { 18 | return $this->data; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasComponents.php: -------------------------------------------------------------------------------- 1 | components = $components; 23 | 24 | return $this; 25 | } 26 | 27 | public function schema(array | Closure $components): static 28 | { 29 | $this->components($components); 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * @deprecated Since version 1.0.0 36 | */ 37 | public function schemaComponentData(array | Closure $data): static 38 | { 39 | $this->componentsData = $data; 40 | 41 | return $this; 42 | } 43 | 44 | public function getComponents(bool $withHidden = false): array 45 | { 46 | $components = array_map(function ($component) { 47 | 48 | if ($component instanceof FilamentComponent) { 49 | 50 | return $component->container($this); 51 | } 52 | else if ($component instanceof TabContainer || $component instanceof TabLayoutComponent) { 53 | 54 | return $component; 55 | } 56 | else if (is_object($component)) { 57 | 58 | if (\Livewire\Livewire::getAlias(get_class($component))) { 59 | 60 | return TabContainer::make(\Livewire\Livewire::getAlias(get_class($component))); 61 | } 62 | $livewireAlias = \Livewire\Livewire::getAlias(ComponentWrapper::class); 63 | return TabContainer::make($livewireAlias)->data(['rawComponent' => $component]); 64 | 65 | } else if (is_string($component)) { 66 | 67 | $livewireAlias = \Livewire\Livewire::getAlias(ComponentWrapper::class); 68 | return TabContainer::make($livewireAlias)->data(['rawComponent' => new \Illuminate\Support\HtmlString($component)]); 69 | } 70 | 71 | return null; 72 | 73 | }, $this->evaluate($this->components)); 74 | 75 | if ($withHidden) { 76 | return $components; 77 | } 78 | 79 | return array_filter( 80 | $components, 81 | function (TabContainer|TabLayoutComponent|TabsLayoutTab|LivewireComponent|null $component) { 82 | if ($component && method_exists($component, 'isHidden')) { 83 | return ! $component->isHidden(); 84 | } 85 | else if ($component) { 86 | 87 | return true; 88 | } 89 | return false; 90 | } 91 | ); 92 | } 93 | 94 | /** 95 | * @deprecated Since version 1.0.0 96 | */ 97 | public function getChildComponentData($key): array 98 | { 99 | $componentData = array_map(function ($data) { 100 | if (is_null($data)) { 101 | return []; 102 | } 103 | else if (! is_array($data)) { 104 | return [$data]; 105 | } 106 | 107 | return $data; 108 | 109 | }, $this->evaluate($this->componentsData)); 110 | 111 | return data_get($componentData, $key, []); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasExtraAttributes.php: -------------------------------------------------------------------------------- 1 | extraAttributes[] = $attributes; 16 | } else { 17 | $this->extraAttributes = [$attributes]; 18 | } 19 | 20 | return $this; 21 | } 22 | 23 | public function getExtraAttributes(): array 24 | { 25 | $temporaryAttributeBag = new ComponentAttributeBag(); 26 | 27 | foreach ($this->extraAttributes as $extraAttributes) { 28 | $temporaryAttributeBag = $temporaryAttributeBag->merge($this->evaluate($extraAttributes)); 29 | } 30 | 31 | return $temporaryAttributeBag->getAttributes(); 32 | } 33 | 34 | public function getExtraAttributeBag(): ComponentAttributeBag 35 | { 36 | return new ComponentAttributeBag($this->getExtraAttributes()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasIcon.php: -------------------------------------------------------------------------------- 1 | icon = $icon; 14 | 15 | return $this; 16 | } 17 | 18 | public function getIcon(): ?string 19 | { 20 | return $this->evaluate($this->icon); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasId.php: -------------------------------------------------------------------------------- 1 | id = $id; 14 | 15 | return $this; 16 | } 17 | 18 | public function getId(): ?string 19 | { 20 | return $this->evaluate($this->id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasLabel.php: -------------------------------------------------------------------------------- 1 | isLabelHidden = $condition; 19 | 20 | return $this; 21 | } 22 | 23 | public function label(string | Htmlable | Closure | null $label): static 24 | { 25 | $this->label = $label; 26 | 27 | return $this; 28 | } 29 | 30 | public function translateLabel(bool $shouldTranslateLabel = true): static 31 | { 32 | $this->shouldTranslateLabel = $shouldTranslateLabel; 33 | 34 | return $this; 35 | } 36 | 37 | public function getLabel(): string | Htmlable | null 38 | { 39 | $label = $this->evaluate($this->label); 40 | 41 | return (is_string($label) && $this->shouldTranslateLabel) ? 42 | __($label) : 43 | $label; 44 | } 45 | 46 | public function isLabelHidden(): bool 47 | { 48 | return $this->evaluate($this->isLabelHidden); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Concerns/Components/HasMaxWidth.php: -------------------------------------------------------------------------------- 1 | maxWidth = $width; 14 | 15 | return $this; 16 | } 17 | 18 | public function getMaxWidth(): ?string 19 | { 20 | return $this->evaluate($this->maxWidth); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Concerns/Layouts/InteractsWithTab.php: -------------------------------------------------------------------------------- 1 | tabs = $this->getTabs(); 16 | } 17 | 18 | public static function tabs(Tabs $tabs): Tabs 19 | { 20 | return $tabs; 21 | } 22 | 23 | protected function getTabSchema(): array 24 | { 25 | return []; 26 | } 27 | 28 | public function getTabs(): Tabs 29 | { 30 | return Tabs::make() 31 | ->tabs($this->schema()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/TabLayoutPluginServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name) 20 | ->hasConfigFile() 21 | ->hasCommands($this->getCommands()) 22 | ->hasViews(); 23 | } 24 | 25 | protected function getCommands(): array 26 | { 27 | return [ 28 | Commands\MakeTabWidgetCommand::class, 29 | Commands\MakeTabComponent::class, 30 | ]; 31 | } 32 | 33 | public function bootingPackage() 34 | { 35 | parent::bootingPackage(); 36 | 37 | Livewire::component(static::$name.'::component-wrapper', ComponentWrapper::class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Widgets/TabsWidget.php: -------------------------------------------------------------------------------- 1 | icon('heroicon-o-bell') 18 | // ->badge('39') 19 | // ->schema([ 20 | // TabContainer::make(\Filament\Widgets\AccountWidget::class), 21 | // ]), 22 | // TabLayoutTab::make('Label 2') 23 | // ->schema([ 24 | // TabContainer::make(\Filament\Widgets\FilamentInfoWidget::class), 25 | // ]), 26 | 27 | ]; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const preset = require('./vendor/filament/filament/tailwind.config.preset') 2 | 3 | module.exports = { 4 | presets: [preset], 5 | content: ['./resources/**/*.{blade.php,js}'], 6 | corePlugins: { 7 | preflight: false, 8 | }, 9 | } 10 | --------------------------------------------------------------------------------