From a8ade4a9a5c833b4e930811addafd781e91754c9 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 11 Feb 2025 23:01:42 +0800 Subject: [PATCH 1/5] feat: add metadata api to vue binding --- packages/vue/src/define-stepper.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/vue/src/define-stepper.ts b/packages/vue/src/define-stepper.ts index 76a6668..f7bcbf7 100644 --- a/packages/vue/src/define-stepper.ts +++ b/packages/vue/src/define-stepper.ts @@ -1,8 +1,9 @@ -import type { Get, Step, Stepper } from "@stepperize/core"; +import type { Get, Metadata, Step, Stepper } from "@stepperize/core"; import { executeStepCallback, generateCommonStepperUseFns, generateStepperUtils, + getInitialMetadata, getInitialStepIndex, } from "@stepperize/core"; import { @@ -24,8 +25,13 @@ export const defineStepper = (...steps: Steps): Step const utils = generateStepperUtils(...steps); - const useStepper = (initialStep?: MaybeRefOrGetter>) => { + const useStepper = ( + initialStep?: MaybeRefOrGetter>, + initialMetadata?: Partial, Metadata>> + ) => { const stepIndex = ref(getInitialStepIndex(steps, toValue(initialStep))); + const metadata = ref(getInitialMetadata(steps, initialMetadata)); + watch( () => toValue(initialStep), (value) => { @@ -47,6 +53,17 @@ export const defineStepper = (...steps: Steps): Step current: currentStep, isLast, isFirst, + metadata: metadata.value, + setMetadata(id, data) { + if (metadata.value[id] === data) return + metadata.value[id] = data + }, + getMetadata(id) { + return metadata.value[id]; + }, + resetMetadata(keepInitialMetadata) { + metadata.value = getInitialMetadata(steps, keepInitialMetadata ? initialMetadata : undefined); + }, async beforeNext(callback) { if (isLast) { throw new Error("Cannot navigate to the next step because it is the last step."); @@ -91,6 +108,14 @@ export const defineStepper = (...steps: Steps): Step get(id) { return steps.find((step) => step.id === id); }, + async beforeGoTo(id, callback) { + const shouldProceed = await executeStepCallback(callback, true); + if (shouldProceed) this.goTo(id); + }, + async afterGoTo(id, callback) { + this.goTo(id); + await executeStepCallback(callback, false); + }, goTo(id) { const index = steps.findIndex((s) => s.id === id); stepIndex.value = index; From c6d1c0b88a24ea735bddf47bc837fd71deafabfb Mon Sep 17 00:00:00 2001 From: Alex <49969959+alexzhang1030@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:04:36 +0800 Subject: [PATCH 2/5] Create strange-singers-fail.md --- .changeset/strange-singers-fail.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strange-singers-fail.md diff --git a/.changeset/strange-singers-fail.md b/.changeset/strange-singers-fail.md new file mode 100644 index 0000000..84e5dc4 --- /dev/null +++ b/.changeset/strange-singers-fail.md @@ -0,0 +1,5 @@ +--- +"@stepperize/vue": major +--- + +feat: add metadata api to vue binding From 873ec5a898c3d7ee9bc9a087bc7ef6f7edb6915b Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 11 Feb 2025 23:21:04 +0800 Subject: [PATCH 3/5] feat: add migration guide --- apps/docs/content/docs/vue/meta.json | 4 +- .../docs/vue/migration/migrating-to-v2.mdx | 22 ++++++ packages/vue/src/define-stepper.ts | 24 +++---- packages/vue/src/types.ts | 67 ++++++++++--------- 4 files changed, 73 insertions(+), 44 deletions(-) create mode 100644 apps/docs/content/docs/vue/migration/migrating-to-v2.mdx diff --git a/apps/docs/content/docs/vue/meta.json b/apps/docs/content/docs/vue/meta.json index 083838f..5a8837e 100644 --- a/apps/docs/content/docs/vue/meta.json +++ b/apps/docs/content/docs/vue/meta.json @@ -10,6 +10,8 @@ "api-references/define", "api-references/composable", "api-references/scoped", - "api-references/utils" + "api-references/utils", + "---Migration---", + "migration/migrating-to-v2" ] } diff --git a/apps/docs/content/docs/vue/migration/migrating-to-v2.mdx b/apps/docs/content/docs/vue/migration/migrating-to-v2.mdx new file mode 100644 index 0000000..6700adf --- /dev/null +++ b/apps/docs/content/docs/vue/migration/migrating-to-v2.mdx @@ -0,0 +1,22 @@ +--- +title: Migrating to v2 +description: Learn how to migrate from v1 to v2 +icon: CircleFadingArrowUp +--- + +`@stepperize/vue` adds a new `metadata` API to the `useStepper` composable. + +## Breaking Changes + +### Name changes in the values returned by the composable + +Now the parameters of useStepper are not a string indicating the initial step, but rather an object with the following values: + +- `initialStep` The ID of the initial step to display +- `initialMetadata` The initial metadata to set for the steps + +## New Features + +### New `metadata` API + +The `metadata` API allows you to set dynamic metadata for each step. \ No newline at end of file diff --git a/packages/vue/src/define-stepper.ts b/packages/vue/src/define-stepper.ts index f7bcbf7..e63c563 100644 --- a/packages/vue/src/define-stepper.ts +++ b/packages/vue/src/define-stepper.ts @@ -25,15 +25,15 @@ export const defineStepper = (...steps: Steps): Step const utils = generateStepperUtils(...steps); - const useStepper = ( - initialStep?: MaybeRefOrGetter>, - initialMetadata?: Partial, Metadata>> - ) => { - const stepIndex = ref(getInitialStepIndex(steps, toValue(initialStep))); - const metadata = ref(getInitialMetadata(steps, initialMetadata)); + const useStepper = (config?: { + initialStep?: MaybeRefOrGetter>; + initialMetadata?: Partial, Metadata>>; + }) => { + const stepIndex = ref(getInitialStepIndex(steps, toValue(config?.initialStep))); + const metadata = ref(getInitialMetadata(steps, toValue(config?.initialMetadata))); watch( - () => toValue(initialStep), + () => toValue(config?.initialStep), (value) => { stepIndex.value = getInitialStepIndex(steps, value); }, @@ -62,7 +62,7 @@ export const defineStepper = (...steps: Steps): Step return metadata.value[id]; }, resetMetadata(keepInitialMetadata) { - metadata.value = getInitialMetadata(steps, keepInitialMetadata ? initialMetadata : undefined); + metadata.value = getInitialMetadata(steps, keepInitialMetadata ? config?.initialMetadata : undefined); }, async beforeNext(callback) { if (isLast) { @@ -121,7 +121,7 @@ export const defineStepper = (...steps: Steps): Step stepIndex.value = index; }, reset() { - stepIndex.value = getInitialStepIndex(steps, toValue(initialStep)); + stepIndex.value = getInitialStepIndex(steps, toValue(config?.initialStep)); }, ...generateCommonStepperUseFns(steps, currentStep, currentStepIndex), } as Stepper; @@ -134,11 +134,11 @@ export const defineStepper = (...steps: Steps): Step steps, utils, Scoped: defineComponent>((props, { slots }) => { - provide(contextKey, useStepper(props.initialStep)); + provide(contextKey, useStepper(props)); return () => slots.default?.(); }), - useStepper(initialStep) { - return inject(contextKey) ?? useStepper(initialStep); + useStepper(props = {}) { + return inject(contextKey) ?? useStepper(props); }, }; }; diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index 504c5e9..d926a18 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -1,5 +1,5 @@ -import type { Get, Step, Stepper, Utils } from "@stepperize/core"; -import type { Component, ComputedRef } from "vue"; +import type { Get, Metadata, Step, Stepper, Utils } from "@stepperize/core"; +import type { Component, ComputedRef, MaybeRefOrGetter } from "vue"; export type ScopedProps = { /** The initial step to display. */ @@ -7,33 +7,38 @@ export type ScopedProps = { }; export type StepperReturn = { - /** The steps of the stepper. */ - steps: Steps; - /** - * `utils` provides helper functions to interact with steps in the stepper. - * These functions allow you to get steps by their ID or index, get the first and last steps, - * and navigate through the steps by retrieving neighbors or adjacent steps. - * - * @returns An object containing utility methods to interact with the steps - */ - utils: Utils; - /** - * `Scoped` component is a wrapper that provides the stepper context to its children. - * It uses the `Context` to pass the stepper instance to the children. - * - * @param props - The props object containing `initialStep` and `children`. - * @param props.initialStep - The ID of the step to start with (optional). - * @param props.children - The child elements to be wrapped by the `Scoped` component. - * @returns A Vue VNode that wraps the children with the stepper context. - */ - Scoped: Component>; - /** - * `useStepper` composable returns an object that manages the current step in the stepper. - * You can use this composable to get the current step, navigate to the next or previous step, - * and reset the stepper to the initial state. - * - * @param initialStep - The ID of the step to start with (optional). - * @returns An object containing properties and methods to interact with the stepper. - */ - useStepper: (initialStep?: Get.Id) => ComputedRef>; + /** The steps of the stepper. */ + steps: Steps; + /** + * `utils` provides helper functions to interact with steps in the stepper. + * These functions allow you to get steps by their ID or index, get the first and last steps, + * and navigate through the steps by retrieving neighbors or adjacent steps. + * + * @returns An object containing utility methods to interact with the steps + */ + utils: Utils; + /** + * `Scoped` component is a wrapper that provides the stepper context to its children. + * It uses the `Context` to pass the stepper instance to the children. + * + * @param props - The props object containing `initialStep` and `children`. + * @param props.initialStep - The ID of the step to start with (optional). + * @param props.initialMetadata - The initial metadata (optional). + * @param props.children - The child elements to be wrapped by the `Scoped` component. + * @returns A Vue VNode that wraps the children with the stepper context. + */ + Scoped: Component>; + /** + * `useStepper` composable returns an object that manages the current step in the stepper. + * You can use this composable to get the current step, navigate to the next or previous step, + * and reset the stepper to the initial state. + * + * @param initialStep - The ID of the step to start with (optional). + * @param initialMetadata - The initial metadata (optional). + * @returns An object containing properties and methods to interact with the stepper. + */ + useStepper: (props?: { + initialStep?: MaybeRefOrGetter>; + initialMetadata?: Partial, Metadata>>; + }) => ComputedRef>; }; From ec708dd41037c290b472f38221b24bfe26b852ce Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 11 Feb 2025 23:33:13 +0800 Subject: [PATCH 4/5] docs: sync --- .../docs/react/api-references/hook.mdx | 2 + .../docs/vue/api-references/composable.mdx | 134 +++++++++++++++--- 2 files changed, 117 insertions(+), 19 deletions(-) diff --git a/apps/docs/content/docs/react/api-references/hook.mdx b/apps/docs/content/docs/react/api-references/hook.mdx index 8458884..81e177b 100644 --- a/apps/docs/content/docs/react/api-references/hook.mdx +++ b/apps/docs/content/docs/react/api-references/hook.mdx @@ -360,6 +360,8 @@ stepper.resetMetadata(true | false); | afterNext | `(callback: () => Promise \| void) => void` | Executes a function after moving to the next step | | beforePrev | `(callback: () => Promise \| boolean) => void` | Executes a function before moving to the previous step | | afterPrev | `(callback: () => Promise \| void) => void` | Executes a function after moving to the previous step | +| beforeGoTo | `(id: string, callback: () => Promise \| boolean) => void` | Executes a function before moving to a specific step | +| afterGoTo | `(id: string, callback: () => Promise \| void) => void` | Executes a function after moving to a specific step | | next | `() => void` | Advances to the next step | | prev | `() => void` | Returns to the previous step | | get | `(id: string) => Step` | Returns a step by its ID | diff --git a/apps/docs/content/docs/vue/api-references/composable.mdx b/apps/docs/content/docs/vue/api-references/composable.mdx index dab0aa1..44f7515 100644 --- a/apps/docs/content/docs/vue/api-references/composable.mdx +++ b/apps/docs/content/docs/vue/api-references/composable.mdx @@ -306,24 +306,120 @@ It returns a promise that resolves to a void value. In case you don't need a promise, you can use the `afterPrev` returning a void value. -## API Reference +### beforeGoTo + +The `beforeGoTo` function allows you to execute a function before moving to a specific step. +It returns a promise that resolves to a boolean value. If the promise resolves to true, +the stepper will move to the specific step. If the promise resolves to false, the stepper +will not move to the specific step. -| Name | Type | Description | -| ---------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| all | `Step[]` | Returns all steps | -| current | `Step` | Returns the current step | -| isLast | `boolean` | Returns true if the current step is the last step | -| isFirst | `boolean` | Returns true if the current step is the first step | -| beforeNext | `(callback: () => Promise \| boolean) => void` | Executes a function before moving to the next step | -| afterNext | `(callback: () => Promise \| void) => void` | Executes a function after moving to the next step | -| beforePrev | `(callback: () => Promise \| boolean) => void` | Executes a function before moving to the previous step | -| afterPrev | `(callback: () => Promise \| void) => void` | Executes a function after moving to the previous step | -| next | `() => void` | Advances to the next step | -| prev | `() => void` | Returns to the previous step | -| get | `(id: string) => Step` | Returns a step by its ID | -| goTo | `(id: string) => void` | Navigates to a specific step by its ID | -| reset | `() => void` | Resets the stepper to its initial state | -| when | `(id: string, whenFn: (step: Step) => React.ReactNode, elseFn?: (step: Step) => React.ReactNode) => React.ReactNode` | Executes a function based on the current step ID | -| switch | `(steps: { [id: string]: (step: Step) => React.ReactNode }) => React.ReactNode` | Executes a function based on a switch-case-like structure for steps | -| match | `(state: string, steps: { [id: string]: (step: Step) => React.ReactNode }) => React.ReactNode` | Matches the current state with a set of possible states and executes the corresponding function | +```vue + + ``` + +### afterGoTo + +The `afterGoTo` function allows you to execute a function after moving to a specific step. +It returns a promise that resolves to a void value. + +```vue + + +``` + +## Metadata + +The metadata is a way to add custom data to a step. It is useful for adding dynamic values to a step, such as a value fetched from a server or any dynamic state in your application. + +In order to add metadata to your stepper, you can add initial metadata in the `useStepper` composable. + +```tsx +const stepper = useStepper({ + initialMetadata: { + first: { + value: "1", + }, + }, +}); +``` + +This metadata will be available in the `metadata` property of the `useStepper` composable and you can also use methods to set, get and reset the metadata. + +### setMetadata + +Allows you to set the metadata for a step. + +```tsx +stepper.setMetadata("first", { + value: "1", +}); +``` + +### getMetadata + +The `getMetadata` method allows you to get the metadata for a step. + +```tsx +const metadata = stepper.getMetadata("first"); +``` + +### resetMetadata + +The `resetMetadata` method allows you to reset the metadata for a step. You can also pass a boolean value to keep the initial metadata defined in the `useStepper` composable. + +```tsx +stepper.resetMetadata(true | false); +``` + +## API Reference + +| Name | Type | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| all | `Step[]` | Returns all steps | +| current | `Step` | Returns the current step | +| isLast | `boolean` | Returns true if the current step is the last step | +| isFirst | `boolean` | Returns true if the current step is the first step | +| metadata | `Record` | Returns the metadata for the current step | +| setMetadata | `(id: string, values: Metadata) => void` | Sets the metadata for a step | +| getMetadata | `(id: string) => Metadata` | Returns the metadata for a step | +| resetMetadata | `(keepInitialMetadata?: boolean) => void` | Resets the metadata. If keepInitialMetadata is true, the initial metadata defined in the useStepper composable will be kept. | +| beforeNext | `(callback: () => Promise \| boolean) => void` | Executes a function before moving to the next step | +| afterNext | `(callback: () => Promise \| void) => void` | Executes a function after moving to the next step | +| beforePrev | `(callback: () => Promise \| boolean) => void` | Executes a function before moving to the previous step | +| afterPrev | `(callback: () => Promise \| void) => void` | Executes a function after moving to the previous step | +| beforeGoTo | `(id: string, callback: () => Promise \| boolean) => void` | Executes a function before moving to a specific step | +| afterGoTo | `(id: string, callback: () => Promise \| void) => void` | Executes a function after moving to a specific step | +| next | `() => void` | Advances to the next step | +| prev | `() => void` | Returns to the previous step | +| get | `(id: string) => Step` | Returns a step by its ID | +| goTo | `(id: string) => void` | Navigates to a specific step by its ID | +| reset | `() => void` | Resets the stepper to its initial state | +| when | `(id: string, whenFn: (step: Step) => React.ReactNode, elseFn?: (step: Step) => React.ReactNode) => React.ReactNode` | Executes a function based on the current step ID | +| switch | `(steps: { [id: string]: (step: Step) => React.ReactNode }) => React.ReactNode` | Executes a function based on a switch-case-like structure for steps | +| match | `(state: string, steps: { [id: string]: (step: Step) => React.ReactNode }) => React.ReactNode` | Matches the current state with a set of possible states and executes the corresponding function | From ef3593b999fbbea69823bd230152f6e463b162f4 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 11 Feb 2025 23:37:58 +0800 Subject: [PATCH 5/5] fix: docs --- apps/docs/content/docs/vue/api-references/composable.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/docs/vue/api-references/composable.mdx b/apps/docs/content/docs/vue/api-references/composable.mdx index 44f7515..1fe6290 100644 --- a/apps/docs/content/docs/vue/api-references/composable.mdx +++ b/apps/docs/content/docs/vue/api-references/composable.mdx @@ -376,7 +376,7 @@ This metadata will be available in the `metadata` property of the `useStepper` c Allows you to set the metadata for a step. ```tsx -stepper.setMetadata("first", { +stepper.value.setMetadata("first", { value: "1", }); ``` @@ -386,7 +386,7 @@ stepper.setMetadata("first", { The `getMetadata` method allows you to get the metadata for a step. ```tsx -const metadata = stepper.getMetadata("first"); +const metadata = stepper.value.getMetadata("first"); ``` ### resetMetadata @@ -394,7 +394,7 @@ const metadata = stepper.getMetadata("first"); The `resetMetadata` method allows you to reset the metadata for a step. You can also pass a boolean value to keep the initial metadata defined in the `useStepper` composable. ```tsx -stepper.resetMetadata(true | false); +stepper.value.resetMetadata(true | false); ``` ## API Reference