@snack-uikit/stepper 0.2.19-preview-85c5f47b.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 +288 -0
- package/LICENSE +201 -0
- package/README.md +55 -0
- package/dist/StepperContext/StepperContext.d.ts +4 -0
- package/dist/StepperContext/StepperContext.js +15 -0
- package/dist/StepperContext/index.d.ts +1 -0
- package/dist/StepperContext/index.js +1 -0
- package/dist/components/Stepper/Stepper.d.ts +29 -0
- package/dist/components/Stepper/Stepper.js +103 -0
- package/dist/components/Stepper/index.d.ts +1 -0
- package/dist/components/Stepper/index.js +1 -0
- package/dist/components/Stepper/styles.module.css +4 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +13 -0
- package/dist/helperComponents/Icon/Icon.d.ts +7 -0
- package/dist/helperComponents/Icon/Icon.js +24 -0
- package/dist/helperComponents/Icon/index.d.ts +1 -0
- package/dist/helperComponents/Icon/index.js +1 -0
- package/dist/helperComponents/Icon/styles.module.css +38 -0
- package/dist/helperComponents/Step/Step.d.ts +8 -0
- package/dist/helperComponents/Step/Step.js +26 -0
- package/dist/helperComponents/Step/index.d.ts +1 -0
- package/dist/helperComponents/Step/index.js +1 -0
- package/dist/helperComponents/Step/styles.module.css +65 -0
- package/dist/helperComponents/index.d.ts +2 -0
- package/dist/helperComponents/index.js +2 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +1 -0
- package/package.json +43 -0
- package/src/StepperContext/StepperContext.ts +20 -0
- package/src/StepperContext/index.ts +1 -0
- package/src/components/Stepper/Stepper.tsx +167 -0
- package/src/components/Stepper/index.ts +1 -0
- package/src/components/Stepper/styles.module.scss +4 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +12 -0
- package/src/helperComponents/Icon/Icon.tsx +38 -0
- package/src/helperComponents/Icon/index.ts +1 -0
- package/src/helperComponents/Icon/styles.module.scss +45 -0
- package/src/helperComponents/Step/Step.tsx +54 -0
- package/src/helperComponents/Step/index.ts +1 -0
- package/src/helperComponents/Step/styles.module.scss +76 -0
- package/src/helperComponents/index.ts +2 -0
- package/src/hooks.ts +5 -0
- package/src/index.ts +2 -0
- package/src/types.ts +21 -0
- package/src/utils.ts +2 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import cn from 'classnames';
|
|
14
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
15
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
16
|
+
import { StepState, StepValidationResult } from '../../constants';
|
|
17
|
+
import { Step } from '../../helperComponents';
|
|
18
|
+
import { StepperContext } from '../../StepperContext';
|
|
19
|
+
import styles from './styles.module.css';
|
|
20
|
+
export function Stepper(_a) {
|
|
21
|
+
var { children, steps, className, onChangeCurrentStep, onCompleteChange, defaultCurrentStepIndex = 0 } = _a, props = __rest(_a, ["children", "steps", "className", "onChangeCurrentStep", "onCompleteChange", "defaultCurrentStepIndex"]);
|
|
22
|
+
const isCompletedByDefault = defaultCurrentStepIndex === steps.length - 1;
|
|
23
|
+
const [currentStepState, setCurrentStepState] = useState(isCompletedByDefault ? StepState.Completed : StepState.Current);
|
|
24
|
+
const [currentStepIndex, setCurrentStepIndexValue] = useState(defaultCurrentStepIndex);
|
|
25
|
+
const [isCompleted, setIsCompleted] = useState(isCompletedByDefault);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
onCompleteChange === null || onCompleteChange === void 0 ? void 0 : onCompleteChange(isCompleted);
|
|
28
|
+
}, [isCompleted, onCompleteChange]);
|
|
29
|
+
const setCurrentStepIndex = useCallback((newValue) => {
|
|
30
|
+
setCurrentStepIndexValue(prevValue => {
|
|
31
|
+
if (prevValue !== newValue) {
|
|
32
|
+
onChangeCurrentStep === null || onChangeCurrentStep === void 0 ? void 0 : onChangeCurrentStep(newValue, prevValue);
|
|
33
|
+
}
|
|
34
|
+
return newValue;
|
|
35
|
+
});
|
|
36
|
+
}, [onChangeCurrentStep]);
|
|
37
|
+
const goNext = useCallback(() => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
if (currentStepIndex >= steps.length || isCompleted) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const currentStep = steps[currentStepIndex];
|
|
43
|
+
const stepperValidPromise = (_b = (_a = currentStep.validation) === null || _a === void 0 ? void 0 : _a.call(currentStep)) !== null && _b !== void 0 ? _b : Promise.resolve(StepState.Completed);
|
|
44
|
+
setCurrentStepState(StepState.Loading);
|
|
45
|
+
stepperValidPromise
|
|
46
|
+
.then(validationResult => {
|
|
47
|
+
switch (validationResult) {
|
|
48
|
+
case StepValidationResult.Completed:
|
|
49
|
+
if (currentStepIndex === steps.length - 1) {
|
|
50
|
+
// завершился последний шаг
|
|
51
|
+
setCurrentStepState(StepState.Completed);
|
|
52
|
+
setIsCompleted(true);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
setCurrentStepIndex(currentStepIndex + 1);
|
|
56
|
+
setCurrentStepState(StepState.Current);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
case StepValidationResult.Rejected:
|
|
60
|
+
setCurrentStepState(StepState.Rejected);
|
|
61
|
+
return;
|
|
62
|
+
default:
|
|
63
|
+
setCurrentStepState(StepState.Current);
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
.catch(() => setCurrentStepState(StepState.Rejected));
|
|
67
|
+
}, [currentStepIndex, isCompleted, setCurrentStepIndex, steps]);
|
|
68
|
+
const goPrev = useCallback((index = currentStepIndex - 1) => {
|
|
69
|
+
if (currentStepIndex === 0 || index < 0 || index > currentStepIndex) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (index === currentStepIndex && !isCompleted) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (isCompleted) {
|
|
76
|
+
setIsCompleted(false);
|
|
77
|
+
}
|
|
78
|
+
setCurrentStepIndex(index);
|
|
79
|
+
setCurrentStepState(StepState.Current);
|
|
80
|
+
}, [currentStepIndex, isCompleted, setCurrentStepIndex]);
|
|
81
|
+
const stepsView = useMemo(() => steps.map((step, index) => {
|
|
82
|
+
const number = index + 1;
|
|
83
|
+
if (index < currentStepIndex) {
|
|
84
|
+
return Object.assign(Object.assign({}, step), { number, state: StepState.Completed, onClick: () => goPrev(index) });
|
|
85
|
+
}
|
|
86
|
+
if (index === currentStepIndex) {
|
|
87
|
+
return Object.assign(Object.assign({}, step), { number, state: currentStepState, onClick: isCompleted ? () => goPrev(index) : undefined });
|
|
88
|
+
}
|
|
89
|
+
if (index - 1 === currentStepIndex) {
|
|
90
|
+
return Object.assign(Object.assign({}, step), { number, state: StepState.Waiting, onClick: () => goNext() });
|
|
91
|
+
}
|
|
92
|
+
return Object.assign(Object.assign({}, step), { number, state: StepState.Waiting });
|
|
93
|
+
}), [steps, currentStepIndex, goPrev, currentStepState, isCompleted, goNext]);
|
|
94
|
+
const resetValidation = useCallback(() => {
|
|
95
|
+
if (currentStepState === StepState.Rejected) {
|
|
96
|
+
setCurrentStepState(StepState.Current);
|
|
97
|
+
}
|
|
98
|
+
}, [currentStepState]);
|
|
99
|
+
const stepper = (_jsx("div", Object.assign({ className: cn(styles.stepper, className) }, extractSupportProps(props), { children: stepsView.map((step, index, all) => (_jsx(Step, { step: step, isLast: all.length - 1 === index, "data-test-id": props['data-test-id'] }, step.title + index))) })));
|
|
100
|
+
const stepperApi = { goNext, goPrev, currentStepIndex, isCompleted, resetValidation, stepCount: steps.length };
|
|
101
|
+
return _jsx(StepperContext.Provider, Object.assign({ value: stepperApi }, { children: children(Object.assign({ stepper }, stepperApi)) }));
|
|
102
|
+
}
|
|
103
|
+
Stepper.validationResults = StepValidationResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Stepper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Stepper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Stepper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Stepper';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export var StepState;
|
|
2
|
+
(function (StepState) {
|
|
3
|
+
StepState["Completed"] = "completed";
|
|
4
|
+
StepState["Current"] = "current";
|
|
5
|
+
StepState["Loading"] = "loading";
|
|
6
|
+
StepState["Waiting"] = "waiting";
|
|
7
|
+
StepState["Rejected"] = "rejected";
|
|
8
|
+
})(StepState || (StepState = {}));
|
|
9
|
+
export var StepValidationResult;
|
|
10
|
+
(function (StepValidationResult) {
|
|
11
|
+
StepValidationResult["Completed"] = "completed";
|
|
12
|
+
StepValidationResult["Rejected"] = "rejected";
|
|
13
|
+
})(StepValidationResult || (StepValidationResult = {}));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { CheckSVG, CrossSVG } from '@snack-uikit/icons';
|
|
5
|
+
import { Sun } from '@snack-uikit/loaders';
|
|
6
|
+
import { Typography } from '@snack-uikit/typography';
|
|
7
|
+
import { StepState } from '../../constants';
|
|
8
|
+
import styles from './styles.module.css';
|
|
9
|
+
function getContent(state, number) {
|
|
10
|
+
switch (state) {
|
|
11
|
+
case StepState.Completed:
|
|
12
|
+
return _jsx(CheckSVG, {});
|
|
13
|
+
case StepState.Rejected:
|
|
14
|
+
return _jsx(CrossSVG, {});
|
|
15
|
+
case StepState.Loading:
|
|
16
|
+
return _jsx(Sun, { size: Sun.sizes.S });
|
|
17
|
+
default:
|
|
18
|
+
return number;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function Icon({ state, number, className }) {
|
|
22
|
+
const content = useMemo(() => getContent(state, number), [number, state]);
|
|
23
|
+
return (_jsx("div", Object.assign({ "data-state": state, className: cn(styles.icon, className) }, { children: typeof content === 'number' ? _jsx(Typography.SansLabelL, { children: content }) : content })));
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Icon';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Icon';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
.icon{
|
|
2
|
+
width:var(--size-stepper-step-status, 32px);
|
|
3
|
+
height:var(--size-stepper-step-status, 32px);
|
|
4
|
+
border-radius:var(--radius-stepper-step-status, 16px);
|
|
5
|
+
border-width:var(--border-width-stepper-step-status, 2px);
|
|
6
|
+
overflow:hidden;
|
|
7
|
+
display:inline-flex;
|
|
8
|
+
align-items:center;
|
|
9
|
+
justify-content:center;
|
|
10
|
+
}
|
|
11
|
+
.icon svg{
|
|
12
|
+
width:var(--dimension-3m, 24px) !important;
|
|
13
|
+
height:var(--dimension-3m, 24px) !important;
|
|
14
|
+
}
|
|
15
|
+
.icon[data-state=completed]{
|
|
16
|
+
color:var(--sys-primary-accent-default, #794ed3);
|
|
17
|
+
background-color:var(--sys-primary-decor-default, #ebdefd);
|
|
18
|
+
}
|
|
19
|
+
.icon[data-state=current]{
|
|
20
|
+
color:var(--sys-primary-on-accent, #fdfbff);
|
|
21
|
+
background-color:var(--sys-primary-accent-default, #794ed3);
|
|
22
|
+
}
|
|
23
|
+
.icon[data-state=waiting]{
|
|
24
|
+
box-sizing:border-box;
|
|
25
|
+
color:var(--sys-neutral-text-support, #565656);
|
|
26
|
+
border-color:var(--sys-neutral-decor-default, #dedede);
|
|
27
|
+
border-style:solid;
|
|
28
|
+
}
|
|
29
|
+
.icon[data-state=loading]{
|
|
30
|
+
box-sizing:border-box;
|
|
31
|
+
color:var(--sys-neutral-accent-default, #757575);
|
|
32
|
+
border-color:var(--sys-neutral-decor-default, #dedede);
|
|
33
|
+
border-style:solid;
|
|
34
|
+
}
|
|
35
|
+
.icon[data-state=rejected]{
|
|
36
|
+
color:var(--sys-red-on-accent, #fffbfa);
|
|
37
|
+
background-color:var(--sys-red-accent-default, #d93f2f);
|
|
38
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { WithSupportProps } from '@snack-uikit/utils';
|
|
2
|
+
import { StepViewData } from '../../types';
|
|
3
|
+
export type StepProps = WithSupportProps<{
|
|
4
|
+
step: StepViewData;
|
|
5
|
+
className?: string;
|
|
6
|
+
isLast: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function Step({ step, className, isLast, 'data-test-id': testId, ...props }: StepProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import cn from 'classnames';
|
|
14
|
+
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
15
|
+
import { Typography } from '@snack-uikit/typography';
|
|
16
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
17
|
+
import { StepState } from '../../constants';
|
|
18
|
+
import { getTestIdBuilder } from '../../utils';
|
|
19
|
+
import { Icon } from '../Icon';
|
|
20
|
+
import styles from './styles.module.css';
|
|
21
|
+
const getTailTestId = getTestIdBuilder('_element-tail');
|
|
22
|
+
const getStepTestId = getTestIdBuilder('_element-step');
|
|
23
|
+
export function Step(_a) {
|
|
24
|
+
var { step, className, isLast, 'data-test-id': testId } = _a, props = __rest(_a, ["step", "className", "isLast", 'data-test-id']);
|
|
25
|
+
return (_jsxs("div", Object.assign({ className: cn(styles.step, className), "data-state": step.state }, extractSupportProps(props), { children: [_jsxs("button", Object.assign({ className: styles.content, onClick: step.onClick, disabled: !step.onClick, "data-test-id": getStepTestId(testId), "data-state": step.state }, { children: [_jsx(Icon, Object.assign({}, step, { className: styles.icon })), _jsx(Typography, Object.assign({ className: styles.title, tag: Typography.tags.div, family: Typography.families.Sans, role: Typography.roles.Body, size: Typography.sizes.M }, { children: _jsx(TruncateString, { text: step.title }) }))] })), !isLast && (_jsx("div", Object.assign({ className: styles.tail, "data-completed": step.state === StepState.Completed || undefined, "data-test-id": getTailTestId(testId) }, { children: _jsx("div", { className: styles.tailLine }) })))] })));
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Step';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Step';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
.tailLine{
|
|
2
|
+
height:var(--size-stepper-step-track-line, 2px);
|
|
3
|
+
flex-grow:1;
|
|
4
|
+
background-color:var(--sys-neutral-decor-default, #dedede);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.tail{
|
|
8
|
+
height:var(--size-stepper-step-track-container, 32px);
|
|
9
|
+
padding-left:var(--space-stepper-step-track, 8px);
|
|
10
|
+
padding-right:var(--space-stepper-step-track, 8px);
|
|
11
|
+
display:flex;
|
|
12
|
+
flex-grow:1;
|
|
13
|
+
flex-shrink:1;
|
|
14
|
+
align-items:center;
|
|
15
|
+
min-width:var(--dimension-2m, 16px);
|
|
16
|
+
}
|
|
17
|
+
.tail[data-completed] .tailLine{
|
|
18
|
+
background-color:var(--sys-primary-accent-default, #794ed3);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.title{
|
|
22
|
+
overflow:hidden;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.step{
|
|
26
|
+
display:flex;
|
|
27
|
+
flex:1;
|
|
28
|
+
align-items:center;
|
|
29
|
+
justify-content:center;
|
|
30
|
+
min-width:0;
|
|
31
|
+
}
|
|
32
|
+
.step:last-child{
|
|
33
|
+
max-width:-moz-max-content;
|
|
34
|
+
max-width:max-content;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.content{
|
|
38
|
+
gap:var(--space-stepper-step-content-gap, 8px);
|
|
39
|
+
overflow:hidden;
|
|
40
|
+
display:flex;
|
|
41
|
+
flex-shrink:1;
|
|
42
|
+
align-items:center;
|
|
43
|
+
margin:0;
|
|
44
|
+
padding:0;
|
|
45
|
+
color:var(--sys-neutral-text-main, #333333);
|
|
46
|
+
background-color:transparent;
|
|
47
|
+
border:none;
|
|
48
|
+
}
|
|
49
|
+
.content:focus-visible{
|
|
50
|
+
outline-width:var(--border-state-focus-s-border-width, 2px);
|
|
51
|
+
outline-style:var(--border-state-focus-s-border-style, solid);
|
|
52
|
+
outline-color:var(--border-state-focus-s-border-color, );
|
|
53
|
+
outline-color:var(--sys-available-complementary, #131313);
|
|
54
|
+
outline-offset:var(--spacing-state-focus-offset, 2px);
|
|
55
|
+
}
|
|
56
|
+
.content:not([disabled]){
|
|
57
|
+
cursor:pointer;
|
|
58
|
+
}
|
|
59
|
+
.content:not([disabled]):focus-visible, .content:not([disabled]):hover{
|
|
60
|
+
color:var(--sys-neutral-text-light, #898989);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.icon{
|
|
64
|
+
flex-shrink:0;
|
|
65
|
+
}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useStepperApi: () => import("./types").StepperApi;
|
package/dist/hooks.js
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StepState, StepValidationResult } from './constants';
|
|
2
|
+
export type StepData = {
|
|
3
|
+
title: string;
|
|
4
|
+
validation?(): Promise<StepValidationResult>;
|
|
5
|
+
};
|
|
6
|
+
export type StepViewData = StepData & {
|
|
7
|
+
onClick?(): void;
|
|
8
|
+
state: StepState;
|
|
9
|
+
number: number;
|
|
10
|
+
};
|
|
11
|
+
export type StepperApi = {
|
|
12
|
+
goNext(): void;
|
|
13
|
+
goPrev(): void;
|
|
14
|
+
resetValidation(): void;
|
|
15
|
+
isCompleted: boolean;
|
|
16
|
+
currentStepIndex: number;
|
|
17
|
+
stepCount: number;
|
|
18
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getTestIdBuilder: (postfix?: string) => (testId?: string) => string | undefined;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const getTestIdBuilder = (postfix) => (testId) => postfix && testId ? `${testId}${postfix}` : undefined;
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@snack-uikit/stepper",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"title": "Stepper",
|
|
7
|
+
"version": "0.2.19-preview-85c5f47b.0",
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"*.css",
|
|
10
|
+
"*.woff",
|
|
11
|
+
"*.woff2"
|
|
12
|
+
],
|
|
13
|
+
"description": "",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"homepage": "https://github.com/cloud-ru-tech/snack-uikit/tree/master/packages/stepper",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/cloud-ru-tech/snack-uikit.git",
|
|
20
|
+
"directory": "packages/stepper"
|
|
21
|
+
},
|
|
22
|
+
"author": "Сергей Хлупин <svhlupin@cloud.ru>",
|
|
23
|
+
"contributors": [
|
|
24
|
+
"Сергей Хлупин <svhlupin@cloud.ru>"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src",
|
|
29
|
+
"./CHANGELOG.md",
|
|
30
|
+
"./LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"license": "Apache-2.0",
|
|
33
|
+
"scripts": {},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@snack-uikit/icons": "0.18.2-preview-85c5f47b.0",
|
|
36
|
+
"@snack-uikit/loaders": "0.3.5-preview-85c5f47b.0",
|
|
37
|
+
"@snack-uikit/truncate-string": "0.2.17-preview-85c5f47b.0",
|
|
38
|
+
"@snack-uikit/typography": "0.4.5-preview-85c5f47b.0",
|
|
39
|
+
"@snack-uikit/utils": "2.0.2-preview-85c5f47b.0",
|
|
40
|
+
"classnames": "2.3.2"
|
|
41
|
+
},
|
|
42
|
+
"gitHead": "6a7eef41d780d945f64f791448eaa72b9e0ae72d"
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import { StepperApi } from '../types';
|
|
4
|
+
|
|
5
|
+
export type StepperContextValue = StepperApi;
|
|
6
|
+
|
|
7
|
+
export const StepperContext = createContext<StepperApi>({
|
|
8
|
+
stepCount: 0,
|
|
9
|
+
isCompleted: false,
|
|
10
|
+
currentStepIndex: 0,
|
|
11
|
+
goNext() {
|
|
12
|
+
/* stub */
|
|
13
|
+
},
|
|
14
|
+
goPrev() {
|
|
15
|
+
/* stub */
|
|
16
|
+
},
|
|
17
|
+
resetValidation() {
|
|
18
|
+
/* stub */
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './StepperContext';
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
|
|
5
|
+
|
|
6
|
+
import { StepState, StepValidationResult } from '../../constants';
|
|
7
|
+
import { Step } from '../../helperComponents';
|
|
8
|
+
import { StepperContext } from '../../StepperContext';
|
|
9
|
+
import { StepData, StepperApi, StepViewData } from '../../types';
|
|
10
|
+
import styles from './styles.module.scss';
|
|
11
|
+
|
|
12
|
+
export type StepperState = StepperApi & {
|
|
13
|
+
stepper: ReactElement;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type StepperProps = WithSupportProps<{
|
|
17
|
+
/** Массив шагов */
|
|
18
|
+
steps: StepData[];
|
|
19
|
+
/** Индекс текущего шага по-дефолту */
|
|
20
|
+
defaultCurrentStepIndex: number;
|
|
21
|
+
/** CSS-класс */
|
|
22
|
+
className?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Render function. Принимает аргументы: `stepper` - JSX-элемент степпера,
|
|
25
|
+
* `goNext` - перейти на след. шаг, `goPrev` - перейти на пред. шаг, `resetValidation` - сбросить состояние валидации для текущего шага, `isCompleted` - окончен ли процесс, `currentStepIndex` - индекс текущего шага, `stepCount` - кол-во шагов.
|
|
26
|
+
* @type ({stepper, ...api}) => ReactElement
|
|
27
|
+
*/
|
|
28
|
+
children: (params: StepperState) => ReactElement;
|
|
29
|
+
/** Колбек смены текущего степа */
|
|
30
|
+
onChangeCurrentStep: (newValue: number, prevValue: number) => void;
|
|
31
|
+
/** Колбек изменения завершенности */
|
|
32
|
+
onCompleteChange: (isCompleted: boolean) => void;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
export function Stepper({
|
|
36
|
+
children,
|
|
37
|
+
steps,
|
|
38
|
+
className,
|
|
39
|
+
onChangeCurrentStep,
|
|
40
|
+
onCompleteChange,
|
|
41
|
+
defaultCurrentStepIndex = 0,
|
|
42
|
+
...props
|
|
43
|
+
}: StepperProps) {
|
|
44
|
+
const isCompletedByDefault = defaultCurrentStepIndex === steps.length - 1;
|
|
45
|
+
const [currentStepState, setCurrentStepState] = useState(
|
|
46
|
+
isCompletedByDefault ? StepState.Completed : StepState.Current,
|
|
47
|
+
);
|
|
48
|
+
const [currentStepIndex, setCurrentStepIndexValue] = useState(defaultCurrentStepIndex);
|
|
49
|
+
const [isCompleted, setIsCompleted] = useState(isCompletedByDefault);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
onCompleteChange?.(isCompleted);
|
|
53
|
+
}, [isCompleted, onCompleteChange]);
|
|
54
|
+
|
|
55
|
+
const setCurrentStepIndex = useCallback(
|
|
56
|
+
(newValue: number) => {
|
|
57
|
+
setCurrentStepIndexValue(prevValue => {
|
|
58
|
+
if (prevValue !== newValue) {
|
|
59
|
+
onChangeCurrentStep?.(newValue, prevValue);
|
|
60
|
+
}
|
|
61
|
+
return newValue;
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
[onChangeCurrentStep],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const goNext = useCallback(() => {
|
|
68
|
+
if (currentStepIndex >= steps.length || isCompleted) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const currentStep = steps[currentStepIndex];
|
|
73
|
+
|
|
74
|
+
const stepperValidPromise = currentStep.validation?.() ?? Promise.resolve(StepState.Completed);
|
|
75
|
+
|
|
76
|
+
setCurrentStepState(StepState.Loading);
|
|
77
|
+
|
|
78
|
+
stepperValidPromise
|
|
79
|
+
.then(validationResult => {
|
|
80
|
+
switch (validationResult) {
|
|
81
|
+
case StepValidationResult.Completed:
|
|
82
|
+
if (currentStepIndex === steps.length - 1) {
|
|
83
|
+
// завершился последний шаг
|
|
84
|
+
setCurrentStepState(StepState.Completed);
|
|
85
|
+
setIsCompleted(true);
|
|
86
|
+
} else {
|
|
87
|
+
setCurrentStepIndex(currentStepIndex + 1);
|
|
88
|
+
setCurrentStepState(StepState.Current);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
case StepValidationResult.Rejected:
|
|
92
|
+
setCurrentStepState(StepState.Rejected);
|
|
93
|
+
return;
|
|
94
|
+
default:
|
|
95
|
+
setCurrentStepState(StepState.Current);
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
.catch(() => setCurrentStepState(StepState.Rejected));
|
|
99
|
+
}, [currentStepIndex, isCompleted, setCurrentStepIndex, steps]);
|
|
100
|
+
|
|
101
|
+
const goPrev = useCallback(
|
|
102
|
+
(index: number = currentStepIndex - 1) => {
|
|
103
|
+
if (currentStepIndex === 0 || index < 0 || index > currentStepIndex) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (index === currentStepIndex && !isCompleted) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (isCompleted) {
|
|
112
|
+
setIsCompleted(false);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setCurrentStepIndex(index);
|
|
116
|
+
setCurrentStepState(StepState.Current);
|
|
117
|
+
},
|
|
118
|
+
[currentStepIndex, isCompleted, setCurrentStepIndex],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const stepsView: StepViewData[] = useMemo(
|
|
122
|
+
() =>
|
|
123
|
+
steps.map((step, index) => {
|
|
124
|
+
const number = index + 1;
|
|
125
|
+
|
|
126
|
+
if (index < currentStepIndex) {
|
|
127
|
+
return { ...step, number, state: StepState.Completed, onClick: () => goPrev(index) };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (index === currentStepIndex) {
|
|
131
|
+
return { ...step, number, state: currentStepState, onClick: isCompleted ? () => goPrev(index) : undefined };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (index - 1 === currentStepIndex) {
|
|
135
|
+
return { ...step, number, state: StepState.Waiting, onClick: () => goNext() };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { ...step, number, state: StepState.Waiting };
|
|
139
|
+
}),
|
|
140
|
+
[steps, currentStepIndex, goPrev, currentStepState, isCompleted, goNext],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const resetValidation = useCallback(() => {
|
|
144
|
+
if (currentStepState === StepState.Rejected) {
|
|
145
|
+
setCurrentStepState(StepState.Current);
|
|
146
|
+
}
|
|
147
|
+
}, [currentStepState]);
|
|
148
|
+
|
|
149
|
+
const stepper = (
|
|
150
|
+
<div className={cn(styles.stepper, className)} {...extractSupportProps(props)}>
|
|
151
|
+
{stepsView.map((step, index, all) => (
|
|
152
|
+
<Step
|
|
153
|
+
key={step.title + index}
|
|
154
|
+
step={step}
|
|
155
|
+
isLast={all.length - 1 === index}
|
|
156
|
+
data-test-id={props['data-test-id']}
|
|
157
|
+
/>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const stepperApi = { goNext, goPrev, currentStepIndex, isCompleted, resetValidation, stepCount: steps.length };
|
|
163
|
+
|
|
164
|
+
return <StepperContext.Provider value={stepperApi}>{children({ stepper, ...stepperApi })}</StepperContext.Provider>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Stepper.validationResults = StepValidationResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Stepper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Stepper';
|
package/src/constants.ts
ADDED