ywana-core8 0.1.74 → 0.1.76
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/ACCORDION_EVALUATION.md +583 -0
- package/CHECKBOX_EVALUATION.md +273 -0
- package/CHIP_EVALUATION.md +542 -0
- package/COLOR_EVALUATION.md +524 -0
- package/COMPONENTS_EVALUATION.md +477 -0
- package/FORM_EVALUATION.md +459 -0
- package/HEADER_EVALUATION.md +436 -0
- package/ICON_EVALUATION.md +254 -0
- package/LIST_EVALUATION.md +574 -0
- package/PROGRESS_EVALUATION.md +450 -0
- package/RADIO_EVALUATION.md +439 -0
- package/RADIO_VISUAL_FIX.md +183 -0
- package/SECTION_IMPROVEMENTS.md +153 -0
- package/SWITCH_EVALUATION.md +335 -0
- package/SWITCH_VISUAL_FIX.md +232 -0
- package/TAB_EVALUATION.md +626 -0
- package/TEXTFIELD_EVALUATION.md +747 -0
- package/TOOLTIP_FIX.md +157 -0
- package/TREE_EVALUATION.md +708 -0
- package/dist/index.cjs +7900 -1615
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +6094 -1122
- package/dist/index.css.map +1 -1
- package/dist/index.modern.js +7929 -1645
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +7900 -1615
- package/dist/index.umd.js.map +1 -1
- package/jest.config.js +24 -0
- package/package.json +10 -1
- package/src/html/accordion.css +208 -4
- package/src/html/accordion.example.js +390 -0
- package/src/html/accordion.js +284 -28
- package/src/html/accordion.unit.test.js +334 -0
- package/src/html/button.css +157 -16
- package/src/html/button.example.js +374 -0
- package/src/html/button.js +240 -60
- package/src/html/button.test.js +422 -0
- package/src/html/checkbox.css +74 -2
- package/src/html/checkbox.example.js +316 -0
- package/src/html/checkbox.js +113 -26
- package/src/html/checkbox.test.js +285 -0
- package/src/html/chip.css +230 -19
- package/src/html/chip.example.js +355 -0
- package/src/html/chip.js +321 -25
- package/src/html/chip.test.js +425 -0
- package/src/html/color.css +435 -6
- package/src/html/color.example.js +527 -0
- package/src/html/color.js +458 -9
- package/src/html/color.test.js +362 -4
- package/src/html/components.example.js +492 -0
- package/src/html/components_enhanced.test.js +581 -0
- package/src/html/form.css +70 -3
- package/src/html/form.example.js +385 -0
- package/src/html/form.js +232 -34
- package/src/html/form.test.js +369 -0
- package/src/html/header2.css +264 -0
- package/src/html/header2.example.js +411 -0
- package/src/html/header2.js +203 -0
- package/src/html/header2.test.js +377 -0
- package/src/html/icon.css +20 -2
- package/src/html/icon.example.js +268 -0
- package/src/html/icon.js +86 -16
- package/src/html/icon.test.js +231 -0
- package/src/html/index.js +1 -1
- package/src/html/list.css +393 -1
- package/src/html/list.example.js +404 -0
- package/src/html/list.js +583 -40
- package/src/html/list.test.js +383 -0
- package/src/html/progress.css +707 -17
- package/src/html/progress.example.js +424 -0
- package/src/html/progress.js +906 -9
- package/src/html/progress.test.js +313 -0
- package/src/html/property.css +399 -0
- package/src/html/property.example.js +553 -0
- package/src/html/property.js +393 -15
- package/src/html/property.test.js +351 -2
- package/src/html/radio-visual-test.js +289 -0
- package/src/html/radio.css +137 -11
- package/src/html/radio.example.js +389 -0
- package/src/html/radio.js +234 -10
- package/src/html/radio.test.js +318 -0
- package/src/html/section.example.js +99 -0
- package/src/html/section.js +40 -3
- package/src/html/section.test.js +131 -0
- package/src/html/selector.css +329 -3
- package/src/html/selector.js +369 -23
- package/src/html/switch-debug.js +197 -0
- package/src/html/switch-test-visual.js +294 -0
- package/src/html/switch.css +200 -0
- package/src/html/switch.example.js +461 -0
- package/src/html/switch.js +283 -23
- package/src/html/switch.test.js +355 -0
- package/src/html/tab.css +288 -0
- package/src/html/tab.example.js +446 -0
- package/src/html/tab.js +387 -22
- package/src/html/tab_enhanced.js +378 -0
- package/src/html/tab_enhanced.test.js +504 -0
- package/src/html/table2.css +576 -0
- package/src/html/table2.example.js +703 -0
- package/src/html/table2.js +1252 -0
- package/src/html/table2.migration.md +328 -0
- package/src/html/table2.test.js +582 -0
- package/src/html/text.css +375 -0
- package/src/html/text.js +311 -20
- package/src/html/textfield.js +1 -1
- package/src/html/textfield2.css +842 -0
- package/src/html/textfield2.example.js +499 -0
- package/src/html/textfield2.js +1130 -0
- package/src/html/textfield2.test.js +950 -0
- package/src/html/thumbnail.css +289 -2
- package/src/html/thumbnail.js +214 -9
- package/src/html/tokenfield.css +449 -1
- package/src/html/tokenfield.example.js +503 -0
- package/src/html/tokenfield.js +561 -56
- package/src/html/tokenfield.test.js +423 -0
- package/src/html/tooltip-positioning-demo.js +187 -0
- package/src/html/tooltip.css +25 -2
- package/src/html/tree.css +228 -0
- package/src/html/tree.example.js +475 -0
- package/src/html/tree.js +712 -28
- package/src/html/tree_enhanced.test.js +495 -0
- package/table2.test.js +454 -0
- package/src/html/button.tsx +0 -38
package/src/html/progress.js
CHANGED
@@ -1,28 +1,925 @@
|
|
1
|
-
import React from 'react'
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react'
|
2
|
+
import PropTypes from 'prop-types'
|
2
3
|
import { Icon } from './icon'
|
3
4
|
import './progress.css'
|
4
5
|
|
5
6
|
/**
|
6
|
-
* Circular Progress
|
7
|
+
* Enhanced Circular Progress component with professional features
|
7
8
|
*/
|
8
9
|
export const CircularProgress = (props) => {
|
10
|
+
const {
|
11
|
+
value = 0,
|
12
|
+
max = 100,
|
13
|
+
size = 'medium',
|
14
|
+
thickness = 4,
|
15
|
+
variant = 'indeterminate',
|
16
|
+
color = 'primary',
|
17
|
+
showValue = false,
|
18
|
+
showLabel = false,
|
19
|
+
label,
|
20
|
+
icon,
|
21
|
+
animated = true,
|
22
|
+
disabled = false,
|
23
|
+
className,
|
24
|
+
style,
|
25
|
+
children,
|
26
|
+
onComplete,
|
27
|
+
formatValue,
|
28
|
+
ariaLabel,
|
29
|
+
...restProps
|
30
|
+
} = props
|
31
|
+
|
32
|
+
const [animatedValue, setAnimatedValue] = useState(0)
|
33
|
+
const [isComplete, setIsComplete] = useState(false)
|
34
|
+
|
35
|
+
// Animate value changes
|
36
|
+
useEffect(() => {
|
37
|
+
if (variant === 'determinate' && animated) {
|
38
|
+
const startValue = animatedValue
|
39
|
+
const endValue = Math.min(Math.max(value, 0), max)
|
40
|
+
const duration = 300
|
41
|
+
const startTime = Date.now()
|
42
|
+
|
43
|
+
const animate = () => {
|
44
|
+
const elapsed = Date.now() - startTime
|
45
|
+
const progress = Math.min(elapsed / duration, 1)
|
46
|
+
const easeOutQuart = 1 - Math.pow(1 - progress, 4)
|
47
|
+
const currentValue = startValue + (endValue - startValue) * easeOutQuart
|
48
|
+
|
49
|
+
setAnimatedValue(currentValue)
|
50
|
+
|
51
|
+
if (progress < 1) {
|
52
|
+
requestAnimationFrame(animate)
|
53
|
+
} else {
|
54
|
+
if (endValue >= max && !isComplete) {
|
55
|
+
setIsComplete(true)
|
56
|
+
if (onComplete) onComplete()
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
requestAnimationFrame(animate)
|
62
|
+
} else {
|
63
|
+
setAnimatedValue(value)
|
64
|
+
}
|
65
|
+
}, [value, max, variant, animated, animatedValue, isComplete, onComplete])
|
66
|
+
|
67
|
+
// Calculate dimensions based on size
|
68
|
+
const dimensions = useMemo(() => {
|
69
|
+
const sizes = {
|
70
|
+
small: { size: 24, strokeWidth: 2 },
|
71
|
+
medium: { size: 40, strokeWidth: 3 },
|
72
|
+
large: { size: 56, strokeWidth: 4 },
|
73
|
+
xlarge: { size: 80, strokeWidth: 5 }
|
74
|
+
}
|
75
|
+
return sizes[size] || sizes.medium
|
76
|
+
}, [size])
|
77
|
+
|
78
|
+
// Calculate circle properties
|
79
|
+
const radius = (dimensions.size - thickness) / 2
|
80
|
+
const circumference = 2 * Math.PI * radius
|
81
|
+
const strokeDasharray = circumference
|
82
|
+
const strokeDashoffset = variant === 'determinate'
|
83
|
+
? circumference - (animatedValue / max) * circumference
|
84
|
+
: 0
|
85
|
+
|
86
|
+
// Format display value
|
87
|
+
const displayValue = useMemo(() => {
|
88
|
+
if (formatValue) {
|
89
|
+
return formatValue(animatedValue, max)
|
90
|
+
}
|
91
|
+
return variant === 'determinate'
|
92
|
+
? `${Math.round((animatedValue / max) * 100)}%`
|
93
|
+
: ''
|
94
|
+
}, [animatedValue, max, formatValue, variant])
|
95
|
+
|
96
|
+
// Generate CSS classes
|
97
|
+
const cssClasses = [
|
98
|
+
'circular-progress',
|
99
|
+
`circular-progress--${size}`,
|
100
|
+
`circular-progress--${variant}`,
|
101
|
+
`circular-progress--${color}`,
|
102
|
+
disabled && 'circular-progress--disabled',
|
103
|
+
isComplete && 'circular-progress--complete',
|
104
|
+
animated && 'circular-progress--animated',
|
105
|
+
className
|
106
|
+
].filter(Boolean).join(' ')
|
107
|
+
|
108
|
+
// Accessibility attributes
|
109
|
+
const ariaAttributes = {
|
110
|
+
'aria-label': ariaLabel || (variant === 'determinate' ? `Progress: ${displayValue}` : 'Loading'),
|
111
|
+
'aria-valuenow': variant === 'determinate' ? animatedValue : undefined,
|
112
|
+
'aria-valuemin': variant === 'determinate' ? 0 : undefined,
|
113
|
+
'aria-valuemax': variant === 'determinate' ? max : undefined,
|
114
|
+
'aria-valuetext': variant === 'determinate' ? displayValue : 'Loading',
|
115
|
+
role: 'progressbar'
|
116
|
+
}
|
117
|
+
|
9
118
|
return (
|
10
|
-
<div
|
11
|
-
|
119
|
+
<div
|
120
|
+
className={cssClasses}
|
121
|
+
style={{
|
122
|
+
width: dimensions.size,
|
123
|
+
height: dimensions.size,
|
124
|
+
...style
|
125
|
+
}}
|
126
|
+
{...ariaAttributes}
|
127
|
+
{...restProps}
|
128
|
+
>
|
129
|
+
<svg
|
130
|
+
className="circular-progress__svg"
|
131
|
+
width={dimensions.size}
|
132
|
+
height={dimensions.size}
|
133
|
+
viewBox={`0 0 ${dimensions.size} ${dimensions.size}`}
|
134
|
+
>
|
135
|
+
{/* Background circle */}
|
136
|
+
<circle
|
137
|
+
className="circular-progress__background"
|
138
|
+
cx={dimensions.size / 2}
|
139
|
+
cy={dimensions.size / 2}
|
140
|
+
r={radius}
|
141
|
+
fill="none"
|
142
|
+
strokeWidth={thickness}
|
143
|
+
/>
|
144
|
+
|
145
|
+
{/* Progress circle */}
|
146
|
+
<circle
|
147
|
+
className="circular-progress__progress"
|
148
|
+
cx={dimensions.size / 2}
|
149
|
+
cy={dimensions.size / 2}
|
150
|
+
r={radius}
|
151
|
+
fill="none"
|
152
|
+
strokeWidth={thickness}
|
153
|
+
strokeDasharray={strokeDasharray}
|
154
|
+
strokeDashoffset={strokeDashoffset}
|
155
|
+
strokeLinecap="round"
|
156
|
+
transform={`rotate(-90 ${dimensions.size / 2} ${dimensions.size / 2})`}
|
157
|
+
/>
|
158
|
+
</svg>
|
159
|
+
|
160
|
+
{/* Content overlay */}
|
161
|
+
<div className="circular-progress__content">
|
162
|
+
{icon && (
|
163
|
+
<Icon
|
164
|
+
icon={icon}
|
165
|
+
size={size === 'small' ? 'small' : 'medium'}
|
166
|
+
className="circular-progress__icon"
|
167
|
+
/>
|
168
|
+
)}
|
169
|
+
|
170
|
+
{showValue && variant === 'determinate' && (
|
171
|
+
<span className="circular-progress__value">
|
172
|
+
{displayValue}
|
173
|
+
</span>
|
174
|
+
)}
|
175
|
+
|
176
|
+
{children}
|
177
|
+
</div>
|
178
|
+
|
179
|
+
{(showLabel || label) && (
|
180
|
+
<div className="circular-progress__label">
|
181
|
+
{label || (variant === 'determinate' ? displayValue : 'Loading...')}
|
182
|
+
</div>
|
183
|
+
)}
|
12
184
|
</div>
|
13
185
|
)
|
14
186
|
}
|
15
187
|
|
16
188
|
/**
|
17
|
-
* Linear Progress
|
189
|
+
* Enhanced Linear Progress component with professional features
|
18
190
|
*/
|
19
191
|
export const LinearProgress = (props) => {
|
192
|
+
const {
|
193
|
+
value = 0,
|
194
|
+
max = 100,
|
195
|
+
variant = 'determinate',
|
196
|
+
color = 'primary',
|
197
|
+
size = 'medium',
|
198
|
+
showValue = false,
|
199
|
+
showLabel = false,
|
200
|
+
label,
|
201
|
+
buffer,
|
202
|
+
animated = true,
|
203
|
+
striped = false,
|
204
|
+
disabled = false,
|
205
|
+
rounded = true,
|
206
|
+
className,
|
207
|
+
style,
|
208
|
+
onComplete,
|
209
|
+
formatValue,
|
210
|
+
ariaLabel,
|
211
|
+
estimatedTime,
|
212
|
+
speed,
|
213
|
+
...restProps
|
214
|
+
} = props
|
215
|
+
|
216
|
+
const [animatedValue, setAnimatedValue] = useState(0)
|
217
|
+
const [animatedBuffer, setAnimatedBuffer] = useState(0)
|
218
|
+
const [isComplete, setIsComplete] = useState(false)
|
219
|
+
const [timeRemaining, setTimeRemaining] = useState(null)
|
220
|
+
|
221
|
+
// Animate value changes
|
222
|
+
useEffect(() => {
|
223
|
+
if (variant === 'determinate' && animated) {
|
224
|
+
const startValue = animatedValue
|
225
|
+
const endValue = Math.min(Math.max(value, 0), max)
|
226
|
+
const duration = 300
|
227
|
+
const startTime = Date.now()
|
228
|
+
|
229
|
+
const animate = () => {
|
230
|
+
const elapsed = Date.now() - startTime
|
231
|
+
const progress = Math.min(elapsed / duration, 1)
|
232
|
+
const easeOutQuart = 1 - Math.pow(1 - progress, 4)
|
233
|
+
const currentValue = startValue + (endValue - startValue) * easeOutQuart
|
234
|
+
|
235
|
+
setAnimatedValue(currentValue)
|
236
|
+
|
237
|
+
if (progress < 1) {
|
238
|
+
requestAnimationFrame(animate)
|
239
|
+
} else {
|
240
|
+
if (endValue >= max && !isComplete) {
|
241
|
+
setIsComplete(true)
|
242
|
+
if (onComplete) onComplete()
|
243
|
+
}
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
requestAnimationFrame(animate)
|
248
|
+
} else {
|
249
|
+
setAnimatedValue(value)
|
250
|
+
}
|
251
|
+
}, [value, max, variant, animated, animatedValue, isComplete, onComplete])
|
252
|
+
|
253
|
+
// Animate buffer changes
|
254
|
+
useEffect(() => {
|
255
|
+
if (buffer !== undefined && animated) {
|
256
|
+
const startBuffer = animatedBuffer
|
257
|
+
const endBuffer = Math.min(Math.max(buffer, 0), max)
|
258
|
+
const duration = 200
|
259
|
+
const startTime = Date.now()
|
260
|
+
|
261
|
+
const animate = () => {
|
262
|
+
const elapsed = Date.now() - startTime
|
263
|
+
const progress = Math.min(elapsed / duration, 1)
|
264
|
+
const easeOutQuart = 1 - Math.pow(1 - progress, 4)
|
265
|
+
const currentBuffer = startBuffer + (endBuffer - startBuffer) * easeOutQuart
|
266
|
+
|
267
|
+
setAnimatedBuffer(currentBuffer)
|
268
|
+
|
269
|
+
if (progress < 1) {
|
270
|
+
requestAnimationFrame(animate)
|
271
|
+
}
|
272
|
+
}
|
20
273
|
|
21
|
-
|
274
|
+
requestAnimationFrame(animate)
|
275
|
+
} else if (buffer !== undefined) {
|
276
|
+
setAnimatedBuffer(buffer)
|
277
|
+
}
|
278
|
+
}, [buffer, max, animated, animatedBuffer])
|
279
|
+
|
280
|
+
// Calculate time remaining
|
281
|
+
useEffect(() => {
|
282
|
+
if (estimatedTime && variant === 'determinate') {
|
283
|
+
const progressPercent = animatedValue / max
|
284
|
+
const remaining = progressPercent > 0
|
285
|
+
? estimatedTime * (1 - progressPercent)
|
286
|
+
: estimatedTime
|
287
|
+
setTimeRemaining(remaining)
|
288
|
+
} else if (speed && variant === 'determinate') {
|
289
|
+
const remaining = (max - animatedValue) / speed
|
290
|
+
setTimeRemaining(remaining)
|
291
|
+
}
|
292
|
+
}, [animatedValue, max, estimatedTime, speed, variant])
|
293
|
+
|
294
|
+
// Calculate percentages
|
295
|
+
const progressPercent = (animatedValue / max) * 100
|
296
|
+
const bufferPercent = buffer !== undefined ? (animatedBuffer / max) * 100 : 0
|
297
|
+
|
298
|
+
// Format display value
|
299
|
+
const displayValue = useMemo(() => {
|
300
|
+
if (formatValue) {
|
301
|
+
return formatValue(animatedValue, max)
|
302
|
+
}
|
303
|
+
return variant === 'determinate'
|
304
|
+
? `${Math.round(progressPercent)}%`
|
305
|
+
: ''
|
306
|
+
}, [animatedValue, max, formatValue, variant, progressPercent])
|
307
|
+
|
308
|
+
// Format time remaining
|
309
|
+
const displayTimeRemaining = useMemo(() => {
|
310
|
+
if (!timeRemaining) return ''
|
311
|
+
|
312
|
+
const minutes = Math.floor(timeRemaining / 60)
|
313
|
+
const seconds = Math.floor(timeRemaining % 60)
|
314
|
+
|
315
|
+
if (minutes > 0) {
|
316
|
+
return `${minutes}m ${seconds}s remaining`
|
317
|
+
}
|
318
|
+
return `${seconds}s remaining`
|
319
|
+
}, [timeRemaining])
|
320
|
+
|
321
|
+
// Generate CSS classes
|
322
|
+
const cssClasses = [
|
323
|
+
'linear-progress',
|
324
|
+
`linear-progress--${size}`,
|
325
|
+
`linear-progress--${variant}`,
|
326
|
+
`linear-progress--${color}`,
|
327
|
+
striped && 'linear-progress--striped',
|
328
|
+
rounded && 'linear-progress--rounded',
|
329
|
+
disabled && 'linear-progress--disabled',
|
330
|
+
isComplete && 'linear-progress--complete',
|
331
|
+
animated && 'linear-progress--animated',
|
332
|
+
className
|
333
|
+
].filter(Boolean).join(' ')
|
334
|
+
|
335
|
+
// Accessibility attributes
|
336
|
+
const ariaAttributes = {
|
337
|
+
'aria-label': ariaLabel || (variant === 'determinate' ? `Progress: ${displayValue}` : 'Loading'),
|
338
|
+
'aria-valuenow': variant === 'determinate' ? animatedValue : undefined,
|
339
|
+
'aria-valuemin': variant === 'determinate' ? 0 : undefined,
|
340
|
+
'aria-valuemax': variant === 'determinate' ? max : undefined,
|
341
|
+
'aria-valuetext': variant === 'determinate' ? displayValue : 'Loading',
|
342
|
+
role: 'progressbar'
|
343
|
+
}
|
344
|
+
|
345
|
+
return (
|
346
|
+
<div className={cssClasses} style={style} {...restProps}>
|
347
|
+
{/* Header with label and value */}
|
348
|
+
{(showLabel || showValue || label || timeRemaining) && (
|
349
|
+
<div className="linear-progress__header">
|
350
|
+
<div className="linear-progress__label">
|
351
|
+
{label || (showLabel && variant === 'determinate' ? 'Progress' : '')}
|
352
|
+
</div>
|
353
|
+
<div className="linear-progress__info">
|
354
|
+
{showValue && variant === 'determinate' && (
|
355
|
+
<span className="linear-progress__value">
|
356
|
+
{displayValue}
|
357
|
+
</span>
|
358
|
+
)}
|
359
|
+
{timeRemaining && (
|
360
|
+
<span className="linear-progress__time">
|
361
|
+
{displayTimeRemaining}
|
362
|
+
</span>
|
363
|
+
)}
|
364
|
+
</div>
|
365
|
+
</div>
|
366
|
+
)}
|
367
|
+
|
368
|
+
{/* Progress track */}
|
369
|
+
<div className="linear-progress__track" {...ariaAttributes}>
|
370
|
+
{/* Buffer bar (if provided) */}
|
371
|
+
{buffer !== undefined && (
|
372
|
+
<div
|
373
|
+
className="linear-progress__buffer"
|
374
|
+
style={{ width: `${bufferPercent}%` }}
|
375
|
+
/>
|
376
|
+
)}
|
377
|
+
|
378
|
+
{/* Progress bar */}
|
379
|
+
<div
|
380
|
+
className="linear-progress__bar"
|
381
|
+
style={{
|
382
|
+
width: variant === 'determinate' ? `${progressPercent}%` : undefined
|
383
|
+
}}
|
384
|
+
>
|
385
|
+
{/* Indeterminate animation elements */}
|
386
|
+
{variant === 'indeterminate' && (
|
387
|
+
<>
|
388
|
+
<div className="linear-progress__bar-primary" />
|
389
|
+
<div className="linear-progress__bar-secondary" />
|
390
|
+
</>
|
391
|
+
)}
|
392
|
+
</div>
|
393
|
+
</div>
|
394
|
+
</div>
|
395
|
+
)
|
396
|
+
}
|
397
|
+
|
398
|
+
/**
|
399
|
+
* Step Progress component for multi-step processes
|
400
|
+
*/
|
401
|
+
export const StepProgress = (props) => {
|
402
|
+
const {
|
403
|
+
steps = [],
|
404
|
+
currentStep = 0,
|
405
|
+
variant = 'horizontal',
|
406
|
+
color = 'primary',
|
407
|
+
size = 'medium',
|
408
|
+
showLabels = true,
|
409
|
+
showNumbers = true,
|
410
|
+
allowClickNavigation = false,
|
411
|
+
animated = true,
|
412
|
+
className,
|
413
|
+
onStepClick,
|
414
|
+
...restProps
|
415
|
+
} = props
|
416
|
+
|
417
|
+
const handleStepClick = useCallback((stepIndex, step) => {
|
418
|
+
if (allowClickNavigation && onStepClick && stepIndex <= currentStep) {
|
419
|
+
onStepClick(stepIndex, step)
|
420
|
+
}
|
421
|
+
}, [allowClickNavigation, onStepClick, currentStep])
|
422
|
+
|
423
|
+
// Generate CSS classes
|
424
|
+
const cssClasses = [
|
425
|
+
'step-progress',
|
426
|
+
`step-progress--${variant}`,
|
427
|
+
`step-progress--${size}`,
|
428
|
+
`step-progress--${color}`,
|
429
|
+
animated && 'step-progress--animated',
|
430
|
+
className
|
431
|
+
].filter(Boolean).join(' ')
|
22
432
|
|
23
433
|
return (
|
24
|
-
<div className=
|
25
|
-
|
434
|
+
<div className={cssClasses} {...restProps}>
|
435
|
+
{steps.map((step, index) => {
|
436
|
+
const isCompleted = index < currentStep
|
437
|
+
const isCurrent = index === currentStep
|
438
|
+
const isClickable = allowClickNavigation && index <= currentStep
|
439
|
+
|
440
|
+
const stepClasses = [
|
441
|
+
'step-progress__step',
|
442
|
+
isCompleted && 'step-progress__step--completed',
|
443
|
+
isCurrent && 'step-progress__step--current',
|
444
|
+
isClickable && 'step-progress__step--clickable',
|
445
|
+
step.error && 'step-progress__step--error'
|
446
|
+
].filter(Boolean).join(' ')
|
447
|
+
|
448
|
+
return (
|
449
|
+
<div key={step.id || index} className={stepClasses}>
|
450
|
+
{/* Connector line (except for last step) */}
|
451
|
+
{index < steps.length - 1 && (
|
452
|
+
<div className="step-progress__connector" />
|
453
|
+
)}
|
454
|
+
|
455
|
+
{/* Step circle */}
|
456
|
+
<div
|
457
|
+
className="step-progress__circle"
|
458
|
+
onClick={() => handleStepClick(index, step)}
|
459
|
+
role={isClickable ? 'button' : undefined}
|
460
|
+
tabIndex={isClickable ? 0 : -1}
|
461
|
+
aria-label={`Step ${index + 1}: ${step.label}`}
|
462
|
+
>
|
463
|
+
{step.error ? (
|
464
|
+
<Icon icon="error" size="small" />
|
465
|
+
) : isCompleted ? (
|
466
|
+
<Icon icon="check" size="small" />
|
467
|
+
) : showNumbers ? (
|
468
|
+
<span className="step-progress__number">
|
469
|
+
{index + 1}
|
470
|
+
</span>
|
471
|
+
) : (
|
472
|
+
step.icon && <Icon icon={step.icon} size="small" />
|
473
|
+
)}
|
474
|
+
</div>
|
475
|
+
|
476
|
+
{/* Step label */}
|
477
|
+
{showLabels && (
|
478
|
+
<div className="step-progress__label">
|
479
|
+
<div className="step-progress__title">
|
480
|
+
{step.label}
|
481
|
+
</div>
|
482
|
+
{step.description && (
|
483
|
+
<div className="step-progress__description">
|
484
|
+
{step.description}
|
485
|
+
</div>
|
486
|
+
)}
|
487
|
+
</div>
|
488
|
+
)}
|
489
|
+
</div>
|
490
|
+
)
|
491
|
+
})}
|
26
492
|
</div>
|
27
493
|
)
|
28
|
-
}
|
494
|
+
}
|
495
|
+
|
496
|
+
/**
|
497
|
+
* Radial Progress component for dashboard-style displays
|
498
|
+
*/
|
499
|
+
export const RadialProgress = (props) => {
|
500
|
+
const {
|
501
|
+
value = 0,
|
502
|
+
max = 100,
|
503
|
+
size = 120,
|
504
|
+
thickness = 8,
|
505
|
+
color = 'primary',
|
506
|
+
backgroundColor = 'rgba(0,0,0,0.1)',
|
507
|
+
showValue = true,
|
508
|
+
showLabel = false,
|
509
|
+
label,
|
510
|
+
icon,
|
511
|
+
animated = true,
|
512
|
+
gradient = false,
|
513
|
+
className,
|
514
|
+
formatValue,
|
515
|
+
children,
|
516
|
+
...restProps
|
517
|
+
} = props
|
518
|
+
|
519
|
+
const [animatedValue, setAnimatedValue] = useState(0)
|
520
|
+
|
521
|
+
// Animate value changes
|
522
|
+
useEffect(() => {
|
523
|
+
if (animated) {
|
524
|
+
const startValue = animatedValue
|
525
|
+
const endValue = Math.min(Math.max(value, 0), max)
|
526
|
+
const duration = 800
|
527
|
+
const startTime = Date.now()
|
528
|
+
|
529
|
+
const animate = () => {
|
530
|
+
const elapsed = Date.now() - startTime
|
531
|
+
const progress = Math.min(elapsed / duration, 1)
|
532
|
+
const easeOutCubic = 1 - Math.pow(1 - progress, 3)
|
533
|
+
const currentValue = startValue + (endValue - startValue) * easeOutCubic
|
534
|
+
|
535
|
+
setAnimatedValue(currentValue)
|
536
|
+
|
537
|
+
if (progress < 1) {
|
538
|
+
requestAnimationFrame(animate)
|
539
|
+
}
|
540
|
+
}
|
541
|
+
|
542
|
+
requestAnimationFrame(animate)
|
543
|
+
} else {
|
544
|
+
setAnimatedValue(value)
|
545
|
+
}
|
546
|
+
}, [value, max, animated, animatedValue])
|
547
|
+
|
548
|
+
// Calculate circle properties
|
549
|
+
const radius = (size - thickness) / 2
|
550
|
+
const circumference = 2 * Math.PI * radius
|
551
|
+
const strokeDasharray = circumference
|
552
|
+
const strokeDashoffset = circumference - (animatedValue / max) * circumference
|
553
|
+
const percentage = Math.round((animatedValue / max) * 100)
|
554
|
+
|
555
|
+
// Format display value
|
556
|
+
const displayValue = formatValue ? formatValue(animatedValue, max) : `${percentage}%`
|
557
|
+
|
558
|
+
// Generate CSS classes
|
559
|
+
const cssClasses = [
|
560
|
+
'radial-progress',
|
561
|
+
`radial-progress--${color}`,
|
562
|
+
gradient && 'radial-progress--gradient',
|
563
|
+
animated && 'radial-progress--animated',
|
564
|
+
className
|
565
|
+
].filter(Boolean).join(' ')
|
566
|
+
|
567
|
+
return (
|
568
|
+
<div
|
569
|
+
className={cssClasses}
|
570
|
+
style={{ width: size, height: size }}
|
571
|
+
{...restProps}
|
572
|
+
>
|
573
|
+
<svg
|
574
|
+
className="radial-progress__svg"
|
575
|
+
width={size}
|
576
|
+
height={size}
|
577
|
+
viewBox={`0 0 ${size} ${size}`}
|
578
|
+
>
|
579
|
+
{/* Gradient definition */}
|
580
|
+
{gradient && (
|
581
|
+
<defs>
|
582
|
+
<linearGradient id={`gradient-${color}`} x1="0%" y1="0%" x2="100%" y2="100%">
|
583
|
+
<stop offset="0%" stopColor="var(--primary-color-light)" />
|
584
|
+
<stop offset="100%" stopColor="var(--primary-color-dark)" />
|
585
|
+
</linearGradient>
|
586
|
+
</defs>
|
587
|
+
)}
|
588
|
+
|
589
|
+
{/* Background circle */}
|
590
|
+
<circle
|
591
|
+
className="radial-progress__background"
|
592
|
+
cx={size / 2}
|
593
|
+
cy={size / 2}
|
594
|
+
r={radius}
|
595
|
+
fill="none"
|
596
|
+
stroke={backgroundColor}
|
597
|
+
strokeWidth={thickness}
|
598
|
+
/>
|
599
|
+
|
600
|
+
{/* Progress circle */}
|
601
|
+
<circle
|
602
|
+
className="radial-progress__progress"
|
603
|
+
cx={size / 2}
|
604
|
+
cy={size / 2}
|
605
|
+
r={radius}
|
606
|
+
fill="none"
|
607
|
+
stroke={gradient ? `url(#gradient-${color})` : undefined}
|
608
|
+
strokeWidth={thickness}
|
609
|
+
strokeDasharray={strokeDasharray}
|
610
|
+
strokeDashoffset={strokeDashoffset}
|
611
|
+
strokeLinecap="round"
|
612
|
+
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
613
|
+
/>
|
614
|
+
</svg>
|
615
|
+
|
616
|
+
{/* Content overlay */}
|
617
|
+
<div className="radial-progress__content">
|
618
|
+
{icon && (
|
619
|
+
<Icon
|
620
|
+
icon={icon}
|
621
|
+
size="large"
|
622
|
+
className="radial-progress__icon"
|
623
|
+
/>
|
624
|
+
)}
|
625
|
+
|
626
|
+
{showValue && (
|
627
|
+
<div className="radial-progress__value">
|
628
|
+
{displayValue}
|
629
|
+
</div>
|
630
|
+
)}
|
631
|
+
|
632
|
+
{(showLabel || label) && (
|
633
|
+
<div className="radial-progress__label">
|
634
|
+
{label || 'Progress'}
|
635
|
+
</div>
|
636
|
+
)}
|
637
|
+
|
638
|
+
{children}
|
639
|
+
</div>
|
640
|
+
</div>
|
641
|
+
)
|
642
|
+
}
|
643
|
+
|
644
|
+
/**
|
645
|
+
* Multi Progress component for showing multiple progress bars
|
646
|
+
*/
|
647
|
+
export const MultiProgress = (props) => {
|
648
|
+
const {
|
649
|
+
items = [],
|
650
|
+
showLabels = true,
|
651
|
+
showValues = true,
|
652
|
+
size = 'medium',
|
653
|
+
animated = true,
|
654
|
+
className,
|
655
|
+
...restProps
|
656
|
+
} = props
|
657
|
+
|
658
|
+
const cssClasses = [
|
659
|
+
'multi-progress',
|
660
|
+
`multi-progress--${size}`,
|
661
|
+
animated && 'multi-progress--animated',
|
662
|
+
className
|
663
|
+
].filter(Boolean).join(' ')
|
664
|
+
|
665
|
+
return (
|
666
|
+
<div className={cssClasses} {...restProps}>
|
667
|
+
{items.map((item, index) => (
|
668
|
+
<div key={item.id || index} className="multi-progress__item">
|
669
|
+
{showLabels && (
|
670
|
+
<div className="multi-progress__header">
|
671
|
+
<span className="multi-progress__label">
|
672
|
+
{item.label}
|
673
|
+
</span>
|
674
|
+
{showValues && (
|
675
|
+
<span className="multi-progress__value">
|
676
|
+
{item.formatValue ?
|
677
|
+
item.formatValue(item.value, item.max) :
|
678
|
+
`${Math.round((item.value / item.max) * 100)}%`
|
679
|
+
}
|
680
|
+
</span>
|
681
|
+
)}
|
682
|
+
</div>
|
683
|
+
)}
|
684
|
+
<LinearProgress
|
685
|
+
value={item.value}
|
686
|
+
max={item.max}
|
687
|
+
color={item.color || 'primary'}
|
688
|
+
size={size}
|
689
|
+
animated={animated}
|
690
|
+
className="multi-progress__bar"
|
691
|
+
/>
|
692
|
+
</div>
|
693
|
+
))}
|
694
|
+
</div>
|
695
|
+
)
|
696
|
+
}
|
697
|
+
|
698
|
+
// PropTypes for all components
|
699
|
+
CircularProgress.propTypes = {
|
700
|
+
/** Current progress value */
|
701
|
+
value: PropTypes.number,
|
702
|
+
/** Maximum value */
|
703
|
+
max: PropTypes.number,
|
704
|
+
/** Size variant */
|
705
|
+
size: PropTypes.oneOf(['small', 'medium', 'large', 'xlarge']),
|
706
|
+
/** Stroke thickness */
|
707
|
+
thickness: PropTypes.number,
|
708
|
+
/** Progress variant */
|
709
|
+
variant: PropTypes.oneOf(['determinate', 'indeterminate']),
|
710
|
+
/** Color theme */
|
711
|
+
color: PropTypes.oneOf(['primary', 'secondary', 'success', 'warning', 'error']),
|
712
|
+
/** Show percentage value */
|
713
|
+
showValue: PropTypes.bool,
|
714
|
+
/** Show label below */
|
715
|
+
showLabel: PropTypes.bool,
|
716
|
+
/** Custom label text */
|
717
|
+
label: PropTypes.string,
|
718
|
+
/** Icon to display */
|
719
|
+
icon: PropTypes.string,
|
720
|
+
/** Enable animations */
|
721
|
+
animated: PropTypes.bool,
|
722
|
+
/** Disabled state */
|
723
|
+
disabled: PropTypes.bool,
|
724
|
+
/** Additional CSS classes */
|
725
|
+
className: PropTypes.string,
|
726
|
+
/** Inline styles */
|
727
|
+
style: PropTypes.object,
|
728
|
+
/** Child elements */
|
729
|
+
children: PropTypes.node,
|
730
|
+
/** Callback when progress completes */
|
731
|
+
onComplete: PropTypes.func,
|
732
|
+
/** Custom value formatter */
|
733
|
+
formatValue: PropTypes.func,
|
734
|
+
/** ARIA label */
|
735
|
+
ariaLabel: PropTypes.string
|
736
|
+
}
|
737
|
+
|
738
|
+
CircularProgress.defaultProps = {
|
739
|
+
value: 0,
|
740
|
+
max: 100,
|
741
|
+
size: 'medium',
|
742
|
+
thickness: 4,
|
743
|
+
variant: 'indeterminate',
|
744
|
+
color: 'primary',
|
745
|
+
showValue: false,
|
746
|
+
showLabel: false,
|
747
|
+
animated: true,
|
748
|
+
disabled: false
|
749
|
+
}
|
750
|
+
|
751
|
+
LinearProgress.propTypes = {
|
752
|
+
/** Current progress value */
|
753
|
+
value: PropTypes.number,
|
754
|
+
/** Maximum value */
|
755
|
+
max: PropTypes.number,
|
756
|
+
/** Progress variant */
|
757
|
+
variant: PropTypes.oneOf(['determinate', 'indeterminate']),
|
758
|
+
/** Color theme */
|
759
|
+
color: PropTypes.oneOf(['primary', 'secondary', 'success', 'warning', 'error']),
|
760
|
+
/** Size variant */
|
761
|
+
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
762
|
+
/** Show percentage value */
|
763
|
+
showValue: PropTypes.bool,
|
764
|
+
/** Show label */
|
765
|
+
showLabel: PropTypes.bool,
|
766
|
+
/** Custom label text */
|
767
|
+
label: PropTypes.string,
|
768
|
+
/** Buffer value for buffered progress */
|
769
|
+
buffer: PropTypes.number,
|
770
|
+
/** Enable animations */
|
771
|
+
animated: PropTypes.bool,
|
772
|
+
/** Show striped pattern */
|
773
|
+
striped: PropTypes.bool,
|
774
|
+
/** Disabled state */
|
775
|
+
disabled: PropTypes.bool,
|
776
|
+
/** Rounded corners */
|
777
|
+
rounded: PropTypes.bool,
|
778
|
+
/** Additional CSS classes */
|
779
|
+
className: PropTypes.string,
|
780
|
+
/** Inline styles */
|
781
|
+
style: PropTypes.object,
|
782
|
+
/** Callback when progress completes */
|
783
|
+
onComplete: PropTypes.func,
|
784
|
+
/** Custom value formatter */
|
785
|
+
formatValue: PropTypes.func,
|
786
|
+
/** ARIA label */
|
787
|
+
ariaLabel: PropTypes.string,
|
788
|
+
/** Estimated time to completion */
|
789
|
+
estimatedTime: PropTypes.number,
|
790
|
+
/** Progress speed (units per second) */
|
791
|
+
speed: PropTypes.number
|
792
|
+
}
|
793
|
+
|
794
|
+
LinearProgress.defaultProps = {
|
795
|
+
value: 0,
|
796
|
+
max: 100,
|
797
|
+
variant: 'determinate',
|
798
|
+
color: 'primary',
|
799
|
+
size: 'medium',
|
800
|
+
showValue: false,
|
801
|
+
showLabel: false,
|
802
|
+
animated: true,
|
803
|
+
striped: false,
|
804
|
+
disabled: false,
|
805
|
+
rounded: true
|
806
|
+
}
|
807
|
+
|
808
|
+
StepProgress.propTypes = {
|
809
|
+
/** Array of step objects */
|
810
|
+
steps: PropTypes.arrayOf(PropTypes.shape({
|
811
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
812
|
+
label: PropTypes.string.isRequired,
|
813
|
+
description: PropTypes.string,
|
814
|
+
icon: PropTypes.string,
|
815
|
+
error: PropTypes.bool
|
816
|
+
})).isRequired,
|
817
|
+
/** Current active step index */
|
818
|
+
currentStep: PropTypes.number,
|
819
|
+
/** Layout variant */
|
820
|
+
variant: PropTypes.oneOf(['horizontal', 'vertical']),
|
821
|
+
/** Color theme */
|
822
|
+
color: PropTypes.oneOf(['primary', 'secondary', 'success', 'warning', 'error']),
|
823
|
+
/** Size variant */
|
824
|
+
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
825
|
+
/** Show step labels */
|
826
|
+
showLabels: PropTypes.bool,
|
827
|
+
/** Show step numbers */
|
828
|
+
showNumbers: PropTypes.bool,
|
829
|
+
/** Allow clicking to navigate */
|
830
|
+
allowClickNavigation: PropTypes.bool,
|
831
|
+
/** Enable animations */
|
832
|
+
animated: PropTypes.bool,
|
833
|
+
/** Additional CSS classes */
|
834
|
+
className: PropTypes.string,
|
835
|
+
/** Callback when step is clicked */
|
836
|
+
onStepClick: PropTypes.func
|
837
|
+
}
|
838
|
+
|
839
|
+
StepProgress.defaultProps = {
|
840
|
+
currentStep: 0,
|
841
|
+
variant: 'horizontal',
|
842
|
+
color: 'primary',
|
843
|
+
size: 'medium',
|
844
|
+
showLabels: true,
|
845
|
+
showNumbers: true,
|
846
|
+
allowClickNavigation: false,
|
847
|
+
animated: true
|
848
|
+
}
|
849
|
+
|
850
|
+
RadialProgress.propTypes = {
|
851
|
+
/** Current progress value */
|
852
|
+
value: PropTypes.number,
|
853
|
+
/** Maximum value */
|
854
|
+
max: PropTypes.number,
|
855
|
+
/** Circle size in pixels */
|
856
|
+
size: PropTypes.number,
|
857
|
+
/** Stroke thickness */
|
858
|
+
thickness: PropTypes.number,
|
859
|
+
/** Color theme */
|
860
|
+
color: PropTypes.oneOf(['primary', 'secondary', 'success', 'warning', 'error']),
|
861
|
+
/** Background color */
|
862
|
+
backgroundColor: PropTypes.string,
|
863
|
+
/** Show percentage value */
|
864
|
+
showValue: PropTypes.bool,
|
865
|
+
/** Show label */
|
866
|
+
showLabel: PropTypes.bool,
|
867
|
+
/** Custom label text */
|
868
|
+
label: PropTypes.string,
|
869
|
+
/** Icon to display */
|
870
|
+
icon: PropTypes.string,
|
871
|
+
/** Enable animations */
|
872
|
+
animated: PropTypes.bool,
|
873
|
+
/** Use gradient colors */
|
874
|
+
gradient: PropTypes.bool,
|
875
|
+
/** Additional CSS classes */
|
876
|
+
className: PropTypes.string,
|
877
|
+
/** Custom value formatter */
|
878
|
+
formatValue: PropTypes.func,
|
879
|
+
/** Child elements */
|
880
|
+
children: PropTypes.node
|
881
|
+
}
|
882
|
+
|
883
|
+
RadialProgress.defaultProps = {
|
884
|
+
value: 0,
|
885
|
+
max: 100,
|
886
|
+
size: 120,
|
887
|
+
thickness: 8,
|
888
|
+
color: 'primary',
|
889
|
+
backgroundColor: 'rgba(0,0,0,0.1)',
|
890
|
+
showValue: true,
|
891
|
+
showLabel: false,
|
892
|
+
animated: true,
|
893
|
+
gradient: false
|
894
|
+
}
|
895
|
+
|
896
|
+
MultiProgress.propTypes = {
|
897
|
+
/** Array of progress items */
|
898
|
+
items: PropTypes.arrayOf(PropTypes.shape({
|
899
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
900
|
+
label: PropTypes.string.isRequired,
|
901
|
+
value: PropTypes.number.isRequired,
|
902
|
+
max: PropTypes.number,
|
903
|
+
color: PropTypes.string,
|
904
|
+
formatValue: PropTypes.func
|
905
|
+
})).isRequired,
|
906
|
+
/** Show item labels */
|
907
|
+
showLabels: PropTypes.bool,
|
908
|
+
/** Show item values */
|
909
|
+
showValues: PropTypes.bool,
|
910
|
+
/** Size variant */
|
911
|
+
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
912
|
+
/** Enable animations */
|
913
|
+
animated: PropTypes.bool,
|
914
|
+
/** Additional CSS classes */
|
915
|
+
className: PropTypes.string
|
916
|
+
}
|
917
|
+
|
918
|
+
MultiProgress.defaultProps = {
|
919
|
+
showLabels: true,
|
920
|
+
showValues: true,
|
921
|
+
size: 'medium',
|
922
|
+
animated: true
|
923
|
+
}
|
924
|
+
|
925
|
+
export default { CircularProgress, LinearProgress, StepProgress, RadialProgress, MultiProgress }
|