@windrun-huaiin/third-ui 11.0.6 → 11.0.8
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/fuma/mdx/gradient-button.d.ts +2 -2
- package/dist/fuma/mdx/gradient-button.js +12 -11
- package/dist/fuma/mdx/gradient-button.mjs +12 -11
- package/dist/main/x-button.d.ts +2 -2
- package/dist/main/x-button.js +15 -10
- package/dist/main/x-button.mjs +16 -11
- package/package.json +1 -1
- package/src/fuma/mdx/gradient-button.tsx +23 -26
- package/src/main/x-button.tsx +19 -13
|
@@ -5,11 +5,11 @@ export interface GradientButtonProps {
|
|
|
5
5
|
align?: 'left' | 'center' | 'right';
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
className?: string;
|
|
8
|
-
|
|
8
|
+
iconClassName?: string;
|
|
9
9
|
href?: string;
|
|
10
10
|
openInNewTab?: boolean;
|
|
11
11
|
onClick?: () => void | Promise<void>;
|
|
12
12
|
loadingText?: React.ReactNode;
|
|
13
13
|
preventDoubleClick?: boolean;
|
|
14
14
|
}
|
|
15
|
-
export declare function GradientButton({ title, icon, align, disabled, className, href, openInNewTab, onClick, loadingText, preventDoubleClick,
|
|
15
|
+
export declare function GradientButton({ title, icon, align, disabled, className, href, openInNewTab, onClick, loadingText, preventDoubleClick, iconClassName, }: GradientButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,18 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
var tslib_es6 = require('../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
-
var ui = require('@windrun-huaiin/base-ui/ui');
|
|
7
6
|
var utils = require('@windrun-huaiin/lib/utils');
|
|
8
7
|
var server = require('@windrun-huaiin/base-ui/components/server');
|
|
9
8
|
var Link = require('next/link');
|
|
10
9
|
var React = require('react');
|
|
11
10
|
|
|
12
|
-
function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true,
|
|
11
|
+
function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true, iconClassName, }) {
|
|
13
12
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
14
13
|
const actualLoadingText = loadingText || (title === null || title === void 0 ? void 0 : title.toString().trim()) || 'Loading...';
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
: 'h-4 w-4';
|
|
14
|
+
const defaultIconClass = "h-4 w-4";
|
|
15
|
+
const finalIconClass = utils.cn("text-white", iconClassName || defaultIconClass);
|
|
18
16
|
// set justify class according to alignment
|
|
19
17
|
const getAlignmentClass = () => {
|
|
20
18
|
switch (align) {
|
|
@@ -55,7 +53,7 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
55
53
|
const iconProvided = icon !== undefined;
|
|
56
54
|
const iconNode = (() => {
|
|
57
55
|
if (isLoading) {
|
|
58
|
-
return jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: utils.cn(
|
|
56
|
+
return jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: utils.cn(finalIconClass, 'animate-spin') });
|
|
59
57
|
}
|
|
60
58
|
if (iconProvided) {
|
|
61
59
|
if (icon === null || icon === false) {
|
|
@@ -63,12 +61,12 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
63
61
|
}
|
|
64
62
|
if (React.isValidElement(icon)) {
|
|
65
63
|
return React.cloneElement(icon, {
|
|
66
|
-
className: utils.cn(
|
|
64
|
+
className: utils.cn(finalIconClass, icon.props.className),
|
|
67
65
|
});
|
|
68
66
|
}
|
|
69
67
|
return icon;
|
|
70
68
|
}
|
|
71
|
-
return jsxRuntime.jsx(server.globalLucideIcons.ArrowRight, { className: utils.cn(
|
|
69
|
+
return jsxRuntime.jsx(server.globalLucideIcons.ArrowRight, { className: utils.cn(finalIconClass) });
|
|
72
70
|
})();
|
|
73
71
|
const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
|
|
74
72
|
const buttonContent = onClick ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [shouldRenderIcon ? jsxRuntime.jsx("span", { children: iconNode }) : null, jsxRuntime.jsx("span", { className: utils.cn(shouldRenderIcon && 'ml-1'), children: displayTitle })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { children: displayTitle }), shouldRenderIcon ? jsxRuntime.jsx("span", { className: "ml-1", children: iconNode }) : null] }));
|
|
@@ -77,12 +75,15 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
77
75
|
: align === 'center'
|
|
78
76
|
? 'justify-center'
|
|
79
77
|
: 'justify-start';
|
|
80
|
-
|
|
78
|
+
// Base styles extracted from Button component + size="lg" (h-11 px-8)
|
|
79
|
+
// Removed [&_svg] constraints
|
|
80
|
+
const baseButtonStyles = "inline-flex items-center gap-2 whitespace-nowrap h-11 px-8 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
|
81
|
+
const buttonClassName = utils.cn(baseButtonStyles, 'bg-linear-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 dark:hover:to-pink-700 text-white text-base font-bold shadow-lg hover:shadow-xl transition-all duration-300 rounded-full', alignmentClass, isDisabled && 'opacity-50 cursor-not-allowed', className);
|
|
81
82
|
return (jsxRuntime.jsx("div", { className: `flex flex-row gap-3 ${getAlignmentClass()}`, children: onClick ? (
|
|
82
83
|
// for click
|
|
83
|
-
jsxRuntime.jsx(
|
|
84
|
+
jsxRuntime.jsx("button", { type: "button", className: buttonClassName, onClick: handleClick, disabled: isDisabled, children: buttonContent })) : (
|
|
84
85
|
// for Link
|
|
85
|
-
jsxRuntime.jsx(
|
|
86
|
+
jsxRuntime.jsx(Link, Object.assign({ href: href || "#", className: utils.cn(buttonClassName, "no-underline hover:no-underline") }, (openInNewTab ? { target: "_blank", rel: "noopener noreferrer" } : {}), { onClick: isDisabled ? (e) => e.preventDefault() : undefined, "aria-disabled": isDisabled, children: buttonContent }))) }));
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
exports.GradientButton = GradientButton;
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
-
import { Button } from '@windrun-huaiin/base-ui/ui';
|
|
5
4
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
6
5
|
import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
|
|
7
6
|
import Link from 'next/link';
|
|
8
7
|
import React, { useState } from 'react';
|
|
9
8
|
|
|
10
|
-
function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true,
|
|
9
|
+
function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true, iconClassName, }) {
|
|
11
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
12
11
|
const actualLoadingText = loadingText || (title === null || title === void 0 ? void 0 : title.toString().trim()) || 'Loading...';
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
: 'h-4 w-4';
|
|
12
|
+
const defaultIconClass = "h-4 w-4";
|
|
13
|
+
const finalIconClass = cn("text-white", iconClassName || defaultIconClass);
|
|
16
14
|
// set justify class according to alignment
|
|
17
15
|
const getAlignmentClass = () => {
|
|
18
16
|
switch (align) {
|
|
@@ -53,7 +51,7 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
53
51
|
const iconProvided = icon !== undefined;
|
|
54
52
|
const iconNode = (() => {
|
|
55
53
|
if (isLoading) {
|
|
56
|
-
return jsx(globalLucideIcons.Loader2, { className: cn(
|
|
54
|
+
return jsx(globalLucideIcons.Loader2, { className: cn(finalIconClass, 'animate-spin') });
|
|
57
55
|
}
|
|
58
56
|
if (iconProvided) {
|
|
59
57
|
if (icon === null || icon === false) {
|
|
@@ -61,12 +59,12 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
61
59
|
}
|
|
62
60
|
if (React.isValidElement(icon)) {
|
|
63
61
|
return React.cloneElement(icon, {
|
|
64
|
-
className: cn(
|
|
62
|
+
className: cn(finalIconClass, icon.props.className),
|
|
65
63
|
});
|
|
66
64
|
}
|
|
67
65
|
return icon;
|
|
68
66
|
}
|
|
69
|
-
return jsx(globalLucideIcons.ArrowRight, { className: cn(
|
|
67
|
+
return jsx(globalLucideIcons.ArrowRight, { className: cn(finalIconClass) });
|
|
70
68
|
})();
|
|
71
69
|
const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
|
|
72
70
|
const buttonContent = onClick ? (jsxs(Fragment, { children: [shouldRenderIcon ? jsx("span", { children: iconNode }) : null, jsx("span", { className: cn(shouldRenderIcon && 'ml-1'), children: displayTitle })] })) : (jsxs(Fragment, { children: [jsx("span", { children: displayTitle }), shouldRenderIcon ? jsx("span", { className: "ml-1", children: iconNode }) : null] }));
|
|
@@ -75,12 +73,15 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
75
73
|
: align === 'center'
|
|
76
74
|
? 'justify-center'
|
|
77
75
|
: 'justify-start';
|
|
78
|
-
|
|
76
|
+
// Base styles extracted from Button component + size="lg" (h-11 px-8)
|
|
77
|
+
// Removed [&_svg] constraints
|
|
78
|
+
const baseButtonStyles = "inline-flex items-center gap-2 whitespace-nowrap h-11 px-8 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
|
79
|
+
const buttonClassName = cn(baseButtonStyles, 'bg-linear-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 dark:hover:to-pink-700 text-white text-base font-bold shadow-lg hover:shadow-xl transition-all duration-300 rounded-full', alignmentClass, isDisabled && 'opacity-50 cursor-not-allowed', className);
|
|
79
80
|
return (jsx("div", { className: `flex flex-row gap-3 ${getAlignmentClass()}`, children: onClick ? (
|
|
80
81
|
// for click
|
|
81
|
-
jsx(
|
|
82
|
+
jsx("button", { type: "button", className: buttonClassName, onClick: handleClick, disabled: isDisabled, children: buttonContent })) : (
|
|
82
83
|
// for Link
|
|
83
|
-
jsx(
|
|
84
|
+
jsx(Link, Object.assign({ href: href || "#", className: cn(buttonClassName, "no-underline hover:no-underline") }, (openInNewTab ? { target: "_blank", rel: "noopener noreferrer" } : {}), { onClick: isDisabled ? (e) => e.preventDefault() : undefined, "aria-disabled": isDisabled, children: buttonContent }))) }));
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
export { GradientButton };
|
package/dist/main/x-button.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ interface SingleButtonProps {
|
|
|
18
18
|
loadingText?: string;
|
|
19
19
|
minWidth?: string;
|
|
20
20
|
className?: string;
|
|
21
|
-
|
|
21
|
+
iconClassName?: string;
|
|
22
22
|
}
|
|
23
23
|
interface SplitButtonProps {
|
|
24
24
|
type: 'split';
|
|
@@ -29,7 +29,7 @@ interface SplitButtonProps {
|
|
|
29
29
|
className?: string;
|
|
30
30
|
mainButtonClassName?: string;
|
|
31
31
|
dropdownButtonClassName?: string;
|
|
32
|
-
|
|
32
|
+
iconClassName?: string;
|
|
33
33
|
}
|
|
34
34
|
type xButtonProps = SingleButtonProps | SplitButtonProps;
|
|
35
35
|
export declare function XButton(props: xButtonProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/main/x-button.js
CHANGED
|
@@ -12,14 +12,19 @@ function XButton(props) {
|
|
|
12
12
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
13
13
|
const [menuOpen, setMenuOpen] = React.useState(false);
|
|
14
14
|
const menuRef = React.useRef(null);
|
|
15
|
-
const {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const { iconClassName } = props;
|
|
16
|
+
const defaultIconClass = "w-5 h-5";
|
|
17
|
+
const finalIconClass = iconClassName || defaultIconClass;
|
|
18
|
+
const loadingIconClass = utils.cn(finalIconClass, "mr-1 animate-spin");
|
|
19
|
+
const chevronIconClass = "w-6 h-6";
|
|
20
|
+
const renderIcon = (icon) => {
|
|
21
|
+
if (React.isValidElement(icon)) {
|
|
22
|
+
return React.cloneElement(icon, {
|
|
23
|
+
className: utils.cn(finalIconClass, icon.props.className),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return icon;
|
|
27
|
+
};
|
|
23
28
|
// click outside to close menu
|
|
24
29
|
React.useEffect(() => {
|
|
25
30
|
if (props.type === 'split') {
|
|
@@ -59,7 +64,7 @@ function XButton(props) {
|
|
|
59
64
|
const isDisabled = button.disabled || isLoading;
|
|
60
65
|
// loadingText: props.loadingText > button.text > 'Loading...'
|
|
61
66
|
const actualLoadingText = loadingText || ((_a = button.text) === null || _a === void 0 ? void 0 : _a.trim()) || 'Loading...';
|
|
62
|
-
return (jsxRuntime.jsx("button", { onClick: () => handleButtonClick(button.onClick), disabled: isDisabled, className: utils.cn("w-full sm:w-auto", minWidth, baseButtonClass, "rounded-full", isDisabled && disabledClass, className), title: button.text, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: loadingIconClass }), jsxRuntime.jsx("span", { children: actualLoadingText })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [button.icon, jsxRuntime.jsx("span", { children: button.text })] })) }));
|
|
67
|
+
return (jsxRuntime.jsx("button", { onClick: () => handleButtonClick(button.onClick), disabled: isDisabled, className: utils.cn("w-full sm:w-auto", minWidth, baseButtonClass, "rounded-full", isDisabled && disabledClass, className), title: button.text, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: loadingIconClass }), jsxRuntime.jsx("span", { children: actualLoadingText })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderIcon(button.icon), jsxRuntime.jsx("span", { children: button.text })] })) }));
|
|
63
68
|
}
|
|
64
69
|
// Split button
|
|
65
70
|
const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props;
|
|
@@ -67,7 +72,7 @@ function XButton(props) {
|
|
|
67
72
|
// loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
|
|
68
73
|
const actualLoadingText = loadingText || ((_b = mainButton.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
|
|
69
74
|
return (jsxRuntime.jsxs("div", { className: utils.cn("relative flex flex-col sm:flex-row items-stretch w-full sm:w-auto bg-neutral-200 dark:bg-neutral-800 rounded-full gap-2 sm:gap-0", className), children: [jsxRuntime.jsx("button", { onClick: () => handleButtonClick(mainButton.onClick), disabled: isMainDisabled, className: utils.cn("flex-1 w-full", baseButtonClass, "rounded-full sm:rounded-l-full sm:rounded-r-none", isMainDisabled && disabledClass, mainButtonClassName), onMouseDown: e => { if (e.button === 2)
|
|
70
|
-
e.preventDefault(); }, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: loadingIconClass }), jsxRuntime.jsx("span", { children: actualLoadingText })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [mainButton.icon, jsxRuntime.jsx("span", { children: mainButton.text })] })) }), jsxRuntime.jsx("button", { type: "button", className: utils.cn("flex items-center justify-center w-full sm:w-10 py-1.5 bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white cursor-pointer transition hover:bg-neutral-300 dark:hover:bg-neutral-700 rounded-full sm:rounded-none sm:rounded-r-full sm:border-l sm:border-neutral-300 sm:dark:border-neutral-700", dropdownButtonClassName), onClick: e => { e.stopPropagation(); setMenuOpen(v => !v); }, "aria-label": "More actions", "aria-expanded": menuOpen, children: jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: chevronIconClass }) }), menuOpen && (jsxRuntime.jsx("div", { ref: menuRef, 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`, children: menuItems.map((item, index) => (jsxRuntime.jsxs("button", { onClick: () => {
|
|
75
|
+
e.preventDefault(); }, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: loadingIconClass }), jsxRuntime.jsx("span", { children: actualLoadingText })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderIcon(mainButton.icon), jsxRuntime.jsx("span", { children: mainButton.text })] })) }), jsxRuntime.jsx("button", { type: "button", className: utils.cn("flex items-center justify-center w-full sm:w-10 py-1.5 bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white cursor-pointer transition hover:bg-neutral-300 dark:hover:bg-neutral-700 rounded-full sm:rounded-none sm:rounded-r-full sm:border-l sm:border-neutral-300 sm:dark:border-neutral-700", dropdownButtonClassName), onClick: e => { e.stopPropagation(); setMenuOpen(v => !v); }, "aria-label": "More actions", "aria-expanded": menuOpen, children: jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: chevronIconClass }) }), menuOpen && (jsxRuntime.jsx("div", { ref: menuRef, 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`, children: menuItems.map((item, index) => (jsxRuntime.jsxs("button", { onClick: () => {
|
|
71
76
|
handleButtonClick(item.onClick);
|
|
72
77
|
setMenuOpen(false);
|
|
73
78
|
}, disabled: item.disabled, 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 : ''}`, style: item.splitTopBorder ? { borderTop: '1px solid #AC62FD' } : undefined, children: [jsxRuntime.jsxs("span", { className: "flex items-center", children: [item.icon, jsxRuntime.jsx("span", { children: item.text })] }), item.tag && (jsxRuntime.jsx("span", { className: "absolute right-3 top-1 text-[10px] font-semibold", style: { color: item.tag.color || '#A855F7', pointerEvents: 'none' }, children: item.tag.text }))] }, index))) }))] }));
|
package/dist/main/x-button.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { __awaiter } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
-
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
5
5
|
import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
|
|
6
6
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
7
7
|
|
|
@@ -10,14 +10,19 @@ function XButton(props) {
|
|
|
10
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
11
11
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
12
12
|
const menuRef = useRef(null);
|
|
13
|
-
const {
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
const { iconClassName } = props;
|
|
14
|
+
const defaultIconClass = "w-5 h-5";
|
|
15
|
+
const finalIconClass = iconClassName || defaultIconClass;
|
|
16
|
+
const loadingIconClass = cn(finalIconClass, "mr-1 animate-spin");
|
|
17
|
+
const chevronIconClass = "w-6 h-6";
|
|
18
|
+
const renderIcon = (icon) => {
|
|
19
|
+
if (React.isValidElement(icon)) {
|
|
20
|
+
return React.cloneElement(icon, {
|
|
21
|
+
className: cn(finalIconClass, icon.props.className),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return icon;
|
|
25
|
+
};
|
|
21
26
|
// click outside to close menu
|
|
22
27
|
useEffect(() => {
|
|
23
28
|
if (props.type === 'split') {
|
|
@@ -57,7 +62,7 @@ function XButton(props) {
|
|
|
57
62
|
const isDisabled = button.disabled || isLoading;
|
|
58
63
|
// loadingText: props.loadingText > button.text > 'Loading...'
|
|
59
64
|
const actualLoadingText = loadingText || ((_a = button.text) === null || _a === void 0 ? void 0 : _a.trim()) || 'Loading...';
|
|
60
|
-
return (jsx("button", { onClick: () => handleButtonClick(button.onClick), disabled: isDisabled, className: cn("w-full sm:w-auto", minWidth, baseButtonClass, "rounded-full", isDisabled && disabledClass, className), title: button.text, children: isLoading ? (jsxs(Fragment, { children: [jsx(globalLucideIcons.Loader2, { className: loadingIconClass }), jsx("span", { children: actualLoadingText })] })) : (jsxs(Fragment, { children: [button.icon, jsx("span", { children: button.text })] })) }));
|
|
65
|
+
return (jsx("button", { onClick: () => handleButtonClick(button.onClick), disabled: isDisabled, className: cn("w-full sm:w-auto", minWidth, baseButtonClass, "rounded-full", isDisabled && disabledClass, className), title: button.text, children: isLoading ? (jsxs(Fragment, { children: [jsx(globalLucideIcons.Loader2, { className: loadingIconClass }), jsx("span", { children: actualLoadingText })] })) : (jsxs(Fragment, { children: [renderIcon(button.icon), jsx("span", { children: button.text })] })) }));
|
|
61
66
|
}
|
|
62
67
|
// Split button
|
|
63
68
|
const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props;
|
|
@@ -65,7 +70,7 @@ function XButton(props) {
|
|
|
65
70
|
// loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
|
|
66
71
|
const actualLoadingText = loadingText || ((_b = mainButton.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
|
|
67
72
|
return (jsxs("div", { className: cn("relative flex flex-col sm:flex-row items-stretch w-full sm:w-auto bg-neutral-200 dark:bg-neutral-800 rounded-full gap-2 sm:gap-0", className), children: [jsx("button", { onClick: () => handleButtonClick(mainButton.onClick), disabled: isMainDisabled, className: cn("flex-1 w-full", baseButtonClass, "rounded-full sm:rounded-l-full sm:rounded-r-none", isMainDisabled && disabledClass, mainButtonClassName), onMouseDown: e => { if (e.button === 2)
|
|
68
|
-
e.preventDefault(); }, children: isLoading ? (jsxs(Fragment, { children: [jsx(globalLucideIcons.Loader2, { className: loadingIconClass }), jsx("span", { children: actualLoadingText })] })) : (jsxs(Fragment, { children: [mainButton.icon, jsx("span", { children: mainButton.text })] })) }), jsx("button", { type: "button", className: cn("flex items-center justify-center w-full sm:w-10 py-1.5 bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white cursor-pointer transition hover:bg-neutral-300 dark:hover:bg-neutral-700 rounded-full sm:rounded-none sm:rounded-r-full sm:border-l sm:border-neutral-300 sm:dark:border-neutral-700", dropdownButtonClassName), onClick: e => { e.stopPropagation(); setMenuOpen(v => !v); }, "aria-label": "More actions", "aria-expanded": menuOpen, children: jsx(globalLucideIcons.ChevronDown, { className: chevronIconClass }) }), menuOpen && (jsx("div", { ref: menuRef, 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`, children: menuItems.map((item, index) => (jsxs("button", { onClick: () => {
|
|
73
|
+
e.preventDefault(); }, children: isLoading ? (jsxs(Fragment, { children: [jsx(globalLucideIcons.Loader2, { className: loadingIconClass }), jsx("span", { children: actualLoadingText })] })) : (jsxs(Fragment, { children: [renderIcon(mainButton.icon), jsx("span", { children: mainButton.text })] })) }), jsx("button", { type: "button", className: cn("flex items-center justify-center w-full sm:w-10 py-1.5 bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white cursor-pointer transition hover:bg-neutral-300 dark:hover:bg-neutral-700 rounded-full sm:rounded-none sm:rounded-r-full sm:border-l sm:border-neutral-300 sm:dark:border-neutral-700", dropdownButtonClassName), onClick: e => { e.stopPropagation(); setMenuOpen(v => !v); }, "aria-label": "More actions", "aria-expanded": menuOpen, children: jsx(globalLucideIcons.ChevronDown, { className: chevronIconClass }) }), menuOpen && (jsx("div", { ref: menuRef, 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`, children: menuItems.map((item, index) => (jsxs("button", { onClick: () => {
|
|
69
74
|
handleButtonClick(item.onClick);
|
|
70
75
|
setMenuOpen(false);
|
|
71
76
|
}, disabled: item.disabled, 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 : ''}`, style: item.splitTopBorder ? { borderTop: '1px solid #AC62FD' } : undefined, children: [jsxs("span", { className: "flex items-center", children: [item.icon, jsx("span", { children: item.text })] }), item.tag && (jsx("span", { className: "absolute right-3 top-1 text-[10px] font-semibold", style: { color: item.tag.color || '#A855F7', pointerEvents: 'none' }, children: item.tag.text }))] }, index))) }))] }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Button } from "@windrun-huaiin/base-ui/ui";
|
|
4
3
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
5
4
|
import { globalLucideIcons as icons } from "@windrun-huaiin/base-ui/components/server";
|
|
6
5
|
import Link from "next/link";
|
|
@@ -12,7 +11,7 @@ export interface GradientButtonProps {
|
|
|
12
11
|
align?: 'left' | 'center' | 'right';
|
|
13
12
|
disabled?: boolean;
|
|
14
13
|
className?: string;
|
|
15
|
-
|
|
14
|
+
iconClassName?: string;
|
|
16
15
|
// for Link
|
|
17
16
|
href?: string;
|
|
18
17
|
openInNewTab?: boolean;
|
|
@@ -34,14 +33,13 @@ export function GradientButton({
|
|
|
34
33
|
onClick,
|
|
35
34
|
loadingText,
|
|
36
35
|
preventDoubleClick = true,
|
|
37
|
-
|
|
36
|
+
iconClassName,
|
|
38
37
|
}: GradientButtonProps) {
|
|
39
38
|
const [isLoading, setIsLoading] = useState(false);
|
|
40
39
|
const actualLoadingText = loadingText || title?.toString().trim() || 'Loading...'
|
|
41
40
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
: 'h-4 w-4';
|
|
41
|
+
const defaultIconClass = "h-4 w-4";
|
|
42
|
+
const finalIconClass = cn("text-white", iconClassName || defaultIconClass);
|
|
45
43
|
|
|
46
44
|
// set justify class according to alignment
|
|
47
45
|
const getAlignmentClass = () => {
|
|
@@ -89,7 +87,7 @@ export function GradientButton({
|
|
|
89
87
|
|
|
90
88
|
const iconNode = (() => {
|
|
91
89
|
if (isLoading) {
|
|
92
|
-
return <icons.Loader2 className={cn(
|
|
90
|
+
return <icons.Loader2 className={cn(finalIconClass, 'animate-spin')} />;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
if (iconProvided) {
|
|
@@ -99,14 +97,14 @@ export function GradientButton({
|
|
|
99
97
|
|
|
100
98
|
if (React.isValidElement<{ className?: string }>(icon)) {
|
|
101
99
|
return React.cloneElement(icon, {
|
|
102
|
-
className: cn(
|
|
100
|
+
className: cn(finalIconClass, icon.props.className),
|
|
103
101
|
});
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
return icon;
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
return <icons.ArrowRight className={cn(
|
|
107
|
+
return <icons.ArrowRight className={cn(finalIconClass)} />;
|
|
110
108
|
})();
|
|
111
109
|
|
|
112
110
|
const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
|
|
@@ -129,7 +127,12 @@ export function GradientButton({
|
|
|
129
127
|
? 'justify-center'
|
|
130
128
|
: 'justify-start';
|
|
131
129
|
|
|
130
|
+
// Base styles extracted from Button component + size="lg" (h-11 px-8)
|
|
131
|
+
// Removed [&_svg] constraints
|
|
132
|
+
const baseButtonStyles = "inline-flex items-center gap-2 whitespace-nowrap h-11 px-8 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
|
133
|
+
|
|
132
134
|
const buttonClassName = cn(
|
|
135
|
+
baseButtonStyles,
|
|
133
136
|
'bg-linear-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 dark:hover:to-pink-700 text-white text-base font-bold shadow-lg hover:shadow-xl transition-all duration-300 rounded-full',
|
|
134
137
|
alignmentClass,
|
|
135
138
|
isDisabled && 'opacity-50 cursor-not-allowed',
|
|
@@ -140,31 +143,25 @@ export function GradientButton({
|
|
|
140
143
|
<div className={`flex flex-row gap-3 ${getAlignmentClass()}`}>
|
|
141
144
|
{onClick ? (
|
|
142
145
|
// for click
|
|
143
|
-
<
|
|
144
|
-
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
145
148
|
className={buttonClassName}
|
|
146
149
|
onClick={handleClick}
|
|
147
150
|
disabled={isDisabled}
|
|
148
151
|
>
|
|
149
152
|
{buttonContent}
|
|
150
|
-
</
|
|
153
|
+
</button>
|
|
151
154
|
) : (
|
|
152
155
|
// for Link
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
<Link
|
|
157
|
+
href={href || "#"}
|
|
158
|
+
className={cn(buttonClassName, "no-underline hover:no-underline")}
|
|
159
|
+
{...(openInNewTab ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
|
160
|
+
onClick={isDisabled ? (e) => e.preventDefault() : undefined}
|
|
161
|
+
aria-disabled={isDisabled}
|
|
158
162
|
>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
className="no-underline hover:no-underline"
|
|
162
|
-
{...(openInNewTab ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
|
163
|
-
onClick={isDisabled ? (e) => e.preventDefault() : undefined}
|
|
164
|
-
>
|
|
165
|
-
{buttonContent}
|
|
166
|
-
</Link>
|
|
167
|
-
</Button>
|
|
163
|
+
{buttonContent}
|
|
164
|
+
</Link>
|
|
168
165
|
)}
|
|
169
166
|
</div>
|
|
170
167
|
);
|
package/src/main/x-button.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState, useRef, useEffect, ReactNode } from 'react'
|
|
3
|
+
import React, { useState, useRef, useEffect, ReactNode } from 'react'
|
|
4
4
|
import { globalLucideIcons as icons } from '@windrun-huaiin/base-ui/components/server'
|
|
5
5
|
import { cn } from '@windrun-huaiin/lib/utils'
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ interface SingleButtonProps {
|
|
|
28
28
|
loadingText?: string
|
|
29
29
|
minWidth?: string
|
|
30
30
|
className?: string
|
|
31
|
-
|
|
31
|
+
iconClassName?: string
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// split button config
|
|
@@ -41,7 +41,7 @@ interface SplitButtonProps {
|
|
|
41
41
|
className?: string
|
|
42
42
|
mainButtonClassName?: string
|
|
43
43
|
dropdownButtonClassName?: string
|
|
44
|
-
|
|
44
|
+
iconClassName?: string
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
type xButtonProps = SingleButtonProps | SplitButtonProps
|
|
@@ -51,16 +51,22 @@ export function XButton(props: xButtonProps) {
|
|
|
51
51
|
const [menuOpen, setMenuOpen] = useState(false)
|
|
52
52
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
53
53
|
|
|
54
|
-
const {
|
|
55
|
-
const
|
|
54
|
+
const { iconClassName } = props
|
|
55
|
+
const defaultIconClass = "w-5 h-5"
|
|
56
|
+
const finalIconClass = iconClassName || defaultIconClass
|
|
56
57
|
|
|
57
|
-
const loadingIconClass =
|
|
58
|
-
? `w-${iconSizeValue} h-${iconSizeValue} mr-1 animate-spin`
|
|
59
|
-
: "w-5 h-5 mr-1 animate-spin"
|
|
58
|
+
const loadingIconClass = cn(finalIconClass, "mr-1 animate-spin")
|
|
60
59
|
|
|
61
|
-
const chevronIconClass =
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const chevronIconClass = "w-6 h-6"
|
|
61
|
+
|
|
62
|
+
const renderIcon = (icon: ReactNode) => {
|
|
63
|
+
if (React.isValidElement<{ className?: string }>(icon)) {
|
|
64
|
+
return React.cloneElement(icon, {
|
|
65
|
+
className: cn(finalIconClass, icon.props.className),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return icon;
|
|
69
|
+
};
|
|
64
70
|
|
|
65
71
|
// click outside to close menu
|
|
66
72
|
useEffect(() => {
|
|
@@ -126,7 +132,7 @@ export function XButton(props: xButtonProps) {
|
|
|
126
132
|
</>
|
|
127
133
|
) : (
|
|
128
134
|
<>
|
|
129
|
-
{button.icon}
|
|
135
|
+
{renderIcon(button.icon)}
|
|
130
136
|
<span>{button.text}</span>
|
|
131
137
|
</>
|
|
132
138
|
)}
|
|
@@ -165,7 +171,7 @@ export function XButton(props: xButtonProps) {
|
|
|
165
171
|
</>
|
|
166
172
|
) : (
|
|
167
173
|
<>
|
|
168
|
-
{mainButton.icon}
|
|
174
|
+
{renderIcon(mainButton.icon)}
|
|
169
175
|
<span>{mainButton.text}</span>
|
|
170
176
|
</>
|
|
171
177
|
)}
|