From 0d07c593fc72ba56afe9245700a29a03aa8c6f41 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Fri, 10 Jun 2022 16:54:29 +0200 Subject: [PATCH 01/12] feat(useWizard): new function --- packages/contributors.json | 6 ++ packages/core/index.ts | 1 + packages/core/useWizard/demo.vue | 103 ++++++++++++++++++++++++++ packages/core/useWizard/index.md | 15 ++++ packages/core/useWizard/index.test.ts | 44 +++++++++++ packages/core/useWizard/index.ts | 83 +++++++++++++++++++++ 6 files changed, 252 insertions(+) create mode 100644 packages/core/useWizard/demo.vue create mode 100644 packages/core/useWizard/index.md create mode 100644 packages/core/useWizard/index.test.ts create mode 100644 packages/core/useWizard/index.ts diff --git a/packages/contributors.json b/packages/contributors.json index 3383703c230..1ca00e0e274 100644 --- a/packages/contributors.json +++ b/packages/contributors.json @@ -58,6 +58,7 @@ "lpj145", "maxma241", "xizher", + "nhedger", "g-plane", "coolhome", "herrmannplatz", @@ -114,6 +115,7 @@ "Maiquu", "Danmer", "emilsgulbis", + "innocenzi", "iendeavor", "web2033", "sp1ker", @@ -132,6 +134,7 @@ "ilkome", "iagafonov", "ivanq3w", + "jd-solanki", "jairoblatt", "Jamiewarb", "itpropro", @@ -147,6 +150,7 @@ "joaoeudes7", "xvaara", "DrJume", + "Julien-Martin", "kidonng", "kirklin", "Spice-King", @@ -192,6 +196,7 @@ "Atinux", "thalesagapito", "thierrymichel", + "LeSuisse", "tmkx", "tobyzerner", "livthomas", @@ -204,6 +209,7 @@ "vhhsyt", "chaii3", "r1ader", + "hsyq", "hchlq", "iGalaxyz", "winter-ice", diff --git a/packages/core/index.ts b/packages/core/index.ts index 5707de41c3b..fb68196b030 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -118,6 +118,7 @@ export * from './useWebWorkerFn' export * from './useWindowFocus' export * from './useWindowScroll' export * from './useWindowSize' +export * from './useWizard' export * from './types' export * from '@vueuse/shared' export * from './ssr-handlers' diff --git a/packages/core/useWizard/demo.vue b/packages/core/useWizard/demo.vue new file mode 100644 index 00000000000..ccea757e76e --- /dev/null +++ b/packages/core/useWizard/demo.vue @@ -0,0 +1,103 @@ + + + diff --git a/packages/core/useWizard/index.md b/packages/core/useWizard/index.md new file mode 100644 index 00000000000..728fc5054f8 --- /dev/null +++ b/packages/core/useWizard/index.md @@ -0,0 +1,15 @@ +--- +category: Utilities +--- + +# useWizard + +Provides helpers for building a multi-step wizard interface. + +## Usage + +```js +import { useWizard } from '@vueuse/core' + +const { current, previous, next, steps, isFirst, isLast } = useWizard() +``` diff --git a/packages/core/useWizard/index.test.ts b/packages/core/useWizard/index.test.ts new file mode 100644 index 00000000000..0529ac5c9cb --- /dev/null +++ b/packages/core/useWizard/index.test.ts @@ -0,0 +1,44 @@ +import { useWizard } from '.' + +describe('useWizard', () => { + it('should be defined', () => { + expect(useWizard).toBeDefined() + }) + + it('can navigate through steps', () => { + const { isFirst, isLast, next, previous, current, index, goTo, backTo, isPrevious, isNext, currentStepIs, currentStepIsAfter, currentStepIsBefore } = useWizard([ + 'First step', + 'Second step', + 'Last step', + ] as const) + + expect(index.value).toBe(0) + expect(current.value).toBe('First step') + expect(isFirst.value).toBe(true) + expect(currentStepIs('First step')).toBe(true) + expect(currentStepIs('Second step')).toBe(false) + expect(currentStepIsAfter('Second step')).toBe(false) + + next() + expect(index.value).toBe(1) + expect(current.value).toBe('Second step') + expect(isPrevious('First step')).toBe(true) + expect(isNext('Last step')).toBe(true) + expect(currentStepIsAfter('First step')).toBe(true) + expect(currentStepIsBefore('First step')).toBe(false) + expect(currentStepIsBefore('Last step')).toBe(true) + + previous() + expect(index.value).toBe(0) + expect(current.value).toBe('First step') + + backTo('Last step') // we can't go back to a future step + expect(index.value).toBe(0) + expect(current.value).toBe('First step') + + goTo('Last step') // we can force a step + expect(index.value).toBe(2) + expect(current.value).toBe('Last step') + expect(isLast.value).toBe(true) + }) +}) diff --git a/packages/core/useWizard/index.ts b/packages/core/useWizard/index.ts new file mode 100644 index 00000000000..1ebf44220a6 --- /dev/null +++ b/packages/core/useWizard/index.ts @@ -0,0 +1,83 @@ +import { computed, ref } from 'vue-demi' + +export function useWizard(steps: readonly T[], initial?: T) { + const index = ref(steps.indexOf(initial ?? steps[0])) + const current = computed(() => steps[index.value]) + const isFirst = computed(() => index.value === 0) + const isLast = computed(() => index.value === steps.length - 1) + + function backTo(step: T) { + if (currentStepIsAfter(step)) + goTo(step) + } + + function goTo(step: T) { + index.value = steps.indexOf(step) + } + + function next() { + if (isLast.value) + return + + index.value++ + } + + function previous() { + if (isFirst.value) + return + + index.value-- + } + + function isNext(step: T) { + return steps.indexOf(step) === index.value + 1 + } + + function isPrevious(step: T) { + return steps.indexOf(step) === index.value - 1 + } + + function currentStepIs(step: T) { + return steps.indexOf(step) === index.value + } + + function currentStepIsAfter(step: T) { + return index.value > steps.indexOf(step) + } + + function currentStepIsBefore(step: T) { + return index.value < steps.indexOf(step) + } + + function completed(step: T) { + return currentStepIsAfter(step) + } + + function active(step: T) { + return currentStepIs(step) + } + + function todo(step: T) { + return currentStepIsBefore(step) + } + + return { + backTo, + goTo, + current, + index, + next, + previous, + steps, + currentStepIs, + currentStepIsAfter, + currentStepIsBefore, + completed, + active, + todo, + isFirst, + isLast, + isNext, + isPrevious, + } +} From 2b7eeeba386557ab6f161f3b855cb83f1871cd4d Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Mon, 13 Jun 2022 10:20:23 +0200 Subject: [PATCH 02/12] chore: rename to `useStepper` --- packages/core/index.ts | 2 +- packages/core/{useWizard => useStepper}/demo.vue | 4 ++-- packages/core/useStepper/index.md | 15 +++++++++++++++ .../core/{useWizard => useStepper}/index.test.ts | 8 ++++---- packages/core/{useWizard => useStepper}/index.ts | 2 +- packages/core/useWizard/index.md | 15 --------------- 6 files changed, 23 insertions(+), 23 deletions(-) rename packages/core/{useWizard => useStepper}/demo.vue (97%) create mode 100644 packages/core/useStepper/index.md rename packages/core/{useWizard => useStepper}/index.test.ts (90%) rename packages/core/{useWizard => useStepper}/index.ts (94%) delete mode 100644 packages/core/useWizard/index.md diff --git a/packages/core/index.ts b/packages/core/index.ts index fb68196b030..427e6b42eb2 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -118,7 +118,7 @@ export * from './useWebWorkerFn' export * from './useWindowFocus' export * from './useWindowScroll' export * from './useWindowSize' -export * from './useWizard' +export * from './useStepper' export * from './types' export * from '@vueuse/shared' export * from './ssr-handlers' diff --git a/packages/core/useWizard/demo.vue b/packages/core/useStepper/demo.vue similarity index 97% rename from packages/core/useWizard/demo.vue rename to packages/core/useStepper/demo.vue index ccea757e76e..f0f2a4108d7 100644 --- a/packages/core/useWizard/demo.vue +++ b/packages/core/useStepper/demo.vue @@ -1,8 +1,8 @@ diff --git a/packages/core/useStepper/index.test.ts b/packages/core/useStepper/index.test.ts index 6ce50f26834..5476b7302e1 100644 --- a/packages/core/useStepper/index.test.ts +++ b/packages/core/useStepper/index.test.ts @@ -6,7 +6,7 @@ describe('useStepper', () => { }) it('can navigate through steps', () => { - const { isFirst, isLast, next, previous, current, index, goTo, backTo, isPrevious, isNext, currentStepIs, currentStepIsAfter, currentStepIsBefore } = useStepper([ + const { isFirst, isLast, goToNext, goToPrevious, nextStep, previousStep, current, index, goTo, backTo, isPrevious, isNext, currentStepIs, currentStepIsAfter, currentStepIsBefore } = useStepper([ 'First step', 'Second step', 'Last step', @@ -15,20 +15,24 @@ describe('useStepper', () => { expect(index.value).toBe(0) expect(current.value).toBe('First step') expect(isFirst.value).toBe(true) + expect(nextStep.value).toBe('Second step') + expect(previousStep.value).toBeUndefined() expect(currentStepIs('First step')).toBe(true) expect(currentStepIs('Second step')).toBe(false) expect(currentStepIsAfter('Second step')).toBe(false) - next() + goToNext() expect(index.value).toBe(1) expect(current.value).toBe('Second step') + expect(nextStep.value).toBe('Last step') + expect(previousStep.value).toBe('First step') expect(isPrevious('First step')).toBe(true) expect(isNext('Last step')).toBe(true) expect(currentStepIsAfter('First step')).toBe(true) expect(currentStepIsBefore('First step')).toBe(false) expect(currentStepIsBefore('Last step')).toBe(true) - previous() + goToPrevious() expect(index.value).toBe(0) expect(current.value).toBe('First step') @@ -37,6 +41,8 @@ describe('useStepper', () => { expect(current.value).toBe('First step') goTo('Last step') // we can force a step + expect(nextStep.value).toBeUndefined() + expect(previousStep.value).toBe('Second step') expect(index.value).toBe(2) expect(current.value).toBe('Last step') expect(isLast.value).toBe(true) diff --git a/packages/core/useStepper/index.ts b/packages/core/useStepper/index.ts index e7c990a7647..9d0761ea035 100644 --- a/packages/core/useStepper/index.ts +++ b/packages/core/useStepper/index.ts @@ -5,6 +5,8 @@ export function useStepper(steps: readonly T[], initial?: T) { const current = computed(() => steps[index.value]) const isFirst = computed(() => index.value === 0) const isLast = computed(() => index.value === steps.length - 1) + const nextStep = computed(() => isLast.value ? undefined : steps[index.value + 1]) + const previousStep = computed(() => isFirst.value ? undefined : steps[index.value - 1]) function backTo(step: T) { if (currentStepIsAfter(step)) @@ -15,14 +17,14 @@ export function useStepper(steps: readonly T[], initial?: T) { index.value = steps.indexOf(step) } - function next() { + function goToNext() { if (isLast.value) return index.value++ } - function previous() { + function goToPrevious() { if (isFirst.value) return @@ -54,8 +56,8 @@ export function useStepper(steps: readonly T[], initial?: T) { goTo, current, index, - next, - previous, + goToNext, + goToPrevious, steps, currentStepIs, currentStepIsAfter, @@ -64,5 +66,7 @@ export function useStepper(steps: readonly T[], initial?: T) { isLast, isNext, isPrevious, + nextStep, + previousStep, } } From a610322d586da23edab65cbaf417386a5d5911c8 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Wed, 15 Jun 2022 17:00:47 +0200 Subject: [PATCH 05/12] feat: allow arbitrary step types --- packages/core/useStepper/index.test.ts | 20 ++++++++++++++++++++ packages/core/useStepper/index.ts | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/core/useStepper/index.test.ts b/packages/core/useStepper/index.test.ts index 5476b7302e1..fecdfaab9c2 100644 --- a/packages/core/useStepper/index.test.ts +++ b/packages/core/useStepper/index.test.ts @@ -47,4 +47,24 @@ describe('useStepper', () => { expect(current.value).toBe('Last step') expect(isLast.value).toBe(true) }) + + it('can use objects as steps', () => { + const { goToNext, index, current } = useStepper([ + { id: 1, label: 'First step' }, + { id: 2, label: 'Second step' }, + { id: 3, label: 'Third step' }, + ]) + + expect(index.value).toBe(0) + expect(current.value).toMatchObject({ id: 1, label: 'First step' }) + + goToNext() + expect(index.value).toBe(1) + expect(current.value).toMatchObject({ id: 2, label: 'Second step' }) + + goToNext() + goToNext() + expect(index.value).toBe(2) + expect(current.value).toMatchObject({ id: 3, label: 'Third step' }) + }) }) diff --git a/packages/core/useStepper/index.ts b/packages/core/useStepper/index.ts index 9d0761ea035..eb75a4c246a 100644 --- a/packages/core/useStepper/index.ts +++ b/packages/core/useStepper/index.ts @@ -1,6 +1,6 @@ import { computed, ref } from 'vue-demi' -export function useStepper(steps: readonly T[], initial?: T) { +export function useStepper(steps: readonly T[], initial?: T) { const index = ref(steps.indexOf(initial ?? steps[0])) const current = computed(() => steps[index.value]) const isFirst = computed(() => index.value === 0) From f55a1daa2309c5b4caf01d729bb3d3b19fed084a Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Thu, 16 Jun 2022 11:25:38 +0200 Subject: [PATCH 06/12] refactor: clean up `nextStep` and `previousStep` --- packages/core/useStepper/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/useStepper/index.ts b/packages/core/useStepper/index.ts index eb75a4c246a..288ee2ba7cf 100644 --- a/packages/core/useStepper/index.ts +++ b/packages/core/useStepper/index.ts @@ -5,8 +5,8 @@ export function useStepper(steps: readonly T[], initial?: T) { const current = computed(() => steps[index.value]) const isFirst = computed(() => index.value === 0) const isLast = computed(() => index.value === steps.length - 1) - const nextStep = computed(() => isLast.value ? undefined : steps[index.value + 1]) - const previousStep = computed(() => isFirst.value ? undefined : steps[index.value - 1]) + const nextStep = computed(() => steps[index.value + 1]) + const previousStep = computed(() => steps[index.value - 1]) function backTo(step: T) { if (currentStepIsAfter(step)) From febeb2c800ab29185b2102527d540b2ec0ea6db7 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Thu, 16 Jun 2022 11:36:20 +0200 Subject: [PATCH 07/12] refactor: add JSDocs and remove `backTo` --- packages/core/useStepper/demo.vue | 10 ++++++++- packages/core/useStepper/index.test.ts | 14 +++++-------- packages/core/useStepper/index.ts | 29 +++++++++++++++++--------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/core/useStepper/demo.vue b/packages/core/useStepper/demo.vue index 374b73347dd..5616d57cbc5 100644 --- a/packages/core/useStepper/demo.vue +++ b/packages/core/useStepper/demo.vue @@ -1,13 +1,19 @@