@mxmweb/zui 1.1.14 → 1.3.0

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.
Files changed (132) hide show
  1. package/README.md +61 -3
  2. package/assets/style.css +1 -0
  3. package/cluster_enter.d.ts +25 -0
  4. package/index.js +33 -0
  5. package/package.json +24 -39
  6. package/.editorconfig +0 -38
  7. package/.prettierignore +0 -16
  8. package/.prettierrc +0 -17
  9. package/.releaserc.json +0 -36
  10. package/CHANGELOG.md +0 -58
  11. package/CONTRIBUTING.md +0 -111
  12. package/NPMREADME.md +0 -0
  13. package/bash.exe.stackdump +0 -40
  14. package/components.json +0 -21
  15. package/dist/Preview.d.ts +0 -3
  16. package/dist/README.md +0 -4
  17. package/dist/assets/style.css +0 -1
  18. package/dist/containers/DashboardContainer.d.ts +0 -35
  19. package/dist/containers/DockContainer.d.ts +0 -24
  20. package/dist/elements/Button.d.ts +0 -17
  21. package/dist/elements/CustomDock.d.ts +0 -25
  22. package/dist/elements/DropDownButton.d.ts +0 -24
  23. package/dist/elements/DropdownMenu.d.ts +0 -18
  24. package/dist/elements/GoggleNavbar.d.ts +0 -31
  25. package/dist/elements/Uploader/UploadItem.d.ts +0 -35
  26. package/dist/elements/Uploader/example.d.ts +0 -7
  27. package/dist/elements/Uploader/index.d.ts +0 -9
  28. package/dist/elements/Uploader/styles.d.ts +0 -65
  29. package/dist/elements/Uploader/types.d.ts +0 -87
  30. package/dist/elements/Uploader/utils.d.ts +0 -31
  31. package/dist/elements/Uploader.d.ts +0 -2
  32. package/dist/examples/DockContainerExample.d.ts +0 -3
  33. package/dist/examples/Dropdown.d.ts +0 -2
  34. package/dist/icons/Icon.d.ts +0 -7
  35. package/dist/icons/Icon.tsx +0 -82
  36. package/dist/icons/index.d.ts +0 -13
  37. package/dist/icons/index.tsx +0 -92
  38. package/dist/icons/lazyIndex.d.ts +0 -7
  39. package/dist/icons/lazyIndex.tsx +0 -49
  40. package/dist/icons/rag/csv.svg +0 -3
  41. package/dist/icons/rag/document.svg +0 -3
  42. package/dist/icons/rag/excel.svg +0 -3
  43. package/dist/icons/rag/file.svg +0 -3
  44. package/dist/icons/rag/folder.svg +0 -5
  45. package/dist/icons/rag/json.svg +0 -3
  46. package/dist/icons/rag/knowledgebase.svg +0 -3
  47. package/dist/icons/rag/netretrive.svg +0 -3
  48. package/dist/icons/rag/odf.svg +0 -7
  49. package/dist/icons/rag/pdf.svg +0 -3
  50. package/dist/icons/rag/pic.svg +0 -3
  51. package/dist/icons/rag/ppt.svg +0 -3
  52. package/dist/icons/rag/think.svg +0 -6
  53. package/dist/icons/rag/txt.svg +0 -3
  54. package/dist/icons/rag/url.svg +0 -3
  55. package/dist/icons/rag/word.svg +0 -3
  56. package/dist/icons/rag/wps.svg +0 -3
  57. package/dist/icons/rag/zip.svg +0 -7
  58. package/dist/index.js +0 -2301
  59. package/dist/lib_enter.d.ts +0 -13
  60. package/dist/main.d.ts +0 -1
  61. package/dist/mock.csv +0 -16
  62. package/dist/mock_/345/211/257/346/234/254.csv +0 -16
  63. package/dist/package.json +0 -30
  64. package/dist/theme/styledTheme.d.ts +0 -101
  65. package/dist/vite.svg +0 -1
  66. package/eslint.config.js +0 -92
  67. package/index.html +0 -13
  68. package/postcss.config.cjs +0 -19
  69. package/public/mock.csv +0 -16
  70. package/public/mock_/345/211/257/346/234/254.csv +0 -16
  71. package/public/vite.svg +0 -1
  72. package/src/Preview.tsx +0 -15
  73. package/src/assets/img/excel.png +0 -0
  74. package/src/assets/img/img.png +0 -0
  75. package/src/assets/img/pdf.png +0 -0
  76. package/src/assets/img/ppt.png +0 -0
  77. package/src/assets/img/txt.png +0 -0
  78. package/src/assets/img/word.png +0 -0
  79. package/src/containers/DashboardContainer.tsx +0 -507
  80. package/src/containers/DockContainer.tsx +0 -186
  81. package/src/containers/style.css +0 -37
  82. package/src/elements/Button.tsx +0 -118
  83. package/src/elements/CustomDock.tsx +0 -287
  84. package/src/elements/DropDownButton.tsx +0 -249
  85. package/src/elements/DropdownMenu.tsx +0 -186
  86. package/src/elements/GoggleNavbar.tsx +0 -184
  87. package/src/elements/Uploader/README.md +0 -249
  88. package/src/elements/Uploader/UploadItem.tsx +0 -298
  89. package/src/elements/Uploader/example.tsx +0 -95
  90. package/src/elements/Uploader/index.tsx +0 -702
  91. package/src/elements/Uploader/styles.tsx +0 -291
  92. package/src/elements/Uploader/types.ts +0 -119
  93. package/src/elements/Uploader/utils.ts +0 -200
  94. package/src/elements/Uploader.tsx +0 -3
  95. package/src/examples/DockContainerExample.tsx +0 -254
  96. package/src/examples/Dropdown.tsx +0 -27
  97. package/src/icons/Icon.tsx +0 -82
  98. package/src/icons/index.tsx +0 -92
  99. package/src/icons/lazyIndex.tsx +0 -49
  100. package/src/icons/rag/csv.svg +0 -3
  101. package/src/icons/rag/document.svg +0 -3
  102. package/src/icons/rag/excel.svg +0 -3
  103. package/src/icons/rag/file.svg +0 -3
  104. package/src/icons/rag/folder.svg +0 -5
  105. package/src/icons/rag/json.svg +0 -3
  106. package/src/icons/rag/knowledgebase.svg +0 -3
  107. package/src/icons/rag/netretrive.svg +0 -3
  108. package/src/icons/rag/odf.svg +0 -7
  109. package/src/icons/rag/pdf.svg +0 -3
  110. package/src/icons/rag/pic.svg +0 -3
  111. package/src/icons/rag/ppt.svg +0 -3
  112. package/src/icons/rag/think.svg +0 -6
  113. package/src/icons/rag/txt.svg +0 -3
  114. package/src/icons/rag/url.svg +0 -3
  115. package/src/icons/rag/word.svg +0 -3
  116. package/src/icons/rag/wps.svg +0 -3
  117. package/src/icons/rag/zip.svg +0 -7
  118. package/src/lib_enter.ts +0 -27
  119. package/src/main.tsx +0 -11
  120. package/src/style.css +0 -9
  121. package/src/theme/styledTheme.tsx +0 -253
  122. package/src/type.d.ts +0 -0
  123. package/src/types/images.d.ts +0 -12
  124. package/src/types/svg-modules.d.ts +0 -24
  125. package/src/vite-env.d.ts +0 -11
  126. package/tailwind.config.js +0 -170
  127. package/tsconfig.app.json +0 -29
  128. package/tsconfig.app.tsbuildinfo +0 -11
  129. package/tsconfig.json +0 -13
  130. package/tsconfig.node.json +0 -22
  131. package/tsconfig.node.tsbuildinfo +0 -1
  132. package/vite.config.ts +0 -165
