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

feat(useStepper): new function #1754

Merged
merged 14 commits into from Jul 6, 2022
1 change: 1 addition & 0 deletions packages/core/index.ts
Expand Up @@ -94,6 +94,7 @@ export * from './useSessionStorage'
export * from './useShare'
export * from './useSpeechRecognition'
export * from './useSpeechSynthesis'
export * from './useStepper'
export * from './useStorage'
export * from './useStorageAsync'
export * from './useStyleTag'
Expand Down
118 changes: 118 additions & 0 deletions packages/core/useStepper/demo.vue
@@ -0,0 +1,118 @@
<script setup lang="ts">
import { useStepper } from '@vueuse/core'
import { reactive } from 'vue'

const form = reactive({
firstName: 'Jon',
lastName: '',
billingAddress: '',
contractAccepted: false,
carbonOffsetting: false,
payment: 'credit-card' as 'paypal' | 'credit-card',
})

const stepper = useStepper({
'user-information': {
title: 'User information',
isValid: () => form.firstName && form.lastName,
},
'billing-address': {
title: 'Billing address',
isValid: () => form.billingAddress?.trim() !== '',
},
'terms': {
title: 'Terms',
isValid: () => form.contractAccepted === true,
},
'payment': {
title: 'Payment',
isValid: () => ['credit-card', 'paypal'].includes(form.payment),
},
})

function submit() {
if (stepper.current.value.isValid())
stepper.goToNext()
}

function allStepsBeforeAreValid(index: number): boolean {
return !Array(index)
.fill(null)
.some((_, i) => !stepper.at(i)?.isValid())
}
</script>

<template>
<div>
<div class="flex gap-2 justify-center">
<div v-for="(step, id, i) in stepper.steps.value" :key="id" class="">
<button
:disabled="!allStepsBeforeAreValid(i) && stepper.isBefore(id)"
@click="stepper.goTo(id)"
v-text="step.title"
/>
</div>
</div>

<form class="mt-10" @submit.prevent="submit">
<span class="text-lg font-bold" v-text="stepper.current.value.title" />
<div class="flex flex-col justify-center gap-2 mt-2">
<div>
<div v-if="stepper.isCurrent('user-information')">
<span>First name:</span>
<input v-model="form.firstName" class="!mt-0.5" type="text">
<span>Last name:</span>
<input v-model="form.lastName" class="!mt-0.5" type="text">
</div>

<div v-if="stepper.isCurrent('billing-address')">
<input v-model="form.billingAddress" type="text">
</div>

<div v-if="stepper.isCurrent('terms')">
<div>
<input id="carbon-offsetting" v-model="form.carbonOffsetting" type="checkbox" class="mr-2">
<label for="carbon-offsetting">I accept to deposit a carbon offsetting fee</label>
</div>
<div>
<input id="contract" v-model="form.contractAccepted" type="checkbox" class="mr-2">
<label for="contract">I accept the terms of the contract</label>
</div>
</div>

<div v-if="stepper.isCurrent('payment')">
<div>
<input id="credit-card" v-model="form.payment" type="radio" class="mr-2" value="credit-card">
<label for="credit-card">Credit card</label>
</div>
<div>
<input id="paypal" v-model="form.payment" type="radio" class="mr-2" value="paypal">
<label for="paypal">PayPal</label>
</div>
</div>
</div>

<div>
<button v-if="!stepper.isLast" :disabled="!stepper.current.value.isValid()">
Next
</button>
<button v-if="stepper.isLast" :disabled="!stepper.current.value.isValid()">
Submit
</button>
</div>
</div>
</form>

<div class="flex flex-col gap-4 mt-12">
<div class="w-full px-4 py-2 rounded border border-main space-y-2 overflow-auto h-full">
<span class="font-bold">Form</span>
<pre v-text="form" />
</div>

<div class="w-full px-4 py-2 rounded border border-main space-y-2 overflow-auto h-full">
<span class="font-bold">Wizard</span>
<pre v-text="stepper" />
</div>
</div>
</div>
</template>
84 changes: 84 additions & 0 deletions packages/core/useStepper/index.md
@@ -0,0 +1,84 @@
---
category: Utilities
---

# useStepper

Provides helpers for building a multi-step wizard interface.

## Usage

### Steps as array

```js
import { useStepper } from '@vueuse/core'

const {
steps,
stepNames,
index,
current,
next,
previous,
isFirst,
isLast,
goTo,
goNext,
goPrevious,
goBackTo,
isNext,
isPrevious,
isCurrent,
isBefore,
isAfter,
} = useStepper([
'billing-address',
'terms',
'payment',
])

// Access the step through `current`
console.log(current.value) // 'billing-address'
```

### Steps as object

```js
import { useStepper } from '@vueuse/core'

const {
steps,
stepNames,
index,
current,
next,
previous,
isFirst,
isLast,
goTo,
goNext,
goPrevious,
goBackTo,
isNext,
isPrevious,
isCurrent,
isBefore,
isAfter,
} = useStepper({
'user-information': {
title: 'User information',
},
'billing-address': {
title: 'Billing address',
},
'terms': {
title: 'Terms',
},
'payment': {
title: 'Payment',
},
})

// Access the step object through `current`
console.log(current.value.title) // 'User information'
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also have a short example of object usage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it 👍