@easy-editor/materials-dashboard-scroll-list 0.0.2 → 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,189 +1,219 @@
1
- /**
2
- * Scroll List Component
3
- * 滚动列表组件 - 用于展示排行榜、数据列表等
4
- */
5
-
6
- import type { CSSProperties, Ref } from 'react'
7
- import { cn } from '@easy-editor/materials-shared'
8
- import styles from './component.module.css'
9
-
10
- export interface ScrollListItem {
11
- rank: number
12
- name: string
13
- value: number
14
- }
15
-
16
- export interface ScrollListProps {
17
- ref?: Ref<HTMLDivElement>
18
- /** 列表数据 */
19
- data?: ScrollListItem[]
20
- /** 最大显示条数 */
21
- maxItems?: number
22
- /** 是否显示排名 */
23
- showRank?: boolean
24
- /** 是否显示奖牌图标 */
25
- showMedal?: boolean
26
- /** 是否显示进度条 */
27
- progressBarEnable?: boolean
28
- /** 是否使用渐变进度条 */
29
- progressBarGradient?: boolean
30
- /** 进度条颜色 [起始色, 结束色] */
31
- progressBarColors?: [string, string]
32
- /** 数值格式化 */
33
- valueFormat?: 'number' | 'currency' | 'percent'
34
- /** 数值前缀 */
35
- valuePrefix?: string
36
- /** 数值后缀 */
37
- valueSuffix?: string
38
- /** 名称颜色 */
39
- nameColor?: string
40
- /** 数值颜色 */
41
- valueColor?: string
42
- /** 背景颜色 */
43
- backgroundColor?: string
44
- /** 边框颜色 */
45
- borderColor?: string
46
- /** 行背景颜色 */
47
- itemBackgroundColor?: string
48
- /** 行边框颜色 */
49
- itemBorderColor?: string
50
- /** 是否显示发光效果 */
51
- glowEnable?: boolean
52
- /** 外部样式 */
53
- style?: CSSProperties
54
- }
55
-
56
- const DEFAULT_DATA: ScrollListItem[] = [
57
- { rank: 1, name: '北京市', value: 9800 },
58
- { rank: 2, name: '上海市', value: 8500 },
59
- { rank: 3, name: '广州市', value: 7200 },
60
- { rank: 4, name: '深圳市', value: 6100 },
61
- { rank: 5, name: '杭州市', value: 4800 },
62
- ]
63
-
64
- const MEDAL_EMOJI: Record<number, string> = {
65
- 1: '🥇',
66
- 2: '🥈',
67
- 3: '🥉',
68
- }
69
-
70
- const getRankClass = (rank: number): string => {
71
- if (rank === 1) {
72
- return styles.rankGold
73
- }
74
- if (rank === 2) {
75
- return styles.rankSilver
76
- }
77
- if (rank === 3) {
78
- return styles.rankBronze
79
- }
80
- return ''
81
- }
82
-
83
- const formatDisplayValue = (value: number, format: string, prefix: string, suffix: string): string => {
84
- let formatted: string
85
- switch (format) {
86
- case 'currency':
87
- formatted = value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
88
- break
89
- case 'percent':
90
- formatted = `${value}%`
91
- break
92
- default:
93
- formatted = value.toLocaleString()
94
- }
95
- return `${prefix}${formatted}${suffix}`
96
- }
97
-
98
- export const ScrollList: React.FC<ScrollListProps> = ({
99
- ref,
100
- data = DEFAULT_DATA,
101
- maxItems = 5,
102
- showRank = true,
103
- showMedal = true,
104
- progressBarEnable = true,
105
- progressBarGradient = true,
106
- progressBarColors = ['#00d4ff', '#9b59b6'],
107
- valueFormat = 'number',
108
- valuePrefix = '',
109
- valueSuffix = '',
110
- nameColor = '#e6e6e6',
111
- valueColor = '#00d4ff',
112
- backgroundColor = 'rgba(10, 10, 26, 0.95)',
113
- borderColor = 'rgba(26, 26, 62, 0.8)',
114
- itemBackgroundColor = 'rgba(15, 15, 42, 0.9)',
115
- itemBorderColor = 'rgba(26, 26, 62, 0.6)',
116
- glowEnable = false,
117
- style: externalStyle,
118
- }) => {
119
- const displayData = data.slice(0, maxItems)
120
- const maxValue = Math.max(...displayData.map(item => item.value), 1)
121
-
122
- const getProgressBarStyle = (value: number): CSSProperties => {
123
- const percentage = (value / maxValue) * 100
124
- return {
125
- width: `${percentage}%`,
126
- background: progressBarGradient
127
- ? `linear-gradient(90deg, ${progressBarColors[0]}, ${progressBarColors[1]})`
128
- : progressBarColors[0],
129
- boxShadow: glowEnable ? `0 0 8px ${progressBarColors[0]}60` : undefined,
130
- }
131
- }
132
-
133
- const containerStyle: CSSProperties = {
134
- ...externalStyle,
135
- backgroundColor,
136
- borderColor,
137
- }
138
-
139
- const itemStyle: CSSProperties = {
140
- backgroundColor: itemBackgroundColor,
141
- borderColor: itemBorderColor,
142
- }
143
-
144
- return (
145
- <div className={styles.container} ref={ref} style={containerStyle}>
146
- <div className={styles.list}>
147
- {displayData.map(item => {
148
- const isTopThree = item.rank <= 3
149
-
150
- return (
151
- <div className={styles.item} key={item.rank} style={itemStyle}>
152
- {/* Rank Badge */}
153
- {showRank ? (
154
- <div
155
- className={cn(
156
- styles.rankBadge,
157
- isTopThree ? styles.rankBadgeTopThree : styles.rankBadgeNormal,
158
- getRankClass(item.rank),
159
- )}
160
- >
161
- {showMedal && isTopThree ? MEDAL_EMOJI[item.rank] : item.rank}
162
- </div>
163
- ) : null}
164
-
165
- {/* Name */}
166
- <div className={styles.name} style={{ color: nameColor }}>
167
- {item.name}
168
- </div>
169
-
170
- {/* Value and Progress */}
171
- <div className={styles.valueContainer}>
172
- <span className={styles.value} style={{ color: valueColor }}>
173
- {formatDisplayValue(item.value, valueFormat, valuePrefix, valueSuffix)}
174
- </span>
175
- {progressBarEnable ? (
176
- <div className={styles.progressBar}>
177
- <div className={styles.progressFill} style={getProgressBarStyle(item.value)} />
178
- </div>
179
- ) : null}
180
- </div>
181
- </div>
182
- )
183
- })}
184
- </div>
185
- </div>
186
- )
187
- }
188
-
189
- export default ScrollList
1
+ /**
2
+ * Scroll List Component
3
+ * 滚动列表组件 - 支持数据源绑定和事件交互
4
+ */
5
+
6
+ import { useMemo, type CSSProperties } from 'react'
7
+ import { cn, useDataSource, type MaterialComponet } from '@easy-editor/materials-shared'
8
+ import styles from './component.module.css'
9
+
10
+ export interface ScrollListItem {
11
+ rank: number
12
+ name: string
13
+ value: number
14
+ }
15
+
16
+ export interface ScrollListProps extends MaterialComponet {
17
+ /** 最大显示条数 */
18
+ maxItems?: number
19
+ /** 是否显示排名 */
20
+ showRank?: boolean
21
+ /** 是否显示奖牌图标 */
22
+ showMedal?: boolean
23
+ /** 是否显示进度条 */
24
+ progressBarEnable?: boolean
25
+ /** 是否使用渐变进度条 */
26
+ progressBarGradient?: boolean
27
+ /** 进度条颜色 [起始色, 结束色] */
28
+ progressBarColors?: [string, string]
29
+ /** 数值格式化 */
30
+ valueFormat?: 'number' | 'currency' | 'percent'
31
+ /** 数值前缀 */
32
+ valuePrefix?: string
33
+ /** 数值后缀 */
34
+ valueSuffix?: string
35
+ /** 名称颜色 */
36
+ nameColor?: string
37
+ /** 数值颜色 */
38
+ valueColor?: string
39
+ /** 背景颜色 */
40
+ backgroundColor?: string
41
+ /** 边框颜色 */
42
+ borderColor?: string
43
+ /** 行背景颜色 */
44
+ itemBackgroundColor?: string
45
+ /** 行边框颜色 */
46
+ itemBorderColor?: string
47
+ /** 是否显示发光效果 */
48
+ glowEnable?: boolean
49
+ /** 点击事件 */
50
+ onClick?: (e: React.MouseEvent) => void
51
+ /** 双击事件 */
52
+ onDoubleClick?: (e: React.MouseEvent) => void
53
+ /** 鼠标进入 */
54
+ onMouseEnter?: (e: React.MouseEvent) => void
55
+ /** 鼠标离开 */
56
+ onMouseLeave?: (e: React.MouseEvent) => void
57
+ /** 行点击事件 */
58
+ onItemClick?: (item: ScrollListItem, index: number) => void
59
+ }
60
+
61
+ const DEFAULT_DATA: ScrollListItem[] = [
62
+ { rank: 1, name: '北京市', value: 9800 },
63
+ { rank: 2, name: '上海市', value: 8500 },
64
+ { rank: 3, name: '广州市', value: 7200 },
65
+ { rank: 4, name: '深圳市', value: 6100 },
66
+ { rank: 5, name: '杭州市', value: 4800 },
67
+ ]
68
+
69
+ const MEDAL_EMOJI: Record<number, string> = {
70
+ 1: '🥇',
71
+ 2: '🥈',
72
+ 3: '🥉',
73
+ }
74
+
75
+ const getRankClass = (rank: number): string => {
76
+ if (rank === 1) {
77
+ return styles.rankGold
78
+ }
79
+ if (rank === 2) {
80
+ return styles.rankSilver
81
+ }
82
+ if (rank === 3) {
83
+ return styles.rankBronze
84
+ }
85
+ return ''
86
+ }
87
+
88
+ const formatDisplayValue = (value: number, format: string, prefix: string, suffix: string): string => {
89
+ let formatted: string
90
+ switch (format) {
91
+ case 'currency':
92
+ formatted = value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
93
+ break
94
+ case 'percent':
95
+ formatted = `${value}%`
96
+ break
97
+ default:
98
+ formatted = value.toLocaleString()
99
+ }
100
+ return `${prefix}${formatted}${suffix}`
101
+ }
102
+
103
+ export const ScrollList: React.FC<ScrollListProps> = ({
104
+ ref,
105
+ $data,
106
+ __dataSource,
107
+ maxItems = 5,
108
+ showRank = true,
109
+ showMedal = true,
110
+ progressBarEnable = true,
111
+ progressBarGradient = true,
112
+ progressBarColors = ['#00d4ff', '#9b59b6'],
113
+ valueFormat = 'number',
114
+ valuePrefix = '',
115
+ valueSuffix = '',
116
+ nameColor = '#e6e6e6',
117
+ valueColor = '#00d4ff',
118
+ backgroundColor = 'rgba(10, 10, 26, 0.95)',
119
+ borderColor = 'rgba(26, 26, 62, 0.8)',
120
+ itemBackgroundColor = 'rgba(15, 15, 42, 0.9)',
121
+ itemBorderColor = 'rgba(26, 26, 62, 0.6)',
122
+ glowEnable = false,
123
+ rotation = 0,
124
+ opacity = 100,
125
+ style: externalStyle,
126
+ onClick,
127
+ onDoubleClick,
128
+ onMouseEnter,
129
+ onMouseLeave,
130
+ onItemClick,
131
+ }) => {
132
+ // 解析数据源
133
+ const dataSource = useDataSource($data, __dataSource)
134
+ const data = useMemo<ScrollListItem[]>(() => {
135
+ if (dataSource.length > 0) {
136
+ return dataSource as ScrollListItem[]
137
+ }
138
+ return DEFAULT_DATA
139
+ }, [dataSource])
140
+
141
+ const displayData = data.slice(0, maxItems)
142
+ const maxValue = Math.max(...displayData.map(item => item.value), 1)
143
+
144
+ const getProgressBarStyle = (value: number): CSSProperties => {
145
+ const percentage = (value / maxValue) * 100
146
+ return {
147
+ width: `${percentage}%`,
148
+ background: progressBarGradient
149
+ ? `linear-gradient(90deg, ${progressBarColors[0]}, ${progressBarColors[1]})`
150
+ : progressBarColors[0],
151
+ boxShadow: glowEnable ? `0 0 8px ${progressBarColors[0]}60` : undefined,
152
+ }
153
+ }
154
+
155
+ const containerStyle: CSSProperties = {
156
+ transform: rotation !== 0 ? `rotate(${rotation}deg)` : undefined,
157
+ opacity: opacity / 100,
158
+ backgroundColor,
159
+ borderColor,
160
+ ...externalStyle,
161
+ }
162
+
163
+ const itemStyle: CSSProperties = {
164
+ backgroundColor: itemBackgroundColor,
165
+ borderColor: itemBorderColor,
166
+ }
167
+
168
+ return (
169
+ <div
170
+ className={styles.container}
171
+ onClick={onClick}
172
+ onDoubleClick={onDoubleClick}
173
+ onMouseEnter={onMouseEnter}
174
+ onMouseLeave={onMouseLeave}
175
+ ref={ref}
176
+ style={containerStyle}
177
+ >
178
+ <div className={styles.list}>
179
+ {displayData.map((item, index) => {
180
+ const isTopThree = item.rank <= 3
181
+
182
+ return (
183
+ <div className={styles.item} key={item.rank} onClick={() => onItemClick?.(item, index)} style={itemStyle}>
184
+ {/* Rank Badge */}
185
+ {showRank ? (
186
+ <div
187
+ className={cn(
188
+ styles.rankBadge,
189
+ isTopThree ? styles.rankBadgeTopThree : styles.rankBadgeNormal,
190
+ getRankClass(item.rank),
191
+ )}
192
+ >
193
+ {showMedal && isTopThree ? MEDAL_EMOJI[item.rank] : item.rank}
194
+ </div>
195
+ ) : null}
196
+
197
+ {/* Name */}
198
+ <div className={styles.name} style={{ color: nameColor }}>
199
+ {item.name}
200
+ </div>
201
+
202
+ {/* Value and Progress */}
203
+ <div className={styles.valueContainer}>
204
+ <span className={styles.value} style={{ color: valueColor }}>
205
+ {formatDisplayValue(item.value, valueFormat, valuePrefix, valueSuffix)}
206
+ </span>
207
+ {progressBarEnable ? (
208
+ <div className={styles.progressBar}>
209
+ <div className={styles.progressFill} style={getProgressBarStyle(item.value)} />
210
+ </div>
211
+ ) : null}
212
+ </div>
213
+ </div>
214
+ )
215
+ })}
216
+ </div>
217
+ </div>
218
+ )
219
+ }