Skip to content

Commit

Permalink
Merge pull request #1209 from PrefectHQ/p-wizard/skip-nav
Browse files Browse the repository at this point in the history
[p-wizard] Skip navigations
  • Loading branch information
collincchoy committed Apr 16, 2024
2 parents f835af1 + 20cda2e commit a1376ee
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 30 deletions.
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

0 comments on commit a1376ee

Please sign in to comment.