From e1a369956d768bc77e8023d566183d55519dab3a Mon Sep 17 00:00:00 2001 From: YvetteNikolov Date: Tue, 13 Jan 2026 10:15:21 +0100 Subject: [PATCH] feat: add wp-scaffold:elasticsearch --- README.md | 9 + docs/elasticsearch.md | 74 +++++++ src/Console/ElasticsearchScaffoldCommand.php | 37 ++++ src/ScaffoldServiceProvider.php | 17 ++ stubs/app/Hooks/Elasticsearch.php | 46 ++++ .../scripts/reactive-search/config/theme.js | 11 + .../reactive-search/reactive-search.jsx | 34 +++ .../reactive-search/styles/autosuggest.css | 56 +++++ .../views/search-bar/SearchBar.jsx | 43 ++++ .../views/search-bar/search-bar.css | 68 ++++++ .../views/search-input/SearchInput.jsx | 43 ++++ .../views/search-input/search-input.css | 64 ++++++ .../views/search-page/SearchPage.jsx | 43 ++++ .../views/search-page/search-page.css | 206 ++++++++++++++++++ .../header/reactive-search-bar.blade.php | 3 + .../views/reactive-search-page.blade.php | 3 + 16 files changed, 757 insertions(+) create mode 100644 docs/elasticsearch.md create mode 100644 src/Console/ElasticsearchScaffoldCommand.php create mode 100644 stubs/app/Hooks/Elasticsearch.php create mode 100644 stubs/resources/scripts/reactive-search/config/theme.js create mode 100644 stubs/resources/scripts/reactive-search/reactive-search.jsx create mode 100644 stubs/resources/scripts/reactive-search/styles/autosuggest.css create mode 100644 stubs/resources/scripts/reactive-search/views/search-bar/SearchBar.jsx create mode 100644 stubs/resources/scripts/reactive-search/views/search-bar/search-bar.css create mode 100644 stubs/resources/scripts/reactive-search/views/search-input/SearchInput.jsx create mode 100644 stubs/resources/scripts/reactive-search/views/search-input/search-input.css create mode 100644 stubs/resources/scripts/reactive-search/views/search-page/SearchPage.jsx create mode 100644 stubs/resources/scripts/reactive-search/views/search-page/search-page.css create mode 100644 stubs/resources/views/partials/header/reactive-search-bar.blade.php create mode 100644 stubs/resources/views/reactive-search-page.blade.php diff --git a/README.md b/README.md index bc85e22..f9abdcd 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ This package provides a set of scaffolding commands to help you quickly set up c in [Brave](https://github.com/yardinternet/brave): ```shell +wp acorn scaffold:wp-events wp acorn scaffold:knowledgebase wp acorn scaffold:news wp acorn scaffold:person @@ -53,6 +54,14 @@ To create a child theme you can run the following command. This will create a ch More information about child themes can be found in [Brave](https://github.com/yardinternet/brave) and [Sage Child Theme Support](https://github.com/yardinternet/sage-child-theme-support) +### Elasticsearch & Reactive Search + +```shell +wp acorn scaffold:elasticsearch +``` + +After scaffolding, [follow these manual steps to complete the setup](docs/elasticsearch.md). + ## About us [![banner](https://raw.githubusercontent.com/yardinternet/.github/refs/heads/main/profile/assets/small-banner-github.svg)](https://www.yard.nl/werken-bij/) diff --git a/docs/elasticsearch.md b/docs/elasticsearch.md new file mode 100644 index 0000000..afa9a0b --- /dev/null +++ b/docs/elasticsearch.md @@ -0,0 +1,74 @@ +# Elasticsearch + +Use the WP Acorn scaffold command to get started: + +```shell +wp acorn scaffold:elasticsearch +``` + +After scaffolding, follow these steps: + +## 1. Install dependencies + +Install the Yard Elasticsearch plugin and the Reactive Search npm package: + +```shell +composer require plugin/yard-elasticsearch && pnpm install @yardinternet/reactive-search +``` + +## 2. Add the Elasticsearch `.env` variables + +Add the following variables to your `.env` and `.env.example` file: + +```env +# Elasticsearch +EP_HOST= +ES_PUBLIC_URL= +``` + +## 3. Add Reactive Search entrypoint to Vite + +Update your `vite.config.js`: + +```js +export default braveConfig( { + entryPoints: [ + 'resources/scripts/reactive-search/reactive-search.jsx', + ], +} ); +``` + +## 4. Register the Elasticsearch Hook + +Add the `Elasticsearch.php` hook to your `hooks.php`: + +```php +return [ + 'elasticsearch' => \App\Hooks\Elasticsearch::class, +]; +``` + +## 5. Add the reactive search bar to the header + +Replace the current `@include('partials.header.search-bar')` of the search-bar with the following in the `header.blade.php`: + +```diff +- @include('partials.header.search-bar') + ++ @if (is_plugin_active('yard-elasticsearch/yard-elasticsearch.php')) ++ @include('partials.header.reactive-search-bar') ++ @else ++ @include('partials.header.search-bar') ++ @endif +``` + +## 6. Activate the Plugin & Configure Settings + +1. Activate the Yard Elasticsearch plugin in WordPress. +2. Go to the settings page `/wp/wp-admin/admin.php?page=acf-options-yard-elastic` +3. Configure the following settings: + - **Bronnen** (required) + - **Filters** (optional) + - **Veld weging** (required) + +Everything should now be set up and ready to use! diff --git a/src/Console/ElasticsearchScaffoldCommand.php b/src/Console/ElasticsearchScaffoldCommand.php new file mode 100644 index 0000000..862a8b3 --- /dev/null +++ b/src/Console/ElasticsearchScaffoldCommand.php @@ -0,0 +1,37 @@ +call('vendor:publish', [ + '--provider' => 'Yard\\Brave\\Scaffold\\ScaffoldServiceProvider', + '--tag' => 'elasticsearch', + ]); + $this->info('You need to do some additional steps after running this scaffold. Please read the docs here:'); + $this->line('https://github.com/yardinternet/brave-scaffold/blob/feat/elasticsearch/docs/elasticsearch.md'); + } +} diff --git a/src/ScaffoldServiceProvider.php b/src/ScaffoldServiceProvider.php index 138d367..af52a6b 100644 --- a/src/ScaffoldServiceProvider.php +++ b/src/ScaffoldServiceProvider.php @@ -7,6 +7,7 @@ use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; use Yard\Brave\Scaffold\Console\ChildThemeScaffoldCommand; +use Yard\Brave\Scaffold\Console\ElasticsearchScaffoldCommand; use Yard\Brave\Scaffold\Console\EventScaffoldCommand; use Yard\Brave\Scaffold\Console\KnowledgebaseScaffoldCommand; use Yard\Brave\Scaffold\Console\NewsScaffoldCommand; @@ -22,6 +23,7 @@ public function configurePackage(Package $package): void ->hasCommands( [ ChildThemeScaffoldCommand::class, + ElasticsearchScaffoldCommand::class, KnowledgebaseScaffoldCommand::class, NewsScaffoldCommand::class, PersonScaffoldCommand::class, @@ -33,6 +35,21 @@ public function configurePackage(Package $package): void public function bootingPackage(): void { + $this->publishes([ + __DIR__ . '/../stubs/app/Hooks/Elasticsearch.php' => app_path('Hooks/Elasticsearch.php'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/reactive-search.jsx' => resource_path('scripts/reactive-search/reactive-search.jsx'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/config/theme.js' => resource_path('scripts/reactive-search/config/theme.js'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/styles/autosuggest.css' => resource_path('scripts/reactive-search/styles/autosuggest.css'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/views/search-bar/search-bar.css' => resource_path('scripts/reactive-search/views/search-bar/search-bar.css'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/views/search-bar/SearchBar.jsx' => resource_path('scripts/reactive-search/views/search-bar/SearchBar.jsx'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/views/search-input/search-input.css' => resource_path('scripts/reactive-search/views/search-input/search-input.css'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/views/search-input/SearchInput.jsx' => resource_path('scripts/reactive-search/views/search-input/SearchInput.jsx'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/views/search-page/search-page.css' => resource_path('scripts/reactive-search/views/search-page/search-page.css'), + __DIR__ . '/../stubs/resources/scripts/reactive-search/views/search-page/SearchPage.jsx' => resource_path('scripts/reactive-search/views/search-page/SearchPage.jsx'), + __DIR__ . '/../stubs/resources/views/partials/header/reactive-search-bar.blade.php' => resource_path('views/partials/header/reactive-search-bar.blade.php'), + __DIR__ . '/../stubs/resources/views/reactive-search-page.blade.php' => resource_path('views/reactive-search-page.blade.php'), + ], 'elasticsearch'); + $this->publishes([ __DIR__ . '/../stubs/View/Components/Card/News.php' => app_path('View/Components/Card/News.php'), __DIR__ . '/../stubs/Data/NewsData.php' => app_path('Data/NewsData.php'), diff --git a/stubs/app/Hooks/Elasticsearch.php b/stubs/app/Hooks/Elasticsearch.php new file mode 100644 index 0000000..970ff58 --- /dev/null +++ b/stubs/app/Hooks/Elasticsearch.php @@ -0,0 +1,46 @@ +toHtml(); + } catch (\Exception $e) { + // Fail silently if entrypoint is not found. + } + } + + #[Filter('template_include')] + public function overrideSearchTemplate(string $template): string + { + if (! is_search() || is_admin()) { + return $template; + } + + $override = get_theme_file_path('resources/views/reactive-search-page.blade.php'); + + if (file_exists($override)) { + return $override; + } + + return $template; + } +} diff --git a/stubs/resources/scripts/reactive-search/config/theme.js b/stubs/resources/scripts/reactive-search/config/theme.js new file mode 100644 index 0000000..d782eb8 --- /dev/null +++ b/stubs/resources/scripts/reactive-search/config/theme.js @@ -0,0 +1,11 @@ +export const theme = { + typography: { + fontFamily: 'inherit', + fontSize: 'inherit', + }, + colors: { + primaryColor: '#3a0056', // @todo + borderColor: '#3a0056', // @todo + textColor: '#000', + }, +}; diff --git a/stubs/resources/scripts/reactive-search/reactive-search.jsx b/stubs/resources/scripts/reactive-search/reactive-search.jsx new file mode 100644 index 0000000..8c78614 --- /dev/null +++ b/stubs/resources/scripts/reactive-search/reactive-search.jsx @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ +import SearchPage from './views/search-page/SearchPage.jsx'; +import SearchBar from './views/search-bar/SearchBar.jsx'; +import SearchInput from './views/search-input/SearchInput.jsx'; + +/** + * WordPress dependencies + */ +import { createRoot } from '@wordpress/element'; + +/** + * Styles + */ +import './styles/autosuggest.css'; + +if ( document.getElementById( 'js-reactive-search-page' ) ) { + createRoot( document.getElementById( 'js-reactive-search-page' ) ).render( + + ); +} + +if ( document.getElementById( 'js-reactive-search-bar' ) ) { + createRoot( document.getElementById( 'js-reactive-search-bar' ) ).render( + + ); +} + +if ( document.getElementById( 'js-reactive-search-input' ) ) { + createRoot( document.getElementById( 'js-reactive-search-input' ) ).render( + + ); +} diff --git a/stubs/resources/scripts/reactive-search/styles/autosuggest.css b/stubs/resources/scripts/reactive-search/styles/autosuggest.css new file mode 100644 index 0000000..60f1e55 --- /dev/null +++ b/stubs/resources/scripts/reactive-search/styles/autosuggest.css @@ -0,0 +1,56 @@ +@reference '@sage/styles/base/config.css'; +@reference '@sage/styles/base/utilities.css'; + +.yrs-wrapper { + .yrs-autosuggest { + @apply z-100 shadow; + } + + .yrs-autosuggest-item { + @apply border-gray-100; + + &:last-child { + @apply font-bold; + + .yrs-autosuggest-link { + @apply gap-x-4; + } + } + + &.is-selected { + .yrs-autosuggest-link { + @apply text-primary bg-white; + animation: focus 0.1s linear both; + outline-color: -webkit-focus-ring-color; + outline-style: auto; + } + } + } + + .yrs-autosuggest-link { + @apply py-2 no-underline; + + &:hover, + &:focus { + @apply text-black; + } + + @variant hocus { + @apply text-primary; + } + + &:focus { + .trim { + @apply underline; + } + } + + i { + @apply text-inherit!; + } + } + + .yrs-autosuggest-link-label { + @apply text-xs whitespace-nowrap sm:pl-4; + } +} diff --git a/stubs/resources/scripts/reactive-search/views/search-bar/SearchBar.jsx b/stubs/resources/scripts/reactive-search/views/search-bar/SearchBar.jsx new file mode 100644 index 0000000..03dc25d --- /dev/null +++ b/stubs/resources/scripts/reactive-search/views/search-bar/SearchBar.jsx @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import { + Base, + SearchBar as ReactiveSearchBar, +} from '@yardinternet/reactive-search'; + +/** + * Internal dependencies + */ +import { theme } from '../../config/theme'; +import './search-bar.css'; + +const SearchBar = () => { + if ( ! window.YS || ! window.YS.indices ) { + console.error( 'Error: Yard Elasticsearch indices has not been set.' ); // eslint-disable-line no-console + return <>; + } + + return ( + + + + ); +}; + +export default SearchBar; diff --git a/stubs/resources/scripts/reactive-search/views/search-bar/search-bar.css b/stubs/resources/scripts/reactive-search/views/search-bar/search-bar.css new file mode 100644 index 0000000..5ba50f2 --- /dev/null +++ b/stubs/resources/scripts/reactive-search/views/search-bar/search-bar.css @@ -0,0 +1,68 @@ +@reference '@sage/styles/base/config.css'; +@reference '@sage/styles/base/utilities.css'; + +.yrs-search-bar-container { + .yrs-search-bar-btn-open { + @apply lg:text-primary flex h-11.5 flex-col items-center justify-between rounded bg-white text-black transition-all lg:size-14 lg:min-w-14 lg:justify-center; + + &::before { + @apply fontawesome pointer-events-none text-4xl leading-none transition md:text-2xl; + --fa-icon: '\f002'; + --fa-weight: 400; + } + + @variant hocus { + @apply text-primary; + } + + .yrs-search-bar-btn-open-text { + @apply text-xs leading-none lg:sr-only; + } + } + + .yrs-search-bar-inner { + @apply top-(--wp-admin-bar-height) h-(--combined-bar-height)! gap-x-2 sm:gap-x-4; + } + + .yrs-datasearch-autosuggest { + @apply w-full max-w-xl; + + > div { + @apply m-0; + } + + .yrs-datasearch-autosuggest-input { + @apply border-primary ring-primary rounded-none border-0 border-b-2 bg-white shadow-none! md:py-4 md:pl-3 md:text-lg; + } + + .input-group { + @apply shadow-none; + } + } + + .yrs-search-bar-btn-submit, + .yrs-search-bar-btn-close { + @apply size-8 min-w-8 sm:text-lg xl:text-xl; + + &::before { + @apply fontawesome transition; + --fa-icon: '\f002'; + } + + @variant hocus { + &::before { + @apply text-primary; + } + } + } + + .yrs-search-bar-btn-submit-text, + .yrs-search-bar-btn-close-text { + @apply sr-only; + } + + .yrs-search-bar-btn-close::before { + @apply fontawesome; + --fa-icon: '\f00d'; + } +} diff --git a/stubs/resources/scripts/reactive-search/views/search-input/SearchInput.jsx b/stubs/resources/scripts/reactive-search/views/search-input/SearchInput.jsx new file mode 100644 index 0000000..feb2c40 --- /dev/null +++ b/stubs/resources/scripts/reactive-search/views/search-input/SearchInput.jsx @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import { + Base, + SearchInput as ReactiveSearchInput, +} from '@yardinternet/reactive-search'; + +/** + * Internal dependencies + */ +import { theme } from '../../config/theme'; +import './search-input.css'; + +const SearchInput = () => { + if ( ! window.YS || ! window.YS.indices ) { + console.error( 'Error: Yard Elasticsearch indices has not been set.' ); // eslint-disable-line no-console + return <>; + } + + return ( + + + + ); +}; + +export default SearchInput; diff --git a/stubs/resources/scripts/reactive-search/views/search-input/search-input.css b/stubs/resources/scripts/reactive-search/views/search-input/search-input.css new file mode 100644 index 0000000..7f7841d --- /dev/null +++ b/stubs/resources/scripts/reactive-search/views/search-input/search-input.css @@ -0,0 +1,64 @@ +@reference '@sage/styles/base/config.css'; +@reference '@sage/styles/base/utilities.css'; + +:root { + --yrs-search-input-height: 60px; +} + +.wp-block-yard-yrs-search-input { + @apply min-h-(--yrs-search-input-height) w-full; +} + +.yrs-search-input-container { + @apply mx-auto w-full shadow; + + &:has( .yrs-datasearch-autosuggest-input:focus ), + &:has( .yrs-datasearch-autosuggest-input:not( :placeholder-shown ) ) { + .yrs-search-input-label { + @apply -translate-y-4 text-xs leading-none; + } + } + + .yrs-search-input-label { + @apply left-2 bg-white px-2 py-1; + } + + .yrs-datasearch-autosuggest { + > div { + @apply m-0; + } + + .input-group { + @apply shadow-none; + + .yrs-datasearch-autosuggest-input { + @apply ring-primary focus:border-primary h-(--yrs-search-input-height) rounded-none py-3! pr-16! pl-4! shadow-none; + + + div > div { + @apply p-0; + + > div { + @apply max-w-full; + } + } + } + + .yrs-search-bar-btn-submit { + @apply bg-primary absolute top-0 right-0 h-full w-18 text-2xl text-white transition; + + &::before { + @apply fontawesome; + --fa-icon: '\f002'; + } + + @variant hocus { + @apply bg-primary-800; + } + } + + .yrs-search-bar-btn-submit-text { + @apply sr-only; + } + } + } +} diff --git a/stubs/resources/scripts/reactive-search/views/search-page/SearchPage.jsx b/stubs/resources/scripts/reactive-search/views/search-page/SearchPage.jsx new file mode 100644 index 0000000..5f25dcc --- /dev/null +++ b/stubs/resources/scripts/reactive-search/views/search-page/SearchPage.jsx @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import { + Base, + SearchPage as ReactiveSearchPage, +} from '@yardinternet/reactive-search'; + +/** + * Internal dependencies + */ +import { theme } from '../../config/theme'; +import './search-page.css'; + +const SearchPage = () => { + if ( ! window.YS || ! window.YS.indices ) { + console.error( 'Error: Yard Elasticsearch indices has not been set.' ); // eslint-disable-line no-console + return <>; + } + + return ( + + + + ); +}; + +export default SearchPage; diff --git a/stubs/resources/scripts/reactive-search/views/search-page/search-page.css b/stubs/resources/scripts/reactive-search/views/search-page/search-page.css new file mode 100644 index 0000000..9b92bcd --- /dev/null +++ b/stubs/resources/scripts/reactive-search/views/search-page/search-page.css @@ -0,0 +1,206 @@ +@reference '@sage/styles/base/config.css'; +@reference '@sage/styles/base/utilities.css'; + +:root { + --yrs-datasearch-input-border-color: var( --color-gray-400 ); + --yrs-datasearch-input-border-color-focus: var( --color-primary ); + --yrs-datasearch-input-btn-color: var( --color-black ); + --yrs-datasearch-input-btn-color-hover: var( --color-primary ); + --yrs-datasearch-input-btn-bg: transparent; + --yrs-datasearch-input-btn-bg-hover: transparent; +} + +.yrs-wrapper { + .yrs-heading { + @apply mb-8 lg:mb-12; + } + + .yrs-row { + @apply gap-4 lg:grid-cols-12 lg:gap-8 xl:gap-10; + + .yrs-column-1 { + @apply lg:col-span-4 xl:col-span-3; + } + + .yrs-column-2 { + @apply lg:col-span-8 xl:col-span-9; + } + } + + .yrs-datasearch-label, + .yrs-multidatalist-title { + @apply mb-2 block text-xl font-bold; + } + + .yrs-datasearch-container { + @apply mb-8; + } + + .yrs-datasearch { + > div { + @apply m-0; + } + + .input-group { + @apply shadow-none; + } + } + + .yrs-datasearch-input { + @apply rounded-theme h-13 border-x-0 border-t-0 border-b-2 border-(--yrs-datasearch-input-border-color) bg-white py-3 pr-10 text-base shadow-sm; + + &:focus { + @apply border-(--yrs-datasearch-input-border-color-focus); + } + + + div > div { + @apply min-w-11; + + &:empty { + @apply hidden; + } + } + } + + .yrs-datasearch-input-btn { + @apply h-full bg-(--yrs-datasearch-input-btn-bg) text-(--yrs-datasearch-input-btn-color); + + &::before { + @apply fontawesome flex h-full w-12 items-center justify-center bg-transparent; + --fa-icon: '\f002'; + } + + @variant hocus { + &::before { + @apply bg-(--yrs-datasearch-input-btn-bg-hover) text-(--yrs-datasearch-input-btn-color-hover); + } + } + + .yrs-datasearch-input-btn-text { + @apply sr-only; + } + } + + .yrs-button-filters { + @apply is-button w-full lg:hidden; + + &::before { + @apply fontawesome mr-2; + --fa-icon: '\f1de'; + } + } + + .yrs-filters-inner { + @apply mt-3; + } + + .yrs-multidatalist-label { + @apply mt-0 mb-2 text-base!; + + &::before { + @apply mr-3; + } + + > span { + @apply flex items-center; + } + + .yrs-multidatalist-count { + @apply text-sm; + } + } + + .yrs-results { + @apply @container; + } + + .yrs-results-info { + @apply text-primary mb-4 justify-end text-sm; + } + + .yrs-results-list { + @apply grid gap-4 @xl:gap-6; + } + + .yrs-card { + @apply rounded-theme relative bg-white shadow transition-all; + + &:focus-within, + &:hover { + .yrs-card-body { + @apply border-primary; + } + } + + .yrs-card-body { + @apply flex flex-col border-b-4 border-transparent p-(--card-padding) transition-all xl:p-8; + } + + .yrs-card-title-link { + @apply a-linkable-area text-inherit! no-underline! focus:underline!; + } + + .yrs-card-excerpt { + @apply mb-2 leading-snug sm:leading-relaxed; + } + + .yrs-card-label { + @apply mt-3 w-fit rounded bg-gray-100 px-2 py-1 text-xs leading-normal! sm:text-sm; + } + + .yrs-card-score { + @apply absolute right-6 bottom-6 hidden text-xs text-gray-700; + + &:is( .logged-in .yrs-card-score ) { + @apply block; + } + } + } + + .yrs-pagination-list { + @apply justify-end gap-x-2 gap-y-4; + } + + .yrs-pagination-button { + @apply size-10 min-h-10 min-w-10 p-2 text-black no-underline transition-all; + + &:not( .is-active ) { + @apply hover:bg-primary-100; + } + + &.is-active { + @apply font-bold; + } + } + + .yrs-pagination-button-prev { + @apply hover:bg-primary! border-primary text-primary mr-4 size-10 border text-[0px]; + + &::before { + @apply fontawesome text-base text-inherit; + --fa-icon: '\f053'; + } + + @variant hocus { + @apply bg-primary text-white; + } + } + + .yrs-pagination-button-next { + @apply bg-primary ml-4 size-10 text-[0px] text-white; + + &::before { + @apply fontawesome text-base; + --fa-icon: '\f054'; + } + + @variant hocus { + @apply bg-primary-700!; + filter: brightness( 1 ); + } + } + + .yrs-pagination-info { + @apply text-right text-sm; + } +} diff --git a/stubs/resources/views/partials/header/reactive-search-bar.blade.php b/stubs/resources/views/partials/header/reactive-search-bar.blade.php new file mode 100644 index 0000000..53430e9 --- /dev/null +++ b/stubs/resources/views/partials/header/reactive-search-bar.blade.php @@ -0,0 +1,3 @@ +
+ +
diff --git a/stubs/resources/views/reactive-search-page.blade.php b/stubs/resources/views/reactive-search-page.blade.php new file mode 100644 index 0000000..94479ea --- /dev/null +++ b/stubs/resources/views/reactive-search-page.blade.php @@ -0,0 +1,3 @@ + +
+