react-restyle-components 0.4.49 → 0.4.50
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/lib/src/core-components/src/components/ProgressStepper/ProgressBar/ProgressBar.d.ts +3 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/ProgressBar.js +237 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/index.d.ts +2 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/index.js +1 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/types.d.ts +29 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressBar/types.js +1 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/ProgressStepper.d.ts +3 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/ProgressStepper.js +42 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/StepItem.d.ts +3 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/StepItem.js +349 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/index.d.ts +2 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/index.js +1 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/types.d.ts +75 -0
- package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/types.js +1 -0
- package/lib/src/core-components/src/components/ProgressStepper/index.d.ts +4 -0
- package/lib/src/core-components/src/components/ProgressStepper/index.js +2 -0
- package/lib/src/core-components/src/components/Table/Table.js +9 -7
- package/lib/src/core-components/src/components/Table/types.d.ts +3 -1
- package/lib/src/core-components/src/components/index.d.ts +1 -0
- package/lib/src/core-components/src/components/index.js +1 -0
- package/lib/src/core-components/src/tc.global.css +1 -269
- package/lib/src/core-components/src/tc.module.css +1 -3
- package/package.json +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useMemo, useEffect } from 'react';
|
|
4
|
+
// Inject keyframes animation styles
|
|
5
|
+
const injectAnimationStyles = () => {
|
|
6
|
+
const styleId = 'progress-bar-animations';
|
|
7
|
+
if (typeof document !== 'undefined' && !document.getElementById(styleId)) {
|
|
8
|
+
const styleSheet = document.createElement('style');
|
|
9
|
+
styleSheet.id = styleId;
|
|
10
|
+
styleSheet.textContent = `
|
|
11
|
+
@keyframes progressBarShimmer {
|
|
12
|
+
0% {
|
|
13
|
+
transform: translateX(-100%);
|
|
14
|
+
}
|
|
15
|
+
100% {
|
|
16
|
+
transform: translateX(100%);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
@keyframes progressBarIndeterminate {
|
|
20
|
+
0% {
|
|
21
|
+
left: -25%;
|
|
22
|
+
}
|
|
23
|
+
100% {
|
|
24
|
+
left: 100%;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
@keyframes progressBarPulse {
|
|
28
|
+
0%, 100% {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
50% {
|
|
32
|
+
opacity: 0.7;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
@keyframes progressBarStripe {
|
|
36
|
+
0% {
|
|
37
|
+
background-position: 0 0;
|
|
38
|
+
}
|
|
39
|
+
100% {
|
|
40
|
+
background-position: 40px 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
@keyframes progressBarGlow {
|
|
44
|
+
0%, 100% {
|
|
45
|
+
box-shadow: 0 0 4px currentColor, inset 0 0 4px rgba(255,255,255,0.2);
|
|
46
|
+
}
|
|
47
|
+
50% {
|
|
48
|
+
box-shadow: 0 0 12px currentColor, inset 0 0 8px rgba(255,255,255,0.3);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
@keyframes progressBarSegmentPulse {
|
|
52
|
+
0%, 100% {
|
|
53
|
+
transform: scale(1);
|
|
54
|
+
opacity: 1;
|
|
55
|
+
}
|
|
56
|
+
50% {
|
|
57
|
+
transform: scale(1.02);
|
|
58
|
+
opacity: 0.9;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
document.head.appendChild(styleSheet);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
// Color constants
|
|
66
|
+
const colors = {
|
|
67
|
+
primary: '#8B5CF6',
|
|
68
|
+
track: '#E5E7EB',
|
|
69
|
+
text: '#374151',
|
|
70
|
+
supportText: '#6B7280',
|
|
71
|
+
};
|
|
72
|
+
// Size configurations
|
|
73
|
+
const sizeConfig = {
|
|
74
|
+
small: {
|
|
75
|
+
height: '4px',
|
|
76
|
+
borderRadius: '2px',
|
|
77
|
+
labelSize: '0.75rem',
|
|
78
|
+
supportSize: '0.625rem',
|
|
79
|
+
},
|
|
80
|
+
medium: {
|
|
81
|
+
height: '6px',
|
|
82
|
+
borderRadius: '3px',
|
|
83
|
+
labelSize: '0.875rem',
|
|
84
|
+
supportSize: '0.75rem',
|
|
85
|
+
},
|
|
86
|
+
large: {
|
|
87
|
+
height: '8px',
|
|
88
|
+
borderRadius: '4px',
|
|
89
|
+
labelSize: '1rem',
|
|
90
|
+
supportSize: '0.875rem',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export const ProgressBar = forwardRef(({ label, supportText, value, max = 100, size = 'medium', variant = 'continuous', segments = 4, color, trackColor, showValue = false, isIndeterminate = false, showAnimation = true, className, style, ...rest }, ref) => {
|
|
94
|
+
// Inject animation styles on mount
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
injectAnimationStyles();
|
|
97
|
+
}, []);
|
|
98
|
+
const percentage = useMemo(() => {
|
|
99
|
+
const clampedValue = Math.max(0, Math.min(value, max));
|
|
100
|
+
return (clampedValue / max) * 100;
|
|
101
|
+
}, [value, max]);
|
|
102
|
+
// Check if progress is active (not 0 and not 100)
|
|
103
|
+
const isActive = percentage > 0 && percentage < 100;
|
|
104
|
+
const activeSegments = useMemo(() => {
|
|
105
|
+
if (variant !== 'segmented')
|
|
106
|
+
return 0;
|
|
107
|
+
return Math.ceil((percentage / 100) * segments);
|
|
108
|
+
}, [percentage, segments, variant]);
|
|
109
|
+
const ariaLabel = useMemo(() => {
|
|
110
|
+
if (isIndeterminate)
|
|
111
|
+
return 'Loading...';
|
|
112
|
+
return `${Math.round(percentage)}% complete`;
|
|
113
|
+
}, [percentage, isIndeterminate]);
|
|
114
|
+
const config = sizeConfig[size];
|
|
115
|
+
const containerStyle = {
|
|
116
|
+
display: 'flex',
|
|
117
|
+
flexDirection: 'column',
|
|
118
|
+
gap: '0.5rem',
|
|
119
|
+
width: '100%',
|
|
120
|
+
};
|
|
121
|
+
const labelStyle = {
|
|
122
|
+
fontSize: config.labelSize,
|
|
123
|
+
fontWeight: 600,
|
|
124
|
+
color: colors.text,
|
|
125
|
+
lineHeight: 1.4,
|
|
126
|
+
};
|
|
127
|
+
const supportTextStyle = {
|
|
128
|
+
fontSize: config.supportSize,
|
|
129
|
+
fontWeight: 400,
|
|
130
|
+
color: colors.supportText,
|
|
131
|
+
lineHeight: 1.4,
|
|
132
|
+
};
|
|
133
|
+
const trackStyle = {
|
|
134
|
+
width: '100%',
|
|
135
|
+
height: config.height,
|
|
136
|
+
backgroundColor: trackColor || colors.track,
|
|
137
|
+
borderRadius: config.borderRadius,
|
|
138
|
+
overflow: 'hidden',
|
|
139
|
+
position: 'relative',
|
|
140
|
+
};
|
|
141
|
+
const fillColor = color || colors.primary;
|
|
142
|
+
const fillStyle = {
|
|
143
|
+
height: '100%',
|
|
144
|
+
backgroundColor: fillColor,
|
|
145
|
+
borderRadius: config.borderRadius,
|
|
146
|
+
transition: 'width 0.5s ease-out',
|
|
147
|
+
width: isIndeterminate ? '30%' : `${percentage}%`,
|
|
148
|
+
position: 'relative',
|
|
149
|
+
overflow: 'hidden',
|
|
150
|
+
...(isIndeterminate
|
|
151
|
+
? {
|
|
152
|
+
position: 'absolute',
|
|
153
|
+
animation: 'progressBarIndeterminate 1.5s ease-in-out infinite',
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
156
|
+
...(showAnimation && isActive && !isIndeterminate
|
|
157
|
+
? {
|
|
158
|
+
animation: 'progressBarPulse 2s ease-in-out infinite',
|
|
159
|
+
}
|
|
160
|
+
: {}),
|
|
161
|
+
};
|
|
162
|
+
// Shimmer overlay for the fill
|
|
163
|
+
const shimmerStyle = {
|
|
164
|
+
position: 'absolute',
|
|
165
|
+
top: 0,
|
|
166
|
+
left: 0,
|
|
167
|
+
right: 0,
|
|
168
|
+
bottom: 0,
|
|
169
|
+
background: `linear-gradient(
|
|
170
|
+
90deg,
|
|
171
|
+
transparent 0%,
|
|
172
|
+
rgba(255, 255, 255, 0.4) 50%,
|
|
173
|
+
transparent 100%
|
|
174
|
+
)`,
|
|
175
|
+
animation: 'progressBarShimmer 1.5s ease-in-out infinite',
|
|
176
|
+
};
|
|
177
|
+
// Striped effect style (alternative animation)
|
|
178
|
+
const stripedStyle = {
|
|
179
|
+
position: 'absolute',
|
|
180
|
+
top: 0,
|
|
181
|
+
left: 0,
|
|
182
|
+
right: 0,
|
|
183
|
+
bottom: 0,
|
|
184
|
+
backgroundImage: `linear-gradient(
|
|
185
|
+
45deg,
|
|
186
|
+
rgba(255, 255, 255, 0.15) 25%,
|
|
187
|
+
transparent 25%,
|
|
188
|
+
transparent 50%,
|
|
189
|
+
rgba(255, 255, 255, 0.15) 50%,
|
|
190
|
+
rgba(255, 255, 255, 0.15) 75%,
|
|
191
|
+
transparent 75%,
|
|
192
|
+
transparent
|
|
193
|
+
)`,
|
|
194
|
+
backgroundSize: '40px 40px',
|
|
195
|
+
animation: 'progressBarStripe 1s linear infinite',
|
|
196
|
+
};
|
|
197
|
+
const segmentedTrackStyle = {
|
|
198
|
+
display: 'flex',
|
|
199
|
+
gap: '0.25rem',
|
|
200
|
+
width: '100%',
|
|
201
|
+
height: config.height,
|
|
202
|
+
};
|
|
203
|
+
const getSegmentStyle = (isActiveSegment, segmentIndex) => {
|
|
204
|
+
const isLastActiveSegment = segmentIndex === activeSegments - 1 && activeSegments > 0;
|
|
205
|
+
return {
|
|
206
|
+
flex: 1,
|
|
207
|
+
height: '100%',
|
|
208
|
+
backgroundColor: isActiveSegment ? fillColor : trackColor || colors.track,
|
|
209
|
+
borderRadius: config.borderRadius,
|
|
210
|
+
transition: 'all 0.3s ease-in-out',
|
|
211
|
+
position: 'relative',
|
|
212
|
+
overflow: 'hidden',
|
|
213
|
+
...(showAnimation && isLastActiveSegment && isActive
|
|
214
|
+
? {
|
|
215
|
+
animation: 'progressBarSegmentPulse 1.5s ease-in-out infinite',
|
|
216
|
+
boxShadow: `0 0 8px ${fillColor}`,
|
|
217
|
+
}
|
|
218
|
+
: {}),
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
const valueContainerStyle = {
|
|
222
|
+
display: 'flex',
|
|
223
|
+
justifyContent: 'space-between',
|
|
224
|
+
alignItems: 'center',
|
|
225
|
+
};
|
|
226
|
+
const valueTextStyle = {
|
|
227
|
+
fontSize: config.supportSize,
|
|
228
|
+
fontWeight: 500,
|
|
229
|
+
color: colors.text,
|
|
230
|
+
};
|
|
231
|
+
return (_jsxs("div", { ref: ref, className: className, style: { ...containerStyle, ...style }, "data-aui": "progress-bar", ...rest, children: [(label || showValue) && (_jsxs("div", { style: valueContainerStyle, children: [label && _jsx("span", { style: labelStyle, children: label }), showValue && !isIndeterminate && (_jsxs("span", { style: valueTextStyle, children: [Math.round(percentage), "%"] }))] })), variant === 'continuous' ? (_jsx("div", { style: trackStyle, role: "progressbar", "aria-valuenow": isIndeterminate ? undefined : value, "aria-valuemin": 0, "aria-valuemax": max, "aria-label": ariaLabel, children: _jsxs("div", { style: fillStyle, children: [showAnimation && (isActive || isIndeterminate) && percentage > 0 && (_jsx("div", { style: shimmerStyle, "aria-hidden": "true" })), showAnimation && isIndeterminate && (_jsx("div", { style: stripedStyle, "aria-hidden": "true" }))] }) })) : (_jsx("div", { style: segmentedTrackStyle, role: "progressbar", "aria-valuenow": value, "aria-valuemin": 0, "aria-valuemax": max, "aria-label": ariaLabel, children: Array.from({ length: segments }).map((_, index) => {
|
|
232
|
+
const isActiveSegment = index < activeSegments;
|
|
233
|
+
const isLastActiveSegment = index === activeSegments - 1 && activeSegments > 0;
|
|
234
|
+
return (_jsx("div", { style: getSegmentStyle(isActiveSegment, index), children: showAnimation && isLastActiveSegment && isActive && (_jsx("div", { style: shimmerStyle, "aria-hidden": "true" })) }, index));
|
|
235
|
+
}) })), supportText && _jsx("span", { style: supportTextStyle, children: supportText })] }));
|
|
236
|
+
});
|
|
237
|
+
ProgressBar.displayName = 'ProgressBar';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ProgressBar';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ProgressBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/** Label text displayed above the progress bar */
|
|
4
|
+
label?: string;
|
|
5
|
+
/** Support text displayed below the progress bar */
|
|
6
|
+
supportText?: string;
|
|
7
|
+
/** Current progress value */
|
|
8
|
+
value: number;
|
|
9
|
+
/** Maximum value (default: 100) */
|
|
10
|
+
max?: number;
|
|
11
|
+
/** Size of the progress bar */
|
|
12
|
+
size?: 'small' | 'medium' | 'large';
|
|
13
|
+
/** Variant of the progress bar */
|
|
14
|
+
variant?: 'continuous' | 'segmented';
|
|
15
|
+
/** Number of segments (for segmented variant) */
|
|
16
|
+
segments?: number;
|
|
17
|
+
/** Custom color for the progress fill */
|
|
18
|
+
color?: string;
|
|
19
|
+
/** Custom color for the track */
|
|
20
|
+
trackColor?: string;
|
|
21
|
+
/** Whether to show the percentage value */
|
|
22
|
+
showValue?: boolean;
|
|
23
|
+
/** Whether the progress bar is in loading/indeterminate state */
|
|
24
|
+
isIndeterminate?: boolean;
|
|
25
|
+
/** Whether to show loading animations (shimmer, pulse effects). Default: true */
|
|
26
|
+
showAnimation?: boolean;
|
|
27
|
+
/** Custom class name */
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/src/core-components/src/components/ProgressStepper/ProgressStepper/ProgressStepper.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { forwardRef, useMemo } from 'react';
|
|
5
|
+
import { StepItem } from './StepItem';
|
|
6
|
+
export const ProgressStepper = forwardRef(({ steps, size = 'medium', orientation = 'horizontal', isPacked = false, indicatorType = 'dot', activeColor = '#8B5CF6', inactiveColor = '#E5E7EB', className, style, ...rest }, ref) => {
|
|
7
|
+
// Generate accessible aria label based on current step
|
|
8
|
+
const ariaLabel = useMemo(() => {
|
|
9
|
+
const currentStepIndex = steps
|
|
10
|
+
.map((step) => step.state !== 'incomplete')
|
|
11
|
+
.lastIndexOf(true);
|
|
12
|
+
const currentStep = steps[currentStepIndex];
|
|
13
|
+
const label = `step ${currentStepIndex + 1} out of ${steps.length}`;
|
|
14
|
+
if (!currentStep?.subSteps?.length) {
|
|
15
|
+
return label;
|
|
16
|
+
}
|
|
17
|
+
if (currentStep.state === 'inprogress') {
|
|
18
|
+
return `${label} part 1`;
|
|
19
|
+
}
|
|
20
|
+
const subStepIndex = currentStep.subSteps?.findIndex((subStep) => subStep.state === 'inprogress');
|
|
21
|
+
if (subStepIndex !== undefined && subStepIndex >= 0) {
|
|
22
|
+
return `${label} part ${subStepIndex + 2}`;
|
|
23
|
+
}
|
|
24
|
+
return label;
|
|
25
|
+
}, [steps]);
|
|
26
|
+
const hiddenAriaStyle = {
|
|
27
|
+
height: 0,
|
|
28
|
+
opacity: 0,
|
|
29
|
+
position: 'absolute',
|
|
30
|
+
overflow: 'hidden',
|
|
31
|
+
};
|
|
32
|
+
const listContainerStyle = {
|
|
33
|
+
display: 'flex',
|
|
34
|
+
flexDirection: orientation === 'horizontal' ? 'row' : 'column',
|
|
35
|
+
alignItems: 'flex-start',
|
|
36
|
+
listStyleType: 'none',
|
|
37
|
+
padding: 0,
|
|
38
|
+
margin: 0,
|
|
39
|
+
};
|
|
40
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { style: hiddenAriaStyle, "aria-live": "polite", role: "status", children: ariaLabel }), _jsx("ul", { "data-aui": "progress-stepper", ref: ref, "aria-hidden": true, "data-testid": "progress-stepper", style: { ...listContainerStyle, ...style }, className: className, ...rest, children: steps.map((step, index) => (_createElement(StepItem, { ...step, key: step.id || index, index: index, stepsAmount: steps.length, size: size, orientation: orientation, isPacked: isPacked, indicatorType: indicatorType, activeColor: activeColor, inactiveColor: inactiveColor }))) })] }));
|
|
41
|
+
});
|
|
42
|
+
ProgressStepper.displayName = 'ProgressStepper';
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useEffect } from 'react';
|
|
4
|
+
// Inject keyframes animation styles
|
|
5
|
+
const injectAnimationStyles = () => {
|
|
6
|
+
const styleId = 'progress-stepper-animations';
|
|
7
|
+
if (typeof document !== 'undefined' && !document.getElementById(styleId)) {
|
|
8
|
+
const styleSheet = document.createElement('style');
|
|
9
|
+
styleSheet.id = styleId;
|
|
10
|
+
styleSheet.textContent = `
|
|
11
|
+
@keyframes progressShimmer {
|
|
12
|
+
0% {
|
|
13
|
+
background-position: -200% 0;
|
|
14
|
+
}
|
|
15
|
+
100% {
|
|
16
|
+
background-position: 200% 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
@keyframes progressPulse {
|
|
20
|
+
0%, 100% {
|
|
21
|
+
opacity: 1;
|
|
22
|
+
}
|
|
23
|
+
50% {
|
|
24
|
+
opacity: 0.6;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
@keyframes progressGlow {
|
|
28
|
+
0%, 100% {
|
|
29
|
+
box-shadow: 0 0 4px currentColor;
|
|
30
|
+
}
|
|
31
|
+
50% {
|
|
32
|
+
box-shadow: 0 0 12px currentColor;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
@keyframes progressFlow {
|
|
36
|
+
0% {
|
|
37
|
+
transform: translateX(-100%);
|
|
38
|
+
}
|
|
39
|
+
100% {
|
|
40
|
+
transform: translateX(100%);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
@keyframes progressFlowVertical {
|
|
44
|
+
0% {
|
|
45
|
+
transform: translateY(-100%);
|
|
46
|
+
}
|
|
47
|
+
100% {
|
|
48
|
+
transform: translateY(100%);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
document.head.appendChild(styleSheet);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// Default colors
|
|
56
|
+
const defaultColors = {
|
|
57
|
+
active: '#8B5CF6',
|
|
58
|
+
inactive: '#E5E7EB',
|
|
59
|
+
complete: '#8B5CF6',
|
|
60
|
+
inprogress: '#8B5CF6',
|
|
61
|
+
warning: '#F59E0B',
|
|
62
|
+
error: '#EF4444',
|
|
63
|
+
white: '#ffffff',
|
|
64
|
+
text: '#374151',
|
|
65
|
+
supportText: '#6B7280',
|
|
66
|
+
};
|
|
67
|
+
// Size configurations
|
|
68
|
+
const sizeConfig = {
|
|
69
|
+
small: {
|
|
70
|
+
diameter: '1.25rem',
|
|
71
|
+
innerDiameter: '0.5rem',
|
|
72
|
+
fontSize: '0.625rem',
|
|
73
|
+
iconSize: '0.625rem',
|
|
74
|
+
connectorWidth: '2px',
|
|
75
|
+
marginLeft: '0.5rem',
|
|
76
|
+
},
|
|
77
|
+
medium: {
|
|
78
|
+
diameter: '1.75rem',
|
|
79
|
+
innerDiameter: '0.75rem',
|
|
80
|
+
fontSize: '0.75rem',
|
|
81
|
+
iconSize: '0.875rem',
|
|
82
|
+
connectorWidth: '2px',
|
|
83
|
+
marginLeft: '0.75rem',
|
|
84
|
+
},
|
|
85
|
+
large: {
|
|
86
|
+
diameter: '2.25rem',
|
|
87
|
+
innerDiameter: '1rem',
|
|
88
|
+
fontSize: '0.875rem',
|
|
89
|
+
iconSize: '1rem',
|
|
90
|
+
connectorWidth: '3px',
|
|
91
|
+
marginLeft: '1rem',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
// Get state color
|
|
95
|
+
const getStateColor = (state, activeColor, inactiveColor) => {
|
|
96
|
+
const stateColors = {
|
|
97
|
+
complete: activeColor,
|
|
98
|
+
inprogress: activeColor,
|
|
99
|
+
incomplete: inactiveColor,
|
|
100
|
+
warning: defaultColors.warning,
|
|
101
|
+
error: defaultColors.error,
|
|
102
|
+
};
|
|
103
|
+
return stateColors[state];
|
|
104
|
+
};
|
|
105
|
+
// Utility functions
|
|
106
|
+
const calcPercentage = (part, total) => total > 0 ? (100 * part) / total : 0;
|
|
107
|
+
// Icon components
|
|
108
|
+
const CheckIcon = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
109
|
+
const WarningIcon = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 9v4m0 4h.01M12 2L2 20h20L12 2z" }) }));
|
|
110
|
+
const ErrorIcon = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
|
|
111
|
+
const getStepItemStyle = (state, size, indicatorType, activeColor, inactiveColor) => {
|
|
112
|
+
const config = sizeConfig[size];
|
|
113
|
+
const stateColor = getStateColor(state, activeColor, inactiveColor);
|
|
114
|
+
const isActive = state !== 'incomplete';
|
|
115
|
+
const baseStyle = {
|
|
116
|
+
position: 'relative',
|
|
117
|
+
borderRadius: '50%',
|
|
118
|
+
display: 'inline-flex',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
flexShrink: 0,
|
|
122
|
+
fontWeight: 600,
|
|
123
|
+
transition: 'all 0.3s ease',
|
|
124
|
+
height: config.diameter,
|
|
125
|
+
width: config.diameter,
|
|
126
|
+
minWidth: config.diameter,
|
|
127
|
+
fontSize: config.fontSize,
|
|
128
|
+
};
|
|
129
|
+
if (indicatorType === 'dot' || indicatorType === 'number' || indicatorType === 'iconCircle') {
|
|
130
|
+
return {
|
|
131
|
+
...baseStyle,
|
|
132
|
+
border: `2px solid ${stateColor}`,
|
|
133
|
+
backgroundColor: isActive ? stateColor : 'transparent',
|
|
134
|
+
color: isActive ? '#fff' : stateColor,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (indicatorType === 'icon') {
|
|
138
|
+
return {
|
|
139
|
+
...baseStyle,
|
|
140
|
+
border: 'none',
|
|
141
|
+
backgroundColor: 'transparent',
|
|
142
|
+
color: stateColor,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (indicatorType === 'check') {
|
|
146
|
+
return {
|
|
147
|
+
...baseStyle,
|
|
148
|
+
border: `2px solid ${stateColor}`,
|
|
149
|
+
backgroundColor: state === 'complete' ? stateColor : 'transparent',
|
|
150
|
+
color: state === 'complete' ? '#fff' : stateColor,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return baseStyle;
|
|
154
|
+
};
|
|
155
|
+
export const StepItem = ({ state = 'incomplete', subSteps, isPacked, stepTitle, stepSubTitle, stepLinkHref, stepLinkText, tagText, tagVariant = 'neutral', icon, iconSrc, stepNumber, index, stepsAmount, orientation, size, indicatorType, activeColor = defaultColors.active, inactiveColor = defaultColors.inactive, }) => {
|
|
156
|
+
// Inject animation styles on mount
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
injectAnimationStyles();
|
|
159
|
+
}, []);
|
|
160
|
+
const steps = subSteps ?? [];
|
|
161
|
+
const config = sizeConfig[size];
|
|
162
|
+
const connectorFillPercentage = useMemo(() => {
|
|
163
|
+
if (steps.length === 0) {
|
|
164
|
+
return state === 'complete' ? 100 : 0;
|
|
165
|
+
}
|
|
166
|
+
if (steps[steps.length - 1]?.state === 'complete') {
|
|
167
|
+
return 100;
|
|
168
|
+
}
|
|
169
|
+
const activeSubSteps = steps.filter((subStep) => subStep.state !== 'incomplete').length;
|
|
170
|
+
return calcPercentage(activeSubSteps, steps.length + 1);
|
|
171
|
+
}, [state, steps]);
|
|
172
|
+
const isLast = useMemo(() => index === stepsAmount - 1, [index, stepsAmount]);
|
|
173
|
+
const showConnectorDot = useMemo(() => connectorFillPercentage > 0 && connectorFillPercentage < 100, [connectorFillPercentage]);
|
|
174
|
+
const stateWithSubSteps = useMemo(() => state === 'complete' && connectorFillPercentage < 100
|
|
175
|
+
? 'inprogress'
|
|
176
|
+
: state, [connectorFillPercentage, state]);
|
|
177
|
+
const displayNumber = stepNumber ?? index + 1;
|
|
178
|
+
const innerDotStyle = {
|
|
179
|
+
position: 'absolute',
|
|
180
|
+
borderRadius: '50%',
|
|
181
|
+
backgroundColor: defaultColors.white,
|
|
182
|
+
opacity: state === 'inprogress' && indicatorType === 'dot' ? 1 : 0,
|
|
183
|
+
transition: 'opacity 0.3s ease',
|
|
184
|
+
height: config.innerDiameter,
|
|
185
|
+
width: config.innerDiameter,
|
|
186
|
+
};
|
|
187
|
+
const iconWrapperStyle = {
|
|
188
|
+
display: 'flex',
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
justifyContent: 'center',
|
|
191
|
+
width: config.iconSize,
|
|
192
|
+
height: config.iconSize,
|
|
193
|
+
};
|
|
194
|
+
const connectorTextGroupStyle = {
|
|
195
|
+
display: 'flex',
|
|
196
|
+
flexDirection: orientation === 'vertical' ? 'row' : 'column',
|
|
197
|
+
...(orientation === 'horizontal' && !isLast ? { width: '100%' } : {}),
|
|
198
|
+
...(orientation === 'horizontal' ? { marginTop: 'auto', marginBottom: 'auto' } : {}),
|
|
199
|
+
};
|
|
200
|
+
const connectorStyle = {
|
|
201
|
+
backgroundColor: inactiveColor,
|
|
202
|
+
...(orientation === 'vertical'
|
|
203
|
+
? {
|
|
204
|
+
height: isPacked ? '1.5rem' : '3rem',
|
|
205
|
+
width: config.connectorWidth,
|
|
206
|
+
marginLeft: config.marginLeft,
|
|
207
|
+
}
|
|
208
|
+
: {
|
|
209
|
+
height: config.connectorWidth,
|
|
210
|
+
width: '100%',
|
|
211
|
+
}),
|
|
212
|
+
};
|
|
213
|
+
// Check if this connector should be animated (when step is inprogress or has partial substeps)
|
|
214
|
+
const isAnimated = state === 'inprogress' || (connectorFillPercentage > 0 && connectorFillPercentage < 100);
|
|
215
|
+
const connectorFillStyle = {
|
|
216
|
+
position: 'relative',
|
|
217
|
+
backgroundColor: activeColor,
|
|
218
|
+
transition: orientation === 'vertical' ? 'height 0.5s ease-out' : 'width 0.5s ease-out',
|
|
219
|
+
overflow: 'hidden',
|
|
220
|
+
...(orientation === 'vertical'
|
|
221
|
+
? { height: `${connectorFillPercentage}%`, width: '100%' }
|
|
222
|
+
: { width: `${connectorFillPercentage}%`, height: '100%' }),
|
|
223
|
+
};
|
|
224
|
+
// Animated shimmer overlay style
|
|
225
|
+
const shimmerOverlayStyle = isAnimated ? {
|
|
226
|
+
position: 'absolute',
|
|
227
|
+
top: 0,
|
|
228
|
+
left: 0,
|
|
229
|
+
right: 0,
|
|
230
|
+
bottom: 0,
|
|
231
|
+
background: `linear-gradient(
|
|
232
|
+
${orientation === 'vertical' ? '180deg' : '90deg'},
|
|
233
|
+
transparent 0%,
|
|
234
|
+
rgba(255, 255, 255, 0.4) 50%,
|
|
235
|
+
transparent 100%
|
|
236
|
+
)`,
|
|
237
|
+
backgroundSize: orientation === 'vertical' ? '100% 200%' : '200% 100%',
|
|
238
|
+
animation: `progressShimmer 1.5s ease-in-out infinite`,
|
|
239
|
+
} : {};
|
|
240
|
+
// Pulse effect on the fill when in progress
|
|
241
|
+
const pulseStyle = state === 'inprogress' ? {
|
|
242
|
+
animation: 'progressPulse 2s ease-in-out infinite',
|
|
243
|
+
} : {};
|
|
244
|
+
const connectorDotStyle = {
|
|
245
|
+
content: '""',
|
|
246
|
+
width: '6px',
|
|
247
|
+
height: '6px',
|
|
248
|
+
position: 'absolute',
|
|
249
|
+
top: 0,
|
|
250
|
+
bottom: 0,
|
|
251
|
+
right: orientation === 'vertical' ? '-2px' : '-3px',
|
|
252
|
+
borderRadius: '50%',
|
|
253
|
+
margin: 'auto 0',
|
|
254
|
+
backgroundColor: activeColor,
|
|
255
|
+
opacity: showConnectorDot ? 1 : 0,
|
|
256
|
+
transition: 'opacity 0.3s ease',
|
|
257
|
+
};
|
|
258
|
+
const stepTextGroupStyle = {
|
|
259
|
+
marginLeft: config.marginLeft,
|
|
260
|
+
marginBottom: '1rem',
|
|
261
|
+
marginTop: '-0.25rem',
|
|
262
|
+
};
|
|
263
|
+
const stepTitleStyle = {
|
|
264
|
+
fontSize: size === 'small' ? '0.875rem' : size === 'medium' ? '1rem' : '1.125rem',
|
|
265
|
+
fontWeight: 500,
|
|
266
|
+
color: defaultColors.text,
|
|
267
|
+
margin: '0 0 0.125rem 0',
|
|
268
|
+
maxWidth: '12rem',
|
|
269
|
+
overflow: 'hidden',
|
|
270
|
+
textOverflow: 'ellipsis',
|
|
271
|
+
display: '-webkit-box',
|
|
272
|
+
WebkitLineClamp: 3,
|
|
273
|
+
WebkitBoxOrient: 'vertical',
|
|
274
|
+
};
|
|
275
|
+
const stepSubTitleStyle = {
|
|
276
|
+
fontSize: '0.75rem',
|
|
277
|
+
fontWeight: 400,
|
|
278
|
+
color: defaultColors.supportText,
|
|
279
|
+
margin: '0.125rem 0',
|
|
280
|
+
maxWidth: '12rem',
|
|
281
|
+
overflow: 'hidden',
|
|
282
|
+
textOverflow: 'ellipsis',
|
|
283
|
+
display: '-webkit-box',
|
|
284
|
+
WebkitLineClamp: 2,
|
|
285
|
+
WebkitBoxOrient: 'vertical',
|
|
286
|
+
};
|
|
287
|
+
const getTagStyle = (variant) => {
|
|
288
|
+
const variants = {
|
|
289
|
+
success: { backgroundColor: '#DCFCE7', color: '#166534' },
|
|
290
|
+
error: { backgroundColor: '#FEE2E2', color: '#991B1B' },
|
|
291
|
+
warning: { backgroundColor: '#FEF3C7', color: '#92400E' },
|
|
292
|
+
info: { backgroundColor: '#DBEAFE', color: '#1E40AF' },
|
|
293
|
+
neutral: { backgroundColor: '#F3F4F6', color: '#374151' },
|
|
294
|
+
};
|
|
295
|
+
return {
|
|
296
|
+
display: 'inline-block',
|
|
297
|
+
fontSize: '0.625rem',
|
|
298
|
+
fontWeight: 600,
|
|
299
|
+
padding: '0.125rem 0.5rem',
|
|
300
|
+
borderRadius: '0.25rem',
|
|
301
|
+
marginTop: '0.25rem',
|
|
302
|
+
...(variants[variant] || variants.neutral),
|
|
303
|
+
};
|
|
304
|
+
};
|
|
305
|
+
const stepLinkStyle = {
|
|
306
|
+
display: 'block',
|
|
307
|
+
fontSize: '0.75rem',
|
|
308
|
+
color: '#3b82f6',
|
|
309
|
+
textDecoration: 'none',
|
|
310
|
+
marginTop: '0.25rem',
|
|
311
|
+
};
|
|
312
|
+
const renderIndicatorContent = () => {
|
|
313
|
+
if (state === 'complete' && indicatorType === 'check') {
|
|
314
|
+
return _jsx("span", { style: iconWrapperStyle, children: _jsx(CheckIcon, {}) });
|
|
315
|
+
}
|
|
316
|
+
if (state === 'warning') {
|
|
317
|
+
return _jsx("span", { style: iconWrapperStyle, children: _jsx(WarningIcon, {}) });
|
|
318
|
+
}
|
|
319
|
+
if (state === 'error') {
|
|
320
|
+
return _jsx("span", { style: iconWrapperStyle, children: _jsx(ErrorIcon, {}) });
|
|
321
|
+
}
|
|
322
|
+
if (icon) {
|
|
323
|
+
return _jsx("span", { style: iconWrapperStyle, children: icon });
|
|
324
|
+
}
|
|
325
|
+
if (iconSrc) {
|
|
326
|
+
return (_jsx("span", { style: iconWrapperStyle, children: _jsx("img", { src: iconSrc, alt: `Step ${displayNumber}`, style: { width: '100%', height: '100%', objectFit: 'contain' } }) }));
|
|
327
|
+
}
|
|
328
|
+
if (indicatorType === 'number') {
|
|
329
|
+
return _jsx("span", { children: displayNumber });
|
|
330
|
+
}
|
|
331
|
+
if (indicatorType === 'dot' && state === 'incomplete') {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
if (state === 'complete') {
|
|
335
|
+
return _jsx("span", { style: iconWrapperStyle, children: _jsx(CheckIcon, {}) });
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
};
|
|
339
|
+
// Glow animation for in-progress step indicator
|
|
340
|
+
const stepIndicatorGlowStyle = state === 'inprogress' ? {
|
|
341
|
+
boxShadow: `0 0 8px ${activeColor}`,
|
|
342
|
+
animation: 'progressGlow 2s ease-in-out infinite',
|
|
343
|
+
} : {};
|
|
344
|
+
return (_jsxs(_Fragment, { children: [_jsxs("li", { style: {
|
|
345
|
+
...getStepItemStyle(stateWithSubSteps, size, indicatorType, activeColor, inactiveColor),
|
|
346
|
+
...stepIndicatorGlowStyle,
|
|
347
|
+
}, "data-testid": `progress-stepper-step-${index}`, children: [indicatorType === 'dot' && state === 'inprogress' && (_jsx("div", { style: innerDotStyle })), renderIndicatorContent()] }), _jsxs("div", { style: connectorTextGroupStyle, children: [!isLast && (_jsx("div", { className: "step-connector", style: connectorStyle, "data-testid": `progress-stepper-step-${index}-connector`, children: _jsxs("div", { style: { ...connectorFillStyle, ...pulseStyle }, "data-testid": `progress-stepper-step-${index}-connector-fill`, children: [isAnimated && connectorFillPercentage > 0 && (_jsx("div", { style: shimmerOverlayStyle, "aria-hidden": "true" })), _jsx("span", { style: connectorDotStyle })] }) })), orientation === 'vertical' && (stepTitle || stepSubTitle) && (_jsxs("div", { style: stepTextGroupStyle, children: [stepTitle && (_jsx("h2", { style: stepTitleStyle, "data-testid": `step-title-${index}`, children: stepTitle })), stepSubTitle && (_jsx("h3", { style: stepSubTitleStyle, "data-testid": `step-subtitle-${index}`, children: stepSubTitle })), tagText && (_jsx("span", { style: getTagStyle(tagVariant), "data-testid": `step-tag-${index}`, children: tagText })), stepLinkText && stepLinkHref && (_jsx("a", { href: stepLinkHref, style: stepLinkStyle, "data-testid": `step-link-${index}`, children: stepLinkText }))] }))] })] }));
|
|
348
|
+
};
|
|
349
|
+
StepItem.displayName = 'StepItem';
|