@windrun-huaiin/third-ui 29.0.0 → 29.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,100 +0,0 @@
1
- "use client";
2
- import { __awaiter } from 'tslib';
3
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import React__default, { useState, useRef, useEffect } from 'react';
5
- import { Loader2Icon, ChevronDownIcon } from '@windrun-huaiin/base-ui/icons';
6
- import { themeIconColor, themeBgColor, themeBorderColor, themeMainBgColor } from '@windrun-huaiin/base-ui/lib';
7
- import { cn } from '@windrun-huaiin/lib/utils';
8
-
9
- function XButton(props) {
10
- var _a, _b, _c;
11
- const [isLoading, setIsLoading] = useState(false);
12
- const [menuOpen, setMenuOpen] = useState(false);
13
- const menuRef = useRef(null);
14
- const { iconClassName } = props;
15
- const defaultIconClass = "w-5 h-5";
16
- const variant = (_a = props.variant) !== null && _a !== void 0 ? _a : 'default';
17
- const finalIconClass = cn(variant === 'default' ? '' : themeIconColor, iconClassName || defaultIconClass);
18
- const loadingIconClass = cn(finalIconClass, "mr-1 animate-spin");
19
- const chevronIconClass = "w-6 h-6";
20
- const renderIcon = (icon) => {
21
- if (React__default.isValidElement(icon)) {
22
- return React__default.cloneElement(icon, {
23
- className: cn(finalIconClass, icon.props.className),
24
- });
25
- }
26
- return icon;
27
- };
28
- // click outside to close menu
29
- useEffect(() => {
30
- if (props.type === 'split') {
31
- const handleClickOutside = (event) => {
32
- if (menuRef.current && !menuRef.current.contains(event.target)) {
33
- setMenuOpen(false);
34
- }
35
- };
36
- if (menuOpen) {
37
- document.addEventListener('mousedown', handleClickOutside);
38
- }
39
- return () => {
40
- document.removeEventListener('mousedown', handleClickOutside);
41
- };
42
- }
43
- }, [menuOpen, props.type]);
44
- // handle button click
45
- const handleButtonClick = (onClick) => __awaiter(this, void 0, void 0, function* () {
46
- if (isLoading)
47
- return;
48
- setIsLoading(true);
49
- try {
50
- yield onClick();
51
- }
52
- catch (error) {
53
- console.error('Button click error:', error);
54
- }
55
- finally {
56
- setIsLoading(false);
57
- }
58
- });
59
- // base style class
60
- const baseButtonClass = "flex items-center justify-center gap-2 px-4 py-2 text-sm font-semibold transition-colors";
61
- const singleButtonVariantClass = variant === 'soft'
62
- ? cn(themeBgColor, themeIconColor, themeBorderColor, "border hover:brightness-95")
63
- : variant === 'subtle'
64
- ? cn(themeMainBgColor, themeIconColor, "border border-neutral-200 hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-800")
65
- : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700";
66
- const splitMainButtonVariantClass = variant === 'soft'
67
- ? cn("bg-transparent hover:bg-black/5 dark:hover:bg-white/5", themeIconColor)
68
- : variant === 'subtle'
69
- ? cn("bg-transparent hover:bg-neutral-50 dark:hover:bg-neutral-800", themeIconColor)
70
- : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700";
71
- const splitDropdownVariantClass = variant === 'soft'
72
- ? cn("bg-transparent hover:bg-black/5 dark:hover:bg-white/5 sm:border-l", themeIconColor, themeBorderColor)
73
- : variant === 'subtle'
74
- ? cn("bg-transparent hover:bg-neutral-50 dark:hover:bg-neutral-800 sm:border-l", themeIconColor, "border-neutral-200 dark:border-neutral-800")
75
- : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700 sm:border-l sm:border-neutral-300 sm:dark:border-neutral-700";
76
- const disabledClass = "opacity-60 cursor-not-allowed";
77
- if (props.type === 'single') {
78
- const { button, loadingText, minWidth = 'min-w-[110px]', className = '' } = props;
79
- const isDisabled = button.disabled || isLoading;
80
- // loadingText: props.loadingText > button.text > 'Loading...'
81
- const actualLoadingText = loadingText || ((_b = button.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
82
- return (jsx("button", { onClick: () => handleButtonClick(button.onClick), disabled: isDisabled, className: cn("w-full sm:w-auto", minWidth, baseButtonClass, singleButtonVariantClass, "rounded-full", isDisabled && disabledClass, className), title: button.text, children: isLoading ? (jsxs(Fragment, { children: [jsx(Loader2Icon, { className: loadingIconClass }), jsx("span", { children: actualLoadingText })] })) : (jsxs(Fragment, { children: [renderIcon(button.icon), jsx("span", { children: button.text })] })) }));
83
- }
84
- // Split button
85
- const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props;
86
- const isMainDisabled = mainButton.disabled || isLoading;
87
- // loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
88
- const actualLoadingText = loadingText || ((_c = mainButton.text) === null || _c === void 0 ? void 0 : _c.trim()) || 'Loading...';
89
- return (jsxs("div", { className: cn("relative flex flex-row items-stretch w-full sm:w-auto rounded-full gap-0", menuOpen && "z-90", variant === 'soft'
90
- ? cn(themeBgColor, themeBorderColor, "border")
91
- : variant === 'subtle'
92
- ? cn(themeMainBgColor, "border border-neutral-200 dark:border-neutral-800")
93
- : "bg-neutral-200 dark:bg-neutral-800", className), children: [jsx("button", { onClick: () => handleButtonClick(mainButton.onClick), disabled: isMainDisabled, className: cn("min-w-0 flex-1", baseButtonClass, splitMainButtonVariantClass, "rounded-l-full rounded-r-none", isMainDisabled && disabledClass, mainButtonClassName), onMouseDown: e => { if (e.button === 2)
94
- e.preventDefault(); }, children: isLoading ? (jsxs(Fragment, { children: [jsx(Loader2Icon, { className: loadingIconClass }), jsx("span", { children: actualLoadingText })] })) : (jsxs(Fragment, { children: [renderIcon(mainButton.icon), jsx("span", { className: "min-w-0 truncate", children: mainButton.text })] })) }), jsx("button", { type: "button", className: cn("flex h-full w-9 shrink-0 items-center justify-center px-0 py-1.5 cursor-pointer transition rounded-r-full rounded-l-none border-l sm:w-10", splitDropdownVariantClass, dropdownButtonClassName), onClick: e => { e.stopPropagation(); setMenuOpen(v => !v); }, "aria-label": "More actions", "aria-expanded": menuOpen, children: jsx(ChevronDownIcon, { 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-100 border border-neutral-200 dark:border-neutral-700 overflow-hidden animate-fade-in`, children: menuItems.map((item, index) => (jsxs("button", { onClick: () => {
95
- handleButtonClick(item.onClick);
96
- setMenuOpen(false);
97
- }, 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))) }))] }));
98
- }
99
-
100
- export { XButton };
@@ -1,32 +0,0 @@
1
- import * as React from 'react';
2
- export type XToggleButtonOption = {
3
- value: string;
4
- label: React.ReactNode;
5
- disabled?: boolean;
6
- className?: string;
7
- badge?: React.ReactNode;
8
- mobileIcon?: React.ReactNode;
9
- };
10
- type XToggleButtonSize = 'default' | 'compact';
11
- export type XToggleButtonProps = {
12
- options: XToggleButtonOption[];
13
- value?: string;
14
- defaultValue?: string;
15
- onChange?: (value: string) => void;
16
- disabled?: boolean;
17
- className?: string;
18
- itemClassName?: string;
19
- activeItemClassName?: string;
20
- inactiveItemClassName?: string;
21
- badgeClassName?: string;
22
- minItemWidthClassName?: string;
23
- maxItemWidthClassName?: string;
24
- itemTextClassName?: string;
25
- itemPaddingClassName?: string;
26
- size?: XToggleButtonSize;
27
- fullWidth?: boolean;
28
- name?: string;
29
- ariaLabel?: string;
30
- };
31
- export declare function XToggleButton({ options, value, defaultValue, onChange, disabled, className, itemClassName, activeItemClassName, inactiveItemClassName, badgeClassName, minItemWidthClassName, maxItemWidthClassName, itemTextClassName, itemPaddingClassName, size, fullWidth, name, ariaLabel, }: XToggleButtonProps): import("react/jsx-runtime").JSX.Element;
32
- export {};
@@ -1,95 +0,0 @@
1
- "use client";
2
- 'use strict';
3
-
4
- var jsxRuntime = require('react/jsx-runtime');
5
- var React = require('react');
6
- var utils = require('@windrun-huaiin/lib/utils');
7
- var lib = require('@windrun-huaiin/base-ui/lib');
8
-
9
- function _interopNamespaceDefault(e) {
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
20
- });
21
- }
22
- n.default = e;
23
- return Object.freeze(n);
24
- }
25
-
26
- var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
27
-
28
- function XToggleButton({ options, value, defaultValue, onChange, disabled = false, className, itemClassName, activeItemClassName, inactiveItemClassName, badgeClassName, minItemWidthClassName, maxItemWidthClassName, itemTextClassName, itemPaddingClassName, size = 'default', fullWidth = false, name, ariaLabel, }) {
29
- const containerRef = React__namespace.useRef(null);
30
- const activeButtonRef = React__namespace.useRef(null);
31
- const [badgeOffset, setBadgeOffset] = React__namespace.useState(0);
32
- const normalizedOptions = React__namespace.useMemo(() => options.filter((option) => option.value.trim()), [options]);
33
- const fallbackValue = React__namespace.useMemo(() => {
34
- var _a, _b;
35
- if (defaultValue && normalizedOptions.some((option) => option.value === defaultValue)) {
36
- return defaultValue;
37
- }
38
- return (_b = (_a = normalizedOptions[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
39
- }, [defaultValue, normalizedOptions]);
40
- const isControlled = value !== undefined;
41
- const [internalValue, setInternalValue] = React__namespace.useState(fallbackValue);
42
- React__namespace.useEffect(() => {
43
- if (!isControlled) {
44
- setInternalValue(fallbackValue);
45
- }
46
- }, [fallbackValue, isControlled]);
47
- const selectedValue = isControlled ? value !== null && value !== void 0 ? value : '' : internalValue;
48
- React__namespace.useEffect(() => {
49
- if (activeButtonRef.current && containerRef.current) {
50
- const buttonRect = activeButtonRef.current.getBoundingClientRect();
51
- const containerRect = containerRef.current.getBoundingClientRect();
52
- const buttonCenterX = buttonRect.left - containerRect.left + buttonRect.width / 2;
53
- setBadgeOffset(buttonCenterX);
54
- }
55
- }, [selectedValue]);
56
- function handleSelect(nextValue, optionDisabled) {
57
- if (disabled || optionDisabled || nextValue === selectedValue) {
58
- return;
59
- }
60
- if (!isControlled) {
61
- setInternalValue(nextValue);
62
- }
63
- onChange === null || onChange === void 0 ? void 0 : onChange(nextValue);
64
- }
65
- const containerSizeClass = size === 'compact'
66
- ? 'px-1.5 py-1.5 gap-0'
67
- : 'px-2 py-2 gap-0 sm:px-3 sm:py-3 sm:gap-0';
68
- const defaultItemTextClass = size === 'compact'
69
- ? 'text-xs'
70
- : 'text-xs sm:text-sm md:text-base';
71
- const finalItemTextClassName = itemTextClassName !== null && itemTextClassName !== void 0 ? itemTextClassName : defaultItemTextClass;
72
- const defaultItemPaddingClass = size === 'compact'
73
- ? 'px-2 py-1'
74
- : 'px-2 py-1.5 sm:px-3 sm:py-2';
75
- const finalItemPaddingClassName = itemPaddingClassName !== null && itemPaddingClassName !== void 0 ? itemPaddingClassName : defaultItemPaddingClass;
76
- const minItemWidthClass = minItemWidthClassName !== null && minItemWidthClassName !== void 0 ? minItemWidthClassName : 'min-w-[80px] sm:min-w-[100px] md:min-w-[120px]';
77
- const maxItemWidthClass = maxItemWidthClassName !== null && maxItemWidthClassName !== void 0 ? maxItemWidthClassName : 'max-w-[120px] sm:max-w-[160px]';
78
- const selectedOption = normalizedOptions.find((opt) => opt.value === selectedValue);
79
- return (jsxRuntime.jsxs("div", { ref: containerRef, role: "radiogroup", "aria-label": ariaLabel, "aria-disabled": disabled, className: utils.cn('relative inline-flex items-center rounded-full border border-gray-300 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-900', fullWidth && 'flex w-full', containerSizeClass, className), children: [(selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.badge) ? (jsxRuntime.jsx("span", { style: {
80
- left: `${badgeOffset}px`,
81
- transform: 'translate(-50%, calc(-50% - 1px))'
82
- }, className: utils.cn('absolute top-0 z-20 whitespace-nowrap rounded-md bg-yellow-100 px-2.5 py-0.5 text-[0.625rem] font-semibold text-yellow-800 shadow-sm sm:text-xs', badgeClassName), children: selectedOption.badge })) : null, normalizedOptions.map((option) => {
83
- const active = option.value === selectedValue;
84
- const optionDisabled = disabled || option.disabled;
85
- return (jsxRuntime.jsx("div", { className: utils.cn('relative flex items-center justify-center', fullWidth && 'flex-1'), children: jsxRuntime.jsx("button", { ref: active ? activeButtonRef : null, type: "button", role: "radio", name: name, "aria-checked": active, "aria-pressed": active, disabled: optionDisabled, onClick: () => handleSelect(option.value, option.disabled), className: utils.cn('relative z-10 inline-flex items-center justify-center rounded-full font-medium text-center transition truncate', fullWidth && 'w-full', !fullWidth && minItemWidthClass, !fullWidth && maxItemWidthClass, finalItemPaddingClassName, finalItemTextClassName, active
86
- ? utils.cn('text-white shadow-sm', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass, activeItemClassName)
87
- : utils.cn('text-gray-800 hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100', inactiveItemClassName), optionDisabled && 'cursor-not-allowed opacity-60', itemClassName, option.className), children: option.mobileIcon ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "hidden sm:block", children: option.label }), jsxRuntime.jsx("span", { className: "block sm:hidden", children: active && React__namespace.isValidElement(option.mobileIcon)
88
- ? React__namespace.cloneElement(option.mobileIcon, {
89
- className: utils.cn(option.mobileIcon.props.className, 'text-white'),
90
- })
91
- : option.mobileIcon })] })) : (option.label) }) }, option.value));
92
- })] }));
93
- }
94
-
95
- exports.XToggleButton = XToggleButton;
@@ -1,74 +0,0 @@
1
- "use client";
2
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import * as React from 'react';
4
- import { cn } from '@windrun-huaiin/lib/utils';
5
- import { themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
6
-
7
- function XToggleButton({ options, value, defaultValue, onChange, disabled = false, className, itemClassName, activeItemClassName, inactiveItemClassName, badgeClassName, minItemWidthClassName, maxItemWidthClassName, itemTextClassName, itemPaddingClassName, size = 'default', fullWidth = false, name, ariaLabel, }) {
8
- const containerRef = React.useRef(null);
9
- const activeButtonRef = React.useRef(null);
10
- const [badgeOffset, setBadgeOffset] = React.useState(0);
11
- const normalizedOptions = React.useMemo(() => options.filter((option) => option.value.trim()), [options]);
12
- const fallbackValue = React.useMemo(() => {
13
- var _a, _b;
14
- if (defaultValue && normalizedOptions.some((option) => option.value === defaultValue)) {
15
- return defaultValue;
16
- }
17
- return (_b = (_a = normalizedOptions[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
18
- }, [defaultValue, normalizedOptions]);
19
- const isControlled = value !== undefined;
20
- const [internalValue, setInternalValue] = React.useState(fallbackValue);
21
- React.useEffect(() => {
22
- if (!isControlled) {
23
- setInternalValue(fallbackValue);
24
- }
25
- }, [fallbackValue, isControlled]);
26
- const selectedValue = isControlled ? value !== null && value !== void 0 ? value : '' : internalValue;
27
- React.useEffect(() => {
28
- if (activeButtonRef.current && containerRef.current) {
29
- const buttonRect = activeButtonRef.current.getBoundingClientRect();
30
- const containerRect = containerRef.current.getBoundingClientRect();
31
- const buttonCenterX = buttonRect.left - containerRect.left + buttonRect.width / 2;
32
- setBadgeOffset(buttonCenterX);
33
- }
34
- }, [selectedValue]);
35
- function handleSelect(nextValue, optionDisabled) {
36
- if (disabled || optionDisabled || nextValue === selectedValue) {
37
- return;
38
- }
39
- if (!isControlled) {
40
- setInternalValue(nextValue);
41
- }
42
- onChange === null || onChange === void 0 ? void 0 : onChange(nextValue);
43
- }
44
- const containerSizeClass = size === 'compact'
45
- ? 'px-1.5 py-1.5 gap-0'
46
- : 'px-2 py-2 gap-0 sm:px-3 sm:py-3 sm:gap-0';
47
- const defaultItemTextClass = size === 'compact'
48
- ? 'text-xs'
49
- : 'text-xs sm:text-sm md:text-base';
50
- const finalItemTextClassName = itemTextClassName !== null && itemTextClassName !== void 0 ? itemTextClassName : defaultItemTextClass;
51
- const defaultItemPaddingClass = size === 'compact'
52
- ? 'px-2 py-1'
53
- : 'px-2 py-1.5 sm:px-3 sm:py-2';
54
- const finalItemPaddingClassName = itemPaddingClassName !== null && itemPaddingClassName !== void 0 ? itemPaddingClassName : defaultItemPaddingClass;
55
- const minItemWidthClass = minItemWidthClassName !== null && minItemWidthClassName !== void 0 ? minItemWidthClassName : 'min-w-[80px] sm:min-w-[100px] md:min-w-[120px]';
56
- const maxItemWidthClass = maxItemWidthClassName !== null && maxItemWidthClassName !== void 0 ? maxItemWidthClassName : 'max-w-[120px] sm:max-w-[160px]';
57
- const selectedOption = normalizedOptions.find((opt) => opt.value === selectedValue);
58
- return (jsxs("div", { ref: containerRef, role: "radiogroup", "aria-label": ariaLabel, "aria-disabled": disabled, className: cn('relative inline-flex items-center rounded-full border border-gray-300 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-900', fullWidth && 'flex w-full', containerSizeClass, className), children: [(selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.badge) ? (jsx("span", { style: {
59
- left: `${badgeOffset}px`,
60
- transform: 'translate(-50%, calc(-50% - 1px))'
61
- }, className: cn('absolute top-0 z-20 whitespace-nowrap rounded-md bg-yellow-100 px-2.5 py-0.5 text-[0.625rem] font-semibold text-yellow-800 shadow-sm sm:text-xs', badgeClassName), children: selectedOption.badge })) : null, normalizedOptions.map((option) => {
62
- const active = option.value === selectedValue;
63
- const optionDisabled = disabled || option.disabled;
64
- return (jsx("div", { className: cn('relative flex items-center justify-center', fullWidth && 'flex-1'), children: jsx("button", { ref: active ? activeButtonRef : null, type: "button", role: "radio", name: name, "aria-checked": active, "aria-pressed": active, disabled: optionDisabled, onClick: () => handleSelect(option.value, option.disabled), className: cn('relative z-10 inline-flex items-center justify-center rounded-full font-medium text-center transition truncate', fullWidth && 'w-full', !fullWidth && minItemWidthClass, !fullWidth && maxItemWidthClass, finalItemPaddingClassName, finalItemTextClassName, active
65
- ? cn('text-white shadow-sm', themeButtonGradientClass, themeButtonGradientHoverClass, activeItemClassName)
66
- : cn('text-gray-800 hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100', inactiveItemClassName), optionDisabled && 'cursor-not-allowed opacity-60', itemClassName, option.className), children: option.mobileIcon ? (jsxs(Fragment, { children: [jsx("span", { className: "hidden sm:block", children: option.label }), jsx("span", { className: "block sm:hidden", children: active && React.isValidElement(option.mobileIcon)
67
- ? React.cloneElement(option.mobileIcon, {
68
- className: cn(option.mobileIcon.props.className, 'text-white'),
69
- })
70
- : option.mobileIcon })] })) : (option.label) }) }, option.value));
71
- })] }));
72
- }
73
-
74
- export { XToggleButton };