@windrun-huaiin/third-ui 14.4.3 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/fuma/mdx/gradient-button.d.ts +5 -1
  2. package/dist/fuma/mdx/gradient-button.js +10 -4
  3. package/dist/fuma/mdx/gradient-button.mjs +14 -8
  4. package/dist/main/index.d.ts +2 -0
  5. package/dist/main/index.js +10 -0
  6. package/dist/main/index.mjs +5 -0
  7. package/dist/main/pill-select/index.d.ts +4 -0
  8. package/dist/main/pill-select/index.js +13 -0
  9. package/dist/main/pill-select/index.mjs +4 -0
  10. package/dist/main/pill-select/x-filter-pills.d.ts +11 -0
  11. package/dist/main/pill-select/x-filter-pills.js +12 -0
  12. package/dist/main/pill-select/x-filter-pills.mjs +10 -0
  13. package/dist/main/pill-select/x-form-pills.d.ts +12 -0
  14. package/dist/main/pill-select/x-form-pills.js +12 -0
  15. package/dist/main/pill-select/x-form-pills.mjs +10 -0
  16. package/dist/main/pill-select/x-pill-select.d.ts +33 -0
  17. package/dist/main/pill-select/x-pill-select.js +142 -0
  18. package/dist/main/pill-select/x-pill-select.mjs +140 -0
  19. package/dist/main/pill-select/x-token-input.d.ts +12 -0
  20. package/dist/main/pill-select/x-token-input.js +71 -0
  21. package/dist/main/pill-select/x-token-input.mjs +69 -0
  22. package/dist/main/rich-text-expert.mjs +2 -2
  23. package/dist/main/x-button.d.ts +3 -0
  24. package/dist/main/x-button.js +29 -8
  25. package/dist/main/x-button.mjs +32 -11
  26. package/dist/main/x-toggle-button.d.ts +32 -0
  27. package/dist/main/x-toggle-button.js +95 -0
  28. package/dist/main/x-toggle-button.mjs +74 -0
  29. package/dist/node_modules/.pnpm/react-medium-image-zoom@5.4.1_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/react-medium-image-zoom/dist/Controlled.mjs +21 -21
  30. package/dist/node_modules/.pnpm/react-medium-image-zoom@5.4.1_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/react-medium-image-zoom/dist/Uncontrolled.mjs +4 -4
  31. package/dist/node_modules/.pnpm/react-medium-image-zoom@5.4.1_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/react-medium-image-zoom/dist/icons.mjs +5 -5
  32. package/dist/node_modules/.pnpm/swiper@12.1.3/node_modules/swiper/swiper-react.mjs +18 -18
  33. package/package.json +8 -8
  34. package/src/fuma/mdx/gradient-button.tsx +40 -4
  35. package/src/main/index.ts +2 -0
  36. package/src/main/pill-select/index.ts +4 -0
  37. package/src/main/pill-select/x-filter-pills.tsx +36 -0
  38. package/src/main/pill-select/x-form-pills.tsx +39 -0
  39. package/src/main/pill-select/x-pill-select.tsx +360 -0
  40. package/src/main/pill-select/x-token-input.tsx +174 -0
  41. package/src/main/x-button.tsx +64 -8
  42. package/src/main/x-toggle-button.tsx +218 -0
  43. package/src/clerk/patch/optional-auth.ts +0 -24
