@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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
5
|
+
import {
|
|
6
|
+
themeButtonGradientClass,
|
|
7
|
+
themeButtonGradientHoverClass,
|
|
8
|
+
} from '@windrun-huaiin/base-ui/lib';
|
|
9
|
+
|
|
10
|
+
export type XToggleButtonOption = {
|
|
11
|
+
value: string;
|
|
12
|
+
label: React.ReactNode;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
badge?: React.ReactNode;
|
|
16
|
+
mobileIcon?: React.ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type XToggleButtonSize = 'default' | 'compact';
|
|
20
|
+
|
|
21
|
+
export type XToggleButtonProps = {
|
|
22
|
+
options: XToggleButtonOption[];
|
|
23
|
+
value?: string;
|
|
24
|
+
defaultValue?: string;
|
|
25
|
+
onChange?: (value: string) => void;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
itemClassName?: string;
|
|
29
|
+
activeItemClassName?: string;
|
|
30
|
+
inactiveItemClassName?: string;
|
|
31
|
+
badgeClassName?: string;
|
|
32
|
+
minItemWidthClassName?: string;
|
|
33
|
+
maxItemWidthClassName?: string;
|
|
34
|
+
itemTextClassName?: string;
|
|
35
|
+
itemPaddingClassName?: string;
|
|
36
|
+
size?: XToggleButtonSize;
|
|
37
|
+
fullWidth?: boolean;
|
|
38
|
+
name?: string;
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function XToggleButton({
|
|
43
|
+
options,
|
|
44
|
+
value,
|
|
45
|
+
defaultValue,
|
|
46
|
+
onChange,
|
|
47
|
+
disabled = false,
|
|
48
|
+
className,
|
|
49
|
+
itemClassName,
|
|
50
|
+
activeItemClassName,
|
|
51
|
+
inactiveItemClassName,
|
|
52
|
+
badgeClassName,
|
|
53
|
+
minItemWidthClassName,
|
|
54
|
+
maxItemWidthClassName,
|
|
55
|
+
itemTextClassName,
|
|
56
|
+
itemPaddingClassName,
|
|
57
|
+
size = 'default',
|
|
58
|
+
fullWidth = false,
|
|
59
|
+
name,
|
|
60
|
+
ariaLabel,
|
|
61
|
+
}: XToggleButtonProps) {
|
|
62
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
63
|
+
const activeButtonRef = React.useRef<HTMLButtonElement>(null);
|
|
64
|
+
const [badgeOffset, setBadgeOffset] = React.useState(0);
|
|
65
|
+
|
|
66
|
+
const normalizedOptions = React.useMemo(
|
|
67
|
+
() => options.filter((option) => option.value.trim()),
|
|
68
|
+
[options]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const fallbackValue = React.useMemo(() => {
|
|
72
|
+
if (defaultValue && normalizedOptions.some((option) => option.value === defaultValue)) {
|
|
73
|
+
return defaultValue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return normalizedOptions[0]?.value ?? '';
|
|
77
|
+
}, [defaultValue, normalizedOptions]);
|
|
78
|
+
|
|
79
|
+
const isControlled = value !== undefined;
|
|
80
|
+
const [internalValue, setInternalValue] = React.useState(fallbackValue);
|
|
81
|
+
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
if (!isControlled) {
|
|
84
|
+
setInternalValue(fallbackValue);
|
|
85
|
+
}
|
|
86
|
+
}, [fallbackValue, isControlled]);
|
|
87
|
+
|
|
88
|
+
const selectedValue = isControlled ? value ?? '' : internalValue;
|
|
89
|
+
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
if (activeButtonRef.current && containerRef.current) {
|
|
92
|
+
const buttonRect = activeButtonRef.current.getBoundingClientRect();
|
|
93
|
+
const containerRect = containerRef.current.getBoundingClientRect();
|
|
94
|
+
const buttonCenterX = buttonRect.left - containerRect.left + buttonRect.width / 2;
|
|
95
|
+
setBadgeOffset(buttonCenterX);
|
|
96
|
+
}
|
|
97
|
+
}, [selectedValue]);
|
|
98
|
+
|
|
99
|
+
function handleSelect(nextValue: string, optionDisabled?: boolean) {
|
|
100
|
+
if (disabled || optionDisabled || nextValue === selectedValue) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!isControlled) {
|
|
105
|
+
setInternalValue(nextValue);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onChange?.(nextValue);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const containerSizeClass = size === 'compact'
|
|
112
|
+
? 'px-1.5 py-1.5 gap-0'
|
|
113
|
+
: 'px-2 py-2 gap-0 sm:px-3 sm:py-3 sm:gap-0';
|
|
114
|
+
|
|
115
|
+
const defaultItemTextClass = size === 'compact'
|
|
116
|
+
? 'text-xs'
|
|
117
|
+
: 'text-xs sm:text-sm md:text-base';
|
|
118
|
+
const finalItemTextClassName = itemTextClassName ?? defaultItemTextClass;
|
|
119
|
+
|
|
120
|
+
const defaultItemPaddingClass = size === 'compact'
|
|
121
|
+
? 'px-2 py-1'
|
|
122
|
+
: 'px-2 py-1.5 sm:px-3 sm:py-2';
|
|
123
|
+
const finalItemPaddingClassName = itemPaddingClassName ?? defaultItemPaddingClass;
|
|
124
|
+
|
|
125
|
+
const minItemWidthClass = minItemWidthClassName ?? 'min-w-[80px] sm:min-w-[100px] md:min-w-[120px]';
|
|
126
|
+
const maxItemWidthClass = maxItemWidthClassName ?? 'max-w-[120px] sm:max-w-[160px]';
|
|
127
|
+
|
|
128
|
+
const selectedOption = normalizedOptions.find((opt) => opt.value === selectedValue);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div
|
|
132
|
+
ref={containerRef}
|
|
133
|
+
role="radiogroup"
|
|
134
|
+
aria-label={ariaLabel}
|
|
135
|
+
aria-disabled={disabled}
|
|
136
|
+
className={cn(
|
|
137
|
+
'relative inline-flex items-center rounded-full border border-gray-300 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-900',
|
|
138
|
+
fullWidth && 'flex w-full',
|
|
139
|
+
containerSizeClass,
|
|
140
|
+
className
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
{selectedOption?.badge ? (
|
|
144
|
+
<span
|
|
145
|
+
style={{
|
|
146
|
+
left: `${badgeOffset}px`,
|
|
147
|
+
transform: 'translate(-50%, calc(-50% - 1px))'
|
|
148
|
+
}}
|
|
149
|
+
className={cn(
|
|
150
|
+
'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',
|
|
151
|
+
badgeClassName
|
|
152
|
+
)}
|
|
153
|
+
>
|
|
154
|
+
{selectedOption.badge}
|
|
155
|
+
</span>
|
|
156
|
+
) : null}
|
|
157
|
+
{normalizedOptions.map((option) => {
|
|
158
|
+
const active = option.value === selectedValue;
|
|
159
|
+
const optionDisabled = disabled || option.disabled;
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
key={option.value}
|
|
164
|
+
className={cn('relative flex items-center justify-center', fullWidth && 'flex-1')}
|
|
165
|
+
>
|
|
166
|
+
<button
|
|
167
|
+
ref={active ? activeButtonRef : null}
|
|
168
|
+
type="button"
|
|
169
|
+
role="radio"
|
|
170
|
+
name={name}
|
|
171
|
+
aria-checked={active}
|
|
172
|
+
aria-pressed={active}
|
|
173
|
+
disabled={optionDisabled}
|
|
174
|
+
onClick={() => handleSelect(option.value, option.disabled)}
|
|
175
|
+
className={cn(
|
|
176
|
+
'relative z-10 inline-flex items-center justify-center rounded-full font-medium text-center transition truncate',
|
|
177
|
+
fullWidth && 'w-full',
|
|
178
|
+
!fullWidth && minItemWidthClass,
|
|
179
|
+
!fullWidth && maxItemWidthClass,
|
|
180
|
+
finalItemPaddingClassName,
|
|
181
|
+
finalItemTextClassName,
|
|
182
|
+
active
|
|
183
|
+
? cn(
|
|
184
|
+
'text-white shadow-sm',
|
|
185
|
+
themeButtonGradientClass,
|
|
186
|
+
themeButtonGradientHoverClass,
|
|
187
|
+
activeItemClassName
|
|
188
|
+
)
|
|
189
|
+
: cn(
|
|
190
|
+
'text-gray-800 hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100',
|
|
191
|
+
inactiveItemClassName
|
|
192
|
+
),
|
|
193
|
+
optionDisabled && 'cursor-not-allowed opacity-60',
|
|
194
|
+
itemClassName,
|
|
195
|
+
option.className
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
{option.mobileIcon ? (
|
|
199
|
+
<>
|
|
200
|
+
<span className="hidden sm:block">{option.label}</span>
|
|
201
|
+
<span className="block sm:hidden">
|
|
202
|
+
{active && React.isValidElement(option.mobileIcon)
|
|
203
|
+
? React.cloneElement(option.mobileIcon as React.ReactElement<any>, {
|
|
204
|
+
className: cn((option.mobileIcon as React.ReactElement<any>).props.className, 'text-white'),
|
|
205
|
+
})
|
|
206
|
+
: option.mobileIcon}
|
|
207
|
+
</span>
|
|
208
|
+
</>
|
|
209
|
+
) : (
|
|
210
|
+
option.label
|
|
211
|
+
)}
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
})}
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { auth } from '@clerk/nextjs/server';
|
|
2
|
-
|
|
3
|
-
export type OptionalAuthResult = {
|
|
4
|
-
userId: string | null;
|
|
5
|
-
sessionId: string | null;
|
|
6
|
-
raw: Awaited<ReturnType<typeof auth>> | null;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 可选鉴权:在缺少 Clerk 标记或未登录时返回 null,避免 auth() 抛错。
|
|
11
|
-
* 仅供服务端使用,请从 @third-ui/clerk/patch/optional-auth 导入。
|
|
12
|
-
*/
|
|
13
|
-
export async function getOptionalAuth(): Promise<OptionalAuthResult> {
|
|
14
|
-
try {
|
|
15
|
-
const res = await auth();
|
|
16
|
-
return {
|
|
17
|
-
userId: res.userId ?? null,
|
|
18
|
-
sessionId: res.sessionId ?? null,
|
|
19
|
-
raw: res,
|
|
20
|
-
};
|
|
21
|
-
} catch {
|
|
22
|
-
return { userId: null, sessionId: null, raw: null };
|
|
23
|
-
}
|
|
24
|
-
}
|