@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,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
- }