@snack-uikit/stepper 0.8.44 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 0.9.0 (2026-05-25)
7
+
8
+
9
+ ### Features
10
+
11
+ * **PDS-3775:** add free navigation to stepper ([8b3ba42](https://github.com/cloud-ru-tech/snack-uikit/commit/8b3ba42d948edb29c7c84a2e50c7e6c29c55d029))
12
+
13
+
14
+
15
+
16
+
6
17
  ## 0.8.44 (2026-05-18)
7
18
 
8
19
  ### Only dependencies have been changed
package/README.md CHANGED
@@ -69,6 +69,7 @@ function Example() {
69
69
  | className | `string` | - | CSS-класс |
70
70
  | onChangeCurrentStep | `(newValue: number, prevValue: number) => void` | - | Колбек смены текущего степа |
71
71
  | onCompleteChange | `(isCompleted: boolean) => void` | - | Колбек изменения завершенности |
72
+ | allowFreeNavigation | `boolean` | - | Позволяет свободно переключаться между разными шагами без валидации |
72
73
 
73
74
 
74
75
  [//]: DOCUMENTATION_SECTION_END
@@ -11,6 +11,9 @@ exports.StepperContext = (0, react_1.createContext)({
11
11
  stepCount: 0,
12
12
  isCompleted: false,
13
13
  currentStepIndex: 0,
14
+ goToStep() {
15
+ /* stub */
16
+ },
14
17
  goNext() {
15
18
  /* stub */
16
19
  },
@@ -23,5 +23,7 @@ export type StepperProps = WithSupportProps<{
23
23
  onChangeCurrentStep?: (newValue: number, prevValue: number) => void;
24
24
  /** Колбек изменения завершенности */
25
25
  onCompleteChange?: (isCompleted: boolean) => void;
26
+ /** Позволяет свободно переключаться между разными шагами без валидации */
27
+ allowFreeNavigation?: boolean;
26
28
  }>;
27
- export declare function Stepper({ children, steps, className, onChangeCurrentStep, onCompleteChange, defaultCurrentStepIndex, validator: validatorProp, ...props }: StepperProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function Stepper({ children, steps, className, onChangeCurrentStep, onCompleteChange, defaultCurrentStepIndex, validator: validatorProp, allowFreeNavigation, ...props }: StepperProps): import("react/jsx-runtime").JSX.Element;
@@ -63,9 +63,10 @@ function Stepper(_a) {
63
63
  onChangeCurrentStep,
64
64
  onCompleteChange,
65
65
  defaultCurrentStepIndex = 0,
66
- validator: validatorProp = DEFAULT_VALIDATOR
66
+ validator: validatorProp = DEFAULT_VALIDATOR,
67
+ allowFreeNavigation = false
67
68
  } = _a,
68
- props = __rest(_a, ["children", "steps", "className", "onChangeCurrentStep", "onCompleteChange", "defaultCurrentStepIndex", "validator"]);
69
+ props = __rest(_a, ["children", "steps", "className", "onChangeCurrentStep", "onCompleteChange", "defaultCurrentStepIndex", "validator", "allowFreeNavigation"]);
69
70
  const isCompletedByDefault = defaultCurrentStepIndex === steps.length - 1;
70
71
  const [currentStepState, setCurrentStepState] = (0, react_1.useState)(isCompletedByDefault ? constants_1.STEP_STATE.Completed : constants_1.STEP_STATE.Current);
71
72
  const [currentStepIndex, setCurrentStepIndexValue] = (0, react_1.useState)(defaultCurrentStepIndex);
@@ -105,6 +106,22 @@ function Stepper(_a) {
105
106
  }
106
107
  });
107
108
  }, [currentStepIndex, isCompleted, setCurrentStepIndex, steps.length, stepsValidator === null || stepsValidator === void 0 ? void 0 : stepsValidator.value, validatorProp]);
109
+ const moveToStep = (0, react_1.useCallback)(stepIndex => {
110
+ if (isCompleted) {
111
+ setIsCompleted(false);
112
+ }
113
+ setCurrentStepIndex(stepIndex);
114
+ setCurrentStepState(constants_1.STEP_STATE.Current);
115
+ }, [isCompleted, setCurrentStepIndex]);
116
+ const goToStep = (0, react_1.useCallback)(newStepIndex => {
117
+ if (newStepIndex < 0 || newStepIndex >= steps.length) {
118
+ return;
119
+ }
120
+ if (newStepIndex === currentStepIndex) {
121
+ return;
122
+ }
123
+ moveToStep(newStepIndex);
124
+ }, [currentStepIndex, steps, moveToStep]);
108
125
  const goPrev = (0, react_1.useCallback)(function () {
109
126
  let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : currentStepIndex - 1;
110
127
  if (currentStepIndex === 0 || index < 0 || index > currentStepIndex) {
@@ -113,40 +130,34 @@ function Stepper(_a) {
113
130
  if (index === currentStepIndex && !isCompleted) {
114
131
  return;
115
132
  }
116
- if (isCompleted) {
117
- setIsCompleted(false);
133
+ moveToStep(index);
134
+ }, [currentStepIndex, isCompleted, moveToStep]);
135
+ const getStepOnClick = (0, react_1.useCallback)(stepIndex => {
136
+ if (allowFreeNavigation) {
137
+ return () => goToStep(stepIndex);
118
138
  }
119
- setCurrentStepIndex(index);
120
- setCurrentStepState(constants_1.STEP_STATE.Current);
121
- }, [currentStepIndex, isCompleted, setCurrentStepIndex]);
139
+ if (stepIndex < currentStepIndex || stepIndex === currentStepIndex && isCompleted) {
140
+ return () => goPrev(stepIndex);
141
+ }
142
+ if (stepIndex === currentStepIndex + 1) {
143
+ return () => goNext();
144
+ }
145
+ return undefined;
146
+ }, [allowFreeNavigation, currentStepIndex, goNext, goPrev, goToStep, isCompleted]);
122
147
  const stepsView = (0, react_1.useMemo)(() => steps.map((step, index) => {
123
148
  const number = index + 1;
149
+ let state = constants_1.STEP_STATE.Waiting;
124
150
  if (index < currentStepIndex) {
125
- return Object.assign(Object.assign({}, step), {
126
- number,
127
- state: constants_1.STEP_STATE.Completed,
128
- onClick: () => goPrev(index)
129
- });
130
- }
131
- if (index === currentStepIndex) {
132
- return Object.assign(Object.assign({}, step), {
133
- number,
134
- state: currentStepState,
135
- onClick: isCompleted ? () => goPrev(index) : undefined
136
- });
137
- }
138
- if (index - 1 === currentStepIndex) {
139
- return Object.assign(Object.assign({}, step), {
140
- number,
141
- state: constants_1.STEP_STATE.Waiting,
142
- onClick: () => goNext()
143
- });
151
+ state = constants_1.STEP_STATE.Completed;
152
+ } else if (index === currentStepIndex) {
153
+ state = currentStepState;
144
154
  }
145
155
  return Object.assign(Object.assign({}, step), {
146
156
  number,
147
- state: constants_1.STEP_STATE.Waiting
157
+ state,
158
+ onClick: getStepOnClick(index)
148
159
  });
149
- }), [steps, currentStepIndex, goPrev, currentStepState, isCompleted, goNext]);
160
+ }), [steps, currentStepIndex, currentStepState, getStepOnClick]);
150
161
  const resetValidation = (0, react_1.useCallback)(() => {
151
162
  if (currentStepState === constants_1.STEP_STATE.Rejected) {
152
163
  setCurrentStepState(constants_1.STEP_STATE.Current);
@@ -174,7 +185,8 @@ function Stepper(_a) {
174
185
  resetValidation,
175
186
  stepCount: steps.length,
176
187
  stepper,
177
- setValidator
188
+ setValidator,
189
+ goToStep: allowFreeNavigation ? goToStep : undefined
178
190
  };
179
191
  return (0, jsx_runtime_1.jsx)(StepperContext_1.StepperContext.Provider, {
180
192
  value: stepperApi,
@@ -16,6 +16,7 @@ export type StepperApi = {
16
16
  stepper: JSX.Element;
17
17
  goNext(stepIndex?: number): void;
18
18
  goPrev(stepIndex?: number): void;
19
+ goToStep?(stepIndex?: number): void;
19
20
  resetValidation(): void;
20
21
  setValidator(validator: StepsValidator): void;
21
22
  isCompleted: boolean;
@@ -5,6 +5,9 @@ export const StepperContext = createContext({
5
5
  stepCount: 0,
6
6
  isCompleted: false,
7
7
  currentStepIndex: 0,
8
+ goToStep() {
9
+ /* stub */
10
+ },
8
11
  goNext() {
9
12
  /* stub */
10
13
  },
@@ -23,5 +23,7 @@ export type StepperProps = WithSupportProps<{
23
23
  onChangeCurrentStep?: (newValue: number, prevValue: number) => void;
24
24
  /** Колбек изменения завершенности */
25
25
  onCompleteChange?: (isCompleted: boolean) => void;
26
+ /** Позволяет свободно переключаться между разными шагами без валидации */
27
+ allowFreeNavigation?: boolean;
26
28
  }>;
27
- export declare function Stepper({ children, steps, className, onChangeCurrentStep, onCompleteChange, defaultCurrentStepIndex, validator: validatorProp, ...props }: StepperProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function Stepper({ children, steps, className, onChangeCurrentStep, onCompleteChange, defaultCurrentStepIndex, validator: validatorProp, allowFreeNavigation, ...props }: StepperProps): import("react/jsx-runtime").JSX.Element;
@@ -28,7 +28,7 @@ import { StepperContext } from '../../StepperContext';
28
28
  import styles from './styles.module.css';
29
29
  const DEFAULT_VALIDATOR = () => __awaiter(void 0, void 0, void 0, function* () { return true; });
30
30
  export function Stepper(_a) {
31
- var { children, steps, className, onChangeCurrentStep, onCompleteChange, defaultCurrentStepIndex = 0, validator: validatorProp = DEFAULT_VALIDATOR } = _a, props = __rest(_a, ["children", "steps", "className", "onChangeCurrentStep", "onCompleteChange", "defaultCurrentStepIndex", "validator"]);
31
+ var { children, steps, className, onChangeCurrentStep, onCompleteChange, defaultCurrentStepIndex = 0, validator: validatorProp = DEFAULT_VALIDATOR, allowFreeNavigation = false } = _a, props = __rest(_a, ["children", "steps", "className", "onChangeCurrentStep", "onCompleteChange", "defaultCurrentStepIndex", "validator", "allowFreeNavigation"]);
32
32
  const isCompletedByDefault = defaultCurrentStepIndex === steps.length - 1;
33
33
  const [currentStepState, setCurrentStepState] = useState(isCompletedByDefault ? STEP_STATE.Completed : STEP_STATE.Current);
34
34
  const [currentStepIndex, setCurrentStepIndexValue] = useState(defaultCurrentStepIndex);
@@ -70,6 +70,22 @@ export function Stepper(_a) {
70
70
  }
71
71
  });
72
72
  }, [currentStepIndex, isCompleted, setCurrentStepIndex, steps.length, stepsValidator === null || stepsValidator === void 0 ? void 0 : stepsValidator.value, validatorProp]);
73
+ const moveToStep = useCallback((stepIndex) => {
74
+ if (isCompleted) {
75
+ setIsCompleted(false);
76
+ }
77
+ setCurrentStepIndex(stepIndex);
78
+ setCurrentStepState(STEP_STATE.Current);
79
+ }, [isCompleted, setCurrentStepIndex]);
80
+ const goToStep = useCallback((newStepIndex) => {
81
+ if (newStepIndex < 0 || newStepIndex >= steps.length) {
82
+ return;
83
+ }
84
+ if (newStepIndex === currentStepIndex) {
85
+ return;
86
+ }
87
+ moveToStep(newStepIndex);
88
+ }, [currentStepIndex, steps, moveToStep]);
73
89
  const goPrev = useCallback((index = currentStepIndex - 1) => {
74
90
  if (currentStepIndex === 0 || index < 0 || index > currentStepIndex) {
75
91
  return;
@@ -77,25 +93,32 @@ export function Stepper(_a) {
77
93
  if (index === currentStepIndex && !isCompleted) {
78
94
  return;
79
95
  }
80
- if (isCompleted) {
81
- setIsCompleted(false);
96
+ moveToStep(index);
97
+ }, [currentStepIndex, isCompleted, moveToStep]);
98
+ const getStepOnClick = useCallback((stepIndex) => {
99
+ if (allowFreeNavigation) {
100
+ return () => goToStep(stepIndex);
82
101
  }
83
- setCurrentStepIndex(index);
84
- setCurrentStepState(STEP_STATE.Current);
85
- }, [currentStepIndex, isCompleted, setCurrentStepIndex]);
102
+ if (stepIndex < currentStepIndex || (stepIndex === currentStepIndex && isCompleted)) {
103
+ return () => goPrev(stepIndex);
104
+ }
105
+ if (stepIndex === currentStepIndex + 1) {
106
+ return () => goNext();
107
+ }
108
+ return undefined;
109
+ }, [allowFreeNavigation, currentStepIndex, goNext, goPrev, goToStep, isCompleted]);
86
110
  const stepsView = useMemo(() => steps.map((step, index) => {
87
111
  const number = index + 1;
112
+ let state = STEP_STATE.Waiting;
88
113
  if (index < currentStepIndex) {
89
- return Object.assign(Object.assign({}, step), { number, state: STEP_STATE.Completed, onClick: () => goPrev(index) });
90
- }
91
- if (index === currentStepIndex) {
92
- return Object.assign(Object.assign({}, step), { number, state: currentStepState, onClick: isCompleted ? () => goPrev(index) : undefined });
114
+ state = STEP_STATE.Completed;
93
115
  }
94
- if (index - 1 === currentStepIndex) {
95
- return Object.assign(Object.assign({}, step), { number, state: STEP_STATE.Waiting, onClick: () => goNext() });
116
+ else if (index === currentStepIndex) {
117
+ state = currentStepState;
96
118
  }
97
- return Object.assign(Object.assign({}, step), { number, state: STEP_STATE.Waiting });
98
- }), [steps, currentStepIndex, goPrev, currentStepState, isCompleted, goNext]);
119
+ return Object.assign(Object.assign({}, step), { number,
120
+ state, onClick: getStepOnClick(index) });
121
+ }), [steps, currentStepIndex, currentStepState, getStepOnClick]);
99
122
  const resetValidation = useCallback(() => {
100
123
  if (currentStepState === STEP_STATE.Rejected) {
101
124
  setCurrentStepState(STEP_STATE.Current);
@@ -114,6 +137,7 @@ export function Stepper(_a) {
114
137
  stepCount: steps.length,
115
138
  stepper,
116
139
  setValidator,
140
+ goToStep: allowFreeNavigation ? goToStep : undefined,
117
141
  };
118
142
  return _jsx(StepperContext.Provider, { value: stepperApi, children: children(stepperApi) });
119
143
  }
@@ -16,6 +16,7 @@ export type StepperApi = {
16
16
  stepper: JSX.Element;
17
17
  goNext(stepIndex?: number): void;
18
18
  goPrev(stepIndex?: number): void;
19
+ goToStep?(stepIndex?: number): void;
19
20
  resetValidation(): void;
20
21
  setValidator(validator: StepsValidator): void;
21
22
  isCompleted: boolean;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Stepper",
7
- "version": "0.8.44",
7
+ "version": "0.9.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -43,5 +43,5 @@
43
43
  "@snack-uikit/utils": "4.0.2",
44
44
  "classnames": "2.5.1"
45
45
  },
46
- "gitHead": "a9f6571146845d828e00206abdedb160980cff97"
46
+ "gitHead": "04eb3d22444b15fb7703fde18a0c0d23ae9d94f6"
47
47
  }
@@ -9,6 +9,9 @@ export const StepperContext = createContext<StepperApi>({
9
9
  stepCount: 0,
10
10
  isCompleted: false,
11
11
  currentStepIndex: 0,
12
+ goToStep() {
13
+ /* stub */
14
+ },
12
15
  goNext() {
13
16
  /* stub */
14
17
  },
@@ -32,6 +32,8 @@ export type StepperProps = WithSupportProps<{
32
32
  onChangeCurrentStep?: (newValue: number, prevValue: number) => void;
33
33
  /** Колбек изменения завершенности */
34
34
  onCompleteChange?: (isCompleted: boolean) => void;
35
+ /** Позволяет свободно переключаться между разными шагами без валидации */
36
+ allowFreeNavigation?: boolean;
35
37
  }>;
36
38
 
37
39
  const DEFAULT_VALIDATOR = async () => true;
@@ -44,6 +46,7 @@ export function Stepper({
44
46
  onCompleteChange,
45
47
  defaultCurrentStepIndex = 0,
46
48
  validator: validatorProp = DEFAULT_VALIDATOR,
49
+ allowFreeNavigation = false,
47
50
  ...props
48
51
  }: StepperProps) {
49
52
  const isCompletedByDefault = defaultCurrentStepIndex === steps.length - 1;
@@ -103,6 +106,33 @@ export function Stepper({
103
106
  [currentStepIndex, isCompleted, setCurrentStepIndex, steps.length, stepsValidator?.value, validatorProp],
104
107
  );
105
108
 
109
+ const moveToStep = useCallback(
110
+ (stepIndex: number) => {
111
+ if (isCompleted) {
112
+ setIsCompleted(false);
113
+ }
114
+
115
+ setCurrentStepIndex(stepIndex);
116
+ setCurrentStepState(STEP_STATE.Current);
117
+ },
118
+ [isCompleted, setCurrentStepIndex],
119
+ );
120
+
121
+ const goToStep = useCallback(
122
+ (newStepIndex: number) => {
123
+ if (newStepIndex < 0 || newStepIndex >= steps.length) {
124
+ return;
125
+ }
126
+
127
+ if (newStepIndex === currentStepIndex) {
128
+ return;
129
+ }
130
+
131
+ moveToStep(newStepIndex);
132
+ },
133
+ [currentStepIndex, steps, moveToStep],
134
+ );
135
+
106
136
  const goPrev = useCallback(
107
137
  (index: number = currentStepIndex - 1) => {
108
138
  if (currentStepIndex === 0 || index < 0 || index > currentStepIndex) {
@@ -113,36 +143,50 @@ export function Stepper({
113
143
  return;
114
144
  }
115
145
 
116
- if (isCompleted) {
117
- setIsCompleted(false);
146
+ moveToStep(index);
147
+ },
148
+ [currentStepIndex, isCompleted, moveToStep],
149
+ );
150
+
151
+ const getStepOnClick = useCallback(
152
+ (stepIndex: number) => {
153
+ if (allowFreeNavigation) {
154
+ return () => goToStep(stepIndex);
118
155
  }
119
156
 
120
- setCurrentStepIndex(index);
121
- setCurrentStepState(STEP_STATE.Current);
157
+ if (stepIndex < currentStepIndex || (stepIndex === currentStepIndex && isCompleted)) {
158
+ return () => goPrev(stepIndex);
159
+ }
160
+
161
+ if (stepIndex === currentStepIndex + 1) {
162
+ return () => goNext();
163
+ }
164
+
165
+ return undefined;
122
166
  },
123
- [currentStepIndex, isCompleted, setCurrentStepIndex],
167
+ [allowFreeNavigation, currentStepIndex, goNext, goPrev, goToStep, isCompleted],
124
168
  );
125
169
 
126
170
  const stepsView: StepViewData[] = useMemo(
127
171
  () =>
128
172
  steps.map((step, index) => {
129
173
  const number = index + 1;
174
+ let state: StepState = STEP_STATE.Waiting;
130
175
 
131
176
  if (index < currentStepIndex) {
132
- return { ...step, number, state: STEP_STATE.Completed, onClick: () => goPrev(index) };
133
- }
134
-
135
- if (index === currentStepIndex) {
136
- return { ...step, number, state: currentStepState, onClick: isCompleted ? () => goPrev(index) : undefined };
137
- }
138
-
139
- if (index - 1 === currentStepIndex) {
140
- return { ...step, number, state: STEP_STATE.Waiting, onClick: () => goNext() };
177
+ state = STEP_STATE.Completed;
178
+ } else if (index === currentStepIndex) {
179
+ state = currentStepState;
141
180
  }
142
181
 
143
- return { ...step, number, state: STEP_STATE.Waiting };
182
+ return {
183
+ ...step,
184
+ number,
185
+ state,
186
+ onClick: getStepOnClick(index),
187
+ };
144
188
  }),
145
- [steps, currentStepIndex, goPrev, currentStepState, isCompleted, goNext],
189
+ [steps, currentStepIndex, currentStepState, getStepOnClick],
146
190
  );
147
191
 
148
192
  const resetValidation = useCallback(() => {
@@ -177,6 +221,7 @@ export function Stepper({
177
221
  stepCount: steps.length,
178
222
  stepper,
179
223
  setValidator,
224
+ goToStep: allowFreeNavigation ? goToStep : undefined,
180
225
  };
181
226
 
182
227
  return <StepperContext.Provider value={stepperApi}>{children(stepperApi)}</StepperContext.Provider>;
package/src/types.ts CHANGED
@@ -23,6 +23,7 @@ export type StepperApi = {
23
23
  stepper: JSX.Element;
24
24
  goNext(stepIndex?: number): void;
25
25
  goPrev(stepIndex?: number): void;
26
+ goToStep?(stepIndex?: number): void;
26
27
  resetValidation(): void;
27
28
  setValidator(validator: StepsValidator): void;
28
29
  isCompleted: boolean;