Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[p-wizard] Skip navigations #1209

Merged
merged 8 commits into from
Apr 16, 2024
10 changes: 9 additions & 1 deletion demo/sections/components/Wizard.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<template>
<ComponentPage title="Wizard" :demos="[{ title: 'Wizard' }]">
<template #description>
<p-checkbox v-model="nonlinear" label="Nonlinear" />
<p-checkbox v-model="showSaveAndExit" label="Show Save & Exit" />
</template>

<template #wizard>
<p-wizard :steps="steps" @next="next" @submit="submit">
<p-wizard :steps :nonlinear :show-save-and-exit @next="next" @submit="submit">
<template #basic-information>
<StepOne v-model="formData.favoriteColor" />
</template>
Expand All @@ -24,6 +29,9 @@
import StepThree from '@/demo/components/wizard/StepThree.vue'
import StepTwo from '@/demo/components/wizard/StepTwo.vue'
const nonlinear = ref(false)
const showSaveAndExit = ref(false)
const steps: WizardStep[] = [
{ title: 'Basic Information' },
{ title: 'Terms and Conditions' },
Expand Down
27 changes: 24 additions & 3 deletions src/components/Wizard/PWizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
:steps="steps"
:loading="loading"
:current-step-index="currentStepIndex"
:nonlinear
/>
</PCard>

Expand All @@ -31,9 +32,16 @@
<p-button :disabled="isOnFirstStep" @click="handlePreviousButtonClick">
Previous
</p-button>
<p-button primary :loading="loading" @click="handleNextButtonClick">
<p-button :primary="!showingExtraActions" :loading="loading" @click="handleNextButtonClick">
{{ nextButtonText }}
</p-button>

<template v-if="showingExtraActions">
<span class="border-l border-divider mx-2" />
<p-button primary @click="saveAndExit">
Save & Exit
</p-button>
</template>
</slot>
</div>
<slot name="footer" />
Expand All @@ -47,14 +55,16 @@
import PCard from '@/components/Card/PCard.vue'
import PWizardHeaders from '@/components/Wizard/PWizardHeaders.vue'
import PWizardStep from '@/components/Wizard/PWizardStep.vue'
import { useWizard } from '@/compositions/wizard'
import { createWizard } from '@/compositions/wizard'
import { WizardStep } from '@/types/wizard'
import { getStepKey } from '@/utilities/wizard'

const props = withDefaults(defineProps<{
steps: WizardStep[],
showCancel?: boolean,
lastStepText?: string,
nonlinear?: boolean,
showSaveAndExit?: boolean,
}>(), {
lastStepText: 'Submit',
})
Expand All @@ -76,7 +86,7 @@
getStep,
setStep,
isValid,
} = useWizard(props.steps)
} = createWizard(props.steps)

