2.8 KB144 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {ReactNode} from 'react';
9
10import {useMemo, useState} from 'react';
11
12export type StepConfig<TKey> = Readonly<{
13 /**
14 * Key to identify this step.
15 */
16 key: TKey;
17
18 /**
19 * Label to display (in the stepper, header, etc.)
20 */
21 label: ReactNode;
22}>;
23
24export type MultiStepperContext<TKey> = Readonly<{
25 /* Getters */
26
27 /**
28 * Gets the current step config.
29 */
30 getCurrentStep: () => StepConfig<TKey>;
31
32 /**
33 * Gets the step config for the given key.
34 */
35 getStep: (step: TKey) => StepConfig<TKey> | undefined;
36
37 /**
38 * Gets the index of the current step.
39 */
40 getStepIndex: () => number;
41
42 /**
43 * Gets the total number of steps.
44 */
45 getStepCount: () => number;
46
47 /**
48 * Get all step configs in order.
49 */
50 getAllSteps: () => Array<StepConfig<TKey>>;
51
52 /* Navigation Methods */
53
54 /**
55 * Go to the step at the given index.
56 */
57 goToStep: (index: number) => void;
58
59 /**
60 * Go to the step with the given key.
61 */
62 goToStepByKey: (key: TKey) => void;
63
64 /**
65 * Go to the next step.
66 */
67 goToNextStep: () => void;
68
69 /**
70 * Go to the previous step.
71 */
72 goToPreviousStep: () => void;
73
74 /**
75 * Go to the first step.
76 */
77 goToFirstStep: () => void;
78
79 /**
80 * Go to the last step.
81 */
82 goToLastStep: () => void;
83}>;
84
85/**
86 * Hook to access the current step and navigate to other steps.
87 */
88export function useMultiStepperContext<TKey>(
89 pages: Array<StepConfig<TKey>>,
90): MultiStepperContext<TKey> {
91 const [currentStep, setCurrentStep] = useState<number>(0);
92
93 const stepByKey = useMemo(
94 () => new Map<TKey, number>(pages.map((page, index) => [page.key, index])),
95 [pages],
96 );
97
98 const value = useMemo(
99 () => ({
100 _steps: pages,
101 _currentStep: currentStep,
102 _setCurrentStep: setCurrentStep,
103
104 getCurrentStep: () => pages[currentStep],
105
106 getStep: (step: TKey) => pages.at(stepByKey.get(step) ?? -1),
107
108 getStepIndex: () => currentStep,
109
110 getStepCount: () => pages.length,
111
112 getAllSteps: () => pages,
113
114 goToStep: (index: number) => setCurrentStep(index),
115
116 goToStepByKey: (key: TKey) => {
117 const index = stepByKey.get(key);
118 if (index != null) {
119 setCurrentStep(index);
120 }
121 },
122
123 goToNextStep: () => {
124 if (currentStep < pages.length - 1) {
125 setCurrentStep(currentStep + 1);
126 }
127 },
128
129 goToPreviousStep: () => {
130 if (currentStep > 0) {
131 setCurrentStep(currentStep - 1);
132 }
133 },
134
135 goToFirstStep: () => setCurrentStep(0),
136
137 goToLastStep: () => setCurrentStep(pages.length - 1),
138 }),
139 [currentStep, pages, stepByKey],
140 );
141
142 return value;
143}
144