@windrun-huaiin/third-ui 11.0.5 → 11.0.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.
@@ -5,10 +5,11 @@ export interface GradientButtonProps {
5
5
  align?: 'left' | 'center' | 'right';
6
6
  disabled?: boolean;
7
7
  className?: string;
8
+ iconClassName?: string;
8
9
  href?: string;
9
10
  openInNewTab?: boolean;
10
11
  onClick?: () => void | Promise<void>;
11
12
  loadingText?: React.ReactNode;
12
13
  preventDoubleClick?: boolean;
13
14
  }
14
- export declare function GradientButton({ title, icon, align, disabled, className, href, openInNewTab, onClick, loadingText, preventDoubleClick, }: GradientButtonProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function GradientButton({ title, icon, align, disabled, className, href, openInNewTab, onClick, loadingText, preventDoubleClick, iconClassName, }: GradientButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -9,9 +9,11 @@ var server = require('@windrun-huaiin/base-ui/components/server');
9
9
  var Link = require('next/link');
10
10
  var React = require('react');
11
11
 
12
- function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true, }) {
12
+ function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true, iconClassName, }) {
13
13
  const [isLoading, setIsLoading] = React.useState(false);
14
14
  const actualLoadingText = loadingText || (title === null || title === void 0 ? void 0 : title.toString().trim()) || 'Loading...';
15
+ const defaultIconClass = "h-4 w-4";
16
+ const finalIconClass = utils.cn("text-white", iconClassName || defaultIconClass);
15
17
  // set justify class according to alignment
16
18
  const getAlignmentClass = () => {
17
19
  switch (align) {
@@ -52,7 +54,7 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
52
54
  const iconProvided = icon !== undefined;
53
55
  const iconNode = (() => {
54
56
  if (isLoading) {
55
- return jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: "h-4 w-4 text-white animate-spin" });
57
+ return jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: utils.cn(finalIconClass, 'animate-spin') });
56
58
  }
57
59
  if (iconProvided) {
58
60
  if (icon === null || icon === false) {
@@ -60,12 +62,12 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
60
62
  }
61
63
  if (React.isValidElement(icon)) {
62
64
  return React.cloneElement(icon, {
63
- className: utils.cn('h-4 w-4 text-white', icon.props.className),
65
+ className: utils.cn(finalIconClass, icon.props.className),
64
66
  });
65
67
  }
66
68
  return icon;
67
69
  }
68
- return jsxRuntime.jsx(server.globalLucideIcons.ArrowRight, { className: "h-4 w-4 text-white" });
70
+ return jsxRuntime.jsx(server.globalLucideIcons.ArrowRight, { className: utils.cn(finalIconClass) });
69
71
  })();
70
72
  const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
71
73
  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] }));
@@ -7,9 +7,11 @@ import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
7
7
  import Link from 'next/link';
8
8
  import React, { useState } from 'react';
9
9
 
10
- function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true, }) {
10
+ function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, onClick, loadingText, preventDoubleClick = true, iconClassName, }) {
11
11
  const [isLoading, setIsLoading] = useState(false);
12
12
  const actualLoadingText = loadingText || (title === null || title === void 0 ? void 0 : title.toString().trim()) || 'Loading...';
13
+ const defaultIconClass = "h-4 w-4";
14
+ const finalIconClass = cn("text-white", iconClassName || defaultIconClass);
13
15
  // set justify class according to alignment
14
16
  const getAlignmentClass = () => {
15
17
  switch (align) {
@@ -50,7 +52,7 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
50
52
  const iconProvided = icon !== undefined;
51
53
  const iconNode = (() => {
52
54
  if (isLoading) {
53
- return jsx(globalLucideIcons.Loader2, { className: "h-4 w-4 text-white animate-spin" });
55
+ return jsx(globalLucideIcons.Loader2, { className: cn(finalIconClass, 'animate-spin') });
54
56
  }
55
57
  if (iconProvided) {
56
58
  if (icon === null || icon === false) {
@@ -58,12 +60,12 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
58
60
  }
59
61
  if (React.isValidElement(icon)) {
60
62
  return React.cloneElement(icon, {
61
- className: cn('h-4 w-4 text-white', icon.props.className),
63
+ className: cn(finalIconClass, icon.props.className),
62
64
  });
63
65
  }
64
66
  return icon;
65
67
  }
66
- return jsx(globalLucideIcons.ArrowRight, { className: "h-4 w-4 text-white" });
68
+ return jsx(globalLucideIcons.ArrowRight, { className: cn(finalIconClass) });
67
69
  })();
68
70
  const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
69
71
  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] }));
