@windrun-huaiin/third-ui 5.9.5 → 5.9.7
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/dist/clerk/index.js +101 -88
- package/dist/clerk/index.js.map +1 -1
- package/dist/clerk/index.mjs +101 -88
- package/dist/clerk/index.mjs.map +1 -1
- package/dist/fuma/mdx/index.js +110 -93
- package/dist/fuma/mdx/index.js.map +1 -1
- package/dist/fuma/mdx/index.mjs +110 -93
- package/dist/fuma/mdx/index.mjs.map +1 -1
- package/dist/fuma/server.js +101 -88
- package/dist/fuma/server.js.map +1 -1
- package/dist/fuma/server.mjs +101 -88
- package/dist/fuma/server.mjs.map +1 -1
- package/dist/main/index.d.mts +44 -2
- package/dist/main/index.d.ts +44 -2
- package/dist/main/index.js +255 -95
- package/dist/main/index.js.map +1 -1
- package/dist/main/index.mjs +252 -94
- package/dist/main/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/fuma/mdx/gradient-button.tsx +14 -14
- package/src/main/index.ts +2 -1
- package/src/main/x-button.tsx +199 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "5.9.
|
|
3
|
+
"version": "5.9.7",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"mermaid": "^11.6.0",
|
|
54
54
|
"react-medium-image-zoom": "^5.2.14",
|
|
55
55
|
"zod": "^3.22.4",
|
|
56
|
-
"@windrun-huaiin/base-ui": "^5.3.
|
|
56
|
+
"@windrun-huaiin/base-ui": "^5.3.4"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"react": "19.1.0",
|
|
@@ -11,15 +11,12 @@ export interface GradientButtonProps {
|
|
|
11
11
|
align?: 'left' | 'center' | 'right';
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
className?: string;
|
|
14
|
-
|
|
15
|
-
// 跳转模式
|
|
14
|
+
// for Link
|
|
16
15
|
href?: string;
|
|
17
16
|
openInNewTab?: boolean;
|
|
18
17
|
|
|
19
|
-
//
|
|
18
|
+
// for click
|
|
20
19
|
onClick?: () => void | Promise<void>;
|
|
21
|
-
|
|
22
|
-
// 加载状态配置
|
|
23
20
|
loadingText?: React.ReactNode;
|
|
24
21
|
preventDoubleClick?: boolean;
|
|
25
22
|
}
|
|
@@ -33,10 +30,11 @@ export function GradientButton({
|
|
|
33
30
|
href,
|
|
34
31
|
openInNewTab = true,
|
|
35
32
|
onClick,
|
|
36
|
-
loadingText
|
|
33
|
+
loadingText,
|
|
37
34
|
preventDoubleClick = true,
|
|
38
35
|
}: GradientButtonProps) {
|
|
39
36
|
const [isLoading, setIsLoading] = useState(false);
|
|
37
|
+
const actualLoadingText = loadingText || title?.toString().trim() || 'Loading...'
|
|
40
38
|
|
|
41
39
|
// set justify class according to alignment
|
|
42
40
|
const getAlignmentClass = () => {
|
|
@@ -50,7 +48,6 @@ export function GradientButton({
|
|
|
50
48
|
}
|
|
51
49
|
};
|
|
52
50
|
|
|
53
|
-
// 处理点击事件
|
|
54
51
|
const handleClick = async (e: React.MouseEvent) => {
|
|
55
52
|
if (disabled || isLoading) {
|
|
56
53
|
e.preventDefault();
|
|
@@ -76,13 +73,11 @@ export function GradientButton({
|
|
|
76
73
|
}
|
|
77
74
|
};
|
|
78
75
|
|
|
79
|
-
// 按钮是否处于禁用状态
|
|
80
76
|
const isDisabled = disabled || isLoading;
|
|
81
77
|
|
|
82
|
-
|
|
83
|
-
const displayTitle = isLoading ? loadingText : title;
|
|
78
|
+
const displayTitle = isLoading ? actualLoadingText : title;
|
|
84
79
|
|
|
85
|
-
//
|
|
80
|
+
// icon
|
|
86
81
|
const displayIcon = isLoading ? (
|
|
87
82
|
<icons.Loader2 className="h-4 w-4 text-white animate-spin" />
|
|
88
83
|
) : icon ? (
|
|
@@ -93,7 +88,12 @@ export function GradientButton({
|
|
|
93
88
|
<icons.ArrowRight className="h-4 w-4 text-white" />
|
|
94
89
|
);
|
|
95
90
|
|
|
96
|
-
const buttonContent = (
|
|
91
|
+
const buttonContent = onClick ? (
|
|
92
|
+
<>
|
|
93
|
+
<span>{displayIcon}</span>
|
|
94
|
+
<span className="ml-1">{displayTitle}</span>
|
|
95
|
+
</>
|
|
96
|
+
) : (
|
|
97
97
|
<>
|
|
98
98
|
<span>{displayTitle}</span>
|
|
99
99
|
<span className="ml-1">{displayIcon}</span>
|
|
@@ -116,7 +116,7 @@ export function GradientButton({
|
|
|
116
116
|
return (
|
|
117
117
|
<div className={`flex flex-col sm:flex-row gap-3 ${getAlignmentClass()}`}>
|
|
118
118
|
{onClick ? (
|
|
119
|
-
//
|
|
119
|
+
// for click
|
|
120
120
|
<Button
|
|
121
121
|
size="lg"
|
|
122
122
|
className={buttonClassName}
|
|
@@ -126,7 +126,7 @@ export function GradientButton({
|
|
|
126
126
|
{buttonContent}
|
|
127
127
|
</Button>
|
|
128
128
|
) : (
|
|
129
|
-
//
|
|
129
|
+
// for Link
|
|
130
130
|
<Button
|
|
131
131
|
asChild
|
|
132
132
|
size="lg"
|
package/src/main/index.ts
CHANGED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect, ReactNode } from 'react'
|
|
4
|
+
import { globalLucideIcons as icons } from '@base-ui/components/global-icon'
|
|
5
|
+
|
|
6
|
+
// base button config
|
|
7
|
+
interface BaseButtonConfig {
|
|
8
|
+
icon: ReactNode
|
|
9
|
+
text: string
|
|
10
|
+
onClick: () => void | Promise<void>
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// menu item config
|
|
15
|
+
interface MenuItemConfig extends BaseButtonConfig {
|
|
16
|
+
tag?: {
|
|
17
|
+
text: string
|
|
18
|
+
color?: string
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// single button config
|
|
23
|
+
interface SingleButtonProps {
|
|
24
|
+
type: 'single'
|
|
25
|
+
button: BaseButtonConfig
|
|
26
|
+
loadingText?: string
|
|
27
|
+
minWidth?: string
|
|
28
|
+
className?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// split button config
|
|
32
|
+
interface SplitButtonProps {
|
|
33
|
+
type: 'split'
|
|
34
|
+
mainButton: BaseButtonConfig
|
|
35
|
+
menuItems: MenuItemConfig[]
|
|
36
|
+
loadingText?: string
|
|
37
|
+
menuWidth?: string
|
|
38
|
+
className?: string
|
|
39
|
+
mainButtonClassName?: string
|
|
40
|
+
dropdownButtonClassName?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type xButtonProps = SingleButtonProps | SplitButtonProps
|
|
44
|
+
|
|
45
|
+
export const XButtonIcons = {
|
|
46
|
+
copy: <icons.Copy className="w-5 h-5 mr-1" />,
|
|
47
|
+
checkCheck: <icons.CheckCheck className="w-5 h-5 mr-1" />,
|
|
48
|
+
globe: <icons.Languages className="w-5 h-5 mr-1" />,
|
|
49
|
+
loader: <icons.Loader2 className="w-5 h-5 mr-1 animate-spin" />,
|
|
50
|
+
download: <icons.Download className="w-5 h-5 mr-1" />,
|
|
51
|
+
upload: <icons.ImageUp className="w-5 h-5 mr-1" />,
|
|
52
|
+
share: <icons.Share className="w-5 h-5 mr-1" />,
|
|
53
|
+
edit: <icons.Pencil className="w-5 h-5 mr-1" />,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function XButton(props: xButtonProps) {
|
|
57
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
58
|
+
const [menuOpen, setMenuOpen] = useState(false)
|
|
59
|
+
const menuRef = useRef<HTMLDivElement>(null)
|
|
60
|
+
|
|
61
|
+
// click outside to close menu
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (props.type === 'split') {
|
|
64
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
65
|
+
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
|
66
|
+
setMenuOpen(false)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (menuOpen) {
|
|
71
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
document.removeEventListener('mousedown', handleClickOutside)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, [menuOpen, props.type])
|
|
79
|
+
|
|
80
|
+
// handle button click
|
|
81
|
+
const handleButtonClick = async (onClick: () => void | Promise<void>) => {
|
|
82
|
+
if (isLoading) return
|
|
83
|
+
|
|
84
|
+
setIsLoading(true)
|
|
85
|
+
try {
|
|
86
|
+
await onClick()
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Button click error:', error)
|
|
89
|
+
} finally {
|
|
90
|
+
setIsLoading(false)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// base style class
|
|
95
|
+
const baseButtonClass = "flex items-center justify-center px-4 py-2 bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white text-sm font-semibold transition-colors hover:bg-neutral-300 dark:hover:bg-neutral-700"
|
|
96
|
+
const disabledClass = "opacity-60 cursor-not-allowed"
|
|
97
|
+
|
|
98
|
+
if (props.type === 'single') {
|
|
99
|
+
const { button, loadingText, minWidth = 'min-w-[110px]', className = '' } = props
|
|
100
|
+
const isDisabled = button.disabled || isLoading
|
|
101
|
+
// loadingText: props.loadingText > button.text > 'Loading...'
|
|
102
|
+
const actualLoadingText = loadingText || button.text?.trim() || 'Loading...'
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<button
|
|
106
|
+
onClick={() => handleButtonClick(button.onClick)}
|
|
107
|
+
disabled={isDisabled}
|
|
108
|
+
className={`${minWidth} ${baseButtonClass} rounded-full ${isDisabled ? disabledClass : ''} ${className}`}
|
|
109
|
+
title={button.text}
|
|
110
|
+
>
|
|
111
|
+
{isLoading ? (
|
|
112
|
+
<>
|
|
113
|
+
<icons.Loader2 className="w-5 h-5 mr-1 animate-spin" />
|
|
114
|
+
<span>{actualLoadingText}</span>
|
|
115
|
+
</>
|
|
116
|
+
) : (
|
|
117
|
+
<>
|
|
118
|
+
{button.icon}
|
|
119
|
+
<span>{button.text}</span>
|
|
120
|
+
</>
|
|
121
|
+
)}
|
|
122
|
+
</button>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Split button
|
|
127
|
+
const { mainButton, menuItems, loadingText, menuWidth = 'w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props
|
|
128
|
+
const isMainDisabled = mainButton.disabled || isLoading
|
|
129
|
+
// loadingText 优先级:props.loadingText > mainButton.text > 'Loading...'
|
|
130
|
+
const actualLoadingText = loadingText || mainButton.text?.trim() || 'Loading...'
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className={`relative flex bg-neutral-200 dark:bg-neutral-800 rounded-full ${className}`}>
|
|
134
|
+
{/* left main button */}
|
|
135
|
+
<button
|
|
136
|
+
onClick={() => handleButtonClick(mainButton.onClick)}
|
|
137
|
+
disabled={isMainDisabled}
|
|
138
|
+
className={`flex-1 ${baseButtonClass} rounded-l-full ${isMainDisabled ? disabledClass : ''} ${mainButtonClassName}`}
|
|
139
|
+
onMouseDown={e => { if (e.button === 2) e.preventDefault() }}
|
|
140
|
+
style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
|
|
141
|
+
>
|
|
142
|
+
{isLoading ? (
|
|
143
|
+
<>
|
|
144
|
+
<icons.Loader2 className="w-5 h-5 mr-1 animate-spin" />
|
|
145
|
+
<span>{actualLoadingText}</span>
|
|
146
|
+
</>
|
|
147
|
+
) : (
|
|
148
|
+
<>
|
|
149
|
+
{mainButton.icon}
|
|
150
|
+
<span>{mainButton.text}</span>
|
|
151
|
+
</>
|
|
152
|
+
)}
|
|
153
|
+
</button>
|
|
154
|
+
|
|
155
|
+
{/* right dropdown button */}
|
|
156
|
+
<span
|
|
157
|
+
className={`flex items-center justify-center w-10 py-2 cursor-pointer transition hover:bg-neutral-300 dark:hover:bg-neutral-700 rounded-r-full ${dropdownButtonClassName}`}
|
|
158
|
+
onClick={e => { e.stopPropagation(); setMenuOpen(v => !v) }}
|
|
159
|
+
tabIndex={0}
|
|
160
|
+
style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}
|
|
161
|
+
>
|
|
162
|
+
<icons.ChevronDown className="w-6 h-6" />
|
|
163
|
+
</span>
|
|
164
|
+
|
|
165
|
+
{/* dropdown menu */}
|
|
166
|
+
{menuOpen && (
|
|
167
|
+
<div
|
|
168
|
+
ref={menuRef}
|
|
169
|
+
className={`absolute right-0 top-full ${menuWidth} bg-white dark:bg-neutral-800 text-neutral-800 dark:text-white text-sm rounded-xl shadow-lg z-50 border border-neutral-200 dark:border-neutral-700 overflow-hidden animate-fade-in`}
|
|
170
|
+
>
|
|
171
|
+
{menuItems.map((item, index) => (
|
|
172
|
+
<button
|
|
173
|
+
key={index}
|
|
174
|
+
onClick={() => {
|
|
175
|
+
handleButtonClick(item.onClick)
|
|
176
|
+
setMenuOpen(false)
|
|
177
|
+
}}
|
|
178
|
+
disabled={item.disabled}
|
|
179
|
+
className={`flex items-center w-full px-4 py-3 transition hover:bg-neutral-300 dark:hover:bg-neutral-600 text-left relative ${item.disabled ? disabledClass : ''}`}
|
|
180
|
+
>
|
|
181
|
+
<span className="flex items-center">
|
|
182
|
+
{item.icon}
|
|
183
|
+
<span>{item.text}</span>
|
|
184
|
+
</span>
|
|
185
|
+
{item.tag && (
|
|
186
|
+
<span
|
|
187
|
+
className="absolute right-3 top-1 text-[10px] font-semibold"
|
|
188
|
+
style={{ color: item.tag.color || '#A855F7', pointerEvents: 'none' }}
|
|
189
|
+
>
|
|
190
|
+
{item.tag.text}
|
|
191
|
+
</span>
|
|
192
|
+
)}
|
|
193
|
+
</button>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|