├── resources
├── views
│ ├── core
│ │ ├── render.blade.php
│ │ ├── css.blade.php
│ │ ├── dynamic-component.blade.php
│ │ ├── render-in-layout.blade.php
│ │ └── js.blade.php
│ ├── components
│ │ ├── form
│ │ │ ├── checkbox.blade.php
│ │ │ ├── datepicker.blade.php
│ │ │ ├── input.blade.php
│ │ │ ├── input-group.blade.php
│ │ │ └── select.blade.php
│ │ ├── loading.blade.php
│ │ ├── dropdown
│ │ │ ├── header.blade.php
│ │ │ └── drop-down.blade.php
│ │ ├── actions
│ │ │ ├── drop-down.blade.php
│ │ │ ├── icon.blade.php
│ │ │ ├── responsive.blade.php
│ │ │ └── icon-and-title.blade.php
│ │ ├── toolbar
│ │ │ ├── search.blade.php
│ │ │ ├── bulk-actions.blade.php
│ │ │ ├── filters.blade.php
│ │ │ ├── toolbar.blade.php
│ │ │ └── sorting.blade.php
│ │ ├── link.blade.php
│ │ ├── buttons
│ │ │ ├── button.blade.php
│ │ │ ├── icon.blade.php
│ │ │ └── select.blade.php
│ │ ├── filters
│ │ │ ├── select-filter.blade.php
│ │ │ ├── date-filter.blade.php
│ │ │ └── boolean-filter.blade.php
│ │ ├── img.blade.php
│ │ ├── modal.blade.php
│ │ ├── attributes-list.blade.php
│ │ ├── icon.blade.php
│ │ ├── badge.blade.php
│ │ ├── alerts-handler.blade.php
│ │ ├── button.blade.php
│ │ ├── tooltip.blade.php
│ │ ├── confirmation-message.blade.php
│ │ ├── editable.blade.php
│ │ ├── alert.blade.php
│ │ └── table.blade.php
│ ├── view
│ │ └── layout.blade.php
│ ├── list-view
│ │ ├── list-item.blade.php
│ │ └── list-view.blade.php
│ ├── detail-view
│ │ └── detail-view.blade.php
│ ├── table-view
│ │ └── table-view.blade.php
│ └── grid-view
│ │ ├── grid-view-item.blade.php
│ │ └── grid-view.blade.php
├── scss
│ ├── tailwindcss.scss
│ ├── app.scss
│ └── _custom-utilities.scss
└── js
│ ├── app.js
│ └── bootstrap.js
├── .gitattributes
├── .vscode
└── settings.json
├── .gitignore
├── tailwind.config.js
├── public
├── mix-manifest.json
└── laravel-views.css
├── src
├── Filters
│ ├── Filter.php
│ ├── DateFilter.php
│ ├── BooleanFilter.php
│ └── BaseFilter.php
├── Facades
│ ├── Header.php
│ ├── Variants.php
│ ├── LaravelViews.php
│ └── UI.php
├── helpers.php
├── Views
│ ├── ListView.php
│ ├── Components
│ │ └── DynamicComponent.php
│ ├── TableView.php
│ ├── GridView.php
│ ├── View.php
│ ├── Traits
│ │ ├── WithSortableDropdown.php
│ │ ├── WithAlerts.php
│ │ └── WithActions.php
│ ├── DetailView.php
│ └── DataView.php
├── Actions
│ ├── Confirmable.php
│ ├── RedirectAction.php
│ └── Action.php
├── Console
│ ├── GridViewMakeCommand.php
│ ├── TableViewMakeCommand.php
│ ├── ListViewMakeCommand.php
│ ├── BaseViewCommand.php
│ ├── ActionMakeCommand.php
│ ├── MakeViewCommand.php
│ └── FilterMakeCommand.php
├── Data
│ ├── Contracts
│ │ ├── Sortable.php
│ │ ├── Searchable.php
│ │ └── Filterable.php
│ ├── TableViewSortData.php
│ ├── TableViewFilterData.php
│ ├── QueryStringData.php
│ └── TableViewSearchData.php
├── Macros
│ ├── StrMacros.php
│ └── LaravelViewsTestMacros.php
├── UI
│ ├── Header.php
│ ├── UI.php
│ └── Variants.php
├── config
│ └── laravel-views.php
├── LaravelViews.php
└── LaravelViewsServiceProvider.php
├── phpcs.xml
├── tests
├── Mock
│ ├── Actions
│ │ ├── TestErrorAction.php
│ │ ├── TestConfirmedAction.php
│ │ ├── TestSuccessAction.php
│ │ ├── TestDeleteUsersAction.php
│ │ └── TestConfirmedDeleteUsersAction.php
│ ├── MockDetailViewWithComponents.php
│ ├── MockDetailViewWithMultipleComponents.php
│ ├── MockListView.php
│ ├── MockTableViewWithModelClass.php
│ ├── MockTableView.php
│ ├── MockGridView.php
│ ├── MockDetailViewWithActions.php
│ ├── MockTableViewWithActions.php
│ ├── MockDetailView.php
│ ├── MockReviewTableViewWithSearch.php
│ ├── MockTableViewWithSearchAndFilters.php
│ ├── MockTableViewWithDefaultFilterValue.php
│ └── MockTableViewWithBulkActions.php
├── Unit
│ ├── RedirectActionTest.php
│ ├── ActionTest.php
│ ├── HeaderTest.php
│ ├── FilterTest.php
│ ├── UITest.php
│ ├── VariantsTest.php
│ └── LaravelViewsTest.php
├── Database
│ ├── ReviewTest.php
│ ├── factories
│ │ ├── ReviewFactory.php
│ │ └── UserFactory.php
│ ├── UserTest.php
│ └── migrations
│ │ └── 2014_10_12_000000_create_test_tables.php
├── Feature
│ ├── ListViewTest.php
│ ├── GridViewTest.php
│ ├── TableViewTest.php
│ ├── SearchDataTest.php
│ ├── FilterDataTest.php
│ ├── SortDataTest.php
│ ├── RelationalSearchTest.php
│ ├── DetailViewTest.php
│ ├── ExecuteActionsTest.php
│ └── BulkActionsTest.php
└── TestCase.php
├── phpstan.neon.dist
├── stubs
├── detail-view.stub
├── date-filter.stub
├── list-view.stub
├── grid-view.stub
├── table-view.stub
├── action.stub
├── filter.stub
├── bulk-action.stub
└── boolean-filter.stub
├── webpack.mix.js
├── CONTRIBUTING.md
├── phpunit.xml
├── LICENSE
├── package.json
├── composer.json
├── .github
└── workflows
│ └── run-tests.yml
├── README.md
└── CHANGELOG.md
/resources/views/core/render.blade.php:
--------------------------------------------------------------------------------
1 | @livewire($view)
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-detectable=false
2 | *.css linguist-detectable=false
--------------------------------------------------------------------------------
/resources/scss/tailwindcss.scss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @tailwind components;
4 |
5 | @tailwind utilities;
--------------------------------------------------------------------------------
/resources/views/core/css.blade.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/views/core/dynamic-component.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "bladeFormatter.format.indentSize": 2,
3 | "bladeFormatter.format.wrapAttributes": "force"
4 | }
--------------------------------------------------------------------------------
/resources/views/components/form/checkbox.blade.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'w-4 h-4 rounded']) }}>
--------------------------------------------------------------------------------
/resources/views/core/render-in-layout.blade.php:
--------------------------------------------------------------------------------
1 | @extends($layout)
2 |
3 | @section($section)
4 | @livewire($view)
5 | @endsection
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | public/hot
3 | vendor
4 | .phpunit.result.cache
5 | public/laravel-views.js.LICENSE.txt
6 | **/.DS_Store
7 |
--------------------------------------------------------------------------------
/resources/scss/app.scss:
--------------------------------------------------------------------------------
1 | @import "~pikaday/scss/pikaday.scss";
2 | @import "custom-utilities";
3 |
4 | [x-cloak] { display: none !important; }
--------------------------------------------------------------------------------
/resources/views/core/js.blade.php:
--------------------------------------------------------------------------------
1 | @livewireScripts
2 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | theme: {
3 | extend: {
4 | },
5 | },
6 | variants: {},
7 | plugins: [
8 | ],
9 | }
10 |
--------------------------------------------------------------------------------
/resources/views/components/loading.blade.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'animate-spin']) }}>
2 | {!! UI::icon('loader', '', 'text-gray-500') !!}
3 |
--------------------------------------------------------------------------------
/public/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/laravel-views.js": "/laravel-views.js",
3 | "/laravel-views.css": "/laravel-views.css",
4 | "/tailwind.css": "/tailwind.css"
5 | }
6 |
--------------------------------------------------------------------------------
/src/Filters/Filter.php:
--------------------------------------------------------------------------------
1 | ''])
2 |
3 |
4 | {{ __($label) }}
5 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The PSR2 coding standard.
4 |
5 | src/
6 | vendor
7 |
--------------------------------------------------------------------------------
/resources/views/view/layout.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{-- Success/Error feedback --}}
3 |
4 |
5 | {{ $slot }}
6 |
7 | {{-- Confirmation message alert --}}
8 |
9 |
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * First we will load all of this project's JavaScript dependencies which
3 | * includes React and other helpers. It's a great starting point while
4 | * building robust, powerful web applications using React + Laravel.
5 | */
6 | require('./bootstrap')
--------------------------------------------------------------------------------
/src/Facades/Header.php:
--------------------------------------------------------------------------------
1 | error();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Facades/LaravelViews.php:
--------------------------------------------------------------------------------
1 | [], 'model' => null])
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/toolbar/search.blade.php:
--------------------------------------------------------------------------------
1 | @if ($searchBy)
2 | @component('laravel-views::components.form.input-group', [
3 | 'placeholder' => 'Search',
4 | 'model' => 'search',
5 | 'onClick' => 'clearSearch',
6 | 'icon' => $search ? 'x-circle' : 'search',
7 | ])
8 | @endcomponent
9 | @endif
10 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | class();
11 | }
12 |
13 | return $variants;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/resources/views/components/link.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.link
2 |
3 | Renders a simple link
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 |
6 | --}}
7 | @props(['to' => '', 'title' => ''])
8 |
9 |
10 | {{ $title }}
11 |
--------------------------------------------------------------------------------
/src/Filters/DateFilter.php:
--------------------------------------------------------------------------------
1 | 'primary', 'type' => 'button'])
2 |
3 |
4 | {{ $slot }}
5 |
--------------------------------------------------------------------------------
/tests/Mock/Actions/TestConfirmedAction.php:
--------------------------------------------------------------------------------
1 | success();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Mock/Actions/TestSuccessAction.php:
--------------------------------------------------------------------------------
1 | emit('test-event');
13 | $this->success();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Views/ListView.php:
--------------------------------------------------------------------------------
1 | renderIf($model, $this))
5 |
6 |
7 |
8 | @endif
9 | @endforeach
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | includes:
2 | - ./vendor/nunomaduro/larastan/extension.neon
3 |
4 | parameters:
5 |
6 | paths:
7 | - src
8 |
9 | # The level 8 is the highest level
10 | level: 5
11 |
12 | ignoreErrors:
13 | - '#Unsafe usage of new static#'
14 |
15 | excludePaths:
16 | - ./*/*/FileToBeExcluded.php
17 |
18 | checkMissingIterableValueType: false
19 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import feather from 'feather-icons'
2 |
3 | try {
4 | const setUpUiLibraries = () => {
5 | feather.replace()
6 | }
7 |
8 | document.addEventListener("DOMContentLoaded", () => {
9 | setUpUiLibraries()
10 |
11 | Livewire.hook('message.processed', () => {
12 | setUpUiLibraries()
13 | })
14 | })
15 | } catch (error) {
16 | throw new Error(error)
17 | }
--------------------------------------------------------------------------------
/resources/views/components/buttons/icon.blade.php:
--------------------------------------------------------------------------------
1 | @props(['icon', 'size' => 'md'])
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/Facades/UI.php:
--------------------------------------------------------------------------------
1 | $model->name
14 | ]);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Views/Components/DynamicComponent.php:
--------------------------------------------------------------------------------
1 | view);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Mock/Actions/TestDeleteUsersAction.php:
--------------------------------------------------------------------------------
1 | delete();
14 | $this->success();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Unit/RedirectActionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($redirectAction->id, 'redirect-action-user');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/resources/views/components/filters/select-filter.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.filters.select.blade
2 |
3 | Renders the dropdown for the select filter
4 | To customize it you should shange the UI component used, YOU SHOULD NOT CUSTOMIZE ANYHING HERE
5 | UI components used:
6 | - form/select --}}
7 |
8 | @component('laravel-views::components.form.select', [
9 | 'name' => "filters[{$view->id}]",
10 | 'model' => "filters.{$view->id}",
11 | 'options' => array_merge(['--' => ''], $view->options()),
12 | ])
13 | @endcomponent
--------------------------------------------------------------------------------
/resources/views/components/actions/responsive.blade.php:
--------------------------------------------------------------------------------
1 | @props(['actions' => [], 'model' => null])
2 |
3 |
4 | @if (count($actions))
5 | {{-- Mobile actions dropdown --}}
6 |
7 |
8 |
9 |
10 | {{-- Desktop action buttons --}}
11 |
12 |
13 |
14 | @endif
15 |
--------------------------------------------------------------------------------
/resources/scss/_custom-utilities.scss:
--------------------------------------------------------------------------------
1 | .line-clamp {
2 | display: -webkit-box;
3 | -webkit-line-clamp: 1;
4 | -webkit-box-orient: vertical;
5 | overflow: hidden
6 | }
7 |
8 | .line-clamp-2 {
9 | @extend .line-clamp;
10 | -webkit-line-clamp: 2;
11 | }
12 |
13 | .line-clamp-3 {
14 | @extend .line-clamp;
15 | -webkit-line-clamp: 3;
16 | }
17 |
18 | .line-clamp-4 {
19 | @extend .line-clamp;
20 | -webkit-line-clamp: 4;
21 | }
22 |
23 | .line-clamp-5 {
24 | @extend .line-clamp;
25 | -webkit-line-clamp: 5;
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Mock/Actions/TestConfirmedDeleteUsersAction.php:
--------------------------------------------------------------------------------
1 | delete();
17 | $this->success();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/resources/views/components/filters/date-filter.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.filters.date.blade
2 |
3 | Renders the datepicker for the date filter
4 | To customize it you should shange the UI component used, YOU SHOULD NOT CUSTOMIZE ANYHING HERE
5 | UI components used:
6 | - form/datepicker --}}
7 |
8 | @component('laravel-views::components.form.datepicker', [
9 | 'name' => "filters[{$view->id}]",
10 | 'model' => "filters.{$view->id}",
11 | 'value' => $view->selected()['selected'],
12 | 'id' => $view->getId(),
13 | ])
14 | @endcomponent
--------------------------------------------------------------------------------
/src/Console/GridViewMakeCommand.php:
--------------------------------------------------------------------------------
1 | '',
20 | 'Email' => '',
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/resources/views/components/img.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.img
2 |
3 | Renders and image with all its variants
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 | it is using the variant helper to get the styles for each variant
6 | it could be img, avatar
7 |
8 | You can customize the variants classes in config/laravel-views.php
9 |
10 | props:
11 | - src
12 | - variant
13 | --}}
14 | @props(['src', 'variant' => ''])
15 |
16 |
--------------------------------------------------------------------------------
/resources/views/components/modal.blade.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/views/components/attributes-list.blade.php:
--------------------------------------------------------------------------------
1 | @props(['data', 'stripe' => false])
2 |
3 |
4 | @foreach ($data as $title => $value)
5 |
6 |
7 | {!! $title !!}
8 |
9 |
10 | {!! $value !!}
11 |
12 |
13 | @endforeach
14 |
--------------------------------------------------------------------------------
/tests/Mock/MockDetailViewWithMultipleComponents.php:
--------------------------------------------------------------------------------
1 | $model->name
15 | ]),
16 | UI::attributes([
17 | 'Email' => $model->email
18 | ]),
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/resources/views/components/icon.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.icon
2 |
3 | Renders an feather icon with its different variants
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 | it is using the variant helper to get the styles it could be success, danger, warning, info, default.
6 |
7 | You can customize the variants classes in config/laravel-views.php
8 |
9 | props
10 | - type
11 | - icon
12 |
13 | --}}
14 | @props(['icon', 'class'])
15 |
--------------------------------------------------------------------------------
/src/Data/Contracts/Sortable.php:
--------------------------------------------------------------------------------
1 | $user->avatar,
20 | 'title' => $user->name,
21 | 'subtitle' => $user->email,
22 | ];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/resources/views/list-view/list-item.blade.php:
--------------------------------------------------------------------------------
1 | @props(['avatar', 'title', 'subtitle', 'actions', 'model'])
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {!! $title !!}
11 |
12 |
13 | {!! $subtitle !!}
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Views/TableView.php:
--------------------------------------------------------------------------------
1 | headers();
18 |
19 | return $data;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/stubs/date-filter.stub:
--------------------------------------------------------------------------------
1 | where('', $value);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Database/ReviewTest.php:
--------------------------------------------------------------------------------
1 | belongsTo(UserTest::class, 'user_id', 'id');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/resources/views/components/badge.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.badge
2 |
3 | Renders a badge with its different variants
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 | it is using the variant helper to get the styles it could be success, danger, warning, info, default.
6 |
7 | You can customize the variants classes in config/laravel-views.php
8 |
9 | props
10 | - type
11 | - title
12 |
13 | --}}
14 | @props(['type', 'title'])
15 |
16 |
17 |
18 | {{ $title }}
19 |
--------------------------------------------------------------------------------
/tests/Mock/MockTableViewWithModelClass.php:
--------------------------------------------------------------------------------
1 | name,
25 | $user->email
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Actions/RedirectAction.php:
--------------------------------------------------------------------------------
1 | title = $title;
14 | $this->icon = $icon;
15 | $this->to = $to;
16 |
17 | // Overrides the original id to create different ids for each redirect action
18 | $this->id = $this->id . '-' . $this->to;
19 | }
20 |
21 | public function handle($item)
22 | {
23 | return redirect()->route($this->to, $item);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Data/Contracts/Searchable.php:
--------------------------------------------------------------------------------
1 | '',
24 | 'title' => '',
25 | 'subtitle' => '',
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Mock/MockTableView.php:
--------------------------------------------------------------------------------
1 | name,
26 | $user->email
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resources/views/components/form/datepicker.blade.php:
--------------------------------------------------------------------------------
1 | {{-- table-view.date.blade
2 |
3 | Renders a datepicker input
4 | To customize it you should shange the UI component used, YOU SHOULD NOT CUSTOMIZE ANYHING HERE
5 | UI components used:
6 | - form/input
7 | props:
8 | - $label
9 | - $name
10 | - $placeholder
11 | - $value
12 | - $model
13 | - $id
14 | --}}
15 |
16 |
24 |
--------------------------------------------------------------------------------
/src/Macros/StrMacros.php:
--------------------------------------------------------------------------------
1 | '',
22 | 'title' => $user->name,
23 | 'subtitle' => $user->email,
24 | 'description' => $user->email
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/resources/views/components/actions/icon-and-title.blade.php:
--------------------------------------------------------------------------------
1 | @props(['actions', 'model' => null])
2 |
3 | @foreach ($actions as $action)
4 | @if ($action->renderIf($model, $this))
5 | id}','{$model->getKey()}')" : "executeBulkAction('{$action->id}')" }}"
7 | title="{{ $action->title}}"
8 | class="group flex items-center px-4 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full focus:outline-none"
9 | >
10 |
11 | {{ $action->title }}
12 |
13 | @endif
14 | @endforeach
--------------------------------------------------------------------------------
/src/Views/View.php:
--------------------------------------------------------------------------------
1 | getRenderData(),
17 | [
18 | 'view' => $this
19 | ]
20 | );
21 |
22 | return view("laravel-views::{$this->view}", $data);
23 | }
24 |
25 | abstract protected function getRenderData();
26 |
27 | public function getClassName()
28 | {
29 | return $this->viewName;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/stubs/grid-view.stub:
--------------------------------------------------------------------------------
1 | '',
24 | 'title' => '',
25 | 'subtitle' => '',
26 | 'description' => ''
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resources/views/components/alerts-handler.blade.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Data/Contracts/Filterable.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $title }}
6 |
7 | @isset ($subtitle)
8 |
{{ $subtitle }}
9 | @endisset
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | @foreach ($components as $component)
18 |
19 | {!! $component !!}
20 |
21 | @endforeach
22 |
23 | @include('laravel-views::components.confirmation-message')
24 |
--------------------------------------------------------------------------------
/src/Console/BaseViewCommand.php:
--------------------------------------------------------------------------------
1 | call('make:view', [
29 | 'type' => $this->viewName,
30 | 'name' => $this->argument('name')
31 | ]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Mock/MockDetailViewWithActions.php:
--------------------------------------------------------------------------------
1 | name,
16 | 'Detail view subtitle ' . $model->email
17 | ];
18 | }
19 |
20 | public function detail(UserTest $model)
21 | {
22 | return [
23 | 'Name' => $model->name,
24 | 'Email' => $model->email,
25 | 'Avatar' => $model->avatar,
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/views/components/filters/boolean-filter.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.filters.boolean.blade
2 | Renders the input radius for the boolean filter
3 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIERE DIRECTIVES
4 | --}}
5 |
6 |
7 | @foreach ($view->options() as $title => $option)
8 |
9 |
16 | {{ $title }}
17 |
18 | @endforeach
19 |
--------------------------------------------------------------------------------
/tests/Unit/ActionTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($action->shouldBeConfirmed());
18 |
19 | $actionWithConfirmation = new class extends Action {
20 | use Confirmable;
21 |
22 | public function handle($item) {}
23 | };
24 |
25 | $this->assertTrue($actionWithConfirmation->shouldBeConfirmed());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/stubs/table-view.stub:
--------------------------------------------------------------------------------
1 | Array of headers
18 | */
19 | public function headers(): array
20 | {
21 | return [];
22 | }
23 |
24 | /**
25 | * Sets the data to every cell of a single row
26 | *
27 | * @param $model Current model for each row
28 | */
29 | public function row($model): array
30 | {
31 | return [];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/resources/views/components/toolbar/bulk-actions.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if (count($selected) > 0)
3 |
4 |
5 |
6 |
7 | @endif
8 |
9 | @if ($this->hasBulkActions && isset($headers) <= 0)
10 |
14 | {{ __($allSelected ? 'Unselect all' : 'Select all') }}
15 |
16 | @endif
17 |
--------------------------------------------------------------------------------
/tests/Unit/HeaderTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($header->title, 'My header');
14 | }
15 |
16 | public function testSetSortBy()
17 | {
18 | $header = Header::sortBy('title');
19 | $this->assertEquals($header->sortBy, 'title');
20 | $this->assertTrue($header->isSortable());
21 | }
22 |
23 | public function testSetWidth()
24 | {
25 | $header = Header::width('100px');
26 | $this->assertEquals($header->width, '100px');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/views/components/button.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.alert
2 |
3 | Renders the a button with its different variants
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 | it is using the variant helper to get the styles for each variant
6 | it could be primary, primary-light
7 |
8 | You can customize the variants classes in config/laravel-views.php
9 |
10 | props
11 | - title
12 | - block
13 | - variant
14 |
15 | --}}
16 |
21 | {{ $title }}
22 |
--------------------------------------------------------------------------------
/src/Data/TableViewSortData.php:
--------------------------------------------------------------------------------
1 | orderBy($field, $order);
23 | }
24 |
25 | return $query;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Mock/MockReviewTableViewWithSearch.php:
--------------------------------------------------------------------------------
1 | id,
30 | $review->user->email,
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Unit/FilterTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($filter->getTitle(), 'Example Test');
30 | $this->assertEquals($filterWithTitle->getTitle(), 'My custom title');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Views/Traits/WithSortableDropdown.php:
--------------------------------------------------------------------------------
1 | sortableBy()) instanceof Collection
17 | ? $sortableBy
18 | : collect($sortableBy);
19 |
20 | return $data;
21 | }
22 |
23 | public function sortableBy()
24 | {
25 | return [];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Feature/ListViewTest.php:
--------------------------------------------------------------------------------
1 | create();
18 | $livewire = Livewire::test(MockListView::class);
19 |
20 | foreach ($users as $user) {
21 | $livewire->assertSee($user->name)
22 | ->assertSee($user->email)
23 | ->assertSeeHtml(htmlentities($user->avatar));
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/stubs/action.stub:
--------------------------------------------------------------------------------
1 | where('active', $value);
13 | }
14 |
15 | public function options(): array
16 | {
17 | return [
18 | 'Active' => 1,
19 | 'Disabled' => 0,
20 | ];
21 | }
22 | }
23 |
24 | class MockTableViewWithSearchAndFilters extends MockTableView
25 | {
26 | public $searchBy = ['email'];
27 |
28 | protected function filters()
29 | {
30 | return [
31 | new ActiveUsersFilter
32 | ];
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/stubs/filter.stub:
--------------------------------------------------------------------------------
1 | where('', $value);
20 | }
21 |
22 | /**
23 | * Defines the title and value for each option
24 | *
25 | * @return Array associative array with the title and values
26 | */
27 | public function options(): Array
28 | {
29 | return [];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Mock/MockTableViewWithDefaultFilterValue.php:
--------------------------------------------------------------------------------
1 | where('active', $value);
15 | }
16 |
17 | public function options(): array
18 | {
19 | return [
20 | 'Active' => 1,
21 | 'Disabled' => 0,
22 | ];
23 | }
24 | }
25 |
26 | class MockTableViewWithDefaultFilterValue extends MockTableView
27 | {
28 | protected function filters()
29 | {
30 | return [
31 | new DefaultFilterValue
32 | ];
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/resources/views/components/form/input.blade.php:
--------------------------------------------------------------------------------
1 | {{-- table-view.select.blade
2 |
3 | Renders an input component
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIERE DIRECTIVES
5 |
6 | props:
7 | - $label
8 | - $name
9 | - $placeholder
10 | - $value
11 | - $model
12 | - $id
13 | - $attributes
14 | --}}
15 | @props(['label' => null])
16 |
17 | @if ($label)
18 |
19 | {{ $label ?? '' }}
20 |
21 | @endif
22 |
23 |
29 |
--------------------------------------------------------------------------------
/tests/Feature/GridViewTest.php:
--------------------------------------------------------------------------------
1 | create();
23 |
24 | $livewire = Livewire::test(MockGridView::class);
25 |
26 | foreach ($users as $user) {
27 | $livewire->assertSee($user->name)
28 | ->assertSee($user->email);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/stubs/bulk-action.stub:
--------------------------------------------------------------------------------
1 | ''])
2 |
3 |
4 |
5 | {{ $slot }}
6 |
7 |
8 |
9 |
10 | {{ $tooltip }}
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/views/components/buttons/select.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.buttons.select
2 |
3 | Renders a button like a select dropdown. Most likely used in conjunction with the components.drop-down
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 | it is using the variant helper to get the styles for each variant
6 | it could be primary, primary-light
7 |
8 | You can customize the variants classes in config/laravel-views.php
9 |
10 | props
11 | - icon --}}
12 | @props(['icon' => 'chevron-down'])
13 |
16 | {{ $slot }}
17 | @if ($icon)
18 |
19 | @endif
20 |
21 |
--------------------------------------------------------------------------------
/stubs/boolean-filter.stub:
--------------------------------------------------------------------------------
1 | where('', $value);
20 | }
21 |
22 | /**
23 | * Defines the title and value for each option
24 | *
25 | * @return Array associative array with the title and values
26 | */
27 | public function options(): Array
28 | {
29 | return [];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix')
2 | const tailwindcss = require('tailwindcss')
3 |
4 | /*
5 | |--------------------------------------------------------------------------
6 | | Mix Asset Management
7 | |--------------------------------------------------------------------------
8 | |
9 | | Mix provides a clean, fluent API for defining some Webpack build steps
10 | | for your Laravel application. By default, we are compiling the Sass
11 | | file for your application, as well as bundling up your JS files.
12 | |
13 | */
14 |
15 | mix.js('resources/js/app.js', 'public/laravel-views.js')
16 | .sass('resources/scss/app.scss', 'public/laravel-views.css')
17 | .sass('resources/scss/tailwindcss.scss', 'public/tailwind.css')
18 | .options({
19 | processCssUrls: false,
20 | postCss: [ tailwindcss('./tailwind.config.js') ],
21 | })
22 | .setPublicPath('public')
--------------------------------------------------------------------------------
/tests/Database/factories/ReviewFactory.php:
--------------------------------------------------------------------------------
1 | define(ReviewTest::class, function (Faker $faker) {
22 | return [
23 | 'food_id' => random_int(1, 10),
24 | 'user_id' => random_int(1, 10),
25 | 'message' => $faker->text,
26 | ];
27 | });
28 |
--------------------------------------------------------------------------------
/tests/Mock/MockTableViewWithBulkActions.php:
--------------------------------------------------------------------------------
1 | setMessage('success', $message);
12 | }
13 |
14 | public function error($message = null)
15 | {
16 | $this->setMessage('danger', $message);
17 | }
18 |
19 | private function setMessage($type = 'success', $message = null)
20 | {
21 | $view = $this->view && $this->view instanceof View ? $this->view : $this;
22 |
23 | $messages = [
24 | 'success' => __('Action was executed successfully'),
25 | 'danger' => __('There was an error executing this action'),
26 | ];
27 |
28 | $view->emitSelf('notify', [
29 | 'message' => $message ? $message : $messages[$type],
30 | 'type' => $type
31 | ]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Filters/BooleanFilter.php:
--------------------------------------------------------------------------------
1 | options())->values()->toArray();
14 | $valuesToFilter = [];
15 |
16 | foreach ($options as $option) {
17 | if (isset($values[$option]) && filter_var($values[$option], FILTER_VALIDATE_BOOLEAN)) {
18 | $valuesToFilter[$option] = true;
19 | } else {
20 | $valuesToFilter[$option] = false;
21 | }
22 | }
23 |
24 | return $valuesToFilter;
25 | }
26 |
27 | public function isChecked($option)
28 | {
29 | $values = $this->value();
30 |
31 | return isset($values[$option]) && $values[$option];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/resources/views/list-view/list-view.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{-- Search input and filters --}}
3 |
4 | @include('laravel-views::components.toolbar.toolbar')
5 |
6 |
7 |
8 | @foreach ($items as $item)
9 |
10 | @if ($this->hasBulkActions)
11 |
12 |
13 |
14 | @endif
15 |
16 |
17 |
18 |
19 | @endforeach
20 |
21 |
22 | {{-- Paginator, loading indicator and totals --}}
23 |
24 | {{ $items->links() }}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | All contributions are welcomed, feel free to open a PR with your contributions.
4 |
5 | ## Here are some points to consider:
6 |
7 | - Your PR must be making only a single change, if you want to suggest multiple features or fix multiple issues please open separate PRs.
8 | - Your PR's description should explain what the change is, what is fixing, inproving or including, if it is possible add an image or gif with the result, this will be helpful as a documentation.
9 | - Your PR must include tests if it is necessary, like with fixes or new features, besides, it must pass the Tracis CI build before it can be merged.
10 | - Your code should follows PSR-2 standard.
11 | - If you have an idea that will require a lot of work, make sure you suggest it in a new [issue](https://github.com/Gustavinho/laravel-views/issues) first to make sure it's admired before investing time into it.
12 | - Keep your code clean. Clean means you're proud of how it turned out.
--------------------------------------------------------------------------------
/resources/views/components/confirmation-message.blade.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 | {{__("Cancel")}}
16 |
17 |
18 | {{ __("Yes, I'm sure") }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | src/
6 |
7 |
8 |
9 |
10 | ./tests/Unit
11 |
12 |
13 | ./tests/Feature
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/resources/views/components/toolbar/filters.blade.php:
--------------------------------------------------------------------------------
1 | {{-- Filters dropdown --}}
2 | @if (isset($filtersViews) && $filtersViews)
3 |
4 | {{-- Each filter view --}}
5 | @foreach ($filtersViews as $filter)
6 | {{-- Filter title --}}
7 |
8 |
9 | {{-- Filter view --}}
10 | @include('laravel-views::components.filters.' . $filter->view, [
11 | 'view' => $filter,
12 | 'filter' => $filter,
13 | ])
14 |
15 | @endforeach
16 |
17 | @if (count($filters) > 0)
18 | {{-- Clear filters button --}}
19 |
20 |
21 |
22 | {{__('Clear filters')}}
23 |
24 |
25 | @endif
26 |
27 | @endif
28 |
--------------------------------------------------------------------------------
/resources/views/components/toolbar/toolbar.blade.php:
--------------------------------------------------------------------------------
1 | {{-- list-view.filters.blade
2 |
3 | Renders the search input and the filters dropdown
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIERE DIRECTIVES
5 | UI components used:
6 | - form/input-group
7 | - dropdown --}}
8 |
9 |
10 | {{-- Search input --}}
11 |
12 | @include('laravel-views::components.toolbar.search')
13 |
14 |
15 | {{-- Actions on the left --}}
16 |
17 |
18 |
19 | {{-- Bulk actions --}}
20 |
21 | @include('laravel-views::components.toolbar.bulk-actions')
22 |
23 |
24 | {{-- Sorting --}}
25 | @if (isset($sortableBy) && $sortableBy->isNotEmpty())
26 |
27 | @include('laravel-views::components.toolbar.sorting')
28 |
29 | @endif
30 |
31 | {{-- Filters --}}
32 |
33 | @include('laravel-views::components.toolbar.filters')
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Kristijan Husak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/tests/Database/UserTest.php:
--------------------------------------------------------------------------------
1 | 'datetime',
39 | ];
40 |
41 |
42 | public function reviews(): \Illuminate\Database\Eloquent\Relations\HasMany
43 | {
44 | return $this->hasMany(ReviewTest::class, 'user_id', 'id');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/resources/views/components/dropdown/drop-down.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components/drop-down.blade
2 |
3 | Renders the a dropdown button with its different variants
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 |
6 | - slots:
7 | - trigger
8 | --}}
9 |
10 | @props(['size' => 'lg', 'dropDownWidth' => null, 'label' => '',])
11 |
12 | @php
13 | $sizes = [
14 | 'full' => 'full',
15 | 'sm' => 'w-48',
16 | 'lg' => 'w-64'
17 | ]
18 | @endphp
19 |
20 |
24 |
25 | @if ($label)
26 |
27 | {{ __($label) }}
28 |
29 | @else
30 | @isset($trigger)
31 | {{ $trigger }}
32 | @endisset
33 | @endif
34 |
35 |
36 |
42 | {{ $slot }}
43 |
44 |
--------------------------------------------------------------------------------
/resources/views/table-view/table-view.blade.php:
--------------------------------------------------------------------------------
1 | {{-- table-view.table-view
2 |
3 | Base layout to render all the UI componentes related to the table view, this is the main file for this view,
4 | the rest of the files are included from here
5 |
6 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIERE DIRECTIVES
7 |
8 | UI components used:
9 | - table-view.filters
10 | - components.alert
11 | - components.table
12 | - components.paginator --}}
13 |
14 |
15 | {{-- Search input and filters --}}
16 |
17 | @include('laravel-views::components.toolbar.toolbar')
18 |
19 |
20 | @if (count($items))
21 | {{-- Content table --}}
22 |
23 | @include('laravel-views::components.table')
24 |
25 |
26 | @else
27 | {{-- Empty data message --}}
28 |
29 |
{{ __('There are no items in this table') }}
30 |
31 | @endif
32 |
33 | {{-- Paginator, loading indicator and totals --}}
34 |
35 | {{ $items->links() }}
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/resources/views/components/form/input-group.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.form.input-group.blade
2 |
3 | Renders a input group component
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIERE DIRECTIVES
5 |
6 | props:
7 | - $label
8 | - $name
9 | - $placeholder
10 | - $value
11 | - $model
12 | - $id
13 | - $onClick
14 | - $icon
15 | --}}
16 |
17 |
18 |
19 | {{ $label ?? '' }}
20 |
21 |
32 |
37 |
--------------------------------------------------------------------------------
/src/Actions/Action.php:
--------------------------------------------------------------------------------
1 | id = $this->getId();
33 | }
34 |
35 | public function isRedirect()
36 | {
37 | return get_class($this) === RedirectAction::class;
38 | }
39 |
40 | public function getId()
41 | {
42 | return Str::camelToDash((new \ReflectionClass($this))->getShortName());
43 | }
44 |
45 | public function renderIf($item, View $view)
46 | {
47 | return true;
48 | }
49 |
50 | public function shouldBeConfirmed()
51 | {
52 | return method_exists($this, 'getConfirmationMessage');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Feature/TableViewTest.php:
--------------------------------------------------------------------------------
1 | create();
19 | $livewire = Livewire::test(MockTableView::class);
20 | $headers = ['name', 'email'];
21 |
22 | foreach ($headers as $header) {
23 | $livewire->assertSee($header);
24 | }
25 | }
26 |
27 | public function testSeeAllDataOnTheTable()
28 | {
29 | $users = factory(UserTest::class, 7)->create();
30 |
31 | Livewire::test(MockTableView::class)
32 | ->assertSeeUsers($users);
33 | }
34 |
35 | public function testSeeAllDataSettingAModelClass()
36 | {
37 | $users = factory(UserTest::class, 7)->create();
38 |
39 | Livewire::test(MockTableViewWithModelClass::class)
40 | ->assertSeeUsers($users);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/resources/views/components/editable.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.editable
2 |
3 | Render an editable input field --}}
4 | @props(['model', 'field' => ''])
5 |
6 |
14 |
24 |
{$refs.input.focus()})"
26 | x-html="value"
27 | class='transition-all duration-300 ease-in-out px-2 py-1 rounded cursor-pointer focus:outline-none hover:bg-white hover:border-gray-500 border border-transparent'>
28 | {!! strip_tags($model->$field) !!}
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/resources/views/components/form/select.blade.php:
--------------------------------------------------------------------------------
1 | {{-- table-view.select.blade
2 |
3 | Renders a select component
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIERE DIRECTIVES
5 |
6 | props:
7 | - $label
8 | - $name
9 | - $options
10 | - $selected
11 | - $model
12 | --}}
13 |
14 | @if (isset($label))
15 |
16 | {{ $label }}
17 |
18 | @endif
19 |
20 |
24 | @if (count($options))
25 | @foreach ($options as $option => $value)
26 |
27 | {{ $option }}
28 |
29 | @endforeach
30 | @endif
31 |
32 |
33 | {{-- Down icon --}}
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/tests/Feature/SearchDataTest.php:
--------------------------------------------------------------------------------
1 | assertDontSee('Search');
20 | }
21 |
22 | public function testSeeAllDataFoundBySearchInput()
23 | {
24 | $users = factory(UserTest::class, 10)->create();
25 | $user = $users->last();
26 |
27 | Livewire::test(MockTableViewWithSearchAndFilters::class)
28 | ->set('search', $user->email)
29 | // Filtered user
30 | ->assertSeeUsers(collect([$user]))
31 | // Rest of the users
32 | ->assertDontSeeUsers($users->splice(0, 9));
33 | }
34 |
35 | public function testClearSearch()
36 | {
37 | Livewire::test(MockTableViewWithSearchAndFilters::class)
38 | ->set('search', 'my-custom-search')
39 | ->call('clearSearch')
40 | ->assertSet('search', '');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Console/ActionMakeCommand.php:
--------------------------------------------------------------------------------
1 | option('bulk');
38 | if ($isBulk) {
39 | return __DIR__ . "/../../stubs/bulk-action.stub";
40 | }
41 |
42 | return __DIR__ . "/../../stubs/action.stub";
43 | }
44 |
45 | /**
46 | * Get the default namespace for the class.
47 | *
48 | * @param string $rootNamespace
49 | *
50 | * @return string
51 | */
52 | protected function getDefaultNamespace($rootNamespace)
53 | {
54 | return $rootNamespace;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "npm run development",
8 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "watch": "npm run development -- --watch",
10 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
11 | "prod": "npm run production",
12 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
13 | },
14 | "author": "",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "laravel-mix": "^5.0.9",
18 | "resolve-url-loader": "^3.1.2",
19 | "sass": "^1.32.0",
20 | "sass-loader": "^8.0.2",
21 | "vue-template-compiler": "^2.6.12"
22 | },
23 | "dependencies": {
24 | "alpinejs": "^2.8.0",
25 | "axios": "^0.19.2",
26 | "feather-icons": "^4.28.0",
27 | "jquery": "^3.5.1",
28 | "moment": "^2.29.1",
29 | "pikaday": "^1.8.2",
30 | "popper.js": "^1.16.1",
31 | "tailwindcss": "^1.9.6",
32 | "tailwindcss-plugins": "^0.3.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/UI/Header.php:
--------------------------------------------------------------------------------
1 | title = $title;
24 |
25 | return $header;
26 | }
27 |
28 | /**
29 | * Sets the sort by field
30 | * @param string $field Field the table view will be sort by
31 | * @return Header
32 | */
33 | public function sortBy(string $field)
34 | {
35 | $this->sortBy = $field;
36 |
37 | return $this;
38 | }
39 |
40 | /**
41 | * Checks if this header is sortable
42 | * @return bool
43 | * @return Header
44 | */
45 | public function isSortable(): bool
46 | {
47 | return !empty($this->sortBy);
48 | }
49 |
50 | /**
51 | * Sets a fixed width of the column
52 | * @return Header
53 | */
54 | public function width(string $width)
55 | {
56 | $this->width = $width;
57 |
58 | return $this;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/Database/migrations/2014_10_12_000000_create_test_tables.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('name');
19 | $table->string('email');
20 | $table->boolean('is_admin')->default(false);
21 | $table->boolean('is_writer')->default(false);
22 | $table->boolean('active')->default(true);
23 | $table->text('avatar')->nullable();
24 | $table->rememberToken();
25 | $table->timestamps();
26 | });
27 |
28 | Schema::create('reviews', function (Blueprint $table) {
29 | $table->bigIncrements('id');
30 | $table->bigInteger('food_id');
31 | $table->bigInteger('user_id');
32 | $table->text('message');
33 | $table->timestamps();
34 | });
35 | }
36 |
37 | /**
38 | * Reverse the migrations.
39 | *
40 | * @return void
41 | */
42 | public function down()
43 | {
44 | Schema::dropIfExists('users');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Console/MakeViewCommand.php:
--------------------------------------------------------------------------------
1 | argument('type');
40 |
41 | return __DIR__ . "/../../stubs/{$view}-view.stub";
42 | }
43 |
44 | /**
45 | * Get the default namespace for the class.
46 | *
47 | * @param string $rootNamespace
48 | *
49 | * @return string
50 | */
51 | protected function getDefaultNamespace($rootNamespace)
52 | {
53 | return $rootNamespace . '\Http\Livewire';
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Feature/FilterDataTest.php:
--------------------------------------------------------------------------------
1 | assertDontSee('Filters');
20 | }
21 |
22 | public function testSeeAllDataFoundByAFilter()
23 | {
24 | $activeUsers = factory(UserTest::class, 5)->create(['active' => true]);
25 | $inactiveUsers = factory(UserTest::class, 5)->create(['active' => false]);
26 |
27 | Livewire::test(MockTableViewWithSearchAndFilters::class)
28 | ->set('filters', [
29 | 'active-users-filter' => 1
30 | ])
31 | ->assertSeeUsers($activeUsers)
32 | ->assertDontSeeUsers($inactiveUsers);
33 | }
34 |
35 |
36 | public function testClearFilters()
37 | {
38 | Livewire::test(MockTableViewWithSearchAndFilters::class)
39 | ->set('filters', [
40 | 'active-users-filter' => 1
41 | ])
42 | ->call('clearFilters')
43 | ->assertSet('filters', []);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Data/TableViewFilterData.php:
--------------------------------------------------------------------------------
1 | 0) {
23 | foreach ($filters as $filter) {
24 | if (isset($filterValues[$filter->id])) {
25 |
26 | /** Applies some transformation bwtween url query and filter class created */
27 | $currentFilterValue = $filterValues[$filter->id];
28 | $value = $filter->passValuesFromRequestToFilter($currentFilterValue);
29 |
30 | if ($value !== "") {
31 | $filter->apply($query, $value, request());
32 | }
33 | }
34 | }
35 | }
36 |
37 | return $query;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Feature/SortDataTest.php:
--------------------------------------------------------------------------------
1 | create();
19 | $lastUsers = factory(UserTest::class, 20)->create();
20 |
21 | // first clic to the sortable header
22 | LiveWire::test(MockTableView::class)
23 | ->call('sort', 'id')
24 | ->assertSeeUsers($firstUsers)
25 | ->assertDontSeeUsers($lastUsers)
26 |
27 | // second clic to the sortable header
28 | ->call('sort', 'id')
29 | ->assertSeeUsers($lastUsers)
30 | ->assertDontSeeUsers($firstUsers);
31 | }
32 |
33 | public function testSeeAllDataFoundByAFilterWithADefaultValue()
34 | {
35 | $activeUsers = factory(UserTest::class, 5)->create(['active' => true]);
36 | $inactiveUsers = factory(UserTest::class, 5)->create(['active' => false]);
37 |
38 | Livewire::test(MockTableViewWithDefaultFilterValue::class)
39 | ->assertSeeUsers($activeUsers)
40 | ->assertDontSeeUsers($inactiveUsers);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/resources/views/components/toolbar/sorting.blade.php:
--------------------------------------------------------------------------------
1 | {{-- list-view.sorting.blade
2 |
3 | Renders the dropdown for the sorting select
4 | To customize it you should shange the UI component used, YOU SHOULD NOT CUSTOMIZE ANYHING HERE
5 | UI components used:
6 | - form/select --}}
7 |
38 |
--------------------------------------------------------------------------------
/resources/views/components/alert.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.alert
2 |
3 | Renders the alert message with its different variants
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 | it is using the variant helper to get the styles, icon and title depending of the alert type,
6 | it could be success, error, warning.
7 |
8 | You can customize the variants classes in config/laravel-views.php
9 | --}}
10 | @props(['type' => 'success', 'onClose' => ''])
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 | {{ variants()->alert($type)->title() }}!
23 |
24 |
25 | {{ $slot }}
26 |
27 |
28 |
29 | {{-- Flush this message from the session --}}
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel-views/laravel-views",
3 | "description": "Laravel package to create beautiful common views like tables using only PHP code",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Gustavo Martinez",
9 | "email": "gustavinho.martinez@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "livewire/livewire": "2.*"
14 | },
15 | "autoload": {
16 | "psr-4": {
17 | "LaravelViews\\": "src",
18 | "LaravelViews\\Test\\": "tests"
19 | }
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "9.5.*",
23 | "laravel/legacy-factories": "^1.1",
24 | "nunomaduro/larastan": "^0.7.6",
25 | "orchestra/testbench": "^7.0"
26 | },
27 | "scripts": {
28 | "test": "./vendor/bin/phpunit",
29 | "test:verbose": "composer run test -- --testdox",
30 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage",
31 | "larastan": "./vendor/bin/phpstan analyse"
32 | },
33 | "extra": {
34 | "laravel": {
35 | "providers": [
36 | "LaravelViews\\LaravelViewsServiceProvider"
37 | ],
38 | "aliases": {
39 | "LaravelViews": "LaravelViews\\Facades\\LaravelViews",
40 | "Variants": "LaravelViews\\Facades\\Variants",
41 | "UI": "LaravelViews\\Facades\\UI"
42 | }
43 | }
44 | },
45 | "minimum-stability": "dev",
46 | "prefer-stable": true
47 | }
48 |
--------------------------------------------------------------------------------
/src/Data/QueryStringData.php:
--------------------------------------------------------------------------------
1 | request = $request;
14 | }
15 |
16 | public function getSearchValue($currentValue)
17 | {
18 | return $this->request->query('search', $currentValue);
19 | }
20 |
21 | /**
22 | * Casts all boolean values of the querystring from string to boolean
23 | * this is needed to set the boolean filter values properly
24 | */
25 | public function getFilterValues($currentValue)
26 | {
27 | $filters = $this->request->query('filters', $currentValue);
28 |
29 | return collect($filters)->map(function ($filter) {
30 | /** If is an array that means it came from a boolean filter */
31 | if (is_array($filter)) {
32 | foreach ($filter as $option => $checked) {
33 | /** Casts from string to boolean */
34 | $filter[$option] = filter_var($checked, FILTER_VALIDATE_BOOLEAN);
35 | }
36 | }
37 |
38 | return $filter;
39 | })->toArray();
40 | }
41 |
42 | /**
43 | * Get a value from the query string
44 | * @param string $field query param name
45 | * @param string $currentValue Default value
46 | */
47 | public function getValue($field, $currentValue)
48 | {
49 | return $this->request->query($field, $currentValue);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Console/FilterMakeCommand.php:
--------------------------------------------------------------------------------
1 | option('type');
47 | $stub = 'filter';
48 |
49 | if ($type == 'boolean') {
50 | $stub = 'boolean-filter';
51 | }
52 |
53 | if ($type == 'date') {
54 | $stub = 'date-filter';
55 | }
56 |
57 | return __DIR__ . "/../../stubs/{$stub}.stub";
58 | }
59 |
60 | /**
61 | * Get the default namespace for the class.
62 | *
63 | * @param string $rootNamespace
64 | *
65 | * @return string
66 | */
67 | protected function getDefaultNamespace($rootNamespace)
68 | {
69 | return $rootNamespace;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Filters/BaseFilter.php:
--------------------------------------------------------------------------------
1 | id = $this->getId();
16 | }
17 |
18 | public function selected()
19 | {
20 | $request = request();
21 | $selected = '';
22 |
23 | if ($request->has('filters')) {
24 | $filters = $request->get('filters');
25 | if (isset($filters[$this->id])) {
26 | $value = $filters[$this->id];
27 | if ($value != "" && $value != null) {
28 | $selected = $value;
29 | if (is_array($selected)) {
30 | foreach ($selected as $key => $value) {
31 | $selected[$key] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
38 | return [
39 | 'selected' => $selected
40 | ];
41 | }
42 |
43 | public function value()
44 | {
45 | return $this->selected()['selected'];
46 | }
47 |
48 | /**
49 | * Get filter title
50 | */
51 | public function getTitle()
52 | {
53 | if (!$this->title) {
54 | return Str::classNameAsSentence((new \ReflectionClass($this))->getShortName());
55 | }
56 |
57 | return $this->title;
58 | }
59 |
60 | public function getId()
61 | {
62 | return Str::camelToDash((new \ReflectionClass($this))->getShortName());
63 | }
64 |
65 | public function passValuesFromRequestToFilter($values)
66 | {
67 | return $values;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/UI/UI.php:
--------------------------------------------------------------------------------
1 | component('laravel-views::components.editable', [
12 | 'model' => $model,
13 | 'field' => $field
14 | ]);
15 | }
16 |
17 | public function badge($title, $type = 'default')
18 | {
19 | return $this->component('laravel-views::components.badge', [
20 | 'title' => $title,
21 | 'type' => $type
22 | ]);
23 | }
24 |
25 | public function avatar($src)
26 | {
27 | return $this->component('laravel-views::components.img', [
28 | 'src' => $src,
29 | 'variant' => 'avatar'
30 | ]);
31 | }
32 |
33 | public function link($title, $to)
34 | {
35 | return $this->component('laravel-views::components.link', compact(
36 | 'to',
37 | 'title'
38 | ));
39 | }
40 |
41 | public function icon($icon, $type = 'default', $class = "")
42 | {
43 | return $this->component('laravel-views::components.icon', compact(
44 | 'icon',
45 | 'type',
46 | 'class'
47 | ));
48 | }
49 |
50 | public function attributes($attributes, $options = [])
51 | {
52 | return $this->component('laravel-views::components.attributes-list', array_merge(
53 | ['data' => $attributes],
54 | $options
55 | ));
56 | }
57 |
58 | public function component($view, $data = [])
59 | {
60 | return View::make('laravel-views::core.dynamic-component')
61 | ->with([
62 | 'view' => $view,
63 | 'data' => $data,
64 | ])
65 | ->render();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/resources/views/grid-view/grid-view-item.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'image' => '',
3 | 'title' => '',
4 | 'subtitle' => '',
5 | 'description' => '',
6 | 'withBackground' => false,
7 | 'model',
8 | 'actions' => [],
9 | 'hasDefaultAction' => false,
10 | 'selected' => false
11 | ])
12 |
13 |
14 | @if ($hasDefaultAction)
15 |
16 |
17 |
18 | @else
19 |
20 | @endif
21 |
22 |
23 |
24 |
25 |
26 | @if ($hasDefaultAction)
27 |
28 | {!! $title !!}
29 |
30 | @else
31 | {!! $title !!}
32 | @endif
33 |
34 | @if ($subtitle)
35 |
36 | {!! $subtitle !!}
37 |
38 | @endif
39 |
40 |
41 | @if (count($actions))
42 |
43 |
44 |
45 | @endif
46 |
47 |
48 | @if (isset($description))
49 |
50 | {!! $description !!}
51 |
52 | @endif
53 |
54 |
55 |
--------------------------------------------------------------------------------
/resources/views/grid-view/grid-view.blade.php:
--------------------------------------------------------------------------------
1 | {{-- grid-view.grid-view
2 |
3 | Base layout to render all the UI componentes related to the grid view, this is the main file for this view,
4 | the rest of the files are included from here
5 |
6 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIERE DIRECTIVES
7 |
8 | --}}
9 | @php
10 | // Mapped this tailwindcss utilities so they can be prged
11 | $cols = [
12 | 1 => 'xl:grid-cols-1',
13 | 2 => 'xl:grid-cols-2',
14 | 3 => 'xl:grid-cols-3',
15 | 4 => 'xl:grid-cols-4',
16 | 5 => 'xl:grid-cols-5',
17 | 6 => 'xl:grid-cols-6',
18 | 7 => 'xl:grid-cols-7',
19 | 8 => 'xl:grid-cols-8',
20 | 9 => 'xl:grid-cols-9',
21 | 10 => 'xl:grid-cols-10',
22 | 11 => 'xl:grid-cols-11',
23 | 12 => 'xl:grid-cols-12',
24 | ]
25 | @endphp
26 |
27 | {{-- Search input and filters --}}
28 |
29 | @include('laravel-views::components.toolbar.toolbar')
30 |
31 |
32 |
33 | @foreach ($items as $item)
34 |
35 | @if ($this->hasBulkActions)
36 |
37 |
38 |
39 | @endif
40 |
50 |
51 | @endforeach
52 |
53 |
54 | {{-- Paginator, loading indicator and totals --}}
55 |
56 | {{ $items->links() }}
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/Macros/LaravelViewsTestMacros.php:
--------------------------------------------------------------------------------
1 | assertEmitted('notify', [
12 | 'message' => $message ?? __('Action was executed successfully'),
13 | 'type' => 'success'
14 | ]);
15 |
16 | return $this;
17 | });
18 |
19 | TestableLivewire::macro('assertShowErrorAlert', function ($message = null) {
20 | $this->assertEmitted('notify', [
21 | 'message' => $message ?? __('There was an error executing this action'),
22 | 'type' => 'danger'
23 | ]);
24 |
25 | return $this;
26 | });
27 |
28 | TestableLivewire::macro('executeAction', function ($actionClass, $model = null) {
29 | $action = new $actionClass;
30 | $id = $model ? (is_numeric($model) ? $model : $model->getKey()) : null;
31 | $this->call('executeAction', $action->getId(), $id);
32 |
33 | return $this;
34 | });
35 |
36 | TestableLivewire::macro('confirmAction', function ($actionClass, $model = null) {
37 | $action = new $actionClass;
38 | $id = $model ? is_numeric($model) ? $model : $model->getKey() : null;
39 | $this->call('confirmAndExecuteAction', $action->getId(), $id);
40 |
41 | return $this;
42 | });
43 |
44 | TestableLivewire::macro('executeBulkAction', function ($actionClass) {
45 | $action = new $actionClass;
46 | $this->call('executeBulkAction', $action->getid());
47 |
48 | return $this;
49 | });
50 |
51 | TestableLivewire::macro('selectAll', function () {
52 | $this->set('allSelected', true);
53 |
54 | return $this;
55 | });
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | loadMigrationsFrom(__DIR__ . '/Database/migrations');
23 | $this->withFactories(__DIR__.'/Database/factories');
24 |
25 | /**
26 | * Macro to check if a user is present in the HTML code
27 | * @example $livewire->assertSeeUsers($users)
28 | */
29 | TestableLivewire::macro('assertSeeUsers', function ($users, $assert = 'assertSee') {
30 | foreach ($users as $user) {
31 | $this->$assert(htmlspecialchars_decode($user->name))
32 | ->$assert($user->email);
33 | }
34 |
35 | return $this;
36 | });
37 |
38 | /**
39 | * Macro to check if a user is not present in the HTML code
40 | * @example $livewire->assertDontSeeUsers($users)
41 | */
42 | TestableLivewire::macro('assertDontSeeUsers', function ($users) {
43 | return TestableLivewire::assertSeeUsers($users, 'assertDontSee');
44 | });
45 | }
46 |
47 | protected function getPackageProviders($app)
48 | {
49 | return [
50 | // \Spatie\LaravelRay\RayServiceProvider::class,
51 | TranslationServiceProvider::class,
52 | LivewireServiceProvider::class,
53 | LaravelViewsServiceProvider::class,
54 | ];
55 | }
56 |
57 | protected function getPackageAliases($app)
58 | {
59 | return [
60 | 'UI' => UI::class
61 | ];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Feature/RelationalSearchTest.php:
--------------------------------------------------------------------------------
1 | users = factory(UserTest::class, 10)
26 | ->create()
27 | ->each(function (UserTest $user) {
28 | $user->reviews()->saveMany(factory(ReviewTest::class, 3)->make());
29 | });
30 |
31 | $this->reviews = ReviewTest::all();
32 | }
33 |
34 |
35 | public function testSeeAllDataFoundBySearchInput()
36 | {
37 | $user = $this->reviews->last()->user;
38 |
39 | $this->livewire = Livewire::test(MockReviewTableViewWithSearch::class);
40 |
41 | $this->livewire->set('search', $user->email);
42 |
43 | $userReviews = $user->reviews;
44 |
45 | $otherUserReviews = ReviewTest::where('user_id', '!=', $user->id)->get();
46 |
47 | // Filtered user
48 | $this->assertSeeReviews($this->livewire, $userReviews)
49 | // Rest of the users
50 | ->assertDontSeeReviews($this->livewire, $otherUserReviews);
51 | }
52 |
53 |
54 | private function assertSeeReviews($livewire, $reviews, $assert = 'assertSee')
55 | {
56 | foreach ($reviews as $review) {
57 | $livewire
58 | ->$assert($review->user->email);
59 | }
60 |
61 | return $this;
62 | }
63 |
64 | private function assertDontSeeReviews($livewire, $reviews)
65 | {
66 | return $this->assertSeeReviews($livewire, $reviews, 'assertDontsee');
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - dev
8 | - release/**
9 | - feature/**
10 | - bug/**
11 | - tests/**
12 | pull_request:
13 | branches:
14 | - master
15 | - dev
16 | - /^release-.*$/
17 | - /^release/.*$/
18 | - /^feature/.*$/
19 | - /^tests/.*$/
20 |
21 | jobs:
22 | test:
23 | runs-on: ${{ matrix.os }}
24 | strategy:
25 | fail-fast: true
26 | matrix:
27 | os: [ubuntu-latest]
28 | php: [7.3, 7.4, 8.0, 8.1]
29 | laravel: [7.*, 8.*, 9.*]
30 | stability: [prefer-stable]
31 | include:
32 | - laravel: 9.*
33 | testbench: ^7.0
34 | - laravel: 8.*
35 | testbench: ^6.6
36 | - laravel: 7.*
37 | testbench: ^5.0
38 | exclude:
39 | - laravel: 7.*
40 | php: 8.1
41 | - laravel: 9.*
42 | php: 7.3
43 | - laravel: 9.*
44 | php: 7.4
45 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - Testbench ${{ matrix.testbench }}
46 |
47 | steps:
48 | - name: Checkout code
49 | uses: actions/checkout@v2
50 |
51 | - name: Setup PHP
52 | uses: shivammathur/setup-php@v2
53 | with:
54 | php-version: ${{ matrix.php }}
55 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
56 | coverage: none
57 |
58 | - name: Setup problem matchers
59 | run: |
60 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
61 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
62 |
63 | - name: Install dependencies
64 | run: |
65 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
66 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
67 |
68 | - name: Execute tests
69 | run: vendor/bin/phpunit
70 |
--------------------------------------------------------------------------------
/tests/Feature/DetailViewTest.php:
--------------------------------------------------------------------------------
1 | create();
20 |
21 | Livewire::test(MockDetailView::class, ['model' => $user->id])
22 | ->assertSee($user->name);
23 | }
24 |
25 | public function testSeeAllDataWithAFieldListAsDefault()
26 | {
27 | $user = factory(UserTest::class)->create();
28 |
29 | Livewire::test(MockDetailView::class, ['model' => $user])
30 | ->assertSee($user->name)
31 | ->assertSee($user->email)
32 | ->assertSeeHtml($user->avatar)
33 | ->assertSee('Name')
34 | ->assertSee('Email')
35 | ->assertSee('Avatar');
36 | }
37 |
38 | public function testSeeAllDatawithACustomComponent()
39 | {
40 | $user = factory(UserTest::class)->create();
41 |
42 | Livewire::test(MockDetailViewWithComponents::class, ['model' => $user])
43 | ->assertSeeHtml($user->name);
44 | }
45 |
46 | public function testSeeAllDataUsingMultipleComponents()
47 | {
48 | $user = factory(UserTest::class)->create();
49 |
50 | Livewire::test(MockDetailViewWithMultipleComponents::class, ['model' => $user])
51 | ->assertSeeHtml($user->email)
52 | ->assertSeeHtml($user->name);
53 | }
54 |
55 | public function testSeeHeading()
56 | {
57 | $user = factory(UserTest::class)->create();
58 |
59 | Livewire::test(MockDetailView::class, ['model' => $user->id])
60 | ->assertSee('Detail view title ' . $user->name)
61 | ->assertSee('Detail view subtitle ' . $user->email);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/config/laravel-views.php:
--------------------------------------------------------------------------------
1 | [
10 | "light" => "hover:bg-gray-100 hover:text-gray-900 focus:bg-gray-100 focus:text-gray-900 active:bg-gray-100 active:text-gray-900",
11 | "primary" => "text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500",
12 | "danger" => "text-white bg-red-600 hover:bg-red-700 focus:ring-red-500",
13 | "white" => "hover:bg-gray-100 focus:ring-gray-200 border border-gray-400",
14 | "primary-light" => "text-blue-700 border border-blue-600 hover:bg-blue-600 hover:text-white focus:bg-blue-600 focus:text-white active:bg-blue-600 active:text-white",
15 | ],
16 |
17 | "alerts" => [
18 | "success" => [
19 | "base" => "bg-green-100 border-green-300 text-green-700",
20 | "icon" => "bg-green-200",
21 | "title" => "text-green-900",
22 | ],
23 | "danger" => [
24 | "base" => "bg-red-100 border-red-300 text-red-700",
25 | "icon" => "bg-red-200",
26 | "title" => "text-red-900",
27 | ],
28 | "warning" => [
29 | "base" => "bg-green-100 border-green-300 text-green-700",
30 | "icon" => "bg-green-200",
31 | "title" => "text-green-900",
32 | ]
33 | ],
34 |
35 | "badges" => [
36 | 'success' => 'bg-green-200 text-green-800',
37 | 'danger' => 'bg-red-200 text-red-800',
38 | 'warning' => 'bg-yellow-200 text-yellow-800',
39 | 'info' => 'bg-blue-200 text-blue-800',
40 | 'default' => 'bg-gray-200 text-gray-800'
41 | ],
42 |
43 | "images" => [
44 | 'avatar' => 'h-8 w-8 object-cover rounded-full shadow-inner'
45 | ],
46 |
47 | "icons" => [
48 | 'success' => 'text-green-500',
49 | 'danger' => 'text-red-500',
50 | 'warning' => 'text-yellow-500',
51 | 'info' => 'text-blue-500',
52 | ],
53 |
54 | 'links' => [
55 | 'default' => 'hover:text-blue-600 hover:underline'
56 | ],
57 |
58 | 'gridView' => [
59 | 'selected' => 'border-2 border-blue-500'
60 | ]
61 | ];
62 |
--------------------------------------------------------------------------------
/tests/Database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | define(UserTest::class, function (Faker $faker) {
21 | return [
22 | 'name' => str_replace("'", "", $faker->name),
23 | 'email' => $faker->unique()->safeEmail,
24 | 'is_admin' => $faker->randomElement([true, false]),
25 | 'is_writer' => $faker->randomElement([true, false]),
26 | 'active' => $faker->randomElement([true, false]),
27 | 'avatar' => $faker->randomElement([
28 | 'https://gravatar.com/avatar/70d54a7e7b8a5d91e0ae87b99ccb3d61?s=400&d=robohash&r=x',
29 | 'https://gravatar.com/avatar/5d913d0e61f6ad19b61b27d1bc350363?s=400&d=robohash&r=x',
30 | // 'https://robohash.org/b842dace7186de3c8fc52ac691b585a7?set=set4&bgset=&size=400x400',
31 | 'https://gravatar.com/avatar/bc8f81437562a9def3992885573bdec5?s=400&d=robohash&r=x',
32 | 'https://gravatar.com/avatar/50746d27c0ecedf70fa5c3934cfe4ead?s=400&d=robohash&r=x',
33 | 'https://gravatar.com/avatar/a7ecb9ab617ef620ee3b8ade1d695f90?s=400&d=robohash&r=pg',
34 | 'https://gravatar.com/avatar/6b661cba4ce7d0038f6c2e5881005e49?s=400&d=robohash&r=pg',
35 | 'https://gravatar.com/avatar/94f1d9a644c6d5b36c9aec515fdaa5d1?s=400&d=robohash&r=pg',
36 | 'https://gravatar.com/avatar/619bb911835caa313806aba5ad2e13cc?s=400&d=robohash&r=pg',
37 | 'https://gravatar.com/avatar/d4c58fa6c7485d5f4b74d74a37779757?s=400&d=robohash&r=pg',
38 | 'https://gravatar.com/avatar/02313d0fa22f1f507630b6a4c32e251f?s=400&d=robohash&r=pg'
39 | ])
40 | ];
41 | });
42 |
--------------------------------------------------------------------------------
/src/Data/TableViewSearchData.php:
--------------------------------------------------------------------------------
1 | where(function ($query) use ($value, $regularFields, $relationalFields) {
30 | $this->applyRegularFields($regularFields, $query, $value);
31 | $this->applyRelationalFields($relationalFields, $query, $value);
32 | });
33 | }
34 |
35 | return $query;
36 | }
37 |
38 | /**
39 | * @param $relationalFields
40 | * @param $query
41 | * @param String $value
42 | */
43 | private static function applyRelationalFields($relationalFields, $query, string $value): void
44 | {
45 | foreach ($relationalFields as $relationalValue) {
46 | [$relationship, $field] = explode('.', $relationalValue);
47 |
48 | $query->orWhereHas($relationship, static function ($query) use ($value, $field) {
49 | $query->where($field, 'like', "%{$value}%");
50 | });
51 | }
52 | }
53 |
54 | /**
55 | * @param array $regularFields
56 | * @param $query
57 | * @param String $value
58 | */
59 | private function applyRegularFields(array $regularFields, $query, string $value): void
60 | {
61 | foreach ($regularFields as $field) {
62 | $query->orWhere($field, 'like', "%{$value}%");
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Unit/UITest.php:
--------------------------------------------------------------------------------
1 |
15 | active
16 | ';
17 |
18 | $this->assertHtmlEquals($badge, $expected);
19 | }
20 |
21 | public function testBagdeSuccesstHelper()
22 | {
23 | $badge = UI::badge('active', 'success');
24 | $expected = '
25 | active
26 | ';
27 |
28 | $this->assertHtmlEquals($badge, $expected);
29 | }
30 |
31 | public function testAvatarHelper()
32 | {
33 | $avatar = UI::avatar('my-avatar-url');
34 | $expected = ' ';
35 |
36 | $this->assertHtmlEquals($avatar, $expected);
37 | }
38 |
39 | public function testLinkHelper()
40 | {
41 | $link = UI::link('title', '/');
42 | $expected = '
43 | title
44 | ';
45 |
46 | $this->assertHtmlEquals($link, $expected);
47 | }
48 |
49 | public function testDefaultIconHelper()
50 | {
51 | $icon = UI::icon('activity');
52 | $expected = ' ';
53 |
54 | $this->assertHtmlEquals($icon, $expected);
55 | }
56 |
57 | public function testIconHelperWithVariant()
58 | {
59 | $icon = UI::icon('activity', 'success');
60 | $expected = ' ';
61 |
62 | $this->assertHtmlEquals($icon, $expected);
63 | }
64 |
65 | private function assertHtmlEquals($html, $expected)
66 | {
67 | $this->assertEquals(
68 | preg_replace('/^\s+|\n|\r|\s+$/m', '', $html),
69 | preg_replace('/^\s+|\n|\r|\s+$/m', '', $expected)
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Views/DetailView.php:
--------------------------------------------------------------------------------
1 | setModel();
27 | $this->setHeading();
28 | }
29 |
30 | protected function getRenderData()
31 | {
32 | $detailData = app()->call([$this, 'detail'], [
33 | 'model' => $this->model,
34 | ]);
35 |
36 | if (is_array($detailData)) {
37 | // If there is an array of data insted of a component
38 | // then creates a new attributes component
39 | if (Arr::isAssoc($detailData)) {
40 | if ($this->detailComponent) {
41 | $components = [UI::component($this->detailComponent, $detailData)];
42 | } else {
43 | $components = [UI::attributes($detailData, ['stripe' => $this->stripe])];
44 | }
45 | } else {
46 | $components = $detailData;
47 | }
48 | // If there is only one component
49 | } else {
50 | $components = [$detailData];
51 | }
52 |
53 | return [
54 | 'components' => $components,
55 | ];
56 | }
57 |
58 | private function setModel()
59 | {
60 | if (is_numeric($this->model)) {
61 | if (!$this->modelClass) {
62 | throw new Exception('A $modelClass should be declared when the initial model value is an ID');
63 | }
64 |
65 | $this->model = $this->modelClass::find($this->model);
66 | }
67 | }
68 |
69 | private function setHeading()
70 | {
71 | if (method_exists($this, 'heading')) {
72 | $heading = app()->call([$this, 'heading'], ['model' => $this->model]);
73 | [$this->title, $this->subtitle] = $heading;
74 | }
75 |
76 | if (!$this->title) {
77 | $this->title = $this->getClassName();
78 | }
79 | }
80 |
81 | public function getActions()
82 | {
83 | if (method_exists($this, 'actions')) {
84 | return $this->actions();
85 | }
86 |
87 | return [];
88 | }
89 |
90 | public function getModelWhoFiredAction()
91 | {
92 | return $this->model;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Unit/VariantsTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(UIVariants::class, variants());
14 | }
15 |
16 | public function testButtonVariants()
17 | {
18 | $this->assertTrue(
19 | Variants::button('primary')->class() ===
20 | 'text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500'
21 | );
22 | $this->assertEquals(
23 | Variants::button('primary-light')->class(),
24 | 'text-blue-700 border border-blue-600 hover:bg-blue-600 hover:text-white focus:bg-blue-600 focus:text-white active:bg-blue-600 active:text-white'
25 | );
26 | }
27 |
28 | public function testAlertVariants()
29 | {
30 | $this->assertEquals(
31 | Variants::alert('success')->class('base'),
32 | 'bg-green-100 border-green-300 text-green-700'
33 | );
34 | $this->assertEquals(
35 | Variants::alert('danger')->class('icon'),
36 | 'bg-red-200'
37 | );
38 | $this->assertEquals(
39 | Variants::alert('warning')->class('title'),
40 | 'text-green-900'
41 | );
42 | $this->assertEquals(
43 | Variants::alert('success')->title(),
44 | 'Success'
45 | );
46 | $this->assertEquals(
47 | Variants::alert('danger')->icon(),
48 | 'x'
49 | );
50 | }
51 |
52 | public function testBadgeVariants()
53 | {
54 | $this->assertEquals(
55 | Variants::badge('success')->class(),
56 | 'bg-green-200 text-green-800'
57 | );
58 | }
59 |
60 | public function testImgVariants()
61 | {
62 | $this->assertEquals(
63 | Variants::img('avatar')->class(),
64 | 'h-8 w-8 object-cover rounded-full shadow-inner'
65 | );
66 | $this->assertEquals(
67 | Variants::img()->class(),
68 | ''
69 | );
70 | }
71 |
72 | public function testVarianstHelperUsingVariantPaths()
73 | {
74 | $this->assertEquals(
75 | variants('images.avatar'),
76 | 'h-8 w-8 object-cover rounded-full shadow-inner'
77 | );
78 |
79 | $this->assertEquals(
80 | variants('alerts.warning.title'),
81 | 'text-green-900'
82 | );
83 |
84 | $this->assertInstanceOf(
85 | UIVariants::class,
86 | variants()
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Feature/ExecuteActionsTest.php:
--------------------------------------------------------------------------------
1 | create();
20 |
21 | Livewire::test(MockTableViewWithActions::class)
22 | ->call('executeAction', 'test-success-action', 1, true)
23 | ->assertShowSuccessAlert();
24 | }
25 |
26 | public function testSeeErrorAlert()
27 | {
28 | factory(UserTest::class)->create();
29 |
30 | Livewire::test(MockTableViewWithActions::class)
31 | ->call('executeAction', 'test-error-action', 1, true)
32 | ->assertShowErrorAlert();
33 | }
34 |
35 | // TODO: Test custom error message
36 |
37 | public function testSeeMultipleRedirectActions()
38 | {
39 | factory(UserTest::class)->create();
40 | $icons = ['eye', 'pencil'];
41 |
42 | $livewire = Livewire::test(MockTableViewWithActions::class);
43 |
44 | foreach ($icons as $icon) {
45 | $livewire->assertSeeHtml('data-feather="' . $icon . '"');
46 | }
47 | }
48 |
49 | public function testSeeConfirmationMessage()
50 | {
51 | $user = factory(UserTest::class)->create();
52 | $message = 'Do you really want to perform this action?';
53 |
54 | Livewire::test(MockTableViewWithActions::class)
55 | ->executeAction(TestConfirmedAction::class, $user)
56 | ->assertEmitted('openConfirmationModal', [
57 | 'message' => $message,
58 | 'id' => 'test-confirmed-action',
59 | 'modelId' => $user->id
60 | ]);
61 | }
62 |
63 | // TODO: test custom confirmation message
64 |
65 | public function testCallActionAfterConfirmationMessage()
66 | {
67 | $user = factory(UserTest::class)->create();
68 |
69 | Livewire::test(MockTableViewWithActions::class)
70 | ->executeAction(TestConfirmedAction::class, $user)
71 | ->assertEmitted('openConfirmationModal', [
72 | 'message' => 'Do you really want to perform this action?',
73 | 'id' => 'test-confirmed-action',
74 | 'modelId' => $user->id
75 | ])
76 | ->confirmAction(TestConfirmedAction::class, $user)
77 | ->assertShowSuccessAlert();
78 | }
79 |
80 | public function testEmitedEventFromAction()
81 | {
82 | Livewire::test(MockTableViewWithActions::class)
83 | ->executeAction(TestSuccessAction::class, 1)
84 | ->assertEmitted('test-event');
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/resources/views/components/table.blade.php:
--------------------------------------------------------------------------------
1 | {{-- components.table
2 |
3 | Renders a data table
4 | You can customize all the html and css classes but YOU MUST KEEP THE BLADE AND LIVEWIRE DIRECTIVES,
5 |
6 | props:
7 | - headers
8 | - itmes
9 | - actionsByRow --}}
10 |
11 |
12 |
13 |
14 |
15 | @if ($this->hasBulkActions)
16 |
17 |
18 |
19 |
20 |
21 | @endif
22 | {{-- Renders all the headers --}}
23 | @foreach ($headers as $header)
24 | width) ? 'width=' . $header->width . '' : '' }}>
25 | @if (is_string($header))
26 | {{ $header }}
27 | @else
28 | @if ($header->isSortable())
29 |
38 | @else
39 | {{ $header->title }}
40 | @endif
41 | @endif
42 |
43 | @endforeach
44 |
45 | {{-- This is a empty cell just in case there are action rows --}}
46 | @if (count($actionsByRow) > 0)
47 |
48 | @endif
49 |
50 |
51 |
52 |
53 | @foreach ($items as $item)
54 |
55 | @if ($this->hasBulkActions)
56 |
57 |
58 |
59 |
60 |
61 | @endif
62 | {{-- Renders all the content cells --}}
63 | @foreach ($view->row($item) as $column)
64 |
65 | {!! $column !!}
66 |
67 | @endforeach
68 |
69 | {{-- Renders all the actions row --}}
70 | @if (count($actionsByRow) > 0)
71 |
72 |
73 |
74 |
75 |
76 | @endif
77 |
78 | @endforeach
79 |
80 |
81 |
--------------------------------------------------------------------------------
/tests/Feature/BulkActionsTest.php:
--------------------------------------------------------------------------------
1 | create();
21 | $input = ' ';
22 |
23 | Livewire::test(MockTableViewWithActions::class)
24 | ->assertDontSeeHtml($input);
25 |
26 | Livewire::test(MockTableViewWithBulkActions::class)
27 | ->assertSeeHtml($input);
28 | }
29 |
30 | public function testSeeActionsButtonWhenThereAreSelectedRows()
31 | {
32 | $user = factory(UserTest::class)->create();
33 |
34 | Livewire::test(MockTableViewWithBulkActions::class)
35 | ->assertDontSee('Actions')
36 | ->set('selected', [$user->id])
37 | ->assertSee('Actions');
38 | }
39 |
40 | public function testSelectUnselectAll()
41 | {
42 | $users = factory(UserTest::class, 7)->create();
43 |
44 | Livewire::test(MockTableViewWithBulkActions::class)
45 | ->selectAll()
46 | ->assertSet('selected', $users->pluck('id')->toArray())
47 | ->set('allSelected', false)
48 | ->assertSet('selected', []);
49 | }
50 |
51 | public function testExecuteActionToSelectedRows()
52 | {
53 | $users = factory(UserTest::class, 7)->create();
54 |
55 | Livewire::test(MockTableViewWithBulkActions::class)
56 | ->selectAll()
57 | ->executeBulkAction(TestDeleteUsersAction::class)
58 | ->assertShowSuccessAlert()
59 | ->assertDontSeeUsers($users);
60 |
61 | foreach ($users as $user) {
62 | $this->assertDatabaseMissing('users', $user->toArray());
63 | }
64 | }
65 |
66 | public function testExecuteBulkActionsWithConfirmationMessage()
67 | {
68 | $users = factory(UserTest::class, 7)->create();
69 |
70 | Livewire::test(MockTableViewWithBulkActions::class)
71 | ->selectAll()
72 | ->executeBulkAction(TestConfirmedDeleteUsersAction::class)
73 | ->assertEmitted('openConfirmationModal', [
74 | 'message' => 'Do you really want to perform this action?',
75 | 'id' => 'test-confirmed-delete-users-action',
76 | ])
77 | ->confirmAction(TestConfirmedDeleteUsersAction::class)
78 | ->assertShowSuccessAlert()
79 | ->assertDontSeeUsers($users);
80 |
81 | foreach ($users as $user) {
82 | $this->assertDatabaseMissing('users', $user->toArray());
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/tests/Unit/LaravelViewsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(
15 | $css,
16 | implode("\n", [
17 | \Livewire\Livewire::styles(),
18 | ' ',
19 | ' ',
20 | ])
21 | );
22 | }
23 |
24 | public function testRenderJsLinks()
25 | {
26 | $js = LaravelViews::js();
27 | $this->assertEquals(
28 | $js,
29 | \Livewire\Livewire::scripts().PHP_EOL.
30 | ''.PHP_EOL.
31 | ''.PHP_EOL.
32 | ''.PHP_EOL.
33 | ''
34 | );
35 | }
36 |
37 | public function testRenderCustomizedCssLinks()
38 | {
39 | $css = LaravelViews::css('laravel-views,livewire');
40 |
41 | $this->assertEquals(
42 | $css,
43 | \Livewire\Livewire::styles()."\n".
44 | ' '
45 | );
46 | }
47 |
48 | public function testRenderCustomizedJsLinks()
49 | {
50 | $js = LaravelViews::js('laravel-views');
51 |
52 | $this->assertEquals(
53 | $js,
54 | ''.PHP_EOL.
55 | ''.PHP_EOL.
56 | ''
57 | );
58 |
59 | $js = LaravelViews::js('laravel-views,alpine');
60 |
61 | $this->assertEquals(
62 | $js,
63 | ''.PHP_EOL.
64 | ''.PHP_EOL.
65 | ''.PHP_EOL.
66 | ''
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/public/laravel-views.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";/*!
2 | * Pikaday
3 | * Copyright © 2014 David Bushell | BSD & MIT license | https://dbushell.com/
4 | */.pika-single{z-index:9999;display:block;position:relative;color:#333;background:#fff;border:1px solid;border-color:#ccc #ccc #bbb;font-family:Helvetica Neue,Helvetica,Arial,sans-serif}.pika-single.is-hidden{display:none}.pika-single.is-bound{position:absolute;box-shadow:0 5px 15px -5px rgba(0,0,0,.5)}.pika-single:after,.pika-single:before{content:" ";display:table}.pika-single:after{clear:both}.pika-lendar{float:left;width:240px;margin:8px}.pika-title{position:relative;text-align:center}.pika-title select{cursor:pointer;position:absolute;z-index:9998;margin:0;left:0;top:5px;opacity:0}.pika-label{display:inline-block;position:relative;z-index:9999;overflow:hidden;margin:0;padding:5px 3px;font-size:14px;line-height:20px;font-weight:700;color:#333;background-color:#fff}.pika-next,.pika-prev{display:block;cursor:pointer;position:relative;outline:none;border:0;padding:0;width:20px;height:30px;text-indent:20px;white-space:nowrap;overflow:hidden;background-color:transparent;background-position:50%;background-repeat:no-repeat;background-size:75% 75%;opacity:.5}.pika-next:hover,.pika-prev:hover{opacity:1}.pika-next.is-disabled,.pika-prev.is-disabled{cursor:default;opacity:.2}.is-rtl .pika-next,.pika-prev{float:left;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==")}.is-rtl .pika-prev,.pika-next{float:right;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=")}.pika-select{display:inline-block}.pika-table{width:100%;border-collapse:collapse;border-spacing:0;border:0}.pika-table td,.pika-table th{width:14.2857142857%;padding:0}.pika-table th{color:#999;font-size:12px;line-height:25px;font-weight:700;text-align:center}.pika-table abbr{border-bottom:none;cursor:help}.pika-button{cursor:pointer;display:block;box-sizing:border-box;outline:none;border:0;margin:0;width:100%;padding:5px;color:#666;font-size:12px;line-height:15px;text-align:right;background:#f5f5f5;height:auto}.has-event .pika-button{color:#fff;background:#3af}.is-today .pika-button{color:#3af;font-weight:700}.is-selected .pika-button{color:#fff;font-weight:700;background:#3af;box-shadow:inset 0 1px 3px #178fe5;border-radius:3px}.is-disabled .pika-button,.is-outside-current-month .pika-button{color:#999;opacity:.3}.is-disabled .pika-button{pointer-events:none;cursor:default}.pika-button:hover{color:#fff;background:#ff8000;box-shadow:none;border-radius:3px}.pika-button .is-selection-disabled{pointer-events:none;cursor:default}.pika-week{font-size:11px;color:#999}.is-inrange .pika-button{color:#666;background:#d5e9f7}.is-startrange .pika-button{color:#fff;background:#6cb31d;box-shadow:none;border-radius:3px}.is-endrange .pika-button{color:#fff;background:#3af;box-shadow:none;border-radius:3px}.line-clamp,.line-clamp-2,.line-clamp-3,.line-clamp-4,.line-clamp-5{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.line-clamp-2{-webkit-line-clamp:2}.line-clamp-3{-webkit-line-clamp:3}.line-clamp-4{-webkit-line-clamp:4}.line-clamp-5{-webkit-line-clamp:5}[x-cloak]{display:none!important}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Laravel views
4 |
5 | Laravel package to create beautiful common views like data tables using the [TALL stack](https://tallstack.dev/).
6 |
7 | # Documentation
8 | Read the [full documentation](https://laravelviews.com)
9 |
10 | # Live examples
11 | See some [live examples](https://laravelviews.com/examples/table-view) for the different views.
12 |
13 | ## Contributing
14 |
15 | Check the [contribution guide](CONTRIBUTING.md)
16 |
17 | ## Roadmap
18 |
19 | Laravel Views is still under heavy development so I will be adding more awesome features and views.
20 |
21 | Here's the plan for what's coming:
22 |
23 | - **New form view**
24 | - **New layout view**
25 | - Add a download action
26 | - Add translations
27 | - Add links as a UI helpers
28 |
29 | ## Upgrade guide
30 | ### From 2.4.0 to 2.4.1
31 | **Publish blade componentes**
32 |
33 | Some of the internal components have changed, if you have published these components before to customize them, you will not have them up to date, unfourtunately you need to publish them again with `php artisan vendor:publish --tag=views --provider='LaravelViews\LaravelViewsServiceProvider'` and customize them as you need.
34 |
35 | ### From 2.2 to 2.3
36 | **Cached views**
37 |
38 |
39 | The blade directives have changed, you need to clear the cached views with `php artisan view:clear`
40 |
41 | **Public assets**
42 |
43 | The main assets (JS and CSS files) have changed, you need to publish the public assets again with `php artisan vendor:publish --tag=public --provider='LaravelViews\LaravelViewsServiceProvider' --force`
44 |
45 | **Publish blade componentes**
46 |
47 | Some of the internal components have changed, if you have published these components before to customize them, you will not have them up to date, unfourtunately you need to publish them again with `php artisan vendor:publish --tag=views --provider='LaravelViews\LaravelViewsServiceProvider'` and customize them as you need.
48 |
49 | **Method `renderIf()` in actions**
50 |
51 | Update the renderIf() function in your action classes adding a new `$view` parameter as follows:
52 | ```php
53 | shouldVerifyConfirmation = true;
19 | $this->executeActionHandler($actionId, $actionableItemId);
20 | }
21 |
22 | public function confirmAndExecuteAction($actionId, $actionableItemId)
23 | {
24 | $this->shouldVerifyConfirmation = false;
25 | $this->executeActionHandler($actionId, $actionableItemId);
26 | }
27 |
28 | public function executeBulkAction($action)
29 | {
30 | $this->shouldVerifyConfirmation = true;
31 | $this->executeActionHandler($action);
32 | }
33 |
34 | public function confirmAndExecuteBulkAction($action)
35 | {
36 | $this->shouldVerifyConfirmation = false;
37 | $this->executeActionHandler($action);
38 | }
39 |
40 | private function executeActionHandler($actionId, $actionableItemId = null)
41 | {
42 | /** @var Action */
43 | $action = $this->findAction($actionId);
44 |
45 | if ($action) {
46 | // If the action needs confirmation
47 | if ($this->shouldVerifyConfirmation && $action->shouldBeConfirmed()) {
48 | $this->confirmAction($action, $actionableItemId);
49 | } else {
50 | // If $actionableItemId is null then it is a bulk action
51 | // and it uses the current selection
52 | $actionableItems = $actionableItemId ? $this->getModelWhoFiredAction($actionableItemId) : $this->selected;
53 | $action->view = $this;
54 | $action->handle($actionableItems, $this);
55 | }
56 | } else {
57 | throw new Exception("Unable to find the {$actionId} action");
58 | }
59 | }
60 |
61 | /**
62 | * Opens confirmation modal for a specific action
63 | * @param Action $action
64 | */
65 | private function confirmAction($action, $modelId = null)
66 | {
67 | $actionData = [
68 | 'message' => $action->getConfirmationMessage($modelId ? $this->getModelWhoFiredAction($modelId) : null),
69 | 'id' => $action->getId()
70 | ];
71 |
72 | if ($modelId) {
73 | $actionData['modelId'] = $modelId;
74 | }
75 | $this->emitSelf('openConfirmationModal', $actionData);
76 | }
77 |
78 | /**
79 | * Finds an action by its id
80 | */
81 | private function findAction(string $actionId)
82 | {
83 | $actions = collect($this->actions)->merge($this->bulkActions);
84 |
85 | return $actions->first(
86 | function ($actionToFind) use ($actionId) {
87 | return $actionToFind->id === $actionId;
88 | }
89 | );
90 | }
91 |
92 | /**
93 | * Computed properties
94 | */
95 | public function getActionsProperty()
96 | {
97 | // This `getActions()` function needs to be defined by the
98 | // view that is using actions
99 | return $this->getActions();
100 | }
101 |
102 | public function getBulkActionsProperty()
103 | {
104 | if (method_exists($this, 'bulkActions')) {
105 | return $this->bulkActions();
106 | }
107 |
108 | return [];
109 | }
110 |
111 | public function getHasBulkActionsProperty()
112 | {
113 | return method_exists($this, 'bulkActions') && count($this->bulkActions) > 0;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/UI/Variants.php:
--------------------------------------------------------------------------------
1 | path = $path;
18 | }
19 |
20 | /**
21 | * Uses the button component
22 | *
23 | * @param String $variant component variant
24 | */
25 | public function button($variant = '')
26 | {
27 | $this->component = 'buttons';
28 | $this->variant = $variant;
29 |
30 | return $this;
31 | }
32 |
33 | /**
34 | * Uses the alert component
35 | *
36 | * @param String $variant component variant
37 | */
38 | public function alert($variant = '')
39 | {
40 | $this->component = 'alerts';
41 | $this->variant = $variant;
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * Uses the paginator component
48 | *
49 | * @param String $variant component variant
50 | */
51 | public function paginator($variant = '')
52 | {
53 | $this->component = 'paginator';
54 | $this->variant = $variant;
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * Uses the badge component
61 | *
62 | * @param String $variant component variant
63 | */
64 | public function badge($variant = '')
65 | {
66 | $this->component = 'badges';
67 | $this->variant = $variant;
68 |
69 | return $this;
70 | }
71 |
72 | /**
73 | * Uses the img component
74 | *
75 | * @param String $variant component variant
76 | */
77 | public function img($variant = '')
78 | {
79 | $this->component = 'images';
80 | $this->variant = $variant;
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * Uses the icon component
87 | *
88 | * @param String $variant component variant
89 | */
90 | public function featherIcon($variant = '')
91 | {
92 | $this->component = 'icons';
93 | $this->variant = $variant;
94 |
95 | return $this;
96 | }
97 |
98 | /**
99 | * Get the class string for the component and variant selected
100 | *
101 | * @param String $element Set the internal element of the component if there are any
102 | */
103 | public function class($element = '')
104 | {
105 | // Returns the config path directly it it was set in the constructor
106 | if ($this->path) {
107 | return config("laravel-views.{$this->path}");
108 | } else {
109 | $config = "laravel-views.{$this->component}.{$this->variant}";
110 | if ($element) {
111 | return config("{$config}.{$element}");
112 | }
113 |
114 | return config($config);
115 | }
116 | }
117 |
118 | /**
119 | * Get the title of the variant and componente selected
120 | */
121 | public function title()
122 | {
123 | $titles = [
124 | 'alerts' => [
125 | 'success' => 'Success',
126 | 'danger' => 'Error',
127 | 'warning' => 'Warning',
128 | ]
129 | ];
130 |
131 | return $titles[$this->component][$this->variant];
132 | }
133 |
134 | /**
135 | * Get the icon of the variant and componente selected
136 | */
137 | public function icon()
138 | {
139 | $icons = [
140 | 'alerts' => [
141 | 'success' => 'check',
142 | 'danger' => 'x',
143 | 'warning' => 'alert-circle',
144 | ]
145 | ];
146 |
147 | return $icons[$this->component][$this->variant];
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/LaravelViews.php:
--------------------------------------------------------------------------------
1 | name
17 | return [
18 | 'buttons.icon' => 'icon-button',
19 | 'buttons.select' => 'select-button',
20 | 'buttons.button' => 'button',
21 | 'dropdown.drop-down' => 'drop-down',
22 | 'dropdown.header' => 'drop-down.header',
23 | 'actions.responsive' => 'actions',
24 | 'actions.drop-down' => 'actions.drop-down',
25 | 'actions.icon-and-title' => 'actions.icon-and-title',
26 | 'actions.icon' => 'actions.icon',
27 | 'attributes-list' => 'attributes-list',
28 | 'alert' => 'alert',
29 | 'alerts-handler' => 'alerts-handler',
30 | 'form.input-group' => 'form.input-group',
31 | 'icon' => 'icon',
32 | 'modal' => 'modal',
33 | 'form.checkbox' => 'checkbox',
34 | 'form.input' => 'input',
35 | 'tooltip' => 'tooltip',
36 | 'confirmation-message' => 'confirmation-message',
37 | 'loading' => 'loading'
38 | ];
39 | }
40 |
41 | public function create($view)
42 | {
43 | $this->view = $view;
44 |
45 | return $this;
46 | }
47 |
48 | public function layout($layout, $section = null, $data = [])
49 | {
50 | $this->layout = $layout;
51 | $this->section = $section;
52 | $this->data = $data;
53 |
54 | return $this;
55 | }
56 |
57 | public function section($section)
58 | {
59 | $this->section = $section;
60 |
61 | return $this;
62 | }
63 |
64 | public function data($data)
65 | {
66 | $this->data = $data;
67 |
68 | return $this;
69 | }
70 |
71 | public function css($options = '')
72 | {
73 | $assets = [
74 | 'livewire' => Livewire::styles(),
75 | 'tailwindcss' => ' ',
76 | 'laravel-views' => ' '
77 | ];
78 |
79 | return $this->getCustomizedLinks($assets, $options);
80 | }
81 |
82 | public function js($options = '')
83 | {
84 | $assets = [
85 | 'livewire' => Livewire::scripts(),
86 | 'alpine' => '',
87 | 'laravel-views' => '
88 |
89 | '
90 | ];
91 |
92 | return $this->getCustomizedLinks($assets, $options);
93 | }
94 |
95 | public function render()
96 | {
97 | $bladeFile = ($this->layout && $this->section) ? 'render-in-layout' : 'render';
98 |
99 | return view("laravel-views::core.{$bladeFile}", array_merge(
100 | [
101 | 'view' => $this->view,
102 | 'layout' => $this->layout,
103 | 'section' => $this->section,
104 | ],
105 | $this->data
106 | ))->render();
107 | }
108 |
109 | private function getCustomizedLinks($assets = [], $options = '')
110 | {
111 | if ($options) {
112 | $options = explode(',', $options);
113 | $options[] = 'laravel-views';
114 | $links = [];
115 |
116 | foreach ($assets as $asset => $link) {
117 | if (in_array($asset, $options)) {
118 | $links[] = $link;
119 | }
120 | }
121 | } else {
122 | $links = $assets;
123 | }
124 |
125 | $linksStr = implode(PHP_EOL, $links);
126 | return <<getKey()` insted of hardcoded `$model->id`
42 |
43 | ## [2.2.1] - 2021-03-29
44 | ### Fixed
45 | - Added `detail-view`, `grid-view` and `list-view` to the views to be published.
46 |
47 | ## [2.2] - 2021-03-01
48 | ### Added
49 | - New list view
50 | - New detail view with
51 | - Default component
52 | - Default component customization
53 | - Multiple components
54 | - Actions
55 | - Feature to set the width for each column in the TableView
56 | - Documentation for the new detail view
57 | - New `attributes-list` component
58 | - New `layout` component
59 |
60 | ### Changed
61 | - The `title` action now is included as an attribute in the `a` tag for each action.
62 | - Internal refactors
63 | - Base `DataView` class with all the features to manage the data (filters, search, actions, pagination etc) on the table, grid, and list view.
64 | - Refactored actions feature to be reused as a trait
65 | - Refactored actions test to be reused as a trait
66 |
67 | ### Fixed
68 | - Bug with pagination and filters, the pagination was not being reset after a new search
69 |
70 | ## [2.1] - 2021-01-10
71 | ### Added
72 | - Option to customize which assets will be imported
73 | - The current view instance is passed as a param to the action to be executed
74 |
75 | ### Changed
76 | - The filter button was changed by an icon button
77 | - Updated documentation
78 | - Added how to purge `laravel-views` styles
79 | - Added how to render components using `Livewire`
80 |
81 |
82 | ## [2.0] - 2020-09-13
83 | ### Added
84 | - Added compatibility for Laravel Livewire 2.x
85 |
86 | ## [1.1] - 2020-09-03
87 | ### Added
88 | - New `GridView` class.
89 | - Confirmation message on actions.
90 | - Search input with support for relational fields.
91 | - Sort table view columns
92 | - More UI components for table and grid view.
93 | - Link
94 | - Icons
95 |
96 | ## [1.0.6] - 2020-08-22
97 | - Fixed typos
98 | - Fixed filter stub file name
99 |
100 | ## [1.0.5] - 2020-08-09
101 | ### Fixed
102 | - Fixed default behavior clicking on actions adding a prevent directive
103 |
104 | ## [1.0.4] - 2020-07-26
105 | ### Fixed
106 | - Fixed some typos and syntax errors
107 | - Fixed the query builder wrapping the table view search
108 |
109 | ## [1.0.3] - 2020-07-18
110 | ### Fixed
111 | - Fixed a bug using multiple redirect actions on the same table
112 | - Fixed some typos on the documentation
113 |
114 | ### Added
115 | - Added road map to the documentation
116 | - Added contribution guide to the documentation
117 |
118 | ### Changed
119 | - Moved helpers load to composer
120 |
121 | ## [1.0.2] - 2020-07-13
122 | ### Fixed
123 | - Added support for newer livewire versions
124 | - Fixed some typos on the documentation
125 | - Fixed assets URL
126 |
127 | ## [1.0.1] - 2020-06-27
128 | ### Added
129 | - Added the changelog file
130 | - Added a overflow-y utility to the table view layout
131 | - Added missing `renderIf` documentation to the actions by row section
132 | ### Changed
133 | - Changed inputs border color
134 | ### Fixed
135 | - Fixed some broken links on the documentation
136 | - Bumped websocket-extensions from 0.1.3 to 0.1.4
137 |
138 | ## [1.0.0] - 2020-06-14
139 | ### Added
140 | - Created base LaravelViews class
141 | - Created the TableView component
142 | - Search inpurt feature
143 | - Customizable filters
144 | - Actions by row
145 | - Definied components customization
--------------------------------------------------------------------------------
/src/Views/DataView.php:
--------------------------------------------------------------------------------
1 | ['except' => ''],
18 | 'filters',
19 | 'sortBy',
20 | 'sortOrder'
21 | ];
22 |
23 | /**
24 | * (Override) int Number of items to be showed,
25 | * @var int $paginate
26 | */
27 | protected $paginate = 20;
28 |
29 | /** @var int $total Total of items found */
30 | public $total = 0;
31 |
32 | /** @var String $search Current query string with the search value */
33 | public $search;
34 |
35 | /** @var String $filters Current query string with the filters value */
36 | public $filters = [];
37 |
38 | /** @var Array $filtersViews All filters customized in the child class */
39 | public $filtersViews;
40 |
41 | /** @var Array $searchBy All fields to search */
42 | public $searchBy;
43 |
44 | public $sortBy = null;
45 |
46 | public $sortOrder = 'asc';
47 |
48 | public $selected = [];
49 | public $allSelected = false;
50 |
51 | public function hydrate()
52 | {
53 | $this->filtersViews = $this->filters();
54 | }
55 |
56 | public function mount(QueryStringData $queryStringData)
57 | {
58 | $this->filtersViews = $this->filters();
59 | $this->search = $queryStringData->getSearchValue($this->search);
60 |
61 | $this->filters = $queryStringData->getFilterValues($this->filters);
62 |
63 | $this->applyDefaultFilters();
64 |
65 | $this->sortBy = $queryStringData->getValue('sortBy', $this->sortBy);
66 | $this->sortOrder = $queryStringData->getValue('sortOrder', $this->sortOrder);
67 | }
68 |
69 | /**
70 | * Check if each of the filters has a default value and it's not already set
71 | */
72 | public function applyDefaultFilters()
73 | {
74 | foreach ($this->filters() as $filter) {
75 | if (empty($this->filters[$filter->id]) && $filter->defaultValue) {
76 | $this->filters[$filter->id] = $filter->defaultValue;
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * Collects all data to be passed to the view, this includes the items searched on the database
83 | * through the filters, this data will be passed to livewire render method
84 | */
85 | protected function getRenderData()
86 | {
87 | return [
88 | 'items' => $this->paginatedQuery,
89 | 'actionsByRow' => $this->actionsByRow()
90 | ];
91 | }
92 |
93 |
94 | /**
95 | * Reset pagination
96 | */
97 | public function updatingSearch()
98 | {
99 | $this->resetPage();
100 | }
101 |
102 | public function updatingFilters()
103 | {
104 | $this->resetPage();
105 | }
106 |
107 | protected function filters()
108 | {
109 | return [];
110 | }
111 |
112 | protected function actionsByRow()
113 | {
114 | return [];
115 | }
116 |
117 | public function getActions()
118 | {
119 | return $this->actionsByRow();
120 | }
121 |
122 | /**
123 | * Clones the initial query (to avoid modifying it)
124 | * and get a model by an Id
125 | */
126 | public function getModelWhoFiredAction($id)
127 | {
128 | return (clone $this->initialQuery)->find($id);
129 | }
130 |
131 | public function updatedAllSelected($value)
132 | {
133 | $this->selected = $value ? $this->paginatedQuery->pluck('id')->map(function ($id) {
134 | return (string)$id;
135 | })->toArray() : [];
136 | }
137 |
138 | public function getInitialQueryProperty()
139 | {
140 | if (method_exists($this, 'repository')) {
141 | return $this->repository();
142 | }
143 | return $this->model::query();
144 | }
145 |
146 | /**
147 | * Returns the items from the database regarding to the filters selected by the user
148 | * applies the search query, the filters used and the total of items found
149 | */
150 | public function getQueryProperty(Searchable $searchable, Filterable $filterable, Sortable $sortable)
151 | {
152 | $query = clone $this->initialQuery;
153 | $query = $searchable->searchItems($query, $this->searchBy, $this->search);
154 | $query = $filterable->applyFilters($query, $this->filters(), $this->filters);
155 | $query = $sortable->sortItems($query, $this->sortBy, $this->sortOrder);
156 |
157 | return $query;
158 | }
159 |
160 | public function getPaginatedQueryProperty()
161 | {
162 | return $this->query->paginate($this->paginate);
163 | }
164 |
165 |
166 | /**
167 | * LIVEWIERE ACTIONS ARE HERE
168 | * All these actions are executed from the UI and those aren't for internal use
169 | */
170 |
171 | /**
172 | * Sets the field the table view data will be sort by
173 | * @param string $field Field to sort by
174 | */
175 | public function sort($field)
176 | {
177 | if ($this->sortBy === $field) {
178 | $this->sortOrder = $this->sortOrder === 'asc' ? 'desc' : 'asc';
179 | } else {
180 | $this->sortBy = $field;
181 | $this->sortOrder = 'asc';
182 | }
183 | }
184 |
185 | public function clearFilters()
186 | {
187 | $this->filters = [];
188 | }
189 |
190 | public function clearSearch()
191 | {
192 | $this->search = '';
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/LaravelViewsServiceProvider.php:
--------------------------------------------------------------------------------
1 | TableViewSearchData::class,
35 | Filterable::class => TableViewFilterData::class,
36 | Sortable::class => TableViewSortData::class,
37 | ];
38 |
39 | /**
40 | * Register services.
41 | *
42 | * @return void
43 | */
44 | public function register()
45 | {
46 | $file = __DIR__ . '/helpers.php';
47 | if (file_exists($file)) {
48 | require_once($file);
49 | }
50 |
51 | $this->app->bind('laravel-views', function () {
52 | return new LaravelViews();
53 | });
54 | $this->app->bind('variants', function () {
55 | return new Variants;
56 | });
57 | $this->app->bind('ui', UI::class);
58 | $this->app->bind('header', function () {
59 | return new Header();
60 | });
61 | }
62 |
63 | /**
64 | * Bootstrap services.
65 | *
66 | * @return void
67 | */
68 | public function boot()
69 | {
70 | $this->loadViews()
71 | ->loadCommands()
72 | ->publish()
73 | ->bladeDirectives()
74 | ->loadComponents()
75 | ->configFiles()
76 | ->macros();
77 | }
78 |
79 | private function publish()
80 | {
81 | $this->publishes([
82 | __DIR__ . '/../public/laravel-views.js' => public_path('vendor/laravel-views.js'),
83 | __DIR__ . '/../public/laravel-views.css' => public_path('vendor/laravel-views.css'),
84 | __DIR__ . '/../public/tailwind.css' => public_path('vendor/tailwind.css'),
85 | ], 'public');
86 |
87 | $this->publishes([
88 | __DIR__ . '/config/laravel-views.php' => config_path('laravel-views.php'),
89 | ], 'config');
90 |
91 | $this->publishes([
92 | __DIR__ . '/../resources/views/components' => resource_path('views/vendor/laravel-views/components'),
93 | __DIR__ . '/../resources/views/detail-view' => resource_path('views/vendor/laravel-views/detail-view'),
94 | __DIR__ . '/../resources/views/grid-view' => resource_path('views/vendor/laravel-views/grid-view'),
95 | __DIR__ . '/../resources/views/list-view' => resource_path('views/vendor/laravel-views/list-view'),
96 | __DIR__ . '/../resources/views/table-view' => resource_path('views/vendor/laravel-views/table-view'),
97 | ], 'views');
98 |
99 | return $this;
100 | }
101 |
102 | private function loadViews()
103 | {
104 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'laravel-views');
105 |
106 | return $this;
107 | }
108 |
109 | private function loadCommands()
110 | {
111 | if ($this->app->runningInConsole()) {
112 | $this->commands([
113 | FilterMakeCommand::class,
114 | ActionMakeCommand::class,
115 | TableViewMakeCommand::class,
116 | GridViewMakeCommand::class,
117 | ListViewMakeCommand::class,
118 | MakeViewCommand::class
119 | ]);
120 | }
121 |
122 | return $this;
123 | }
124 |
125 | private function bladeDirectives()
126 | {
127 | $laravelViews = new LaravelViews;
128 |
129 | Blade::directive('laravelViewsScripts', function ($options) use ($laravelViews) {
130 | return $laravelViews->js($options);
131 | });
132 |
133 | Blade::directive('laravelViewsStyles', function ($options) use ($laravelViews) {
134 | return $laravelViews->css($options);
135 | });
136 |
137 | return $this;
138 | }
139 |
140 | private function loadComponents()
141 | {
142 | $laravelViews = new LaravelViews;
143 |
144 | // Registers anonymous components
145 | foreach ($laravelViews->components() as $path => $component) {
146 | Blade::component('laravel-views::components.' . $path, 'lv-' . $component);
147 | }
148 | Blade::component('laravel-views::view.layout', 'lv-layout');
149 |
150 | // Registering class components
151 | Blade::component('lv-dynamic-component', DynamicComponent::class);
152 |
153 | // This is only for laravel 8
154 | // Blade::componentNamespace('LaravelViews\\Views\\Components', 'lv');
155 |
156 | return $this;
157 | }
158 |
159 | private function configFiles()
160 | {
161 | $this->mergeConfigFrom(__DIR__ . '/config/laravel-views.php', 'laravel-views');
162 |
163 | return $this;
164 | }
165 |
166 | private function macros()
167 | {
168 | $macros = [
169 | LaravelViewsTestMacros::class,
170 | StrMacros::class
171 | ];
172 |
173 | foreach ($macros as $macroClass) {
174 | $macro = new $macroClass;
175 | $macro->register();
176 | }
177 |
178 | return $this;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------