@@ -18,6 +18,7 @@ interface SingleButtonProps {
18
18
  loadingText?: string;
19
19
  minWidth?: string;
20
20
  className?: string;
21
+ iconClassName?: string;
21
22
  }
22
23
  interface SplitButtonProps {
23
24
  type: 'split';
@@ -28,6 +29,7 @@ interface SplitButtonProps {
28
29
  className?: string;
29
30
  mainButtonClassName?: string;
30
31
  dropdownButtonClassName?: string;
32
+ iconClassName?: string;
31
33
  }
32
34
  type xButtonProps = SingleButtonProps | SplitButtonProps;
33
35
  export declare function XButton(props: xButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -12,6 +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 { 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
+ };
15
28
  // click outside to close menu
16
29
  React.useEffect(() => {
17
30
  if (props.type === 'split') {
@@ -51,7 +64,7 @@ function XButton(props) {
51
64
  const isDisabled = button.disabled || isLoading;
52
65
  // loadingText: props.loadingText > button.text > 'Loading...'
53
66
  const actualLoadingText = loadingText || ((_a = button.text) === null || _a === void 0 ? void 0 : _a.trim()) || 'Loading...';
54
- 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: "w-5 h-5 mr-1 animate-spin" }), 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 })] })) }));
55
68
  }
56
69
  // Split button
57
70
  const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props;
@@ -59,7 +72,7 @@ function XButton(props) {
59
72
  // loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
60
73
  const actualLoadingText = loadingText || ((_b = mainButton.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
61
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)
62
- e.preventDefault(); }, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(server.globalLucideIcons.Loader2, { className: "w-5 h-5 mr-1 animate-spin" }), 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: "w-6 h-6" }) }), 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: () => {
63
76
  handleButtonClick(item.onClick);
64
77
  setMenuOpen(false);
65
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))) }))] }));
@@ -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,6 +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 { 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
+ };
13
26
  // click outside to close menu
14
27
  useEffect(() => {
15
28
  if (props.type === 'split') {
@@ -49,7 +62,7 @@ function XButton(props) {
49
62
  const isDisabled = button.disabled || isLoading;
50
63
  // loadingText: props.loadingText > button.text > 'Loading...'
51
64
  const actualLoadingText = loadingText || ((_a = button.text) === null || _a === void 0 ? void 0 : _a.trim()) || 'Loading...';
52
- 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: "w-5 h-5 mr-1 animate-spin" }), 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 })] })) }));
53
66
  }
54
67
  // Split button
55
68
  const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props;
