@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.
- package/dist/fuma/mdx/gradient-button.d.ts +5 -1
- package/dist/fuma/mdx/gradient-button.js +10 -4
- package/dist/fuma/mdx/gradient-button.mjs +14 -8
- package/dist/main/index.d.ts +2 -0
- package/dist/main/index.js +10 -0
- package/dist/main/index.mjs +5 -0
- package/dist/main/pill-select/index.d.ts +4 -0
- package/dist/main/pill-select/index.js +13 -0
- package/dist/main/pill-select/index.mjs +4 -0
- package/dist/main/pill-select/x-filter-pills.d.ts +11 -0
- package/dist/main/pill-select/x-filter-pills.js +12 -0
- package/dist/main/pill-select/x-filter-pills.mjs +10 -0
- package/dist/main/pill-select/x-form-pills.d.ts +12 -0
- package/dist/main/pill-select/x-form-pills.js +12 -0
- package/dist/main/pill-select/x-form-pills.mjs +10 -0
- package/dist/main/pill-select/x-pill-select.d.ts +33 -0
- package/dist/main/pill-select/x-pill-select.js +142 -0
- package/dist/main/pill-select/x-pill-select.mjs +140 -0
- package/dist/main/pill-select/x-token-input.d.ts +12 -0
- package/dist/main/pill-select/x-token-input.js +71 -0
- package/dist/main/pill-select/x-token-input.mjs +69 -0
- package/dist/main/rich-text-expert.mjs +2 -2
- package/dist/main/x-button.d.ts +3 -0
- package/dist/main/x-button.js +29 -8
- package/dist/main/x-button.mjs +32 -11
- package/dist/main/x-toggle-button.d.ts +32 -0
- package/dist/main/x-toggle-button.js +95 -0
- package/dist/main/x-toggle-button.mjs +74 -0
- 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
- 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
- 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
- package/dist/node_modules/.pnpm/swiper@12.1.3/node_modules/swiper/swiper-react.mjs +18 -18
- package/package.json +8 -8
- package/src/fuma/mdx/gradient-button.tsx +40 -4
- package/src/main/index.ts +2 -0
- package/src/main/pill-select/index.ts +4 -0
- package/src/main/pill-select/x-filter-pills.tsx +36 -0
- package/src/main/pill-select/x-form-pills.tsx +39 -0
- package/src/main/pill-select/x-pill-select.tsx +360 -0
- package/src/main/pill-select/x-token-input.tsx +174 -0
- package/src/main/x-button.tsx +64 -8
- package/src/main/x-toggle-button.tsx +218 -0
- package/src/clerk/patch/optional-auth.ts +0 -24
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
type GradientButtonVariant = 'default' | 'soft' | 'subtle';
|
|
2
3
|
export interface GradientButtonProps {
|
|
3
4
|
title: React.ReactNode;
|
|
4
5
|
icon?: React.ReactNode;
|
|
6
|
+
iconForcePosition?: 'left' | 'right';
|
|
5
7
|
align?: 'left' | 'center' | 'right';
|
|
6
8
|
disabled?: boolean;
|
|
7
9
|
className?: string;
|
|
@@ -12,5 +14,7 @@ export interface GradientButtonProps {
|
|
|
12
14
|
onClick?: () => void | Promise<void>;
|
|
13
15
|
loadingText?: React.ReactNode;
|
|
14
16
|
preventDoubleClick?: boolean;
|
|
17
|
+
variant?: GradientButtonVariant;
|
|
15
18
|
}
|
|
16
|
-
export declare function GradientButton({ title, icon, align, disabled, className, href, openInNewTab, preserveReferrer, onClick, loadingText, preventDoubleClick, iconClassName, }: GradientButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function GradientButton({ title, icon, iconForcePosition, align, disabled, className, href, openInNewTab, preserveReferrer, onClick, loadingText, preventDoubleClick, iconClassName, variant, }: GradientButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -9,11 +9,11 @@ var lib = require('@windrun-huaiin/base-ui/lib');
|
|
|
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, preserveReferrer = false, onClick, loadingText, preventDoubleClick = true, iconClassName, }) {
|
|
12
|
+
function GradientButton({ title, icon, iconForcePosition, align = 'left', disabled = false, className = "", href, openInNewTab = true, preserveReferrer = false, onClick, loadingText, preventDoubleClick = true, iconClassName, variant = 'default', }) {
|
|
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
15
|
const defaultIconClass = "h-4 w-4";
|
|
16
|
-
const finalIconClass = utils.cn(
|
|
16
|
+
const finalIconClass = utils.cn(variant === 'default' ? 'text-white' : lib.themeIconColor, iconClassName || defaultIconClass);
|
|
17
17
|
// set justify class according to alignment
|
|
18
18
|
const getAlignmentClass = () => {
|
|
19
19
|
switch (align) {
|
|
@@ -70,7 +70,8 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
70
70
|
return jsxRuntime.jsx(server.globalLucideIcons.ArrowRight, { className: utils.cn(finalIconClass) });
|
|
71
71
|
})();
|
|
72
72
|
const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
|
|
73
|
-
const
|
|
73
|
+
const iconPosition = iconForcePosition !== null && iconForcePosition !== void 0 ? iconForcePosition : (onClick ? 'left' : 'right');
|
|
74
|
+
const buttonContent = iconPosition === 'left' ? (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] }));
|
|
74
75
|
const alignmentClass = align === 'right'
|
|
75
76
|
? 'justify-end'
|
|
76
77
|
: align === 'center'
|
|
@@ -79,7 +80,12 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
79
80
|
// Base styles extracted from Button component + size="lg" (h-11 px-8)
|
|
80
81
|
// Removed [&_svg] constraints
|
|
81
82
|
const baseButtonStyles = "inline-flex items-center gap-2 whitespace-nowrap h-11 px-8 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
|
82
|
-
const
|
|
83
|
+
const variantClassName = variant === 'soft'
|
|
84
|
+
? utils.cn(lib.themeBgColor, lib.themeIconColor, lib.themeBorderColor, 'border shadow-sm hover:shadow-md hover:brightness-95')
|
|
85
|
+
: variant === 'subtle'
|
|
86
|
+
? utils.cn(lib.themeMainBgColor, lib.themeIconColor, 'border border-neutral-200 shadow-sm hover:shadow-md hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-800')
|
|
87
|
+
: utils.cn(lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass, 'text-white shadow-lg hover:shadow-xl');
|
|
88
|
+
const buttonClassName = utils.cn(baseButtonStyles, variantClassName, 'text-base font-bold transition-all duration-300 rounded-full', alignmentClass, isDisabled && 'opacity-50 cursor-not-allowed', className);
|
|
83
89
|
return (jsxRuntime.jsx("div", { className: `flex flex-row gap-3 ${getAlignmentClass()}`, children: onClick ? (
|
|
84
90
|
// for click
|
|
85
91
|
jsxRuntime.jsx("button", { type: "button", className: buttonClassName, onClick: handleClick, disabled: isDisabled, children: buttonContent })) : (
|
|
@@ -3,15 +3,15 @@ import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12
|
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
5
5
|
import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
|
|
6
|
-
import { themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
|
|
6
|
+
import { themeIconColor, themeBgColor, themeBorderColor, themeMainBgColor, themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
|
|
7
7
|
import Link from 'next/link';
|
|
8
|
-
import
|
|
8
|
+
import React__default, { useState } from 'react';
|
|
9
9
|
|
|
10
|
-
function GradientButton({ title, icon, align = 'left', disabled = false, className = "", href, openInNewTab = true, preserveReferrer = false, onClick, loadingText, preventDoubleClick = true, iconClassName, }) {
|
|
10
|
+
function GradientButton({ title, icon, iconForcePosition, align = 'left', disabled = false, className = "", href, openInNewTab = true, preserveReferrer = false, onClick, loadingText, preventDoubleClick = true, iconClassName, variant = 'default', }) {
|
|
11
11
|
const [isLoading, setIsLoading] = useState(false);
|
|
12
12
|
const actualLoadingText = loadingText || (title === null || title === void 0 ? void 0 : title.toString().trim()) || 'Loading...';
|
|
13
13
|
const defaultIconClass = "h-4 w-4";
|
|
14
|
-
const finalIconClass = cn(
|
|
14
|
+
const finalIconClass = cn(variant === 'default' ? 'text-white' : themeIconColor, iconClassName || defaultIconClass);
|
|
15
15
|
// set justify class according to alignment
|
|
16
16
|
const getAlignmentClass = () => {
|
|
17
17
|
switch (align) {
|
|
@@ -58,8 +58,8 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
58
58
|
if (icon === null || icon === false) {
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
|
-
if (
|
|
62
|
-
return
|
|
61
|
+
if (React__default.isValidElement(icon)) {
|
|
62
|
+
return React__default.cloneElement(icon, {
|
|
63
63
|
className: cn(finalIconClass, icon.props.className),
|
|
64
64
|
});
|
|
65
65
|
}
|
|
@@ -68,7 +68,8 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
68
68
|
return jsx(globalLucideIcons.ArrowRight, { className: cn(finalIconClass) });
|
|
69
69
|
})();
|
|
70
70
|
const shouldRenderIcon = iconNode !== null && iconNode !== undefined;
|
|
71
|
-
const
|
|
71
|
+
const iconPosition = iconForcePosition !== null && iconForcePosition !== void 0 ? iconForcePosition : (onClick ? 'left' : 'right');
|
|
72
|
+
const buttonContent = iconPosition === 'left' ? (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] }));
|
|
72
73
|
const alignmentClass = align === 'right'
|
|
73
74
|
? 'justify-end'
|
|
74
75
|
: align === 'center'
|
|
@@ -77,7 +78,12 @@ function GradientButton({ title, icon, align = 'left', disabled = false, classNa
|
|
|
77
78
|
// Base styles extracted from Button component + size="lg" (h-11 px-8)
|
|
78
79
|
// Removed [&_svg] constraints
|
|
79
80
|
const baseButtonStyles = "inline-flex items-center gap-2 whitespace-nowrap h-11 px-8 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
|
80
|
-
const
|
|
81
|
+
const variantClassName = variant === 'soft'
|
|
82
|
+
? cn(themeBgColor, themeIconColor, themeBorderColor, 'border shadow-sm hover:shadow-md hover:brightness-95')
|
|
83
|
+
: variant === 'subtle'
|
|
84
|
+
? cn(themeMainBgColor, themeIconColor, 'border border-neutral-200 shadow-sm hover:shadow-md hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-800')
|
|
85
|
+
: cn(themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white shadow-lg hover:shadow-xl');
|
|
86
|
+
const buttonClassName = cn(baseButtonStyles, variantClassName, 'text-base font-bold transition-all duration-300 rounded-full', alignmentClass, isDisabled && 'opacity-50 cursor-not-allowed', className);
|
|
81
87
|
return (jsx("div", { className: `flex flex-row gap-3 ${getAlignmentClass()}`, children: onClick ? (
|
|
82
88
|
// for click
|
|
83
89
|
jsx("button", { type: "button", className: buttonClassName, onClick: handleClick, disabled: isDisabled, children: buttonContent })) : (
|
package/dist/main/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './loading';
|
|
|
3
3
|
export * from './nprogress-bar';
|
|
4
4
|
export * from './ads-alert-dialog';
|
|
5
5
|
export * from './x-button';
|
|
6
|
+
export * from './x-toggle-button';
|
|
6
7
|
export * from './ai-prompt-textarea';
|
|
7
8
|
export * from './rich-text-expert';
|
|
8
9
|
export * from './faq-interactive';
|
|
@@ -10,6 +11,7 @@ export * from './price-plan-interactive';
|
|
|
10
11
|
export * from './gallery/gallery-interactive';
|
|
11
12
|
export * from './delayed-img';
|
|
12
13
|
export * from './snake-loading-frame';
|
|
14
|
+
export * from './pill-select';
|
|
13
15
|
export { MoneyPriceInteractive } from './money-price/money-price-interactive';
|
|
14
16
|
export { MoneyPriceButton } from './money-price/money-price-button';
|
|
15
17
|
export { CreditOverviewClient } from './credit/credit-overview-client';
|
package/dist/main/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var loading = require('./loading.js');
|
|
|
6
6
|
var nprogressBar = require('./nprogress-bar.js');
|
|
7
7
|
var adsAlertDialog = require('./ads-alert-dialog.js');
|
|
8
8
|
var xButton = require('./x-button.js');
|
|
9
|
+
var xToggleButton = require('./x-toggle-button.js');
|
|
9
10
|
var aiPromptTextarea = require('./ai-prompt-textarea.js');
|
|
10
11
|
var richTextExpert = require('./rich-text-expert.js');
|
|
11
12
|
var faqInteractive = require('./faq-interactive.js');
|
|
@@ -13,6 +14,10 @@ var pricePlanInteractive = require('./price-plan-interactive.js');
|
|
|
13
14
|
var galleryInteractive = require('./gallery/gallery-interactive.js');
|
|
14
15
|
var delayedImg = require('./delayed-img.js');
|
|
15
16
|
var snakeLoadingFrame = require('./snake-loading-frame.js');
|
|
17
|
+
var xPillSelect = require('./pill-select/x-pill-select.js');
|
|
18
|
+
var xFilterPills = require('./pill-select/x-filter-pills.js');
|
|
19
|
+
var xFormPills = require('./pill-select/x-form-pills.js');
|
|
20
|
+
var xTokenInput = require('./pill-select/x-token-input.js');
|
|
16
21
|
var moneyPriceInteractive = require('./money-price/money-price-interactive.js');
|
|
17
22
|
var moneyPriceButton = require('./money-price/money-price-button.js');
|
|
18
23
|
var creditOverviewClient = require('./credit/credit-overview-client.js');
|
|
@@ -26,6 +31,7 @@ exports.getLoadingCycleDurationMs = loading.getLoadingCycleDurationMs;
|
|
|
26
31
|
exports.NProgressBar = nprogressBar.NProgressBar;
|
|
27
32
|
exports.AdsAlertDialog = adsAlertDialog.AdsAlertDialog;
|
|
28
33
|
exports.XButton = xButton.XButton;
|
|
34
|
+
exports.XToggleButton = xToggleButton.XToggleButton;
|
|
29
35
|
exports.AIPromptTextarea = aiPromptTextarea.AIPromptTextarea;
|
|
30
36
|
exports.createRichTextRenderer = richTextExpert.createRichTextRenderer;
|
|
31
37
|
exports.richText = richTextExpert.richText;
|
|
@@ -35,6 +41,10 @@ exports.GalleryInteractive = galleryInteractive.GalleryInteractive;
|
|
|
35
41
|
exports.DelayedImg = delayedImg.DelayedImg;
|
|
36
42
|
exports.SnakeLoadingFrame = snakeLoadingFrame.SnakeLoadingFrame;
|
|
37
43
|
exports.SnakeLoadingPreview = snakeLoadingFrame.SnakeLoadingPreview;
|
|
44
|
+
exports.XPillSelect = xPillSelect.XPillSelect;
|
|
45
|
+
exports.XFilterPills = xFilterPills.XFilterPills;
|
|
46
|
+
exports.XFormPills = xFormPills.XFormPills;
|
|
47
|
+
exports.XTokenInput = xTokenInput.XTokenInput;
|
|
38
48
|
exports.MoneyPriceInteractive = moneyPriceInteractive.MoneyPriceInteractive;
|
|
39
49
|
exports.MoneyPriceButton = moneyPriceButton.MoneyPriceButton;
|
|
40
50
|
exports.CreditOverviewClient = creditOverviewClient.CreditOverviewClient;
|
package/dist/main/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ export { Loading, getLoadingCycleDurationMs } from './loading.mjs';
|
|
|
4
4
|
export { NProgressBar } from './nprogress-bar.mjs';
|
|
5
5
|
export { AdsAlertDialog } from './ads-alert-dialog.mjs';
|
|
6
6
|
export { XButton } from './x-button.mjs';
|
|
7
|
+
export { XToggleButton } from './x-toggle-button.mjs';
|
|
7
8
|
export { AIPromptTextarea } from './ai-prompt-textarea.mjs';
|
|
8
9
|
export { createRichTextRenderer, richText } from './rich-text-expert.mjs';
|
|
9
10
|
export { FAQInteractive } from './faq-interactive.mjs';
|
|
@@ -11,6 +12,10 @@ export { PricePlanInteractive } from './price-plan-interactive.mjs';
|
|
|
11
12
|
export { GalleryInteractive } from './gallery/gallery-interactive.mjs';
|
|
12
13
|
export { DelayedImg } from './delayed-img.mjs';
|
|
13
14
|
export { SnakeLoadingFrame, SnakeLoadingPreview } from './snake-loading-frame.mjs';
|
|
15
|
+
export { XPillSelect } from './pill-select/x-pill-select.mjs';
|
|
16
|
+
export { XFilterPills } from './pill-select/x-filter-pills.mjs';
|
|
17
|
+
export { XFormPills } from './pill-select/x-form-pills.mjs';
|
|
18
|
+
export { XTokenInput } from './pill-select/x-token-input.mjs';
|
|
14
19
|
export { MoneyPriceInteractive } from './money-price/money-price-interactive.mjs';
|
|
15
20
|
export { MoneyPriceButton } from './money-price/money-price-button.mjs';
|
|
16
21
|
export { CreditOverviewClient } from './credit/credit-overview-client.mjs';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var xPillSelect = require('./x-pill-select.js');
|
|
4
|
+
var xFilterPills = require('./x-filter-pills.js');
|
|
5
|
+
var xFormPills = require('./x-form-pills.js');
|
|
6
|
+
var xTokenInput = require('./x-token-input.js');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
exports.XPillSelect = xPillSelect.XPillSelect;
|
|
11
|
+
exports.XFilterPills = xFilterPills.XFilterPills;
|
|
12
|
+
exports.XFormPills = xFormPills.XFormPills;
|
|
13
|
+
exports.XTokenInput = xTokenInput.XTokenInput;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type XPillOption } from './x-pill-select';
|
|
2
|
+
type XFilterPillsProps = {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
options: XPillOption[];
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
allLabel: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function XFilterPills({ label, value, options, onChange, allLabel, className, }: XFilterPillsProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var utils = require('@windrun-huaiin/lib/utils');
|
|
6
|
+
var xPillSelect = require('./x-pill-select.js');
|
|
7
|
+
|
|
8
|
+
function XFilterPills({ label, value, options, onChange, allLabel, className, }) {
|
|
9
|
+
return (jsxRuntime.jsxs("div", { className: utils.cn('space-y-1.5', className), children: [jsxRuntime.jsx("div", { className: "text-xs font-medium text-slate-700 dark:text-slate-200", children: label }), jsxRuntime.jsx(xPillSelect.XPillSelect, { mode: "single", value: value, onChange: onChange, options: [{ label: allLabel, value: '' }, ...options], size: "compact", maxPillWidthClassName: "max-w-[150px] sm:max-w-[220px]" })] }));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.XFilterPills = XFilterPills;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
+
import { XPillSelect } from './x-pill-select.mjs';
|
|
5
|
+
|
|
6
|
+
function XFilterPills({ label, value, options, onChange, allLabel, className, }) {
|
|
7
|
+
return (jsxs("div", { className: cn('space-y-1.5', className), children: [jsx("div", { className: "text-xs font-medium text-slate-700 dark:text-slate-200", children: label }), jsx(XPillSelect, { mode: "single", value: value, onChange: onChange, options: [{ label: allLabel, value: '' }, ...options], size: "compact", maxPillWidthClassName: "max-w-[150px] sm:max-w-[220px]" })] }));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { XFilterPills };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type XPillOption } from './x-pill-select';
|
|
2
|
+
type XFormPillsProps = {
|
|
3
|
+
label: React.ReactNode;
|
|
4
|
+
value: string;
|
|
5
|
+
options: XPillOption[];
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
emptyLabel: string;
|
|
8
|
+
allowClear?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function XFormPills({ label, value, options, onChange, emptyLabel, allowClear, className, }: XFormPillsProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var utils = require('@windrun-huaiin/lib/utils');
|
|
6
|
+
var xPillSelect = require('./x-pill-select.js');
|
|
7
|
+
|
|
8
|
+
function XFormPills({ label, value, options, onChange, emptyLabel, allowClear = false, className, }) {
|
|
9
|
+
return (jsxRuntime.jsxs("div", { className: utils.cn('space-y-2 text-sm', className), children: [jsxRuntime.jsx("div", { className: "font-medium text-slate-700 dark:text-slate-200", children: label }), jsxRuntime.jsx(xPillSelect.XPillSelect, { mode: "single", value: value, onChange: onChange, options: options, emptyLabel: emptyLabel, allowClear: allowClear, maxPillWidthClassName: "max-w-[150px] sm:max-w-[220px]" })] }));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.XFormPills = XFormPills;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
+
import { XPillSelect } from './x-pill-select.mjs';
|
|
5
|
+
|
|
6
|
+
function XFormPills({ label, value, options, onChange, emptyLabel, allowClear = false, className, }) {
|
|
7
|
+
return (jsxs("div", { className: cn('space-y-2 text-sm', className), children: [jsx("div", { className: "font-medium text-slate-700 dark:text-slate-200", children: label }), jsx(XPillSelect, { mode: "single", value: value, onChange: onChange, options: options, emptyLabel: emptyLabel, allowClear: allowClear, maxPillWidthClassName: "max-w-[150px] sm:max-w-[220px]" })] }));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { XFormPills };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type XPillOption = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
};
|
|
5
|
+
type XPillSelectBaseProps = {
|
|
6
|
+
options?: XPillOption[];
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
className?: string;
|
|
9
|
+
pillClassName?: string;
|
|
10
|
+
emptyLabel?: string;
|
|
11
|
+
maxPillWidthClassName?: string;
|
|
12
|
+
size?: 'default' | 'compact';
|
|
13
|
+
inputEnabled?: boolean;
|
|
14
|
+
inputPlaceholder?: string;
|
|
15
|
+
onInputTransform?: (value: string) => string;
|
|
16
|
+
allSelectedLabel?: string;
|
|
17
|
+
maxVisiblePills?: number;
|
|
18
|
+
};
|
|
19
|
+
type XPillSelectSingleProps = XPillSelectBaseProps & {
|
|
20
|
+
mode: 'single';
|
|
21
|
+
value: string;
|
|
22
|
+
onChange: (value: string) => void;
|
|
23
|
+
allowClear?: boolean;
|
|
24
|
+
};
|
|
25
|
+
type XPillSelectMultipleProps = XPillSelectBaseProps & {
|
|
26
|
+
mode: 'multiple';
|
|
27
|
+
value: string[];
|
|
28
|
+
onChange: (value: string[]) => void;
|
|
29
|
+
allowClear?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type XPillSelectProps = XPillSelectSingleProps | XPillSelectMultipleProps;
|
|
32
|
+
export declare function XPillSelect(props: XPillSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
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 sanitizeInputValue(value) {
|
|
11
|
+
return value.replaceAll(',', '').trim();
|
|
12
|
+
}
|
|
13
|
+
function dedupeValues(values) {
|
|
14
|
+
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean)));
|
|
15
|
+
}
|
|
16
|
+
function XPillSelect(props) {
|
|
17
|
+
const { options = [], disabled = false, className, pillClassName, emptyLabel, maxPillWidthClassName = 'max-w-[180px] sm:max-w-[220px]', size = 'default', inputEnabled = false, inputPlaceholder, onInputTransform, allSelectedLabel, maxVisiblePills, } = props;
|
|
18
|
+
const [draftValue, setDraftValue] = React.useState('');
|
|
19
|
+
const [open, setOpen] = React.useState(false);
|
|
20
|
+
const [hovered, setHovered] = React.useState(false);
|
|
21
|
+
const rootRef = React.useRef(null);
|
|
22
|
+
const compact = size === 'compact';
|
|
23
|
+
const normalizedOptions = React.useMemo(() => options.map((option) => (Object.assign(Object.assign({}, option), { value: option.value.trim() }))).filter((option) => option.value), [options]);
|
|
24
|
+
const selectedValues = props.mode === 'single' ? (props.value ? [props.value] : []) : dedupeValues(props.value);
|
|
25
|
+
const allOptionValues = normalizedOptions.map((option) => option.value);
|
|
26
|
+
const isAllSelected = props.mode === 'multiple' &&
|
|
27
|
+
allOptionValues.length > 0 &&
|
|
28
|
+
allOptionValues.every((value) => selectedValues.includes(value));
|
|
29
|
+
const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || '全部' : null;
|
|
30
|
+
const hasVisiblePillLimit = props.mode === 'multiple' && typeof maxVisiblePills === 'number' && maxVisiblePills >= 0;
|
|
31
|
+
const visibleSelectedValues = aggregatedSelectedLabel || !hasVisiblePillLimit
|
|
32
|
+
? selectedValues
|
|
33
|
+
: selectedValues.slice(0, maxVisiblePills);
|
|
34
|
+
const hiddenSelectedCount = aggregatedSelectedLabel || !hasVisiblePillLimit
|
|
35
|
+
? 0
|
|
36
|
+
: Math.max(selectedValues.length - visibleSelectedValues.length, 0);
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
function handlePointerDown(event) {
|
|
39
|
+
var _a;
|
|
40
|
+
if (!((_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))) {
|
|
41
|
+
setOpen(false);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (open) {
|
|
45
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
46
|
+
}
|
|
47
|
+
return () => {
|
|
48
|
+
document.removeEventListener('mousedown', handlePointerDown);
|
|
49
|
+
};
|
|
50
|
+
}, [open]);
|
|
51
|
+
function isSelected(optionValue) {
|
|
52
|
+
return selectedValues.includes(optionValue);
|
|
53
|
+
}
|
|
54
|
+
function commitInputValue(rawValue) {
|
|
55
|
+
if (!inputEnabled || disabled) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const nextValue = sanitizeInputValue(onInputTransform ? onInputTransform(rawValue) : rawValue);
|
|
59
|
+
if (!nextValue) {
|
|
60
|
+
setDraftValue('');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (props.mode === 'single') {
|
|
64
|
+
props.onChange(nextValue);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
props.onChange(dedupeValues([...selectedValues, nextValue]));
|
|
68
|
+
}
|
|
69
|
+
setDraftValue('');
|
|
70
|
+
}
|
|
71
|
+
function toggleValue(nextValue) {
|
|
72
|
+
if (disabled) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (props.mode === 'single') {
|
|
76
|
+
if (props.allowClear && props.value === nextValue) {
|
|
77
|
+
props.onChange('');
|
|
78
|
+
setOpen(false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
props.onChange(nextValue);
|
|
82
|
+
setOpen(false);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (selectedValues.includes(nextValue)) {
|
|
86
|
+
props.onChange(selectedValues.filter((item) => item !== nextValue));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
props.onChange([...selectedValues, nextValue]);
|
|
90
|
+
}
|
|
91
|
+
function removeValue(nextValue) {
|
|
92
|
+
if (disabled) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (props.mode === 'single') {
|
|
96
|
+
props.onChange('');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
props.onChange(selectedValues.filter((item) => item !== nextValue));
|
|
100
|
+
}
|
|
101
|
+
function clearAllSelectedValues() {
|
|
102
|
+
if (disabled || props.mode !== 'multiple') {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
props.onChange([]);
|
|
106
|
+
}
|
|
107
|
+
function toggleOpen() {
|
|
108
|
+
if (disabled) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
setOpen((current) => !current);
|
|
112
|
+
}
|
|
113
|
+
return (jsxRuntime.jsxs("div", { ref: rootRef, className: utils.cn('relative', className), children: [jsxRuntime.jsxs("div", { role: "button", tabIndex: disabled ? -1 : 0, "aria-disabled": disabled, "aria-expanded": open, "aria-haspopup": "listbox", onMouseEnter: () => !disabled && setHovered(true), onMouseLeave: () => setHovered(false), onClick: toggleOpen, onKeyDown: (event) => {
|
|
114
|
+
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
toggleOpen();
|
|
119
|
+
}, className: utils.cn('flex w-full items-center justify-between rounded-full border border-black/10 text-left transition dark:border-white/10', compact ? 'min-h-9 gap-2 px-3 py-1.5' : 'min-h-11 gap-3 px-4 py-2.5', !disabled && 'cursor-pointer', !disabled && (hovered || open) && lib.themeBorderColor, disabled && 'cursor-not-allowed opacity-60'), children: [jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: selectedValues.length > 0 ? (aggregatedSelectedLabel ? (jsxRuntime.jsx("button", { type: "button", onClick: (event) => {
|
|
120
|
+
event.stopPropagation();
|
|
121
|
+
clearAllSelectedValues();
|
|
122
|
+
}, disabled: disabled, 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.5 px-3 py-1 text-xs', lib.themeBgColor, lib.themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: aggregatedSelectedLabel, children: jsxRuntime.jsx("span", { className: utils.cn('truncate', maxPillWidthClassName), children: aggregatedSelectedLabel }) })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [visibleSelectedValues.map((selectedValue) => {
|
|
123
|
+
var _a, _b;
|
|
124
|
+
const optionLabel = (_b = (_a = normalizedOptions.find((option) => option.value === selectedValue)) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : selectedValue;
|
|
125
|
+
return (jsxRuntime.jsx("button", { type: "button", onClick: (event) => {
|
|
126
|
+
event.stopPropagation();
|
|
127
|
+
removeValue(selectedValue);
|
|
128
|
+
}, disabled: disabled, 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.5 px-3 py-1 text-xs', lib.themeBgColor, lib.themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: optionLabel, children: jsxRuntime.jsx("span", { className: utils.cn('truncate', maxPillWidthClassName), children: optionLabel }) }, selectedValue));
|
|
129
|
+
}), hiddenSelectedCount > 0 ? (jsxRuntime.jsxs("span", { className: utils.cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `还有 ${hiddenSelectedCount} 项未展开`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsxRuntime.jsx("span", { className: utils.cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: utils.cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: utils.cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && lib.themeBorderColor), children: [inputEnabled ? (jsxRuntime.jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
|
|
130
|
+
if (event.key !== 'Enter') {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
commitInputValue(draftValue);
|
|
135
|
+
}, disabled: disabled, placeholder: inputPlaceholder, className: utils.cn('w-full rounded-full border border-black/10 text-slate-700 outline-none transition focus:border-purple-400 dark:border-white/10 dark:text-white', compact ? 'px-3 py-1.5 text-xs' : 'px-4 py-2.5 text-sm') })) : null, normalizedOptions.length > 0 ? (jsxRuntime.jsx("div", { className: utils.cn('flex flex-wrap', compact ? 'gap-1.5' : 'gap-2'), children: normalizedOptions.map((option) => {
|
|
136
|
+
const active = isSelected(option.value);
|
|
137
|
+
return (jsxRuntime.jsx("button", { type: "button", onClick: () => toggleValue(option.value), disabled: disabled, className: utils.cn('inline-flex items-center justify-center rounded-full border font-semibold transition-colors', compact ? 'px-3 py-1 text-[11px]' : 'px-4 py-2 text-xs', 'bg-neutral-200 text-neutral-700 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-white dark:hover:bg-neutral-700', active &&
|
|
138
|
+
[lib.themeBgColor, lib.themeBorderColor, lib.themeIconColor], disabled && 'cursor-not-allowed opacity-60', pillClassName), title: option.label, children: jsxRuntime.jsx("span", { className: utils.cn('truncate', maxPillWidthClassName), children: option.label }) }, option.value));
|
|
139
|
+
}) })) : null] })) : null] }));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
exports.XPillSelect = XPillSelect;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { useState, useRef, useMemo, useEffect } from 'react';
|
|
4
|
+
import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
|
|
5
|
+
import { themeBgColor, themeIconColor, themeBorderColor } from '@windrun-huaiin/base-ui/lib';
|
|
6
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
7
|
+
|
|
8
|
+
function sanitizeInputValue(value) {
|
|
9
|
+
return value.replaceAll(',', '').trim();
|
|
10
|
+
}
|
|
11
|
+
function dedupeValues(values) {
|
|
12
|
+
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean)));
|
|
13
|
+
}
|
|
14
|
+
function XPillSelect(props) {
|
|
15
|
+
const { options = [], disabled = false, className, pillClassName, emptyLabel, maxPillWidthClassName = 'max-w-[180px] sm:max-w-[220px]', size = 'default', inputEnabled = false, inputPlaceholder, onInputTransform, allSelectedLabel, maxVisiblePills, } = props;
|
|
16
|
+
const [draftValue, setDraftValue] = useState('');
|
|
17
|
+
const [open, setOpen] = useState(false);
|
|
18
|
+
const [hovered, setHovered] = useState(false);
|
|
19
|
+
const rootRef = useRef(null);
|
|
20
|
+
const compact = size === 'compact';
|
|
21
|
+
const normalizedOptions = useMemo(() => options.map((option) => (Object.assign(Object.assign({}, option), { value: option.value.trim() }))).filter((option) => option.value), [options]);
|
|
22
|
+
const selectedValues = props.mode === 'single' ? (props.value ? [props.value] : []) : dedupeValues(props.value);
|
|
23
|
+
const allOptionValues = normalizedOptions.map((option) => option.value);
|
|
24
|
+
const isAllSelected = props.mode === 'multiple' &&
|
|
25
|
+
allOptionValues.length > 0 &&
|
|
26
|
+
allOptionValues.every((value) => selectedValues.includes(value));
|
|
27
|
+
const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || '全部' : null;
|
|
28
|
+
const hasVisiblePillLimit = props.mode === 'multiple' && typeof maxVisiblePills === 'number' && maxVisiblePills >= 0;
|
|
29
|
+
const visibleSelectedValues = aggregatedSelectedLabel || !hasVisiblePillLimit
|
|
30
|
+
? selectedValues
|
|
31
|
+
: selectedValues.slice(0, maxVisiblePills);
|
|
32
|
+
const hiddenSelectedCount = aggregatedSelectedLabel || !hasVisiblePillLimit
|
|
33
|
+
? 0
|
|
34
|
+
: Math.max(selectedValues.length - visibleSelectedValues.length, 0);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
function handlePointerDown(event) {
|
|
37
|
+
var _a;
|
|
38
|
+
if (!((_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))) {
|
|
39
|
+
setOpen(false);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (open) {
|
|
43
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
44
|
+
}
|
|
45
|
+
return () => {
|
|
46
|
+
document.removeEventListener('mousedown', handlePointerDown);
|
|
47
|
+
};
|
|
48
|
+
}, [open]);
|
|
49
|
+
function isSelected(optionValue) {
|
|
50
|
+
return selectedValues.includes(optionValue);
|
|
51
|
+
}
|
|
52
|
+
function commitInputValue(rawValue) {
|
|
53
|
+
if (!inputEnabled || disabled) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const nextValue = sanitizeInputValue(onInputTransform ? onInputTransform(rawValue) : rawValue);
|
|
57
|
+
if (!nextValue) {
|
|
58
|
+
setDraftValue('');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (props.mode === 'single') {
|
|
62
|
+
props.onChange(nextValue);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
props.onChange(dedupeValues([...selectedValues, nextValue]));
|
|
66
|
+
}
|
|
67
|
+
setDraftValue('');
|
|
68
|
+
}
|
|
69
|
+
function toggleValue(nextValue) {
|
|
70
|
+
if (disabled) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (props.mode === 'single') {
|
|
74
|
+
if (props.allowClear && props.value === nextValue) {
|
|
75
|
+
props.onChange('');
|
|
76
|
+
setOpen(false);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
props.onChange(nextValue);
|
|
80
|
+
setOpen(false);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (selectedValues.includes(nextValue)) {
|
|
84
|
+
props.onChange(selectedValues.filter((item) => item !== nextValue));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
props.onChange([...selectedValues, nextValue]);
|
|
88
|
+
}
|
|
89
|
+
function removeValue(nextValue) {
|
|
90
|
+
if (disabled) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (props.mode === 'single') {
|
|
94
|
+
props.onChange('');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
props.onChange(selectedValues.filter((item) => item !== nextValue));
|
|
98
|
+
}
|
|
99
|
+
function clearAllSelectedValues() {
|
|
100
|
+
if (disabled || props.mode !== 'multiple') {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
props.onChange([]);
|
|
104
|
+
}
|
|
105
|
+
function toggleOpen() {
|
|
106
|
+
if (disabled) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
setOpen((current) => !current);
|
|
110
|
+
}
|
|
111
|
+
return (jsxs("div", { ref: rootRef, className: cn('relative', className), children: [jsxs("div", { role: "button", tabIndex: disabled ? -1 : 0, "aria-disabled": disabled, "aria-expanded": open, "aria-haspopup": "listbox", onMouseEnter: () => !disabled && setHovered(true), onMouseLeave: () => setHovered(false), onClick: toggleOpen, onKeyDown: (event) => {
|
|
112
|
+
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
toggleOpen();
|
|
117
|
+
}, className: cn('flex w-full items-center justify-between rounded-full border border-black/10 text-left transition dark:border-white/10', compact ? 'min-h-9 gap-2 px-3 py-1.5' : 'min-h-11 gap-3 px-4 py-2.5', !disabled && 'cursor-pointer', !disabled && (hovered || open) && themeBorderColor, disabled && 'cursor-not-allowed opacity-60'), children: [jsx("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: selectedValues.length > 0 ? (aggregatedSelectedLabel ? (jsx("button", { type: "button", onClick: (event) => {
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
clearAllSelectedValues();
|
|
120
|
+
}, disabled: disabled, 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.5 px-3 py-1 text-xs', themeBgColor, themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: aggregatedSelectedLabel, children: jsx("span", { className: cn('truncate', maxPillWidthClassName), children: aggregatedSelectedLabel }) })) : (jsxs(Fragment, { children: [visibleSelectedValues.map((selectedValue) => {
|
|
121
|
+
var _a, _b;
|
|
122
|
+
const optionLabel = (_b = (_a = normalizedOptions.find((option) => option.value === selectedValue)) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : selectedValue;
|
|
123
|
+
return (jsx("button", { type: "button", onClick: (event) => {
|
|
124
|
+
event.stopPropagation();
|
|
125
|
+
removeValue(selectedValue);
|
|
126
|
+
}, disabled: disabled, 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.5 px-3 py-1 text-xs', themeBgColor, themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: optionLabel, children: jsx("span", { className: cn('truncate', maxPillWidthClassName), children: optionLabel }) }, selectedValue));
|
|
127
|
+
}), hiddenSelectedCount > 0 ? (jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `还有 ${hiddenSelectedCount} 项未展开`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsx("span", { className: cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsx(globalLucideIcons.ChevronDown, { className: cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && themeBorderColor), children: [inputEnabled ? (jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
|
|
128
|
+
if (event.key !== 'Enter') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
commitInputValue(draftValue);
|
|
133
|
+
}, disabled: disabled, placeholder: inputPlaceholder, className: cn('w-full rounded-full border border-black/10 text-slate-700 outline-none transition focus:border-purple-400 dark:border-white/10 dark:text-white', compact ? 'px-3 py-1.5 text-xs' : 'px-4 py-2.5 text-sm') })) : null, normalizedOptions.length > 0 ? (jsx("div", { className: cn('flex flex-wrap', compact ? 'gap-1.5' : 'gap-2'), children: normalizedOptions.map((option) => {
|
|
134
|
+
const active = isSelected(option.value);
|
|
135
|
+
return (jsx("button", { type: "button", onClick: () => toggleValue(option.value), disabled: disabled, className: cn('inline-flex items-center justify-center rounded-full border font-semibold transition-colors', compact ? 'px-3 py-1 text-[11px]' : 'px-4 py-2 text-xs', 'bg-neutral-200 text-neutral-700 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-white dark:hover:bg-neutral-700', active &&
|
|
136
|
+
[themeBgColor, themeBorderColor, themeIconColor], disabled && 'cursor-not-allowed opacity-60', pillClassName), title: option.label, children: jsx("span", { className: cn('truncate', maxPillWidthClassName), children: option.label }) }, option.value));
|
|
137
|
+
}) })) : null] })) : null] }));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { XPillSelect };
|