@easy-editor/materials-dashboard-carousel 0.0.2

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.
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Carousel Component
3
+ * 轮播组件
4
+ */
5
+
6
+ import { useState, useEffect, useCallback, type CSSProperties, type Ref } from 'react'
7
+ import styles from './component.module.css'
8
+
9
+ export interface CarouselItem {
10
+ /** 图片地址 */
11
+ src: string
12
+ /** 图片描述 */
13
+ alt?: string
14
+ /** 点击链接 */
15
+ link?: string
16
+ }
17
+
18
+ export interface CarouselProps {
19
+ ref?: Ref<HTMLDivElement>
20
+ /** 轮播项 */
21
+ items?: CarouselItem[]
22
+ /** 自动播放 */
23
+ autoPlay?: boolean
24
+ /** 自动播放间隔(毫秒) */
25
+ interval?: number
26
+ /** 是否显示导航按钮 */
27
+ showNav?: boolean
28
+ /** 是否显示指示器 */
29
+ showIndicators?: boolean
30
+ /** 是否循环播放 */
31
+ loop?: boolean
32
+ /** 外部样式 */
33
+ style?: CSSProperties
34
+ }
35
+
36
+ const DEFAULT_ITEMS: CarouselItem[] = [
37
+ { src: 'https://picsum.photos/800/400?random=1', alt: 'Slide 1' },
38
+ { src: 'https://picsum.photos/800/400?random=2', alt: 'Slide 2' },
39
+ { src: 'https://picsum.photos/800/400?random=3', alt: 'Slide 3' },
40
+ ]
41
+
42
+ const ChevronLeft = () => (
43
+ <svg aria-hidden='true' fill='none' height='24' stroke='currentColor' strokeWidth='2' viewBox='0 0 24 24' width='24'>
44
+ <title>Previous</title>
45
+ <path d='M15 18l-6-6 6-6' />
46
+ </svg>
47
+ )
48
+
49
+ const ChevronRight = () => (
50
+ <svg aria-hidden='true' fill='none' height='24' stroke='currentColor' strokeWidth='2' viewBox='0 0 24 24' width='24'>
51
+ <title>Next</title>
52
+ <path d='M9 18l6-6-6-6' />
53
+ </svg>
54
+ )
55
+
56
+ export const Carousel: React.FC<CarouselProps> = ({
57
+ ref,
58
+ items = DEFAULT_ITEMS,
59
+ autoPlay = true,
60
+ interval = 3000,
61
+ showNav = true,
62
+ showIndicators = true,
63
+ loop = true,
64
+ style: externalStyle,
65
+ }) => {
66
+ const [currentIndex, setCurrentIndex] = useState(0)
67
+
68
+ const goToNext = useCallback(() => {
69
+ setCurrentIndex(prev => {
70
+ if (prev >= items.length - 1) {
71
+ return loop ? 0 : prev
72
+ }
73
+ return prev + 1
74
+ })
75
+ }, [items.length, loop])
76
+
77
+ const goToPrev = useCallback(() => {
78
+ setCurrentIndex(prev => {
79
+ if (prev <= 0) {
80
+ return loop ? items.length - 1 : prev
81
+ }
82
+ return prev - 1
83
+ })
84
+ }, [items.length, loop])
85
+
86
+ const goToSlide = useCallback((index: number) => {
87
+ setCurrentIndex(index)
88
+ }, [])
89
+
90
+ // 自动播放
91
+ useEffect(() => {
92
+ if (!autoPlay || items.length <= 1) {
93
+ return
94
+ }
95
+
96
+ const timer = setInterval(goToNext, interval)
97
+ return () => clearInterval(timer)
98
+ }, [autoPlay, interval, goToNext, items.length])
99
+
100
+ if (items.length === 0) {
101
+ return null
102
+ }
103
+
104
+ return (
105
+ <div className={styles.container} ref={ref} style={externalStyle}>
106
+ <div className={styles.track} style={{ transform: `translateX(-${currentIndex * 100}%)` }}>
107
+ {items.map((item, index) => (
108
+ <div className={styles.slide} key={index}>
109
+ {item.link ? (
110
+ <a href={item.link} rel='noopener noreferrer' target='_blank'>
111
+ <img
112
+ alt={item.alt || ''}
113
+ className={styles.slideImage}
114
+ draggable={false}
115
+ height='200'
116
+ src={item.src}
117
+ width='100%'
118
+ />
119
+ </a>
120
+ ) : (
121
+ <img
122
+ alt={item.alt || ''}
123
+ className={styles.slideImage}
124
+ draggable={false}
125
+ height='200'
126
+ src={item.src}
127
+ width='100%'
128
+ />
129
+ )}
130
+ </div>
131
+ ))}
132
+ </div>
133
+
134
+ {/* 导航按钮 */}
135
+ {showNav && items.length > 1 && (
136
+ <>
137
+ <button
138
+ aria-label='Previous slide'
139
+ className={`${styles.navButton} ${styles.navButtonPrev}`}
140
+ onClick={goToPrev}
141
+ type='button'
142
+ >
143
+ <ChevronLeft />
144
+ </button>
145
+ <button
146
+ aria-label='Next slide'
147
+ className={`${styles.navButton} ${styles.navButtonNext}`}
148
+ onClick={goToNext}
149
+ type='button'
150
+ >
151
+ <ChevronRight />
152
+ </button>
153
+ </>
154
+ )}
155
+
156
+ {/* 指示器 */}
157
+ {showIndicators && items.length > 1 && (
158
+ <div className={styles.indicators}>
159
+ {items.map((_, index) => (
160
+ <button
161
+ aria-label={`Go to slide ${index + 1}`}
162
+ className={`${styles.indicator} ${index === currentIndex ? styles.indicatorActive : ''}`}
163
+ key={index}
164
+ onClick={() => goToSlide(index)}
165
+ type='button'
166
+ />
167
+ ))}
168
+ </div>
169
+ )}
170
+ </div>
171
+ )
172
+ }
173
+
174
+ export default Carousel
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Carousel Configure
3
+ * 轮播组件配置
4
+ */
5
+
6
+ import type { Configure } from '@easy-editor/core'
7
+
8
+ export const configure: Configure = {
9
+ props: [
10
+ {
11
+ type: 'group',
12
+ title: '属性',
13
+ setter: 'TabSetter',
14
+ items: [
15
+ {
16
+ type: 'group',
17
+ key: 'config',
18
+ title: '配置',
19
+ setter: {
20
+ componentName: 'CollapseSetter',
21
+ props: {
22
+ icon: false,
23
+ },
24
+ },
25
+ items: [
26
+ // 基础配置
27
+ {
28
+ name: 'id',
29
+ title: 'ID',
30
+ setter: 'NodeIdSetter',
31
+ extraProps: {
32
+ // @ts-expect-error label is not a valid extra prop
33
+ label: false,
34
+ },
35
+ },
36
+ {
37
+ name: 'title',
38
+ title: '标题',
39
+ setter: 'StringSetter',
40
+ extraProps: {
41
+ getValue(target) {
42
+ return target.getExtraPropValue('title')
43
+ },
44
+ setValue(target, value) {
45
+ target.setExtraPropValue('title', value)
46
+ },
47
+ },
48
+ },
49
+ {
50
+ type: 'group',
51
+ title: '基础属性',
52
+ setter: {
53
+ componentName: 'CollapseSetter',
54
+ props: {
55
+ icon: false,
56
+ },
57
+ },
58
+ items: [
59
+ {
60
+ name: 'rect',
61
+ title: '位置尺寸',
62
+ setter: 'RectSetter',
63
+ extraProps: {
64
+ getValue(target) {
65
+ return target.getExtraPropValue('$dashboard.rect')
66
+ },
67
+ setValue(target, value) {
68
+ target.setExtraPropValue('$dashboard.rect', value)
69
+ },
70
+ },
71
+ },
72
+ ],
73
+ },
74
+ // 组件配置
75
+ {
76
+ type: 'group',
77
+ title: '数据',
78
+ setter: {
79
+ componentName: 'CollapseSetter',
80
+ props: {
81
+ icon: false,
82
+ },
83
+ },
84
+ items: [
85
+ {
86
+ name: 'items',
87
+ title: '轮播项',
88
+ setter: 'JsonSetter',
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ type: 'group',
94
+ title: '行为',
95
+ setter: {
96
+ componentName: 'CollapseSetter',
97
+ props: {
98
+ icon: false,
99
+ },
100
+ },
101
+ items: [
102
+ {
103
+ name: 'autoPlay',
104
+ title: '自动播放',
105
+ setter: 'SwitchSetter',
106
+ extraProps: {
107
+ defaultValue: true,
108
+ },
109
+ },
110
+ {
111
+ name: 'interval',
112
+ title: '播放间隔(ms)',
113
+ setter: 'NumberSetter',
114
+ extraProps: {
115
+ defaultValue: 3000,
116
+ },
117
+ },
118
+ {
119
+ name: 'loop',
120
+ title: '循环播放',
121
+ setter: 'SwitchSetter',
122
+ extraProps: {
123
+ defaultValue: true,
124
+ },
125
+ },
126
+ ],
127
+ },
128
+ {
129
+ type: 'group',
130
+ title: '显示',
131
+ setter: {
132
+ componentName: 'CollapseSetter',
133
+ props: {
134
+ icon: false,
135
+ },
136
+ },
137
+ items: [
138
+ {
139
+ name: 'showNav',
140
+ title: '显示导航按钮',
141
+ setter: 'SwitchSetter',
142
+ extraProps: {
143
+ defaultValue: true,
144
+ },
145
+ },
146
+ {
147
+ name: 'showIndicators',
148
+ title: '显示指示器',
149
+ setter: 'SwitchSetter',
150
+ extraProps: {
151
+ defaultValue: true,
152
+ },
153
+ },
154
+ ],
155
+ },
156
+ ],
157
+ },
158
+ {
159
+ type: 'group',
160
+ key: 'data',
161
+ title: '数据',
162
+ items: [
163
+ {
164
+ name: 'dataBinding',
165
+ title: '数据绑定',
166
+ setter: 'DataBindingSetter',
167
+ },
168
+ ],
169
+ },
170
+ {
171
+ type: 'group',
172
+ key: 'advanced',
173
+ title: '高级',
174
+ items: [
175
+ {
176
+ name: 'condition',
177
+ title: '显隐控制',
178
+ setter: 'SwitchSetter',
179
+ extraProps: {
180
+ defaultValue: true,
181
+ supportVariable: true,
182
+ },
183
+ },
184
+ ],
185
+ },
186
+ ],
187
+ },
188
+ ],
189
+ component: {},
190
+ supports: {},
191
+ advanced: {},
192
+ }
193
+
194
+ export default configure
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 物料常量配置
3
+ * 统一管理全局变量名等配置,确保 meta.ts 和 rollup.config.js 使用相同的值
4
+ */
5
+
6
+ /**
7
+ * UMD 全局变量基础名称
8
+ * 用于构建:
9
+ * - 元数据:${GLOBAL_NAME}Meta (例如: EasyEditorMaterialsButtonMeta)
10
+ * - 组件:${GLOBAL_NAME}Component (例如: EasyEditorMaterialsButtonComponent)
11
+ * - 完整构建:${GLOBAL_NAME} (例如: EasyEditorMaterialsButton)
12
+ */
13
+ export const COMPONENT_NAME = 'EasyEditorMaterialsCarousel'
14
+
15
+ /**
16
+ * 包名
17
+ */
18
+ export const PACKAGE_NAME = '@easy-editor/materials-dashboard-carousel'
package/src/index.tsx ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Carousel Entry
3
+ * 轮播组件入口
4
+ */
5
+
6
+ export { Carousel as component } from './component'
7
+ export { default as meta } from './meta'
package/src/meta.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Carousel Meta
3
+ * 轮播组件元数据
4
+ */
5
+
6
+ import type { ComponentMetadata } from '@easy-editor/core'
7
+ import { MaterialGroup } from '@easy-editor/materials-shared'
8
+ import { COMPONENT_NAME, PACKAGE_NAME } from './constants'
9
+ import configure from './configure'
10
+ import snippets from './snippets'
11
+ import pkg from '../package.json'
12
+
13
+ export const meta: ComponentMetadata = {
14
+ componentName: COMPONENT_NAME,
15
+ title: '轮播',
16
+ group: MaterialGroup.DISPLAY,
17
+ devMode: 'proCode',
18
+ npm: {
19
+ package: PACKAGE_NAME,
20
+ version: pkg.version,
21
+ globalName: COMPONENT_NAME,
22
+ componentName: COMPONENT_NAME,
23
+ },
24
+ snippets,
25
+ configure,
26
+ }
27
+
28
+ export default meta
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Carousel Snippets
3
+ * 轮播组件代码片段
4
+ */
5
+
6
+ import type { Snippet } from '@easy-editor/core'
7
+ import { COMPONENT_NAME } from './constants'
8
+
9
+ export const snippets: Snippet[] = [
10
+ {
11
+ title: '图片轮播',
12
+ screenshot: '',
13
+ schema: {
14
+ componentName: COMPONENT_NAME,
15
+ props: {
16
+ items: [
17
+ { src: 'https://picsum.photos/800/400?random=1', alt: 'Slide 1' },
18
+ { src: 'https://picsum.photos/800/400?random=2', alt: 'Slide 2' },
19
+ { src: 'https://picsum.photos/800/400?random=3', alt: 'Slide 3' },
20
+ ],
21
+ autoPlay: true,
22
+ interval: 3000,
23
+ showNav: true,
24
+ showIndicators: true,
25
+ loop: true,
26
+ },
27
+ $dashboard: {
28
+ rect: {
29
+ width: 600,
30
+ height: 300,
31
+ },
32
+ },
33
+ },
34
+ },
35
+ {
36
+ title: '手动轮播',
37
+ screenshot: '',
38
+ schema: {
39
+ componentName: COMPONENT_NAME,
40
+ props: {
41
+ items: [
42
+ { src: 'https://picsum.photos/800/400?random=4', alt: 'Slide 1' },
43
+ { src: 'https://picsum.photos/800/400?random=5', alt: 'Slide 2' },
44
+ ],
45
+ autoPlay: false,
46
+ showNav: true,
47
+ showIndicators: true,
48
+ loop: true,
49
+ },
50
+ $dashboard: {
51
+ rect: {
52
+ width: 600,
53
+ height: 300,
54
+ },
55
+ },
56
+ },
57
+ },
58
+ ]
59
+
60
+ export default snippets
package/src/type.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * CSS Modules Type Declarations
3
+ */
4
+
5
+ declare module '*.module.css' {
6
+ const classes: { readonly [key: string]: string }
7
+ export default classes
8
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "strictPropertyInitialization": false,
6
+ "outDir": "./dist",
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true
10
+ },
11
+ "include": ["./src"]
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react-jsx",
4
+ "esModuleInterop": true,
5
+ "allowSyntheticDefaultImports": true
6
+ },
7
+ "extends": "./tsconfig.build.json",
8
+ "include": ["./src", "./test"]
9
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["./src", "./test"]
7
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Vite Configuration for Material Development
3
+ * 物料开发 Vite 配置
4
+ */
5
+
6
+ import { defineConfig } from 'vite'
7
+ import react from '@vitejs/plugin-react'
8
+ import { materialDevPlugin } from './.vite/plugins/vite-plugin-material-dev'
9
+ import { externalDeps } from './.vite/plugins/vite-plugin-external-deps'
10
+
11
+ export default defineConfig({
12
+ plugins: [
13
+ react(),
14
+ // 外部化 React/ReactDOM,使用父应用提供的实例
15
+ externalDeps({
16
+ externals: ['react', 'react-dom', 'react/jsx-runtime', '@easy-editor/core'],
17
+ globals: {
18
+ react: 'React',
19
+ 'react-dom': 'ReactDOM',
20
+ 'react/jsx-runtime': 'jsxRuntime',
21
+ '@easy-editor/core': 'EasyEditorCore',
22
+ },
23
+ }),
24
+ materialDevPlugin({
25
+ entry: '/src/index.tsx',
26
+ }),
27
+ ],
28
+ server: {
29
+ port: 5001,
30
+ host: 'localhost',
31
+ cors: true,
32
+ hmr: {
33
+ port: 5001,
34
+ },
35
+ },
36
+ build: {
37
+ target: 'esnext',
38
+ rollupOptions: {
39
+ // 确保生产构建也外部化这些依赖
40
+ external: ['react', 'react-dom', 'react/jsx-runtime', '@easy-editor/core'],
41
+ output: {
42
+ globals: {
43
+ react: 'React',
44
+ 'react-dom': 'ReactDOM',
45
+ 'react/jsx-runtime': 'jsxRuntime',
46
+ '@easy-editor/core': 'EasyEditorCore',
47
+ },
48
+ },
49
+ },
50
+ },
51
+ resolve: {
52
+ dedupe: ['react', 'react-dom'],
53
+ },
54
+ })