defineExpose({
steps,
Expand All @@ -98,6 +108,8 @@

const nextButtonText = computed(() => isOnLastStep.value ? props.lastStepText : 'Next')

const showingExtraActions = computed(() => props.showSaveAndExit && !isOnLastStep.value)

async function handlePreviousButtonClick(): Promise<void> {
const { success } = await previous()

Expand All @@ -122,6 +134,15 @@
}
}

async function saveAndExit(): Promise<void> {
const { success } = await next()

if (success) {
emit('next')
emit('submit')
}
}

function setLoading(value: boolean): void {
loading.value = value
}
Expand Down
16 changes: 14 additions & 2 deletions src/components/Wizard/PWizardHeaders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
:index="index"
:current="index === currentStepIndex"
:loading="loading && index === currentStepIndex"
:complete="index < currentStepIndex"
:complete="index < wizard.furthestStepIndex.value"
@click="handleStepHeaderClick(index)"
>
<template #default="data">
<slot :name="`${getStepKey(step)}-heading`" v-bind="data" />
Expand All @@ -22,19 +23,30 @@
import { useChildrenAreWrapped } from '@prefecthq/vue-compositions'
import { computed, ref } from 'vue'
import PWizardStepHeader from '@/components/Wizard/PWizardStepHeader.vue'
import { useWizard } from '@/compositions'
import { WizardStep } from '@/types/wizard'
import { getStepKey } from '@/utilities/wizard'
defineProps<{
const props = defineProps<{
steps: WizardStep[],
currentStepIndex: number,
loading: boolean,
nonlinear?: boolean,
}>()
const container = ref<HTMLDivElement>()
const children = ref<HTMLSpanElement[]>([])
const wrapped = useChildrenAreWrapped(children, container)
const wizard = useWizard()
function handleStepHeaderClick(index: number): void {
if (!props.nonlinear && index > wizard.furthestStepIndex.value) {
return
}
wizard.goto(index + 1)
}
const classes = computed(() => ({
container: {
'p-wizard-headers--wrapped': wrapped.value,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Wizard/PWizardStepHeader.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="p-wizard-step-header" :class="classes">
<button class="p-wizard-step-header" :class="classes" type="button">
<div class="p-wizard-step-header__index">
<template v-if="complete">
<PIcon icon="CheckIcon" />
Expand All @@ -16,7 +16,7 @@
{{ step.title }}
</slot>
</div>
</div>
</button>
</template>

<script lang="ts" setup>
Expand Down
25 changes: 18 additions & 7 deletions src/compositions/wizard/useWizard.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/* eslint-disable no-redeclare */
import { computed, InjectionKey, provide, Ref, ref } from 'vue'
import { computed, inject, InjectionKey, provide, Ref, ref } from 'vue'
import { WizardNotFound } from '@/models'
import { WizardStep, UseWizard, ValidationState, WizardNavigation } from '@/types/wizard'
import { getStepKey } from '@/utilities/wizard'

export const useWizardKey: InjectionKey<UseWizard> = Symbol('UseWizard')

export function useWizard(steps: WizardStep[] | Ref<WizardStep[]>): UseWizard {
export function createWizard(steps: WizardStep[] | Ref<WizardStep[]>): UseWizard {
const loading = ref(false)
const stepsRef = ref(steps)
const currentStepIndex = ref(0)
const furthestStepIndex = ref(0)
const currentStep = computed(() => stepsRef.value[currentStepIndex.value])

function next(): Promise<WizardNavigation> {
Expand Down Expand Up @@ -57,17 +59,17 @@ export function useWizard(steps: WizardStep[] | Ref<WizardStep[]>): UseWizard {
}

function setCurrentStepIndex(index: number): void {
let newIndex = index
if (index < 0) {
currentStepIndex.value = 0
return
newIndex = 0
}

if (index >= stepsRef.value.length) {
currentStepIndex.value = stepsRef.value.length - 1
return
newIndex = stepsRef.value.length - 1
}

currentStepIndex.value = index
currentStepIndex.value = newIndex
furthestStepIndex.value = Math.max(furthestStepIndex.value, newIndex)
}

function getZeroBasedIndex(index: number): number {
Expand Down Expand Up @@ -138,6 +140,7 @@ export function useWizard(steps: WizardStep[] | Ref<WizardStep[]>): UseWizard {
steps: stepsRef,
currentStepIndex,
currentStep,
furthestStepIndex,
loading,
next,
previous,
Expand All @@ -150,5 +153,13 @@ export function useWizard(steps: WizardStep[] | Ref<WizardStep[]>): UseWizard {

provide(useWizardKey, wizard)

return wizard
}

export function useWizard(): UseWizard {
const wizard = inject(useWizardKey)
if (!wizard) {
throw new WizardNotFound()
}
return wizard
}
18 changes: 4 additions & 14 deletions src/compositions/wizard/useWizardStep.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable no-redeclare */
import { inject, InjectionKey, ref, Ref, computed } from 'vue'
import { useWizardKey } from '@/compositions/wizard/useWizard'
import { WizardNotFound, WizardStepNotFound } from '@/models/wizard'
import { UseWizard, UseWizardStep, WizardStepValidator } from '@/types/wizard'
import { useWizard } from '@/compositions/wizard/useWizard'
import { WizardStepNotFound } from '@/models/wizard'
import { UseWizardStep, WizardStepValidator } from '@/types/wizard'

export const useWizardStepKey: InjectionKey<UseWizardStep> = Symbol('UseWizardStep')

Expand All @@ -17,19 +17,9 @@ export function useWizardStep(key?: string | Ref<string>): UseWizardStep {
return step
}

const wizard = getWizard()
const wizard = useWizard()
const keyRef = ref(key)

function getWizard(): UseWizard {
const wizardOrUndefined = inject(useWizardKey)

if (!wizardOrUndefined) {
throw new WizardNotFound()
}

return wizardOrUndefined
}

const step = computed(() => {
const value = wizard.getStep(keyRef.value)

Expand Down
2 changes: 1 addition & 1 deletion src/models/wizard/wizardNotFound.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export class WizardNotFound extends Error {
public constructor() {
super('Wizard not found. Are you sure the component calling useWizardStep() exists within a <p-wizard>?')
super('Wizard not found. Are you sure the component calling useWizard() exists within a <p-wizard>?')
}
}
1 change: 1 addition & 0 deletions src/types/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type UseWizard = {
steps: Ref<WizardStep[]>,
currentStepIndex: Ref<number>,
currentStep: Ref<WizardStep | undefined>,
furthestStepIndex: Ref<number>,
loading: Ref<boolean>,
next: () => Promise<WizardNavigation>,
previous: () => Promise<WizardNavigation>,
Expand Down
Loading