@easy-editor/materials-dashboard-progress 0.0.3 → 0.0.4

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/src/component.tsx CHANGED
@@ -1,262 +1,304 @@
1
- /**
2
- * Progress Component
3
- * 进度组件 - 支持环形和线性进度条
4
- */
5
-
6
- import { useId, useRef, useEffect, useState, type CSSProperties, type Ref } from 'react'
7
- import styles from './component.module.css'
8
-
9
- export interface ProgressProps {
10
- ref?: Ref<HTMLDivElement>
11
- /** 当前值 */
12
- value?: number
13
- /** 最大值 */
14
- maxValue?: number
15
- /** 进度条类型 */
16
- type?: 'ring' | 'bar'
17
- /** 是否显示数值 */
18
- showValue?: boolean
19
- /** 是否显示标签 */
20
- showLabel?: boolean
21
- /** 标签文本 */
22
- label?: string
23
- /** 数值格式 */
24
- valueFormat?: 'percent' | 'number'
25
- /** 线条宽度比例(相对于尺寸的百分比) */
26
- strokeWidthRatio?: number
27
- /** 轨道颜色 */
28
- trackColor?: string
29
- /** 进度颜色 */
30
- progressColor?: string
31
- /** 是否启用渐变 */
32
- gradientEnable?: boolean
33
- /** 渐变颜色 [起始色, 结束色] */
34
- gradientColors?: [string, string]
35
- /** 外部样式 */
36
- style?: CSSProperties
37
- }
38
-
39
- // 格式化数值
40
- const formatValue = (value: number, percentage: number, valueFormat: string): string => {
41
- if (valueFormat === 'percent') {
42
- return `${Math.round(percentage)}%`
43
- }
44
- return `${value}`
45
- }
46
-
47
- // 环形进度条组件
48
- const RingProgress = ({
49
- percentage,
50
- strokeWidthRatio,
51
- trackColor,
52
- progressColor,
53
- gradientEnable,
54
- gradientColors,
55
- showValue,
56
- showLabel,
57
- label,
58
- valueFormat,
59
- displayColor,
60
- gradientId,
61
- }: {
62
- percentage: number
63
- strokeWidthRatio: number
64
- trackColor: string
65
- progressColor: string
66
- gradientEnable: boolean
67
- gradientColors: [string, string]
68
- showValue: boolean
69
- showLabel: boolean
70
- label: string
71
- valueFormat: string
72
- displayColor: string
73
- gradientId: string
74
- }) => {
75
- const containerRef = useRef<HTMLDivElement>(null)
76
- const [size, setSize] = useState(100)
77
-
78
- useEffect(() => {
79
- const container = containerRef.current
80
- if (!container) {
81
- return
82
- }
83
-
84
- const updateSize = () => {
85
- const { width, height } = container.getBoundingClientRect()
86
- const minSize = Math.min(width, height)
87
- if (minSize > 0) {
88
- setSize(minSize)
89
- }
90
- }
91
-
92
- updateSize()
93
-
94
- const resizeObserver = new ResizeObserver(updateSize)
95
- resizeObserver.observe(container)
96
-
97
- return () => resizeObserver.disconnect()
98
- }, [])
99
-
100
- const strokeWidth = Math.max(2, size * strokeWidthRatio)
101
- const radius = (size - strokeWidth) / 2
102
- const circumference = 2 * Math.PI * radius
103
- const strokeDashoffset = circumference - (percentage / 100) * circumference
104
- const center = size / 2
105
-
106
- return (
107
- <div className={styles.container} ref={containerRef}>
108
- <svg aria-label='Progress ring' className={styles.ring} height={size} role='img' width={size}>
109
- <title>Progress indicator</title>
110
- {gradientEnable ? (
111
- <defs>
112
- <linearGradient id={gradientId} x1='0%' x2='100%' y1='0%' y2='0%'>
113
- <stop offset='0%' stopColor={gradientColors[0]} />
114
- <stop offset='100%' stopColor={gradientColors[1]} />
115
- </linearGradient>
116
- </defs>
117
- ) : null}
118
- {/* 轨道 */}
119
- <circle cx={center} cy={center} fill='none' r={radius} stroke={trackColor} strokeWidth={strokeWidth} />
120
- {/* 进度 */}
121
- <circle
122
- cx={center}
123
- cy={center}
124
- fill='none'
125
- r={radius}
126
- stroke={gradientEnable ? `url(#${gradientId})` : progressColor}
127
- strokeDasharray={circumference}
128
- strokeDashoffset={strokeDashoffset}
129
- strokeLinecap='round'
130
- strokeWidth={strokeWidth}
131
- style={{
132
- transition: 'stroke-dashoffset 0.5s ease',
133
- }}
134
- />
135
- </svg>
136
- <div className={styles.centerContent}>
137
- {showValue ? (
138
- <span className={styles.value} style={{ fontSize: size * 0.2, color: displayColor }}>
139
- {formatValue(Math.round(percentage), percentage, valueFormat)}
140
- </span>
141
- ) : null}
142
- {showLabel && label ? (
143
- <span className={styles.label} style={{ fontSize: size * 0.1 }}>
144
- {label}
145
- </span>
146
- ) : null}
147
- </div>
148
- </div>
149
- )
150
- }
151
-
152
- // 线性进度条组件
153
- const BarProgress = ({
154
- percentage,
155
- trackColor,
156
- progressColor,
157
- gradientEnable,
158
- gradientColors,
159
- showValue,
160
- showLabel,
161
- label,
162
- valueFormat,
163
- displayColor,
164
- }: {
165
- percentage: number
166
- trackColor: string
167
- progressColor: string
168
- gradientEnable: boolean
169
- gradientColors: [string, string]
170
- showValue: boolean
171
- showLabel: boolean
172
- label: string
173
- valueFormat: string
174
- displayColor: string
175
- }) => (
176
- <div className={styles.barContainer}>
177
- {showLabel || showValue ? (
178
- <div className={styles.barLabels}>
179
- {showLabel ? <span className={styles.barLabel}>{label}</span> : null}
180
- {showValue ? (
181
- <span className={styles.barValue} style={{ color: displayColor }}>
182
- {formatValue(Math.round(percentage), percentage, valueFormat)}
183
- </span>
184
- ) : null}
185
- </div>
186
- ) : null}
187
- <div className={styles.barWrapper} style={{ background: trackColor }}>
188
- <div
189
- className={styles.barFill}
190
- style={{
191
- width: `${percentage}%`,
192
- background: gradientEnable
193
- ? `linear-gradient(90deg, ${gradientColors[0]}, ${gradientColors[1]})`
194
- : progressColor,
195
- }}
196
- />
197
- </div>
198
- </div>
199
- )
200
-
201
- export const Progress: React.FC<ProgressProps> = ({
202
- ref,
203
- value = 0,
204
- maxValue = 100,
205
- type = 'ring',
206
- showValue = true,
207
- showLabel = false,
208
- label = '',
209
- valueFormat = 'percent',
210
- strokeWidthRatio = 0.07,
211
- trackColor = 'rgba(26, 26, 62, 0.8)',
212
- progressColor = '#00d4ff',
213
- gradientEnable = false,
214
- gradientColors = ['#00d4ff', '#9b59b6'],
215
- style: externalStyle,
216
- }) => {
217
- const gradientId = useId()
218
-
219
- const normalizedValue = Math.min(Math.max(value, 0), maxValue)
220
- const percentage = (normalizedValue / maxValue) * 100
221
- const displayColor = gradientEnable ? gradientColors[0] : progressColor
222
-
223
- if (type === 'ring') {
224
- return (
225
- <div className={styles.wrapper} ref={ref} style={externalStyle}>
226
- <RingProgress
227
- displayColor={displayColor}
228
- gradientColors={gradientColors}
229
- gradientEnable={gradientEnable}
230
- gradientId={gradientId}
231
- label={label}
232
- percentage={percentage}
233
- progressColor={progressColor}
234
- showLabel={showLabel}
235
- showValue={showValue}
236
- strokeWidthRatio={strokeWidthRatio}
237
- trackColor={trackColor}
238
- valueFormat={valueFormat}
239
- />
240
- </div>
241
- )
242
- }
243
-
244
- return (
245
- <div className={styles.wrapper} ref={ref} style={externalStyle}>
246
- <BarProgress
247
- displayColor={displayColor}
248
- gradientColors={gradientColors}
249
- gradientEnable={gradientEnable}
250
- label={label}
251
- percentage={percentage}
252
- progressColor={progressColor}
253
- showLabel={showLabel}
254
- showValue={showValue}
255
- trackColor={trackColor}
256
- valueFormat={valueFormat}
257
- />
258
- </div>
259
- )
260
- }
261
-
262
- export default Progress
1
+ /**
2
+ * Progress Component
3
+ * 进度组件 - 支持数据源绑定和事件交互
4
+ */
5
+
6
+ import { useId, useRef, useEffect, useState, useMemo, type CSSProperties } from 'react'
7
+ import { type MaterialComponet, useDataSource } from '@easy-editor/materials-shared'
8
+ import styles from './component.module.css'
9
+
10
+ export interface ProgressProps extends MaterialComponet {
11
+ /** 最大值 */
12
+ maxValue?: number
13
+ /** 进度条类型 */
14
+ type?: 'ring' | 'bar'
15
+ /** 是否显示数值 */
16
+ showValue?: boolean
17
+ /** 是否显示标签 */
18
+ showLabel?: boolean
19
+ /** 标签文本 */
20
+ label?: string
21
+ /** 数值格式 */
22
+ valueFormat?: 'percent' | 'number'
23
+ /** 线条宽度比例(相对于尺寸的百分比) */
24
+ strokeWidthRatio?: number
25
+ /** 轨道颜色 */
26
+ trackColor?: string
27
+ /** 进度颜色 */
28
+ progressColor?: string
29
+ /** 是否启用渐变 */
30
+ gradientEnable?: boolean
31
+ /** 渐变颜色 [起始色, 结束色] */
32
+ gradientColors?: [string, string]
33
+ /** 点击事件 */
34
+ onClick?: (e: React.MouseEvent) => void
35
+ /** 双击事件 */
36
+ onDoubleClick?: (e: React.MouseEvent) => void
37
+ /** 鼠标进入 */
38
+ onMouseEnter?: (e: React.MouseEvent) => void
39
+ /** 鼠标离开 */
40
+ onMouseLeave?: (e: React.MouseEvent) => void
41
+ }
42
+
43
+ // 格式化数值
44
+ const formatValue = (value: number, percentage: number, valueFormat: string): string => {
45
+ if (valueFormat === 'percent') {
46
+ return `${Math.round(percentage)}%`
47
+ }
48
+ return `${value}`
49
+ }
50
+
51
+ // 环形进度条组件
52
+ const RingProgress = ({
53
+ percentage,
54
+ strokeWidthRatio,
55
+ trackColor,
56
+ progressColor,
57
+ gradientEnable,
58
+ gradientColors,
59
+ showValue,
60
+ showLabel,
61
+ label,
62
+ valueFormat,
63
+ displayColor,
64
+ gradientId,
65
+ }: {
66
+ percentage: number
67
+ strokeWidthRatio: number
68
+ trackColor: string
69
+ progressColor: string
70
+ gradientEnable: boolean
71
+ gradientColors: [string, string]
72
+ showValue: boolean
73
+ showLabel: boolean
74
+ label: string
75
+ valueFormat: string
76
+ displayColor: string
77
+ gradientId: string
78
+ }) => {
79
+ const containerRef = useRef<HTMLDivElement>(null)
80
+ const [size, setSize] = useState(100)
81
+
82
+ useEffect(() => {
83
+ const container = containerRef.current
84
+ if (!container) {
85
+ return
86
+ }
87
+
88
+ const updateSize = () => {
89
+ const { width, height } = container.getBoundingClientRect()
90
+ const minSize = Math.min(width, height)
91
+ if (minSize > 0) {
92
+ setSize(minSize)
93
+ }
94
+ }
95
+
96
+ updateSize()
97
+
98
+ const resizeObserver = new ResizeObserver(updateSize)
99
+ resizeObserver.observe(container)
100
+
101
+ return () => resizeObserver.disconnect()
102
+ }, [])
103
+
104
+ const strokeWidth = Math.max(2, size * strokeWidthRatio)
105
+ const radius = (size - strokeWidth) / 2
106
+ const circumference = 2 * Math.PI * radius
107
+ const strokeDashoffset = circumference - (percentage / 100) * circumference
108
+ const center = size / 2
109
+
110
+ return (
111
+ <div className={styles.container} ref={containerRef}>
112
+ <svg aria-label='Progress ring' className={styles.ring} height={size} role='img' width={size}>
113
+ <title>Progress indicator</title>
114
+ {gradientEnable ? (
115
+ <defs>
116
+ <linearGradient id={gradientId} x1='0%' x2='100%' y1='0%' y2='0%'>
117
+ <stop offset='0%' stopColor={gradientColors[0]} />
118
+ <stop offset='100%' stopColor={gradientColors[1]} />
119
+ </linearGradient>
120
+ </defs>
121
+ ) : null}
122
+ {/* 轨道 */}
123
+ <circle cx={center} cy={center} fill='none' r={radius} stroke={trackColor} strokeWidth={strokeWidth} />
124
+ {/* 进度 */}
125
+ <circle
126
+ cx={center}
127
+ cy={center}
128
+ fill='none'
129
+ r={radius}
130
+ stroke={gradientEnable ? `url(#${gradientId})` : progressColor}
131
+ strokeDasharray={circumference}
132
+ strokeDashoffset={strokeDashoffset}
133
+ strokeLinecap='round'
134
+ strokeWidth={strokeWidth}
135
+ style={{
136
+ transition: 'stroke-dashoffset 0.5s ease',
137
+ }}
138
+ />
139
+ </svg>
140
+ <div className={styles.centerContent}>
141
+ {showValue ? (
142
+ <span className={styles.value} style={{ fontSize: size * 0.2, color: displayColor }}>
143
+ {formatValue(Math.round(percentage), percentage, valueFormat)}
144
+ </span>
145
+ ) : null}
146
+ {showLabel && label ? (
147
+ <span className={styles.label} style={{ fontSize: size * 0.1 }}>
148
+ {label}
149
+ </span>
150
+ ) : null}
151
+ </div>
152
+ </div>
153
+ )
154
+ }
155
+
156
+ // 线性进度条组件
157
+ const BarProgress = ({
158
+ percentage,
159
+ trackColor,
160
+ progressColor,
161
+ gradientEnable,
162
+ gradientColors,
163
+ showValue,
164
+ showLabel,
165
+ label,
166
+ valueFormat,
167
+ displayColor,
168
+ }: {
169
+ percentage: number
170
+ trackColor: string
171
+ progressColor: string
172
+ gradientEnable: boolean
173
+ gradientColors: [string, string]
174
+ showValue: boolean
175
+ showLabel: boolean
176
+ label: string
177
+ valueFormat: string
178
+ displayColor: string
179
+ }) => (
180
+ <div className={styles.barContainer}>
181
+ {showLabel || showValue ? (
182
+ <div className={styles.barLabels}>
183
+ {showLabel ? <span className={styles.barLabel}>{label}</span> : null}
184
+ {showValue ? (
185
+ <span className={styles.barValue} style={{ color: displayColor }}>
186
+ {formatValue(Math.round(percentage), percentage, valueFormat)}
187
+ </span>
188
+ ) : null}
189
+ </div>
190
+ ) : null}
191
+ <div className={styles.barWrapper} style={{ background: trackColor }}>
192
+ <div
193
+ className={styles.barFill}
194
+ style={{
195
+ width: `${percentage}%`,
196
+ background: gradientEnable
197
+ ? `linear-gradient(90deg, ${gradientColors[0]}, ${gradientColors[1]})`
198
+ : progressColor,
199
+ }}
200
+ />
201
+ </div>
202
+ </div>
203
+ )
204
+
205
+ export const Progress: React.FC<ProgressProps> = ({
206
+ ref,
207
+ $data,
208
+ __dataSource,
209
+ rotation = 0,
210
+ opacity = 100,
211
+ background = 'transparent',
212
+ style: externalStyle,
213
+ maxValue = 100,
214
+ type = 'ring',
215
+ showValue = true,
216
+ showLabel = false,
217
+ label = '',
218
+ valueFormat = 'percent',
219
+ strokeWidthRatio = 0.07,
220
+ trackColor = 'rgba(26, 26, 62, 0.8)',
221
+ progressColor = '#00d4ff',
222
+ gradientEnable = false,
223
+ gradientColors = ['#00d4ff', '#9b59b6'],
224
+ onClick,
225
+ onDoubleClick,
226
+ onMouseEnter,
227
+ onMouseLeave,
228
+ }) => {
229
+ const gradientId = useId()
230
+
231
+ // 解析数据源
232
+ const dataSource = useDataSource($data, __dataSource)
233
+ const value = useMemo<number>(() => {
234
+ if (dataSource.length > 0 && typeof dataSource[0]?.value === 'number') {
235
+ return dataSource[0].value
236
+ }
237
+ return 0
238
+ }, [dataSource])
239
+
240
+ const normalizedValue = Math.min(Math.max(value, 0), maxValue)
241
+ const percentage = (normalizedValue / maxValue) * 100
242
+ const displayColor = gradientEnable ? gradientColors[0] : progressColor
243
+
244
+ const wrapperStyle: CSSProperties = {
245
+ transform: rotation !== 0 ? `rotate(${rotation}deg)` : undefined,
246
+ opacity: opacity / 100,
247
+ backgroundColor: background,
248
+ ...externalStyle,
249
+ }
250
+
251
+ if (type === 'ring') {
252
+ return (
253
+ <div
254
+ className={styles.wrapper}
255
+ onClick={onClick}
256
+ onDoubleClick={onDoubleClick}
257
+ onMouseEnter={onMouseEnter}
258
+ onMouseLeave={onMouseLeave}
259
+ ref={ref}
260
+ style={wrapperStyle}
261
+ >
262
+ <RingProgress
263
+ displayColor={displayColor}
264
+ gradientColors={gradientColors}
265
+ gradientEnable={gradientEnable}
266
+ gradientId={gradientId}
267
+ label={label}
268
+ percentage={percentage}
269
+ progressColor={progressColor}
270
+ showLabel={showLabel}
271
+ showValue={showValue}
272
+ strokeWidthRatio={strokeWidthRatio}
273
+ trackColor={trackColor}
274
+ valueFormat={valueFormat}
275
+ />
276
+ </div>
277
+ )
278
+ }
279
+
280
+ return (
281
+ <div
282
+ className={styles.wrapper}
283
+ onClick={onClick}
284
+ onDoubleClick={onDoubleClick}
285
+ onMouseEnter={onMouseEnter}
286
+ onMouseLeave={onMouseLeave}
287
+ ref={ref}
288
+ style={wrapperStyle}
289
+ >
290
+ <BarProgress
291
+ displayColor={displayColor}
292
+ gradientColors={gradientColors}
293
+ gradientEnable={gradientEnable}
294
+ label={label}
295
+ percentage={percentage}
296
+ progressColor={progressColor}
297
+ showLabel={showLabel}
298
+ showValue={showValue}
299
+ trackColor={trackColor}
300
+ valueFormat={valueFormat}
301
+ />
302
+ </div>
303
+ )
304
+ }