You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

linear-wizard.blade.php 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. @php
  2. $isContained = $isContained();
  3. $statePath = $getStatePath();
  4. $previousAction = $getAction('previous');
  5. $nextAction = $getAction('next');
  6. $currentStepDescription = $getCurrentStepDescription();
  7. $areStepTabsHidden = $areStepTabsHidden();
  8. @endphp
  9. <div
  10. wire:ignore.self
  11. x-cloak
  12. x-data="{
  13. step: null,
  14. nextStep: function () {
  15. let nextStepIndex = this.getStepIndex(this.step) + 1
  16. if (nextStepIndex >= this.getSteps().length) {
  17. return
  18. }
  19. this.step = this.getSteps()[nextStepIndex]
  20. this.autofocusFields()
  21. this.scroll()
  22. },
  23. previousStep: function () {
  24. let previousStepIndex = this.getStepIndex(this.step) - 1
  25. if (previousStepIndex < 0) {
  26. return
  27. }
  28. this.step = this.getSteps()[previousStepIndex]
  29. this.autofocusFields()
  30. this.scroll()
  31. },
  32. scroll: function () {
  33. this.$nextTick(() => {
  34. this.$refs.header.children[
  35. this.getStepIndex(this.step)
  36. ].scrollIntoView({ behavior: 'smooth', block: 'start' })
  37. })
  38. },
  39. autofocusFields: function () {
  40. $nextTick(() =>
  41. this.$refs[`step-${this.step}`]
  42. .querySelector('[autofocus]')
  43. ?.focus(),
  44. )
  45. },
  46. getStepIndex: function (step) {
  47. let index = this.getSteps().findIndex(
  48. (indexedStep) => indexedStep === step,
  49. )
  50. if (index === -1) {
  51. return 0
  52. }
  53. return index
  54. },
  55. getSteps: function () {
  56. return JSON.parse(this.$refs.stepsData.value)
  57. },
  58. isFirstStep: function () {
  59. return this.getStepIndex(this.step) <= 0
  60. },
  61. isLastStep: function () {
  62. return this.getStepIndex(this.step) + 1 >= this.getSteps().length
  63. },
  64. isStepAccessible: function (stepId) {
  65. return (
  66. @js($isSkippable()) || this.getStepIndex(this.step) > this.getStepIndex(stepId)
  67. )
  68. },
  69. updateQueryString: function () {
  70. if (! @js($isStepPersistedInQueryString())) {
  71. return
  72. }
  73. const url = new URL(window.location.href)
  74. url.searchParams.set(@js($getStepQueryStringKey()), this.step)
  75. history.pushState(null, document.title, url.toString())
  76. },
  77. }"
  78. x-init="
  79. $watch('step', () => updateQueryString())
  80. step = getSteps().at({{ $getStartStep() - 1 }})
  81. autofocusFields()
  82. "
  83. x-on:next-wizard-step.window="if ($event.detail.statePath === '{{ $statePath }}') nextStep()"
  84. {{
  85. $attributes
  86. ->merge([
  87. 'id' => $getId(),
  88. ], escape: false)
  89. ->merge($getExtraAttributes(), escape: false)
  90. ->merge($getExtraAlpineAttributes(), escape: false)
  91. ->class([
  92. 'fi-fo-wizard',
  93. 'fi-contained rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10' => $isContained,
  94. ])
  95. }}
  96. >
  97. <input
  98. type="hidden"
  99. value="{{
  100. collect($getChildComponentContainer()->getComponents())
  101. ->filter(static fn (\Filament\Forms\Components\Wizard\Step $step): bool => $step->isVisible())
  102. ->map(static fn (\Filament\Forms\Components\Wizard\Step $step) => $step->getId())
  103. ->values()
  104. ->toJson()
  105. }}"
  106. x-ref="stepsData"
  107. />
  108. @foreach ($getChildComponentContainer()->getComponents() as $step)
  109. <div
  110. x-ref="step-{{ $step->getId() }}"
  111. wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $step->getId() }}.step"
  112. x-bind:class="{ 'hidden': step !== '{{ $step->getId() }}' }"
  113. >
  114. <x-filament::section
  115. :id="'wizard-step-{{ $step->getId() }}'"
  116. :icon="$step->getIcon()"
  117. >
  118. @if (!$step->isLabelHidden())
  119. <x-slot name="heading">
  120. {{ $step->getLabel() }}
  121. </x-slot>
  122. @endif
  123. @if (filled($description = $step->getDescription()))
  124. <x-slot name="description">
  125. {{ $description }}
  126. </x-slot>
  127. @endif
  128. {{ $step->getChildComponentContainer() }}
  129. <footer
  130. @class([
  131. 'fi-section-footer py-6',
  132. ])
  133. >
  134. <div
  135. @class([
  136. 'flex items-center justify-between gap-x-3',
  137. ])
  138. >
  139. <span
  140. x-cloak
  141. @if (! $previousAction->isDisabled())
  142. x-on:click="previousStep"
  143. @endif
  144. x-show="! isFirstStep()"
  145. >
  146. {{ $previousAction }}
  147. </span>
  148. <span x-show="isFirstStep()">
  149. {{ $getCancelAction() }}
  150. </span>
  151. <span
  152. x-cloak
  153. @if (! $nextAction->isDisabled())
  154. x-on:click="
  155. $wire.dispatchFormEvent(
  156. 'wizard::nextStep',
  157. '{{ $statePath }}',
  158. getStepIndex(step),
  159. )
  160. "
  161. @endif
  162. x-bind:class="{ 'hidden': isLastStep(), 'block': ! isLastStep() }"
  163. >
  164. {{ $nextAction }}
  165. </span>
  166. <span
  167. x-bind:class="{ 'hidden': ! isLastStep(), 'block': isLastStep() }"
  168. >
  169. {{ $getSubmitAction() }}
  170. </span>
  171. </div>
  172. </footer>
  173. </x-filament::section>
  174. </div>
  175. @endforeach
  176. </div>