@@ -1,37 +0,0 @@
1
- .osx-example {
2
- background-image: url('images/osx/background.jpg');
3
- background-color: #886e96;
4
- background-position: center;
5
- background-size: cover;
6
- }
7
-
8
- .osx-example .dock {
9
- align-self: flex-end;
10
- }
11
-
12
- .osx-example .dock .dock-item {
13
- padding: 6px 0px 3px 0px;
14
- --active-indicator-height: 4px;
15
- --active-indicator-margin: 2px;
16
- }
17
-
18
- .osx-example .dock .dock-item img {
19
- height: calc(
20
- 100% - var(--active-indicator-height) - var(--active-indicator-margin)
21
- );
22
- }
23
-
24
- .osx-example .dock .dock-item .active-indicator {
25
- width: var(--active-indicator-height);
26
- height: var(--active-indicator-height);
27
- margin-top: var(--active-indicator-margin);
28
- background-color: #444;
29
- border-radius: 50%;
30
- }
31
-
32
- .osx-example .dock .dock-background {
33
- background-color: #ccc;
34
- opacity: 0.6;
35
- border-radius: 4px 4px 0px 0px;
36
- box-shadow: 1px 1px 50px 4px rgba(0, 0, 0, 0.8);
37
- }
@@ -1,118 +0,0 @@
1
- import React from 'react';
2
- import styled from 'styled-components';
3
- import { defaultTheme, deepMergeTheme, type AppTheme, type Styles } from '../theme/styledTheme';
4
-
5
- // 按钮类型定义
6
- export type ButtonType = 'primary' | 'default' | 'error' | 'text' ;
7
-
8
- // 按钮属性接口
9
- export interface ButtonProps {
10
- mode?: ButtonType;
11
- label?: string;
12
- styles?: Styles;
13
- icon?: React.ReactNode;
14
- disabled?: boolean;
15
- loading?: boolean; // 新增:loading 状态
16
- onClick?: () => void;
17
- style?: React.CSSProperties;
18
- className?: string;
19
- children?: React.ReactNode;
20
- }
21
-
22
- const StyledButton = styled.button<{ $btnType: ButtonType; $disabled: boolean; $loading: boolean; $theme: AppTheme }>`
23
- height: 36px;
24
- min-height: 36px;
25
- max-height: 36px;
26
- line-height: 34px;
27
- padding: 0 12px;
28
- border-radius: ${props => props.$theme?.space?.radius || '4px'};
29
- font-size: 12px;
30
- border: none;
31
- cursor: ${props => props.$disabled || props.$loading ? 'not-allowed' : 'pointer'};
32
- transition: all 0.2s;
33
- opacity: ${props => props.$disabled ? 0.5 : 1};
34
- display: inline-flex;
35
- align-items: center;
36
- justify-content: center;
37
- gap: 6px;
38
- position: relative;
39
- background: ${({ $btnType, $theme }) =>
40
- $btnType === 'text' ? 'none' :
41
- $btnType === 'primary' ? $theme?.colors?.primary || '#007bff' :
42
- $btnType === 'error' ? $theme?.colors?.error || '#FF0000' :
43
- $btnType === 'default' ? $theme?.colors?.background || '#f8f9fa' :
44
- 'none'};
45
- color: ${({ $btnType, $theme }) =>
46
- $btnType === 'text' ? $theme?.colors?.primary || '#007bff' :
47
- $btnType === 'primary' || $btnType === 'error'
48
- ? '#ffffff'
49
- : $theme?.colors?.text || '#343a40'};
50
- border: ${({ $btnType, $theme }) =>
51
- $btnType === 'default' ? `1px solid ${$theme?.colors?.border || '#dee2e6'}` : 'none'};
52
- box-shadow: none;
53
-
54
- /* Loading 状态样式 */
55
- ${props => props.$loading && `
56
- pointer-events: none;
57
- opacity: 0.8;
58
-
59
- .button-loading {
60
- width: 14px;
61
- height: 14px;
62
- border: 2px solid transparent;
63
- border-top: 2px solid currentColor;
64
- border-radius: 50%;
65
- animation: spin 1s linear infinite;
66
- margin-right: 6px;
67
- }
68
- `}
69
-
70
- @keyframes spin {
71
- 0% { transform: rotate(0deg); }
72
- 100% { transform: rotate(360deg); }
73
- }
74
- `;
75
-
76
- const Button: React.FC<ButtonProps> = ({
77
- mode = 'default',
78
- label,
79
- icon,
80
- disabled = false,
81
- loading = false, // 新增:loading 状态
82
- onClick,
83
- styles,
84
- style,
85
- className,
86
- children,
87
- ...rest
88
- }) => {
89
- // 使用统一的主题合并函数,确保 theme 有完整的结构
90
- const theme = deepMergeTheme(defaultTheme, styles);
91
-
92
- const handleClick = () => {
93
- if (!disabled && !loading && onClick) {
94
- onClick();
95
- }
96
- };
97
-
98
- return (
99
- <StyledButton
100
- $btnType={mode}
101
- $disabled={disabled}
102
- $loading={loading}
103
- $theme={theme.theme || defaultTheme}
104
- onClick={handleClick}
105
- disabled={disabled || loading}
106
- style={style}
107
- className={className}
108
- {...rest}
109
- >
110
- {loading && <span className="button-loading" />}
111
- {icon && <span className="button-icon" style={{ marginRight: 4 }}>{icon}</span>}
112
- {label && <span className="button-label">{label}</span>}
113
- {children}
114
- </StyledButton>
115
- );
116
- };
117
-
118
- export default Button;
@@ -1,287 +0,0 @@
1
- import React, { useRef, useEffect, useState } from 'react';
2
- import styled from 'styled-components';
3
- import anime from 'animejs/lib/anime.es.js';
4
-
5
- // Dock 项接口定义
6
- export interface DockItem {
7
- id: string;
8
- label: string;
9
- icon: string;
10
- onClick?: () => void;
11
- isActive?: boolean;
12
- }
13
-
14
- // 激活模式类型
15
- export type ActiveMode = 'single' | 'multiple';
16
-
17
- // Dock 组件属性
18
- export interface CustomDockProps {
19
- items: DockItem[];
20
- itemWidth?: number;
21
- itemHeight?: number;
22
- magnification?: number;
23
- itemGap?: number;
24
- activeMode?: ActiveMode; // 激活模式:single | multiple
25
- defaultActiveId?: string; // 单选模式:默认激活的项ID
26
- defaultActiveIds?: string[]; // 多选模式:默认激活的项ID数组
27
- onItemClick?: (item: DockItem, index: number) => void;
28
- onActiveChange?: (activeId: string | null, item: DockItem | null) => void; // 单选模式:激活状态变化事件
29
- onActiveChangeMultiple?: (activeIds: string[], items: DockItem[]) => void; // 多选模式:激活状态变化事件
30
- className?: string;
31
- }
32
-
33
- // 毛玻璃背景容器
34
- const DockContainer = styled.div.withConfig({
35
- shouldForwardProp: (prop) => !['itemWidth', 'itemGap'].includes(prop),
36
- })<{ itemWidth: number; itemGap: number }>`
37
- position: relative;
38
- display: flex;
39
- align-items: flex-end;
40
- justify-content: center;
41
- padding: 12px 20px;
42
- background: rgba(255, 255, 255, 0.18);
43
- backdrop-filter: saturate(180%) blur(20px);
44
- -webkit-backdrop-filter: saturate(180%) blur(20px);
45
- border: 1px solid rgba(255, 255, 255, 0.28);
46
- border-radius: 14px;
47
- box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25), inset 0 0.5px 0 rgba(255, 255, 255, 0.4);
48
- width: fit-content;
49
- margin: 0 auto;
50
- min-width: ${props => props.itemWidth * 2}px;
51
- `;
52
-
53
- // 图标容器
54
- const IconContainer = styled.div.withConfig({
55
- shouldForwardProp: (prop) => !['itemWidth', 'itemHeight', 'itemGap', 'isHovered', 'magnification'].includes(prop),
56
- })<{
57
- itemWidth: number;
58
- itemHeight: number;
59
- itemGap: number;
60
- isHovered: boolean;
61
- magnification: number;
62
- }>`
63
- position: relative;
64
- width: ${props => props.itemWidth}px;
65
- height: ${props => props.itemHeight}px;
66
- margin: 0 ${props => props.itemGap / 2}px;
67
- cursor: pointer;
68
- transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
69
- transform: ${props => props.isHovered ? `scale(${props.magnification})` : 'scale(1)'};
70
- transform-origin: center bottom;
71
- z-index: ${props => props.isHovered ? 10 : 1};
72
- `;
73
-
74
- // 图标图片
75
- const IconImage = styled.img.withConfig({
76
- shouldForwardProp: (prop) => !['isActive'].includes(prop),
77
- })<{ isActive: boolean }>`
78
- width: 80%;
79
- height: 80%;
80
- object-fit: contain;
81
- border-radius: 8px;
82
- transition: all 0.15s ease;
83
- filter: ${props => props.isActive ? 'none' : 'grayscale(0.3) brightness(0.8)'};
84
-
85
- &:hover {
86
- filter: none;
87
- }
88
- `;
89
-
90
- // 激活指示器
91
- const ActiveIndicator = styled.div.withConfig({
92
- shouldForwardProp: (prop) => !['isActive'].includes(prop),
93
- })<{ isActive: boolean }>`
94
- position: absolute;
95
- bottom: -4px;
96
- left: 40%;
97
- transform: translateX(-50%);
98
- width: 6px;
99
- height: 6px;
100
- background: #007AFF;
101
- border-radius: 50%;
102
- opacity: ${props => props.isActive ? 1 : 0};
103
- transition: opacity 0.2s ease;
104
- `;
105
-
106
- // 工具提示
107
- const Tooltip = styled.div.withConfig({
108
- shouldForwardProp: (prop) => !['visible'].includes(prop),
109
- })<{ visible: boolean }>`
110
- position: absolute;
111
- bottom: 100%;
112
- left: 40%;
113
- transform: translateX(-50%) translateY(-8px);
114
- background: rgba(0, 0, 0, 0.8);
115
- color: white;
116
- padding: 4px 8px;
117
- border-radius: 4px;
118
- font-size: 10px;
119
- white-space: nowrap;
120
- opacity: ${props => props.visible ? 1 : 0};
121
- visibility: ${props => props.visible ? 'visible' : 'hidden'};
122
- transition: all 0.2s ease;
123
- z-index: 1000;
124
-
125
- &::after {
126
- content: '';
127
- position: absolute;
128
- top: 100%;
129
- left: 50%;
130
- transform: translateX(-50%);
131
- border: 4px solid transparent;
132
- border-top-color: rgba(0, 0, 0, 0.8);
133
- }
134
- `;
135
-
136
- const CustomDock: React.FC<CustomDockProps> = ({
137
- items,
138
- itemWidth = 48,
139
- itemHeight = 48,
140
- magnification = 1.2,
141
- itemGap = 13,
142
- activeMode = 'single',
143
- defaultActiveId,
144
- defaultActiveIds = [],
145
- onItemClick,
146
- onActiveChange,
147
- onActiveChangeMultiple,
148
- className
149
- }) => {
150
- const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
151
- const [showTooltip, setShowTooltip] = useState<number | null>(null);
152
-
153
- // 根据模式初始化激活状态
154
- const getInitialActiveState = () => {
155
- if (activeMode === 'multiple') {
156
- return defaultActiveIds || [];
157
- } else {
158
- // 单选模式:如果没有传默认激活且没有传默认激活ID,则激活第一个
159
- if (defaultActiveId) {
160
- return defaultActiveId;
161
- } else if (items.length > 0) {
162
- return items[0].id;
163
- }
164
- return null;
165
- }
166
- };
167
-
168
- const [activeId, setActiveId] = useState<string | null>(
169
- activeMode === 'single' ? getInitialActiveState() as string | null : null
170
- );
171
- const [activeIds, setActiveIds] = useState<string[]>(
172
- activeMode === 'multiple' ? getInitialActiveState() as string[] : []
173
- );
174
-
175
- const dockRef = useRef<HTMLDivElement>(null);
176
- const iconRefs = useRef<(HTMLDivElement | null)[]>([]);
177
-
178
- // 处理鼠标进入
179
- const handleMouseEnter = (index: number) => {
180
- setHoveredIndex(index);
181
- setShowTooltip(index);
182
-
183
- // 使用 anime.js 实现平滑的放大动画
184
- const iconElement = iconRefs.current[index];
185
- if (iconElement) {
186
- anime({
187
- targets: iconElement,
188
- scale: magnification,
189
- duration: 200,
190
- easing: 'easeOutCubic'
191
- });
192
- }
193
- };
194
-
195
- // 处理鼠标离开
196
- const handleMouseLeave = (index: number) => {
197
- setHoveredIndex(null);
198
- setShowTooltip(null);
199
-
200
- // 恢复原始大小
201
- const iconElement = iconRefs.current[index];
202
- if (iconElement) {
203
- anime({
204
- targets: iconElement,
205
- scale: 1,
206
- duration: 200,
207
- easing: 'easeOutCubic'
208
- });
209
- }
210
- };
211
-
212
- // 处理点击
213
- const handleClick = (item: DockItem, index: number) => {
214
- if (activeMode === 'single') {
215
- // 单选模式:不支持取消激活,只能切换激活项
216
- if (activeId !== item.id) {
217
- setActiveId(item.id);
218
-
219
- // 触发激活状态变化事件
220
- if (onActiveChange) {
221
- onActiveChange(item.id, item);
222
- }
223
- }
224
- } else {
225
- // 多选模式:支持激活/取消激活
226
- const newActiveIds = activeIds.includes(item.id)
227
- ? activeIds.filter(id => id !== item.id)
228
- : [...activeIds, item.id];
229
-
230
- setActiveIds(newActiveIds);
231
-
232
- // 触发多选激活状态变化事件
233
- if (onActiveChangeMultiple) {
234
- const activeItems = items.filter(i => newActiveIds.includes(i.id));
235
- onActiveChangeMultiple(newActiveIds, activeItems);
236
- }
237
- }
238
-
239
- if (onItemClick) {
240
- onItemClick(item, index);
241
- }
242
- if (item.onClick) {
243
- item.onClick();
244
- }
245
- };
246
-
247
- // 初始化图标引用数组
248
- useEffect(() => {
249
- iconRefs.current = iconRefs.current.slice(0, items.length);
250
- }, [items.length]);
251
-
252
- return (
253
- <DockContainer
254
- ref={dockRef}
255
- itemWidth={itemWidth}
256
- itemGap={itemGap}
257
- className={className}
258
- >
259
- {items.map((item, index) => (
260
- <IconContainer
261
- key={item.id}
262
- ref={el => iconRefs.current[index] = el}
263
- itemWidth={itemWidth}
264
- itemHeight={itemHeight}
265
- itemGap={itemGap}
266
- isHovered={hoveredIndex === index}
267
- magnification={magnification}
268
- onMouseEnter={() => handleMouseEnter(index)}
269
- onMouseLeave={() => handleMouseLeave(index)}
270
- onClick={() => handleClick(item, index)}
271
- >
272
- <IconImage
273
- src={item.icon}
274
- alt={item.label}
275
- isActive={activeMode === 'single' ? activeId === item.id : activeIds.includes(item.id)}
276
- />
277
- <ActiveIndicator isActive={activeMode === 'single' ? activeId === item.id : activeIds.includes(item.id)} />
278
- <Tooltip visible={showTooltip === index}>
279
- {item.label}
280
- </Tooltip>
281
- </IconContainer>
282
- ))}
283
- </DockContainer>
284
- );
285
- };
286
-
287
- export default CustomDock;
@@ -1,249 +0,0 @@
1
- import React, { useState, useRef, useEffect } from 'react';
2
- import { createPortal } from 'react-dom';
3
- import { ChevronDown } from 'lucide-react';
4
- import styled from 'styled-components';
5
- import { defaultTheme, deepMergeTheme, type AppTheme, type Styles } from '../theme/styledTheme';
6
- import Button from './Button';
7
-
8
- // 下拉菜单选项接口 - 与DropdownMenu保持一致
9
- export interface DropDownButtonAction {
10
- key: string;
11
- label?: string;
12
- component?: React.ReactNode; // 新增component支持
13
- type?: 'divider' | 'action';
14
- icon?: React.ReactNode;
15
- onClick?: () => void;
16
- }
17
-
18
- // 下拉按钮属性接口
19
- export interface DropDownButtonProps {
20
- mode?: 'primary' | 'default' | 'error' | 'text';
21
- label?: string;
22
- actions: DropDownButtonAction[];
23
- styles?: Styles;
24
- icon?: React.ReactNode;
25
- disabled?: boolean;
26
- style?: React.CSSProperties;
27
- className?: string;
28
- children?: React.ReactNode;
29
- // 事件回传函数
30
- eventEmit?: (eventName: string, data?: any, innerFn?: any) => void;
31
- }
32
-
33
- // 下拉菜单容器样式
34
- const DropdownContainer = styled.div<{ $theme: AppTheme; $buttonRect: DOMRect | null }>`
35
- position: fixed;
36
- background: ${props => props.$theme?.colors?.background || '#f8f9fa'};
37
- border: 1px solid ${props => props.$theme?.colors?.border || '#dee2e6'};
38
- border-radius: ${props => props.$theme?.space?.radius || '4px'};
39
- box-shadow: ${props => props.$theme?.colors?.shadow || '0 1px 3px rgba(0, 0, 0, 0.12)'};
40
- z-index: 9999;
41
- min-width: ${props => props.$buttonRect ? `${props.$buttonRect.width}px` : 'auto'};
42
- max-width: 300px;
43
- overflow: hidden;
44
- padding: 4px 0;
45
- `;
46
-
47
- // 下拉菜单项样式
48
- const DropdownItem = styled.div<{ $theme: AppTheme }>`
49
- padding: 8px 12px;
50
- cursor: pointer;
51
- display: flex;
52
- align-items: center;
53
- gap: 8px;
54
- font-size: 12px;
55
- color: ${props => props.$theme?.colors?.text || '#343a40'};
56
- background: transparent;
57
- transition: background-color 0.2s;
58
-
59
- &:hover {
60
- background: ${props => props.$theme?.colors?.disabledBackground || '#F5F5F5'};
61
- }
62
-
63
- &:first-child {
64
- border-top-left-radius: ${props => props.$theme?.space?.radius || '4px'};
65
- border-top-right-radius: ${props => props.$theme?.space?.radius || '4px'};
66
- }
67
-
68
- &:last-child {
69
- border-bottom-left-radius: ${props => props.$theme?.space?.radius || '4px'};
70
- border-bottom-right-radius: ${props => props.$theme?.space?.radius || '4px'};
71
- }
72
- `;
73
-
74
- // 分割线样式
75
- const Divider = styled.div<{ $theme: AppTheme }>`
76
- height: 1px;
77
- background: ${props => props.$theme?.colors?.border || '#dee2e6'};
78
- margin: 4px 0;
79
- `;
80
-
81
- const DropDownButton: React.FC<DropDownButtonProps> = ({
82
- mode = 'default',
83
- label = '选择选项',
84
- actions,
85
- styles,
86
- icon,
87
- disabled = false,
88
- style,
89
- className,
90
- children,
91
- eventEmit,
92
- ...rest
93
- }) => {
94
- const [isOpen, setIsOpen] = useState(false);
95
- const buttonRef = useRef<HTMLDivElement>(null);
96
- const [buttonRect, setButtonRect] = useState<DOMRect | null>(null);
97
- const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
98
-
99
- // 使用统一的主题合并函数,确保 theme 有完整的结构
100
- const theme = deepMergeTheme(defaultTheme, styles?.theme);
101
-
102
- // 检查eventEmit属性是否正确传递
103
- useEffect(() => {
104
- console.log('[DropDownButton] 组件挂载,eventEmit:', eventEmit);
105
- console.log('[DropDownButton] actions:', actions);
106
- }, [eventEmit, actions]);
107
-
108
- // 计算下拉菜单位置
109
- const calculatePosition = () => {
110
- if (!buttonRef.current) return;
111
-
112
- const rect = buttonRef.current.getBoundingClientRect();
113
- setButtonRect(rect);
114
-
115
- // 计算下拉菜单位置
116
- const top = rect.bottom + 4; // 按钮下方4px
117
- const left = rect.left;
118
-
119
- setDropdownPosition({ top, left });
120
- };
121
-
122
- // 点击外部关闭下拉菜单
123
- useEffect(() => {
124
- if (!isOpen) return;
125
-
126
- const handleClickOutside = (event: MouseEvent) => {
127
- if (
128
- buttonRef.current &&
129
- !buttonRef.current.contains(event.target as Node)
130
- ) {
131
- setIsOpen(false);
132
- }
133
- };
134
-
135
- const handleScroll = () => setIsOpen(false);
136
- const handleResize = () => setIsOpen(false);
137
-
138
- document.addEventListener('mousedown', handleClickOutside);
139
- window.addEventListener('scroll', handleScroll, true);
140
- window.addEventListener('resize', handleResize);
141
-
142
- return () => {
143
- document.removeEventListener('mousedown', handleClickOutside);
144
- window.removeEventListener('scroll', handleScroll, true);
145
- window.removeEventListener('resize', handleResize);
146
- };
147
- }, [isOpen]);
148
-
149
- // 处理选项点击 - 与DropdownMenu保持一致
150
- const handleAction = (key: string) => {
151
- // 使用eventsEmit进行事件回传
152
- if (eventEmit) {
153
- eventEmit('dropdown_action', { key });
154
- }
155
-
156
- setIsOpen(false);
157
- };
158
-
159
- // 处理按钮点击
160
- const handleButtonClick = () => {
161
- if (disabled) return;
162
-
163
- if (!isOpen) {
164
- calculatePosition();
165
- }
166
-
167
- setIsOpen(!isOpen);
168
- };
169
-
170
- // 渲染下拉菜单
171
- const renderDropdown = () => {
172
- if (!isOpen || !buttonRect) return null;
173
-
174
- return (
175
- <DropdownContainer
176
- $theme={theme.theme || defaultTheme}
177
- $buttonRect={buttonRect}
178
- style={{
179
- top: dropdownPosition.top,
180
- left: dropdownPosition.left,
181
- }}
182
- >
183
- {actions.map((action, index) => {
184
- // 处理divider类型
185
- if (action.type === 'divider') {
186
- return (
187
- <Divider
188
- key={action.key || `divider-${index}`}
189
- $theme={theme.theme || defaultTheme}
190
- />
191
- );
192
- }
193
-
194
- // 处理普通action类型
195
- return (
196
- <DropdownItem
197
- key={action.key || `action-${index}`}
198
- $theme={theme.theme || defaultTheme}
199
- onMouseDown={(e) => {
200
- e.preventDefault();
201
- e.stopPropagation();
202
- handleAction(action.key);
203
- if (action.onClick) action.onClick();
204
- }}
205
- >
206
- {action.icon && <span className="mr-2">{action.icon}</span>}
207
- {/* 优先显示component,如果没有则显示label */}
208
- {action.component || action.label}
209
- </DropdownItem>
210
- );
211
- })}
212
- </DropdownContainer>
213
- );
214
- };
215
-
216
- return (
217
- <div
218
- ref={buttonRef}
219
- style={{ position: 'relative', display: 'inline-block' }}
220
- className={className}
221
- >
222
- <Button
223
- mode={mode}
224
- label={label}
225
- icon={icon}
226
- disabled={disabled}
227
- onClick={handleButtonClick} // 保留这个点击事件用于显示/隐藏下拉菜单
228
- styles={styles}
229
- style={style}
230
- {...rest}
231
- >
232
- {children}
233
- <ChevronDown
234
- size={14}
235
- style={{
236
- marginLeft: 4,
237
- transition: 'transform 0.2s',
238
- transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)'
239
- }}
240
- />
241
- </Button>
242
-
243
- {/* 使用Portal渲染下拉菜单到body */}
244
- {isOpen && createPortal(renderDropdown(), document.body)}
245
- </div>
246
- );
247
- };
248
-
249
- export default DropDownButton;