@@ -0,0 +1,12 @@
1
+ type XTokenInputProps = {
2
+ value: string[];
3
+ onChange: (value: string[]) => void;
4
+ placeholder?: string;
5
+ emptyLabel?: string;
6
+ disabled?: boolean;
7
+ className?: string;
8
+ maxPillWidthClassName?: string;
9
+ size?: 'default' | 'compact';
10
+ };
11
+ export declare function XTokenInput({ value, onChange, placeholder, emptyLabel, disabled, className, maxPillWidthClassName, size, }: XTokenInputProps): import("react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var React = require('react');
6
+ var server = require('@windrun-huaiin/base-ui/components/server');
7
+ var lib = require('@windrun-huaiin/base-ui/lib');
8
+ var utils = require('@windrun-huaiin/lib/utils');
9
+
10
+ function sanitizeToken(value) {
11
+ return value.replaceAll(',', '').trim();
12
+ }
13
+ function dedupeTokens(values) {
14
+ return Array.from(new Set(values.map((item) => sanitizeToken(item)).filter(Boolean)));
15
+ }
16
+ function XTokenInput({ value, onChange, placeholder, emptyLabel, disabled = false, className, maxPillWidthClassName = 'max-w-[180px] sm:max-w-[220px]', size = 'default', }) {
17
+ const [draftValue, setDraftValue] = React.useState('');
18
+ const [focused, setFocused] = React.useState(false);
19
+ const rootRef = React.useRef(null);
20
+ const inputRef = React.useRef(null);
21
+ const tokens = dedupeTokens(value);
22
+ const compact = size === 'compact';
23
+ function commitToken(rawValue) {
24
+ if (disabled) {
25
+ return;
26
+ }
27
+ const nextValue = sanitizeToken(rawValue);
28
+ if (!nextValue) {
29
+ setDraftValue('');
30
+ return;
31
+ }
32
+ onChange(dedupeTokens([...tokens, nextValue]));
33
+ setDraftValue('');
34
+ }
35
+ function removeToken(target) {
36
+ var _a;
37
+ if (disabled) {
38
+ return;
39
+ }
40
+ onChange(tokens.filter((item) => item !== target));
41
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
42
+ }
43
+ return (jsxRuntime.jsxs("div", { className: utils.cn('w-full min-w-0 space-y-2', className), children: [jsxRuntime.jsx("div", { ref: rootRef, onClick: () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, onFocusCapture: () => setFocused(true), onBlurCapture: (event) => {
44
+ var _a;
45
+ if ((_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.relatedTarget)) {
46
+ return;
47
+ }
48
+ commitToken(draftValue);
49
+ setFocused(false);
50
+ }, className: utils.cn('w-full min-w-0 rounded-3xl border border-black/10 transition dark:border-white/10', compact ? 'min-h-9 px-3 py-1.5' : 'min-h-11 px-4 py-2.5', focused && lib.themeBorderColor), children: jsxRuntime.jsxs("div", { className: utils.cn('flex w-full min-w-0 flex-wrap items-center', compact ? 'gap-1.5' : 'gap-2'), children: [tokens.length > 0 ? (jsxRuntime.jsx("ul", { className: "contents", role: "list", children: tokens.map((token) => (jsxRuntime.jsx("li", { className: "max-w-full list-none", children: jsxRuntime.jsxs("span", { className: utils.cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'gap-1 px-2.5 py-0.5 text-[11px]' : 'gap-1 px-3 py-1 text-xs', lib.themeBgColor, lib.themeIconColor, disabled && 'opacity-60'), title: token, children: [jsxRuntime.jsx("span", { className: utils.cn('truncate', maxPillWidthClassName), children: token }), jsxRuntime.jsx("button", { type: "button", onClick: (event) => {
51
+ event.stopPropagation();
52
+ removeToken(token);
53
+ }, disabled: disabled, "aria-label": `Remove ${token}`, className: utils.cn('inline-flex shrink-0 items-center justify-center rounded-full transition', compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'hover:bg-black/10 dark:hover:bg-white/10', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1', lib.themeRingColor, disabled && 'cursor-not-allowed'), children: jsxRuntime.jsx(server.globalLucideIcons.X, { className: utils.cn(compact ? 'h-2 w-2' : 'h-2.5 w-2.5') }) })] }) }, token))) })) : null, jsxRuntime.jsx("div", { className: utils.cn('min-w-0 overflow-hidden', tokens.length === 0
54
+ ? 'flex-1 min-w-[160px]'
55
+ : draftValue || focused
56
+ ? 'flex-1 min-w-[120px]'
57
+ : 'w-0 flex-none'), children: jsxRuntime.jsx("input", { ref: inputRef, value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
58
+ if (event.key === 'Backspace' && !draftValue && tokens.length > 0) {
59
+ event.preventDefault();
60
+ removeToken(tokens[tokens.length - 1]);
61
+ return;
62
+ }
63
+ if (event.key !== 'Enter') {
64
+ return;
65
+ }
66
+ event.preventDefault();
67
+ commitToken(draftValue);
68
+ }, disabled: disabled, placeholder: tokens.length === 0 ? placeholder : undefined, className: utils.cn('bg-transparent outline-none dark:text-white', compact ? 'py-0 text-xs text-slate-700' : 'py-0.5 text-sm text-slate-700', tokens.length === 0 || draftValue || focused ? 'w-full' : 'w-0') }) })] }) }), tokens.length === 0 && emptyLabel ? (jsxRuntime.jsx("div", { className: utils.cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) : null] }));
69
+ }
70
+
71
+ exports.XTokenInput = XTokenInput;
@@ -0,0 +1,69 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState, useRef } from 'react';
4
+ import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
5
+ import { themeRingColor, themeBgColor, themeIconColor, themeBorderColor } from '@windrun-huaiin/base-ui/lib';
6
+ import { cn } from '@windrun-huaiin/lib/utils';
7
+
8
+ function sanitizeToken(value) {
9
+ return value.replaceAll(',', '').trim();
10
+ }
11
+ function dedupeTokens(values) {
12
+ return Array.from(new Set(values.map((item) => sanitizeToken(item)).filter(Boolean)));
13
+ }
14
+ function XTokenInput({ value, onChange, placeholder, emptyLabel, disabled = false, className, maxPillWidthClassName = 'max-w-[180px] sm:max-w-[220px]', size = 'default', }) {
15
+ const [draftValue, setDraftValue] = useState('');
16
+ const [focused, setFocused] = useState(false);
17
+ const rootRef = useRef(null);
18
+ const inputRef = useRef(null);
19
+ const tokens = dedupeTokens(value);
20
+ const compact = size === 'compact';
21
+ function commitToken(rawValue) {
22
+ if (disabled) {
23
+ return;
24
+ }
25
+ const nextValue = sanitizeToken(rawValue);
26
+ if (!nextValue) {
27
+ setDraftValue('');
28
+ return;
29
+ }
30
+ onChange(dedupeTokens([...tokens, nextValue]));
31
+ setDraftValue('');
32
+ }
33
+ function removeToken(target) {
34
+ var _a;
35
+ if (disabled) {
36
+ return;
37
+ }
38
+ onChange(tokens.filter((item) => item !== target));
39
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
40
+ }
41
+ return (jsxs("div", { className: cn('w-full min-w-0 space-y-2', className), children: [jsx("div", { ref: rootRef, onClick: () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, onFocusCapture: () => setFocused(true), onBlurCapture: (event) => {
42
+ var _a;
43
+ if ((_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.relatedTarget)) {
44
+ return;
45
+ }
46
+ commitToken(draftValue);
47
+ setFocused(false);
48
+ }, className: cn('w-full min-w-0 rounded-3xl border border-black/10 transition dark:border-white/10', compact ? 'min-h-9 px-3 py-1.5' : 'min-h-11 px-4 py-2.5', focused && themeBorderColor), children: jsxs("div", { className: cn('flex w-full min-w-0 flex-wrap items-center', compact ? 'gap-1.5' : 'gap-2'), children: [tokens.length > 0 ? (jsx("ul", { className: "contents", role: "list", children: tokens.map((token) => (jsx("li", { className: "max-w-full list-none", children: jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'gap-1 px-2.5 py-0.5 text-[11px]' : 'gap-1 px-3 py-1 text-xs', themeBgColor, themeIconColor, disabled && 'opacity-60'), title: token, children: [jsx("span", { className: cn('truncate', maxPillWidthClassName), children: token }), jsx("button", { type: "button", onClick: (event) => {
49
+ event.stopPropagation();
50
+ removeToken(token);
51
+ }, disabled: disabled, "aria-label": `Remove ${token}`, className: cn('inline-flex shrink-0 items-center justify-center rounded-full transition', compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'hover:bg-black/10 dark:hover:bg-white/10', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1', themeRingColor, disabled && 'cursor-not-allowed'), children: jsx(globalLucideIcons.X, { className: cn(compact ? 'h-2 w-2' : 'h-2.5 w-2.5') }) })] }) }, token))) })) : null, jsx("div", { className: cn('min-w-0 overflow-hidden', tokens.length === 0
52
+ ? 'flex-1 min-w-[160px]'
53
+ : draftValue || focused
54
+ ? 'flex-1 min-w-[120px]'
55
+ : 'w-0 flex-none'), children: jsx("input", { ref: inputRef, value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
56
+ if (event.key === 'Backspace' && !draftValue && tokens.length > 0) {
57
+ event.preventDefault();
58
+ removeToken(tokens[tokens.length - 1]);
59
+ return;
60
+ }
61
+ if (event.key !== 'Enter') {
62
+ return;
63
+ }
64
+ event.preventDefault();
65
+ commitToken(draftValue);
66
+ }, disabled: disabled, placeholder: tokens.length === 0 ? placeholder : undefined, className: cn('bg-transparent outline-none dark:text-white', compact ? 'py-0 text-xs text-slate-700' : 'py-0.5 text-sm text-slate-700', tokens.length === 0 || draftValue || focused ? 'w-full' : 'w-0') }) })] }) }), tokens.length === 0 && emptyLabel ? (jsx("div", { className: cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) : null] }));
67
+ }
68
+
69
+ export { XTokenInput };
@@ -1,12 +1,12 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React from 'react';
2
+ import React__default from 'react';
3
3
  import { cn } from '@windrun-huaiin/lib/utils';
4
4
  import { themeRichTextMarkClass } from '@windrun-huaiin/base-ui/lib';
5
5
 
6
6
  // default tag renderers
7
7
  const defaultTagRenderers = {
8
8
  // text next line
9
- br: (chunks) => (jsxs(React.Fragment, { children: [jsx("br", {}), chunks] })),
9
+ br: (chunks) => (jsxs(React__default.Fragment, { children: [jsx("br", {}), chunks] })),
10
10
  // text Stong
11
11
  strong: (chunks) => jsx("strong", { children: chunks }),
12
12
  // text Emphasis
@@ -1,4 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
+ type XButtonVariant = 'default' | 'soft' | 'subtle';
2
3
  interface BaseButtonConfig {
3
4
  icon: ReactNode;
4
5
  text: string;
@@ -19,6 +20,7 @@ interface SingleButtonProps {
19
20
  minWidth?: string;
20
21
  className?: string;
21
22
  iconClassName?: string;
23
+ variant?: XButtonVariant;
22
24
  }
23
25
  interface SplitButtonProps {
24
26
  type: 'split';
@@ -30,6 +32,7 @@ interface SplitButtonProps {
30
32
  mainButtonClassName?: string;
31
33
  dropdownButtonClassName?: string;
32
34
  iconClassName?: string;
35
+ variant?: XButtonVariant;
33
36
  }
34
37
  type xButtonProps = SingleButtonProps | SplitButtonProps;
35
38
  export declare function XButton(props: xButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -5,16 +5,18 @@ var tslib_es6 = require('../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var React = require('react');
7
7
  var server = require('@windrun-huaiin/base-ui/components/server');
8
+ var lib = require('@windrun-huaiin/base-ui/lib');
8
9
  var utils = require('@windrun-huaiin/lib/utils');
9
10
 
10
11
  function XButton(props) {
11
- var _a, _b;
12
+ var _a, _b, _c;
12
13
  const [isLoading, setIsLoading] = React.useState(false);
13
14
  const [menuOpen, setMenuOpen] = React.useState(false);
14
15
  const menuRef = React.useRef(null);
15
16
  const { iconClassName } = props;
16
17
  const defaultIconClass = "w-5 h-5";
17
- const finalIconClass = iconClassName || defaultIconClass;
18
+ const variant = (_a = props.variant) !== null && _a !== void 0 ? _a : 'default';
19
+ const finalIconClass = utils.cn(variant === 'default' ? '' : lib.themeIconColor, iconClassName || defaultIconClass);
18
20
  const loadingIconClass = utils.cn(finalIconClass, "mr-1 animate-spin");
19
21
  const chevronIconClass = "w-6 h-6";
20
22
  const renderIcon = (icon) => {
@@ -57,22 +59,41 @@ function XButton(props) {
57
59
  }
58
60
  });
59
61
  // base style class
60
- const baseButtonClass = "flex items-center justify-center gap-2 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";
62
+ const baseButtonClass = "flex items-center justify-center gap-2 px-4 py-2 text-sm font-semibold transition-colors";
63
+ const singleButtonVariantClass = variant === 'soft'
64
+ ? utils.cn(lib.themeBgColor, lib.themeIconColor, lib.themeBorderColor, "border hover:brightness-95")
65
+ : variant === 'subtle'
66
+ ? utils.cn(lib.themeMainBgColor, lib.themeIconColor, "border border-neutral-200 hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-800")
67
+ : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700";
68
+ const splitMainButtonVariantClass = variant === 'soft'
69
+ ? utils.cn("bg-transparent hover:bg-black/5 dark:hover:bg-white/5", lib.themeIconColor)
70
+ : variant === 'subtle'
71
+ ? utils.cn("bg-transparent hover:bg-neutral-50 dark:hover:bg-neutral-800", lib.themeIconColor)
72
+ : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700";
73
+ const splitDropdownVariantClass = variant === 'soft'
74
+ ? utils.cn("bg-transparent hover:bg-black/5 dark:hover:bg-white/5 sm:border-l", lib.themeIconColor, lib.themeBorderColor)
75
+ : variant === 'subtle'
76
+ ? utils.cn("bg-transparent hover:bg-neutral-50 dark:hover:bg-neutral-800 sm:border-l", lib.themeIconColor, "border-neutral-200 dark:border-neutral-800")
77
+ : "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";
61
78
  const disabledClass = "opacity-60 cursor-not-allowed";
62
79
  if (props.type === 'single') {
63
80
  const { button, loadingText, minWidth = 'min-w-[110px]', className = '' } = props;
64
81
  const isDisabled = button.disabled || isLoading;
65
82
  // loadingText: props.loadingText > button.text > 'Loading...'
66
- const actualLoadingText = loadingText || ((_a = button.text) === null || _a === void 0 ? void 0 : _a.trim()) || 'Loading...';
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 })] })) }));
83
+ const actualLoadingText = loadingText || ((_b = button.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
84
+ return (jsxRuntime.jsx("button", { onClick: () => handleButtonClick(button.onClick), disabled: isDisabled, className: utils.cn("w-full sm:w-auto", minWidth, baseButtonClass, singleButtonVariantClass, "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 })] })) }));
68
85
  }
69
86
  // Split button
70
87
  const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props;
71
88
  const isMainDisabled = mainButton.disabled || isLoading;
72
89
  // loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
73
- const actualLoadingText = loadingText || ((_b = mainButton.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
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)
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: () => {
90
+ const actualLoadingText = loadingText || ((_c = mainButton.text) === null || _c === void 0 ? void 0 : _c.trim()) || 'Loading...';
91
+ return (jsxRuntime.jsxs("div", { className: utils.cn("relative flex flex-row items-stretch w-full sm:w-auto rounded-full gap-0", menuOpen && "z-[90]", variant === 'soft'
92
+ ? utils.cn(lib.themeBgColor, lib.themeBorderColor, "border")
93
+ : variant === 'subtle'
94
+ ? utils.cn(lib.themeMainBgColor, "border border-neutral-200 dark:border-neutral-800")
95
+ : "bg-neutral-200 dark:bg-neutral-800", className), children: [jsxRuntime.jsx("button", { onClick: () => handleButtonClick(mainButton.onClick), disabled: isMainDisabled, className: utils.cn("min-w-0 flex-1", baseButtonClass, splitMainButtonVariantClass, "rounded-l-full rounded-r-none", isMainDisabled && disabledClass, mainButtonClassName), onMouseDown: e => { if (e.button === 2)
96
+ 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", { className: "min-w-0 truncate", children: mainButton.text })] })) }), jsxRuntime.jsx("button", { type: "button", className: utils.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: 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-[100] border border-neutral-200 dark:border-neutral-700 overflow-hidden animate-fade-in`, children: menuItems.map((item, index) => (jsxRuntime.jsxs("button", { onClick: () => {
76
97
  handleButtonClick(item.onClick);
77
98
  setMenuOpen(false);
78
99
  }, 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,23 +1,25 @@
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 React, { useState, useRef, useEffect } from 'react';
4
+ import React__default, { useState, useRef, useEffect } from 'react';
5
5
  import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
6
+ import { themeIconColor, themeBgColor, themeBorderColor, themeMainBgColor } from '@windrun-huaiin/base-ui/lib';
6
7
  import { cn } from '@windrun-huaiin/lib/utils';
7
8
 
8
9
  function XButton(props) {
9
- var _a, _b;
10
+ var _a, _b, _c;
10
11
  const [isLoading, setIsLoading] = useState(false);
11
12
  const [menuOpen, setMenuOpen] = useState(false);
12
13
  const menuRef = useRef(null);
13
14
  const { iconClassName } = props;
14
15
  const defaultIconClass = "w-5 h-5";
15
- const finalIconClass = iconClassName || defaultIconClass;
16
+ const variant = (_a = props.variant) !== null && _a !== void 0 ? _a : 'default';
17
+ const finalIconClass = cn(variant === 'default' ? '' : themeIconColor, iconClassName || defaultIconClass);
16
18
  const loadingIconClass = cn(finalIconClass, "mr-1 animate-spin");
17
19
  const chevronIconClass = "w-6 h-6";
18
20
  const renderIcon = (icon) => {
19
- if (React.isValidElement(icon)) {
20
- return React.cloneElement(icon, {
21
+ if (React__default.isValidElement(icon)) {
22
+ return React__default.cloneElement(icon, {
21
23
  className: cn(finalIconClass, icon.props.className),
22
24
  });
23
25
  }
@@ -55,22 +57,41 @@ function XButton(props) {
55
57
  }
56
58
  });
57
59
  // base style class
58
- const baseButtonClass = "flex items-center justify-center gap-2 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";
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";
59
76
  const disabledClass = "opacity-60 cursor-not-allowed";
60
77
  if (props.type === 'single') {
61
78
  const { button, loadingText, minWidth = 'min-w-[110px]', className = '' } = props;
62
79
  const isDisabled = button.disabled || isLoading;
63
80
  // loadingText: props.loadingText > button.text > 'Loading...'
64
- const actualLoadingText = loadingText || ((_a = button.text) === null || _a === void 0 ? void 0 : _a.trim()) || 'Loading...';
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 })] })) }));
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(globalLucideIcons.Loader2, { className: loadingIconClass }), jsx("span", { children: actualLoadingText })] })) : (jsxs(Fragment, { children: [renderIcon(button.icon), jsx("span", { children: button.text })] })) }));
66
83
  }
67
84
  // Split button
68
85
  const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props;
69
86
  const isMainDisabled = mainButton.disabled || isLoading;
70
87
  // loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
71
- const actualLoadingText = loadingText || ((_b = mainButton.text) === null || _b === void 0 ? void 0 : _b.trim()) || 'Loading...';
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)
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: () => {
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(globalLucideIcons.Loader2, { 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(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-[100] border border-neutral-200 dark:border-neutral-700 overflow-hidden animate-fade-in`, children: menuItems.map((item, index) => (jsxs("button", { onClick: () => {
74
95
  handleButtonClick(item.onClick);
75
96
  setMenuOpen(false);
76
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))) }))] }));
@@ -0,0 +1,32 @@
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 {};
@@ -0,0 +1,95 @@
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;
@@ -0,0 +1,74 @@
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 };