├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── resources └── views │ └── wizard.blade.php └── src ├── Components ├── Concerns │ ├── MountsWizard.php │ └── StepAware.php ├── StepComponent.php └── WizardComponent.php ├── Enums └── StepStatus.php ├── Exceptions ├── InvalidStateClassName.php ├── InvalidStepComponent.php ├── NoNextStep.php ├── NoPreviousStep.php ├── NoStepsReturned.php └── StepDoesNotExist.php ├── Support ├── EventEmitter.php ├── State.php ├── Step.php └── StepSynth.php └── WizardServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-livewire-wizard` will be documented in this file. 4 | 5 | ## 2.4.2 - 2025-02-21 6 | 7 | ### What's Changed 8 | 9 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/laravel-livewire-wizard/pull/126 10 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-livewire-wizard/pull/127 11 | 12 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/2.4.1...2.4.2 13 | 14 | ## 2.4.1 - 2025-01-17 15 | 16 | ### What's Changed 17 | 18 | * Make nullable parameter type hint explicitly nullable by @nimah79 in https://github.com/spatie/laravel-livewire-wizard/pull/125 19 | 20 | ### New Contributors 21 | 22 | * @nimah79 made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/125 23 | 24 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/2.4.0...2.4.1 25 | 26 | ## 2.4.0 - 2024-12-16 27 | 28 | ### What's Changed 29 | 30 | * Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/spatie/laravel-livewire-wizard/pull/114 31 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-livewire-wizard/pull/120 32 | * Add alternative package to README by @satoved in https://github.com/spatie/laravel-livewire-wizard/pull/122 33 | * Add `Component::testStep` to allow easy testing of wizard StepComponents by @musa11971 in https://github.com/spatie/laravel-livewire-wizard/pull/123 34 | 35 | ### New Contributors 36 | 37 | * @satoved made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/122 38 | * @musa11971 made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/123 39 | 40 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/2.3.0...2.4.0 41 | 42 | ## 2.3.0 - 2024-04-22 43 | 44 | ### What's Changed 45 | 46 | * merge initialStep with allStepState by @bangnokia in https://github.com/spatie/laravel-livewire-wizard/pull/112 47 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/spatie/laravel-livewire-wizard/pull/111 48 | 49 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/2.2.0...2.3.0 50 | 51 | ## 2.2.0 - 2024-03-08 52 | 53 | ### What's Changed 54 | 55 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-livewire-wizard/pull/109 56 | 57 | ### New Contributors 58 | 59 | * @laravel-shift made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/109 60 | 61 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/2.1.0...2.2.0 62 | 63 | ## 2.1.0 - 2024-02-21 64 | 65 | ### What's Changed 66 | 67 | * add forStepClass method to get state more convenient by @bangnokia in https://github.com/spatie/laravel-livewire-wizard/pull/108 68 | 69 | ### New Contributors 70 | 71 | * @bangnokia made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/108 72 | 73 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/2.0.1...2.1.0 74 | 75 | ## 2.0.1 - 2023-09-13 76 | 77 | ### What's Changed 78 | 79 | - Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/spatie/laravel-livewire-wizard/pull/92 80 | - Fixes step navigation when there are multiple wizards on a page by @OrangeJuiced in https://github.com/spatie/laravel-livewire-wizard/pull/94 81 | 82 | ### New Contributors 83 | 84 | - @OrangeJuiced made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/94 85 | 86 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/2.0.0...2.0.1 87 | 88 | ## 2.0.0 - 2023-08-25 89 | 90 | ### What's Changed 91 | 92 | - Fixed bug where tests would fail by @Bird87ZA in https://github.com/spatie/laravel-livewire-wizard/pull/79 93 | - Update to Livewire 3 by @fredericlesueurs in https://github.com/spatie/laravel-livewire-wizard/pull/87 94 | 95 | ### New Contributors 96 | 97 | - @mabdullahsari made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/84 98 | - @fredericlesueurs made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/87 99 | 100 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.2.0...2.0.0 101 | 102 | ## 1.2.0 - 2023-04-05 103 | 104 | ### What's Changed 105 | 106 | - Add missing comma in example code by @astersnake in https://github.com/spatie/laravel-livewire-wizard/pull/72 107 | - add missing colon (:) by @Enaah in https://github.com/spatie/laravel-livewire-wizard/pull/73 108 | - Add has step checks by @Bird87ZA in https://github.com/spatie/laravel-livewire-wizard/pull/78 109 | 110 | ### New Contributors 111 | 112 | - @astersnake made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/72 113 | - @Enaah made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/73 114 | - @Bird87ZA made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/78 115 | 116 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.1.5...1.2.0 117 | 118 | ## 1.1.5 - 2023-03-01 119 | 120 | ### What's Changed 121 | 122 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/laravel-livewire-wizard/pull/68 123 | - Fix `Creation of dynamic property` warning in PHP 8.2 by @JayBizzle in https://github.com/spatie/laravel-livewire-wizard/pull/71 124 | 125 | ### New Contributors 126 | 127 | - @JayBizzle made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/71 128 | 129 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.1.4...1.1.5 130 | 131 | ## 1.1.4 - 2023-01-25 132 | 133 | - allow L10 134 | 135 | ## 1.1.3 - 2022-06-09 136 | 137 | - allow initial step to be overridden 138 | 139 | ## 1.1.2 - 2022-06-06 140 | 141 | ### What's Changed 142 | 143 | - tests: fetch state for steps without state by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/31 144 | 145 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.1.1...1.1.2 146 | 147 | ## 1.1.1 - 2022-06-03 148 | 149 | ### What's Changed 150 | 151 | - fix: move EventEmitter to src by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/30 152 | 153 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.1.0...1.1.1 154 | 155 | ## 1.1.0 - 2022-06-03 156 | 157 | ### What's Changed 158 | 159 | - feat: make wizards testable by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/29 160 | 161 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.0.3...1.1.0 162 | 163 | ## 1.0.3 - 2022-05-30 164 | 165 | ### What's Changed 166 | 167 | - Fix typo by @damms005 in https://github.com/spatie/laravel-livewire-wizard/pull/20 168 | - Fix typo by @damms005 in https://github.com/spatie/laravel-livewire-wizard/pull/21 169 | - Fix typo by @damms005 in https://github.com/spatie/laravel-livewire-wizard/pull/22 170 | - chore: remove old code by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/23 171 | - refactor: move logic into dedicated method by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/26 172 | 173 | ### New Contributors 174 | 175 | - @damms005 made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/20 176 | 177 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.0.2...1.0.3 178 | 179 | ## 1.0.2 - 2022-05-25 180 | 181 | ## What's Changed 182 | 183 | - Update creating-your-first-wizard.md by @nickfls in https://github.com/spatie/laravel-livewire-wizard/pull/18 184 | - Fix memory leak (@freekmurze) 185 | 186 | ## New Contributors 187 | 188 | - @nickfls made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/18 189 | 190 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/1.0.1...1.0.2 191 | 192 | ## 1.0.1 - 2022-05-23 193 | 194 | - remove buddy skip step functionality 195 | 196 | ## 1.0.0 - 2022-05-21 197 | 198 | - stable release 199 | 200 | ## 0.0.8 - 2022-05-21 201 | 202 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/0.0.7...0.0.8 203 | 204 | ## 0.0.7 - 2022-05-21 205 | 206 | ## What's Changed 207 | 208 | - Code review by @riasvdv in https://github.com/spatie/laravel-livewire-wizard/pull/11 209 | 210 | ## New Contributors 211 | 212 | - @riasvdv made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/11 213 | 214 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/0.0.6...0.0.7 215 | 216 | ## 0.0.6 - 2022-05-17 217 | 218 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/0.0.5...0.0.6 219 | 220 | ## 0.0.5 - 2022-05-17 221 | 222 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/0.0.4...0.0.5 223 | 224 | ## 0.0.4 - 2022-05-16 225 | 226 | ## What's Changed 227 | 228 | - feat: add support for initial state by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/7 229 | 230 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/0.0.3...0.0.4 231 | 232 | ## 0.0.3 - 2022-05-16 233 | 234 | ## What's Changed 235 | 236 | - info function should be stepInfo by @xHeinrich in https://github.com/spatie/laravel-livewire-wizard/pull/1 237 | - feat: skip step functionality by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/3 238 | - tests: add complete tests for shouldSkip by @ju5t in https://github.com/spatie/laravel-livewire-wizard/pull/5 239 | 240 | ## New Contributors 241 | 242 | - @xHeinrich made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/1 243 | - @ju5t made their first contribution in https://github.com/spatie/laravel-livewire-wizard/pull/3 244 | 245 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/0.0.2...0.0.3 246 | 247 | ## 0.0.2 - 2022-05-15 248 | 249 | **Full Changelog**: https://github.com/spatie/laravel-livewire-wizard/compare/0.0.1...0.0.2 250 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Logo for laravel-livewire-wizard 6 | 7 | 8 | 9 |

Build wizards using Livewire

10 | 11 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-livewire-wizard.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-livewire-wizard) 12 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-livewire-wizard/run-tests.yml?branch=main&label=tests)](https://github.com/spatie/laravel-livewire-wizard/actions?query=workflow%3Arun-tests+branch%3Amain) 13 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-livewire-wizard/php-cs-fixer.yml?branch=main&label=code%20style)](https://github.com/spatie/laravel-livewire-wizard/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 14 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-livewire-wizard.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-livewire-wizard) 15 | 16 |
17 | 18 | This package offers lightweight Livewire components that allow you to easily build a wizard. With "wizard" we mean a multi-step process in which each step has its own screen. 19 | 20 | ![screenshot](https://github.com/spatie/laravel-livewire-wizard/blob/main/docs/images/screenshot.png?raw=true) 21 | 22 | Here's what a wizard component class could look like. 23 | 24 | ```php 25 | use Spatie\LivewireWizard\Components\WizardComponent; 26 | 27 | class CheckoutWizardComponent extends WizardComponent 28 | { 29 | public function steps() : array 30 | { 31 | return [ 32 | CartStepComponent::class, 33 | DeliveryAddressStepComponent::class, 34 | ConfirmOrderStepComponent::class, 35 | ]; 36 | } 37 | } 38 | ``` 39 | 40 | A step is class that extends `StepComponent` (which in its turn extends `Livewire\Component`). You can do anything in here that you can do with a regular Livewire component. 41 | 42 | ```php 43 | namespace App\Components; 44 | 45 | class CartStepComponent extends StepComponent 46 | { 47 | // add any Livewire powered method you want 48 | 49 | public function render() 50 | { 51 | return view('checkout-wizard.steps.cart'); 52 | } 53 | } 54 | ``` 55 | 56 | You can easily [control which step is displayed](https://spatie.be/docs/laravel-livewire-wizard/v1/usage/navigating-steps), [access state of other steps](https://spatie.be/docs/laravel-livewire-wizard/v1/usage/accessing-state), and [build any navigation](https://spatie.be/docs/laravel-livewire-wizard/v1/usage/rendering-navigation) you desire. 57 | 58 | In [this repo on GitHub](https://github.com/spatie/laravel-livewire-wizard-demo-app), you'll find a demo Laravel application that uses the laravel-livewire-wizard package to create a simple checkout flow. 59 | 60 | ## Support us 61 | 62 | [](https://spatie.be/github-ad-click/laravel-livewire-wizard) 63 | 64 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 65 | 66 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 67 | 68 | ## Documentation 69 | 70 | All documentation is available [on our documentation site](https://spatie.be/docs/laravel-livewire-wizard). 71 | 72 | ## Testing 73 | 74 | ```bash 75 | composer test 76 | ``` 77 | 78 | ## Alternatives 79 | 80 | Our package is headless. This means that it does not provide UI, but it offers functions to easily build any UI you want. If you do not wish to build your own UI, you could consider using [vildanbina/livewire-wizard](https://github.com/vildanbina/livewire-wizard), which includes pre-built navigation and CSS. 81 | 82 | [Filament](https://filamentphp.com) users could also take a look at [the built-in wizard functionality](https://filamentphp.com/docs/2.x/forms/layout#wizard). 83 | 84 | Another headless package [satoved/laravel-livewire-steps](https://github.com/satoved/laravel-livewire-steps) that uses Livewire form objects for steps instead of Livewire components can be considered for simpler use cases. 85 | 86 | ## Changelog 87 | 88 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 89 | 90 | ## Contributing 91 | 92 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 93 | 94 | ## Security Vulnerabilities 95 | 96 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 97 | 98 | ## Credits 99 | 100 | - [Freek Van der Herten](https://github.com/freekmurze) 101 | - [Rias Van der Veken](https://github.com/riasvdv) 102 | - [All Contributors](../../contributors) 103 | 104 | ## License 105 | 106 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 107 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-livewire-wizard", 3 | "description": "Build wizards using Livewire", 4 | "keywords": [ 5 | "spatie", 6 | "laravel", 7 | "laravel-livewire-wizard" 8 | ], 9 | "homepage": "https://github.com/spatie/laravel-livewire-wizard", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Freek Van der Herten", 14 | "email": "freek@spatie.be", 15 | "role": "Developer" 16 | }, 17 | { 18 | "name": "Rias Van der Veken", 19 | "email": "rias@spatie.be", 20 | "role": "Developer" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.2", 25 | "illuminate/contracts": "^10.0|^11.0|^12.0", 26 | "illuminate/support": "^10.0|^11.0|^12.0", 27 | "livewire/livewire": "^3.0", 28 | "spatie/laravel-collection-macros": "^7.13|^8.0", 29 | "spatie/laravel-package-tools": "^1.16.1" 30 | }, 31 | "require-dev": { 32 | "ext-dom": "*", 33 | "nunomaduro/collision": "^7.8.1|^8.0", 34 | "orchestra/testbench": "^8.9.1|^9.0|^10.0", 35 | "pestphp/pest": "^2.16|^3.7", 36 | "pestphp/pest-plugin-laravel": "^2.2|^3.1", 37 | "spatie/laravel-ray": "^1.32.6", 38 | "spatie/pest-plugin-snapshots": "^2.0.1" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Spatie\\LivewireWizard\\": "src" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Spatie\\LivewireWizard\\Tests\\": "tests" 48 | } 49 | }, 50 | "scripts": { 51 | "analyse": "vendor/bin/phpstan analyse", 52 | "test": "vendor/bin/pest", 53 | "test-coverage": "vendor/bin/pest --coverage" 54 | }, 55 | "config": { 56 | "sort-packages": true, 57 | "allow-plugins": { 58 | "pestphp/pest-plugin": true 59 | } 60 | }, 61 | "extra": { 62 | "laravel": { 63 | "providers": [ 64 | "Spatie\\LivewireWizard\\WizardServiceProvider" 65 | ] 66 | } 67 | }, 68 | "minimum-stability": "dev", 69 | "prefer-stable": true 70 | } 71 | -------------------------------------------------------------------------------- /resources/views/wizard.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @livewire($currentStepName, $currentStepState, key($currentStepName)) 3 |
4 | -------------------------------------------------------------------------------- /src/Components/Concerns/MountsWizard.php: -------------------------------------------------------------------------------- 1 | currentStepName ?? $this->stepNames()->first(); 13 | 14 | $initialState = $initialState ?? $this->initialState() ?? []; 15 | 16 | $initialState = array_merge($initialState, $this->allStepState); 17 | 18 | $this->showStep($stepName, $initialState[$stepName] ?? []); 19 | 20 | foreach ($initialState as $stepName => $state) { 21 | $this->setStepState($stepName, $state); 22 | } 23 | 24 | if (! is_a($this->stateClass(), State::class, true)) { 25 | throw InvalidStateClassName::doesNotExtendState(static::class, $this->stateClass()); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Components/Concerns/StepAware.php: -------------------------------------------------------------------------------- 1 | getName(static::class); 18 | 19 | $this->steps = collect($this->allStepNames) 20 | ->map(function (string $stepName) use (&$currentFound, $currentStepName) { 21 | $className = app(ComponentRegistry::class)->getClass($stepName); 22 | 23 | $info = (new $className())->stepInfo(); 24 | 25 | $status = $currentFound ? StepStatus::Next : StepStatus::Previous; 26 | 27 | 28 | if ($stepName === $currentStepName) { 29 | $currentFound = true; 30 | $status = StepStatus::Current; 31 | } 32 | 33 | return new Step($stepName, $info, $status); 34 | }) 35 | ->toArray(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Components/StepComponent.php: -------------------------------------------------------------------------------- 1 | */ 20 | public string $stateClassName = State::class; 21 | 22 | public function previousStep() 23 | { 24 | $this->dispatch('previousStep', $this->state()->currentStep())->to($this->wizardClassName); 25 | } 26 | 27 | public function nextStep() 28 | { 29 | $this->dispatch('nextStep', $this->state()->currentStep())->to($this->wizardClassName); 30 | } 31 | 32 | public function showStep(string $stepName) 33 | { 34 | $this->dispatch('showStep', toStepName: $stepName, currentStepState: $this->state()->currentStep())->to($this->wizardClassName); 35 | } 36 | 37 | public function hasPreviousStep() 38 | { 39 | return ! empty($this->allStepNames) && $this->allStepNames[0] !== app(ComponentRegistry::class)->getName(static::class); 40 | } 41 | 42 | public function hasNextStep() 43 | { 44 | return end($this->allStepNames) !== app(ComponentRegistry::class)->getName(static::class); 45 | } 46 | 47 | public function stepInfo(): array 48 | { 49 | return []; 50 | } 51 | 52 | public function state(): State 53 | { 54 | /** @var State $stateClass */ 55 | $stateClass = new $this->stateClassName(); 56 | 57 | $stepName = app(ComponentRegistry::class)->getName(static::class); 58 | 59 | $allState = array_merge( 60 | $this->allStepsState ?? [], 61 | [$stepName => $this->all()] 62 | ); 63 | 64 | $stateClass 65 | ->setAllState($allState) 66 | ->setCurrentStepName($stepName); 67 | 68 | return $stateClass; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Components/WizardComponent.php: -------------------------------------------------------------------------------- 1 | */ 25 | abstract public function steps(): array; 26 | 27 | public function initialState(): ?array 28 | { 29 | return null; 30 | } 31 | 32 | public function stepNames(): Collection 33 | { 34 | $steps = collect($this->steps()) 35 | ->each(function (string $stepClassName) { 36 | if (! is_a($stepClassName, StepComponent::class, true)) { 37 | throw InvalidStepComponent::doesNotExtendStepComponent(static::class, $stepClassName); 38 | } 39 | }) 40 | ->map(function (string $stepClassName) { 41 | $alias = app(ComponentRegistry::class)->getName($stepClassName); 42 | 43 | if (is_null($alias)) { 44 | throw InvalidStepComponent::notRegisteredWithLivewire(static::class, $stepClassName); 45 | } 46 | 47 | return $alias; 48 | }); 49 | 50 | if ($steps->isEmpty()) { 51 | throw NoStepsReturned::make(static::class); 52 | } 53 | 54 | return $steps; 55 | } 56 | 57 | #[On('previousStep')] 58 | public function previousStep(array $currentStepState) 59 | { 60 | $previousStep = collect($this->stepNames()) 61 | ->before(fn (string $step) => $step === $this->currentStepName); 62 | 63 | if (! $previousStep) { 64 | throw NoPreviousStep::make(self::class, $this->currentStepName); 65 | } 66 | 67 | $this->showStep($previousStep, $currentStepState); 68 | } 69 | 70 | #[On('nextStep')] 71 | public function nextStep(array $currentStepState) 72 | { 73 | $nextStep = collect($this->stepNames()) 74 | ->after(fn (string $step) => $step === $this->currentStepName); 75 | 76 | if (! $nextStep) { 77 | throw NoNextStep::make(self::class, $this->currentStepName); 78 | } 79 | 80 | $this->showStep($nextStep, $currentStepState); 81 | } 82 | 83 | #[On('showStep')] 84 | public function showStep($toStepName, array $currentStepState = []) 85 | { 86 | if ($this->currentStepName) { 87 | $this->setStepState($this->currentStepName, $currentStepState); 88 | } 89 | 90 | $this->currentStepName = $toStepName; 91 | } 92 | 93 | public function setStepState(string $step, array $state = []): void 94 | { 95 | if (! $this->stepNames()->contains($step)) { 96 | throw StepDoesNotExist::doesNotHaveState($step); 97 | } 98 | 99 | $this->allStepState[$step] = $state; 100 | } 101 | 102 | public function getCurrentStepState(?string $step = null): array 103 | { 104 | $stepName = $step ?? $this->currentStepName; 105 | 106 | $stepName = class_exists($stepName) 107 | ? app(ComponentRegistry::class)->getName($stepName) 108 | : $stepName; 109 | 110 | throw_if( 111 | ! $this->stepNames()->contains($stepName), 112 | StepDoesNotExist::stepNotFound($stepName) 113 | ); 114 | 115 | return array_merge( 116 | $this->allStepState[$stepName] ?? [], 117 | [ 118 | 'allStepNames' => $this->stepNames()->toArray(), 119 | 'allStepsState' => $this->allStepState, 120 | 'stateClassName' => $this->stateClass(), 121 | 'wizardClassName' => static::class, 122 | ], 123 | ); 124 | } 125 | 126 | public function render() 127 | { 128 | $currentStepState = $this->getCurrentStepState(); 129 | 130 | return view('livewire-wizard::wizard', compact('currentStepState')); 131 | } 132 | 133 | /** @return class-string */ 134 | public function stateClass(): string 135 | { 136 | return State::class; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Enums/StepStatus.php: -------------------------------------------------------------------------------- 1 | emittingComponent->effects, 'dispatches', []); 17 | 18 | foreach ($events as $event) { 19 | $component->dispatch($event['name'], ...$event['params']); 20 | } 21 | 22 | return $this->emittingComponent; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Support/State.php: -------------------------------------------------------------------------------- 1 | allState = $state; 16 | 17 | return $this; 18 | } 19 | 20 | public function setCurrentStepName(string $currentStepName): self 21 | { 22 | $this->currentStepName = $currentStepName; 23 | 24 | return $this; 25 | } 26 | 27 | public function forStep(string $stepName): array 28 | { 29 | $state = $this->allState[$stepName] ?? []; 30 | 31 | if (array_key_exists('allStepsState', $state)) { 32 | unset($state['allStepsState']); 33 | } 34 | 35 | return $state; 36 | } 37 | 38 | public function forStepClass(string $stepClass): array 39 | { 40 | $stepName = app(ComponentRegistry::class)->getName($stepClass); 41 | 42 | return $this->forStep($stepName); 43 | } 44 | 45 | public function get(string $key) 46 | { 47 | return Arr::get($this->allState, $key); 48 | } 49 | 50 | public function currentStep(): array 51 | { 52 | return $this->forStep($this->currentStepName); 53 | } 54 | 55 | public function all(): array 56 | { 57 | return $this->allState; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Support/Step.php: -------------------------------------------------------------------------------- 1 | status === StepStatus::Previous; 20 | } 21 | 22 | public function isCurrent(): bool 23 | { 24 | return $this->status === StepStatus::Current; 25 | } 26 | 27 | public function isNext(): bool 28 | { 29 | return $this->status === StepStatus::Next; 30 | } 31 | 32 | public function show(): string 33 | { 34 | return "showStep('{$this->stepName}')"; 35 | } 36 | 37 | public function __get(string $key): mixed 38 | { 39 | return Arr::get($this->info, $key); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Support/StepSynth.php: -------------------------------------------------------------------------------- 1 | $target->stepName, 20 | 'info' => $target->info, 21 | 'status' => $target->status->value, 22 | ], []]; 23 | } 24 | 25 | public function hydrate($value) 26 | { 27 | return new Step( 28 | $value['stepName'], 29 | $value['info'], 30 | StepStatus::from($value['status']), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/WizardServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-livewire-wizard') 19 | ->hasViews(); 20 | } 21 | 22 | public function bootingPackage() 23 | { 24 | Livewire::propertySynthesizer(StepSynth::class); 25 | $this->registerLivewireTestMacros(); 26 | } 27 | 28 | public function registerLivewireTestMacros() 29 | { 30 | Component::macro('testStep', function (string $stepClass, array $state = []) { 31 | $wizardComponent = Livewire::test(static::class, ['initialState' => $state]); 32 | $wizard = $wizardComponent->invade(); 33 | $wizard->mountMountsWizard($stepClass, $state); 34 | 35 | return Livewire::test($stepClass, $wizard->getCurrentStepState($stepClass)) 36 | ->emitEvents()->in($wizardComponent); 37 | }); 38 | 39 | Testable::macro('emitEvents', function () { 40 | return new EventEmitter($this); 41 | }); 42 | 43 | Testable::macro('getStepState', function (?string $step = null) { 44 | return $this->instance()->getCurrentStepState($step); 45 | }); 46 | } 47 | } 48 | --------------------------------------------------------------------------------