@@ -57,7 +70,7 @@ function XButton(props) {
57
70
  // loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
58
71
  const actualLoadingText = loadingText || ((_b = mainButton.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
59
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)
60
- e.preventDefault(); }, children: isLoading ? (jsxs(Fragment, { children: [jsx(globalLucideIcons.Loader2, { className: "w-5 h-5 mr-1 animate-spin" }), 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: "w-6 h-6" }) }), 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: () => {
61
74
  handleButtonClick(item.onClick);
62
75
  setMenuOpen(false);
63
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,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "11.0.5",
3
+ "version": "11.0.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",
@@ -12,6 +12,7 @@ export interface GradientButtonProps {
12
12
  align?: 'left' | 'center' | 'right';
13
13
  disabled?: boolean;
14
14
  className?: string;
15
+ iconClassName?: string;
15
16
  // for Link
16
17
  href?: string;
17
18
  openInNewTab?: boolean;
@@ -33,10 +34,14 @@ export function GradientButton({
33
34
  onClick,
34
35
  loadingText,
35
36
  preventDoubleClick = true,
37
+ iconClassName,
36
38
  }: GradientButtonProps) {
37
39
  const [isLoading, setIsLoading] = useState(false);
38
40
  const actualLoadingText = loadingText || title?.toString().trim() || 'Loading...'
39
41
 
42
+ const defaultIconClass = "h-4 w-4";
43
+ const finalIconClass = cn("text-white", iconClassName || defaultIconClass);
44
+
40
45
  // set justify class according to alignment
41
46
  const getAlignmentClass = () => {
42
47
  switch (align) {
@@ -83,7 +88,7 @@ export function GradientButton({
83
88
 
84
89
  const iconNode = (() => {
85
90
  if (isLoading) {
86
- return <icons.Loader2 className="h-4 w-4 text-white animate-spin" />;
91
+ return <icons.Loader2 className={cn(finalIconClass, 'animate-spin')} />;
87
92
  }
88
93
 
89
94
  if (iconProvided) {
@@ -93,14 +98,14 @@ export function GradientButton({
93
98
 
94
99
  if (React.isValidElement<{ className?: string }>(icon)) {
95
100
  return React.cloneElement(icon, {
96
- className: cn('h-4 w-4 text-white', icon.props.className),
101
+ className: cn(finalIconClass, icon.props.className),
97
102
  });
98
103
  }
99
104
 
100
105
  return icon;
101
106
  }
102
107
 
103
- return <icons.ArrowRight className="h-4 w-4 text-white" />;
108
+ return <icons.ArrowRight className={cn(finalIconClass)} />;
104
109
  })();
105
110
 
106
111
  const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
@@ -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,6 +28,7 @@ interface SingleButtonProps {
28
28
  loadingText?: string
29
29
  minWidth?: string
30
30
  className?: string
31
+ iconClassName?: string
31
32
  }
32
33
 
33
34
  // split button config
@@ -40,6 +41,7 @@ interface SplitButtonProps {
40
41
  className?: string
41
42
  mainButtonClassName?: string
42
43
  dropdownButtonClassName?: string
44
+ iconClassName?: string
43
45
  }
44
46
 
45
47
  type xButtonProps = SingleButtonProps | SplitButtonProps
@@ -49,6 +51,23 @@ export function XButton(props: xButtonProps) {
49
51
  const [menuOpen, setMenuOpen] = useState(false)
50
52
  const menuRef = useRef<HTMLDivElement>(null)
51
53
 
54
+ const { iconClassName } = props
55
+ const defaultIconClass = "w-5 h-5"
56
+ const finalIconClass = iconClassName || defaultIconClass
57
+
58
+ const loadingIconClass = cn(finalIconClass, "mr-1 animate-spin")
59
+
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
+ };
70
+
52
71
  // click outside to close menu
53
72
  useEffect(() => {
54
73
  if (props.type === 'split') {
@@ -108,12 +127,12 @@ export function XButton(props: xButtonProps) {
108
127
  >
109
128
  {isLoading ? (
110
129
  <>
111
- <icons.Loader2 className="w-5 h-5 mr-1 animate-spin" />
130
+ <icons.Loader2 className={loadingIconClass} />
112
131
  <span>{actualLoadingText}</span>
113
132
  </>
114
133
  ) : (
115
134
  <>
116
- {button.icon}
135
+ {renderIcon(button.icon)}
117
136
  <span>{button.text}</span>
118
137
  </>
119
138
  )}
@@ -147,12 +166,12 @@ export function XButton(props: xButtonProps) {
147
166
  >
148
167
  {isLoading ? (
149
168
  <>
150
- <icons.Loader2 className="w-5 h-5 mr-1 animate-spin" />
169
+ <icons.Loader2 className={loadingIconClass} />
151
170
  <span>{actualLoadingText}</span>
152
171
  </>
153
172
  ) : (
154
173
  <>
155
- {mainButton.icon}
174
+ {renderIcon(mainButton.icon)}
156
175
  <span>{mainButton.text}</span>
157
176
  </>
158
177
  )}
@@ -169,7 +188,7 @@ export function XButton(props: xButtonProps) {
169
188
  aria-label="More actions"
170
189
  aria-expanded={menuOpen}
171
190
  >
172
- <icons.ChevronDown className="w-6 h-6" />
191
+ <icons.ChevronDown className={chevronIconClass} />
173
192
  </button>
174
193
 
175
194
  {/* dropdown menu */}