@windrun-huaiin/third-ui 5.14.1 → 6.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 (70) hide show
  1. package/dist/clerk/index.d.mts +2 -21
  2. package/dist/clerk/index.d.ts +2 -21
  3. package/dist/clerk/index.js +5 -2884
  4. package/dist/clerk/index.js.map +1 -1
  5. package/dist/clerk/index.mjs +3 -2872
  6. package/dist/clerk/index.mjs.map +1 -1
  7. package/dist/clerk/server.d.mts +28 -0
  8. package/dist/clerk/server.d.ts +28 -0
  9. package/dist/clerk/server.js +3025 -0
  10. package/dist/clerk/server.js.map +1 -0
  11. package/dist/clerk/server.mjs +2991 -0
  12. package/dist/clerk/server.mjs.map +1 -0
  13. package/dist/fuma/mdx/index.d.mts +1 -12
  14. package/dist/fuma/mdx/index.d.ts +1 -12
  15. package/dist/fuma/mdx/index.js +49 -263
  16. package/dist/fuma/mdx/index.js.map +1 -1
  17. package/dist/fuma/mdx/index.mjs +50 -262
  18. package/dist/fuma/mdx/index.mjs.map +1 -1
  19. package/dist/fuma/server.d.mts +15 -2
  20. package/dist/fuma/server.d.ts +15 -2
  21. package/dist/fuma/server.js +234 -49
  22. package/dist/fuma/server.js.map +1 -1
  23. package/dist/fuma/server.mjs +231 -48
  24. package/dist/fuma/server.mjs.map +1 -1
  25. package/dist/lib/server.d.mts +509 -465
  26. package/dist/lib/server.d.ts +509 -465
  27. package/dist/main/index.d.mts +5 -56
  28. package/dist/main/index.d.ts +5 -56
  29. package/dist/main/index.js +646 -1322
  30. package/dist/main/index.js.map +1 -1
  31. package/dist/main/index.mjs +675 -1342
  32. package/dist/main/index.mjs.map +1 -1
  33. package/dist/main/server.d.mts +64 -0
  34. package/dist/main/server.d.ts +64 -0
  35. package/dist/main/server.js +4166 -0
  36. package/dist/main/server.js.map +1 -0
  37. package/dist/main/server.mjs +4128 -0
  38. package/dist/main/server.mjs.map +1 -0
  39. package/package.json +12 -2
  40. package/src/clerk/clerk-organization-client.tsx +50 -0
  41. package/src/clerk/clerk-organization.tsx +21 -38
  42. package/src/clerk/clerk-page-generator.tsx +0 -2
  43. package/src/clerk/clerk-provider-client.tsx +1 -1
  44. package/src/clerk/clerk-user-client.tsx +64 -0
  45. package/src/clerk/clerk-user.tsx +29 -58
  46. package/src/clerk/index.ts +1 -4
  47. package/src/clerk/server.ts +3 -0
  48. package/src/fuma/{mdx/fuma-banner-suit.tsx → fuma-banner-suit.tsx} +3 -6
  49. package/src/fuma/mdx/banner.tsx +0 -1
  50. package/src/fuma/mdx/index.ts +0 -2
  51. package/src/fuma/mdx/mermaid.tsx +3 -1
  52. package/src/fuma/mdx/toc-footer-wrapper.tsx +1 -0
  53. package/src/fuma/mdx/zia-file.tsx +0 -1
  54. package/src/fuma/server.ts +3 -1
  55. package/src/fuma/{mdx/site-x.tsx → site-x.tsx} +4 -5
  56. package/src/main/cta.tsx +33 -10
  57. package/src/main/faq-interactive.tsx +68 -0
  58. package/src/main/faq.tsx +62 -38
  59. package/src/main/features.tsx +40 -11
  60. package/src/main/footer.tsx +27 -16
  61. package/src/main/gallery-interactive.tsx +171 -0
  62. package/src/main/gallery.tsx +54 -101
  63. package/src/main/index.ts +1 -10
  64. package/src/main/language-detector.tsx +175 -0
  65. package/src/main/price-plan-interactive.tsx +273 -0
  66. package/src/main/price-plan.tsx +112 -129
  67. package/src/main/seo-content.tsx +46 -13
  68. package/src/main/server.ts +10 -0
  69. package/src/main/tips.tsx +48 -22
  70. package/src/main/usage.tsx +43 -11
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @license
3
+ * MIT License
4
+ * Copyright (c) 2025 D8ger
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ 'use client'
10
+
11
+ import { globalLucideIcons as icons } from "@base-ui/components/global-icon"
12
+ import { useLocale, useTranslations } from 'next-intl'
13
+ import { useRouter } from 'next/navigation'
14
+ import { useEffect, useState } from 'react'
15
+
16
+ type I18nConfig = {
17
+ locales: readonly string[];
18
+ detector: {
19
+ storagePrefix: string;
20
+ storageKey: string;
21
+ autoCloseTimeout: number;
22
+ expirationDays: number;
23
+ };
24
+ };
25
+
26
+ interface LanguageDetectorProps {
27
+ i18nConfig: I18nConfig;
28
+ }
29
+
30
+ type Locale = string;
31
+
32
+ interface LanguagePreference {
33
+ locale: string;
34
+ status: 'accepted' | 'rejected';
35
+ timestamp: number;
36
+ }
37
+
38
+ export function LanguageDetector({ i18nConfig }: LanguageDetectorProps) {
39
+ const [show, setShow] = useState(false)
40
+ const [detectedLocale, setDetectedLocale] = useState<Locale | null>(null)
41
+ const currentLocale = useLocale()
42
+ const router = useRouter()
43
+ const t = useTranslations('languageDetection')
44
+
45
+ // Get the storage key from the configuration
46
+ const LANGUAGE_PREFERENCE_KEY = `${i18nConfig.detector.storagePrefix}-${i18nConfig.detector.storageKey}`
47
+
48
+ useEffect(() => {
49
+ // Get the browser language
50
+ const browserLang = navigator.language.split('-')[0] as Locale
51
+
52
+ // Get the language preference from localStorage
53
+ const savedPreference = localStorage.getItem(LANGUAGE_PREFERENCE_KEY)
54
+ const preference: LanguagePreference | null = savedPreference
55
+ ? JSON.parse(savedPreference)
56
+ : null
57
+
58
+ // Check if the language detection box should be displayed
59
+ const shouldShowDetector = () => {
60
+ if (!preference) return true;
61
+
62
+ // If the stored language is the same as the current language, do not display the detection box
63
+ if (preference.locale === currentLocale) return false;
64
+
65
+ // If the user has previously rejected switching to this language, do not display the detection box
66
+ if (preference.status === 'rejected' && preference.locale === browserLang) return false;
67
+
68
+ // If the user has previously accepted switching to this language, do not display the detection box
69
+ if (preference.status === 'accepted' && preference.locale === currentLocale) return false;
70
+
71
+ // Use the expiration time from the configuration
72
+ const expirationMs = i18nConfig.detector.expirationDays * 24 * 60 * 60 * 1000;
73
+ if (Date.now() - preference.timestamp < expirationMs) return false;
74
+
75
+ return true;
76
+ }
77
+
78
+ // Check if the browser language is in the supported language list and needs to display the detection box
79
+ if ((i18nConfig.locales as string[]).includes(browserLang) &&
80
+ browserLang !== currentLocale &&
81
+ shouldShowDetector()) {
82
+ setDetectedLocale(browserLang)
83
+ setShow(true)
84
+
85
+ // Use the automatic closing time from the configuration
86
+ const timer = setTimeout(() => {
87
+ console.log('[LanguageDetector] Auto closing after timeout')
88
+ setShow(false)
89
+ // Save the rejected state when the automatic closing occurs
90
+ savePreference(browserLang, 'rejected')
91
+ }, i18nConfig.detector.autoCloseTimeout)
92
+
93
+ return () => clearTimeout(timer)
94
+ }
95
+ }, [currentLocale])
96
+
97
+ // Save the language preference to localStorage
98
+ const savePreference = (locale: string, status: 'accepted' | 'rejected') => {
99
+ const preference: LanguagePreference = {
100
+ locale,
101
+ status,
102
+ timestamp: Date.now()
103
+ }
104
+ localStorage.setItem(LANGUAGE_PREFERENCE_KEY, JSON.stringify(preference))
105
+ }
106
+
107
+ const handleLanguageChange = () => {
108
+ if (detectedLocale) {
109
+ // Save the accepted state
110
+ savePreference(detectedLocale, 'accepted')
111
+
112
+ // Get the current path
113
+ const pathname = window.location.pathname
114
+ // Replace the language part
115
+ const newPathname = pathname.replace(`/${currentLocale}`, `/${detectedLocale}`)
116
+ // Redirect to the new path
117
+ router.push(newPathname)
118
+ setShow(false)
119
+ }
120
+ }
121
+
122
+ const handleClose = () => {
123
+ if (detectedLocale) {
124
+ // Save the rejected state
125
+ savePreference(detectedLocale, 'rejected')
126
+ }
127
+ setShow(false)
128
+ }
129
+
130
+ if (!detectedLocale || !show) return null
131
+
132
+ return (
133
+ <div className="fixed top-16 right-4 z-40 w-[420px]">
134
+ <div className={`shadow-lg rounded-lg transition-all duration-300 ${show ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
135
+ bg-linear-to-r from-purple-100/95 via-white/95 to-purple-100/95 backdrop-blur-xs
136
+ animate-gradient-x`}>
137
+ <div className="relative px-6 py-4 overflow-hidden">
138
+ <div className="relative z-10 flex flex-col gap-3">
139
+ <div className="flex items-start justify-between gap-4">
140
+ <div className="flex flex-col gap-1.5">
141
+ <h3 className="text-lg font-semibold text-gray-900">
142
+ {t('title')}
143
+ </h3>
144
+ <p className="text-base text-gray-600">
145
+ {t('description')} <span className="text-purple-500 font-semibold">{detectedLocale === 'zh' ? '中文' : 'English'}</span>?
146
+ </p>
147
+ </div>
148
+ <button
149
+ onClick={handleClose}
150
+ className="text-gray-500 hover:text-gray-700"
151
+ >
152
+ <icons.X className="h-5 w-5" />
153
+ </button>
154
+ </div>
155
+ <div className="flex items-center gap-3">
156
+ <button
157
+ onClick={handleClose}
158
+ className="flex-1 px-4 py-2 text-base bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200"
159
+ >
160
+ {t('close')}
161
+ </button>
162
+ <button
163
+ onClick={handleLanguageChange}
164
+ className="flex-1 px-4 py-2 text-base bg-purple-500 text-white rounded-md hover:bg-purple-600"
165
+ >
166
+ {t('changeAction')}
167
+ </button>
168
+ </div>
169
+ </div>
170
+ <div className="absolute inset-0 bg-linear-to-r from-transparent via-purple-200/30 to-transparent animate-shimmer" />
171
+ </div>
172
+ </div>
173
+ </div>
174
+ )
175
+ }
@@ -0,0 +1,273 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ 'use client';
3
+
4
+ import React, { useState, useEffect } from 'react';
5
+ import { useRouter } from 'next/navigation';
6
+ import { cn } from '@lib/utils';
7
+
8
+ // Import interfaces from the main component
9
+ interface BillingOption {
10
+ key: string;
11
+ discount: number;
12
+ }
13
+
14
+ interface Prices {
15
+ [key: string]: number | string;
16
+ }
17
+
18
+ interface PricePlanAppConfig {
19
+ billingOptions: BillingOption[];
20
+ prices: Prices;
21
+ minPlanFeaturesCount: number;
22
+ }
23
+
24
+ interface PricePlanData {
25
+ title: string;
26
+ subtitle: string;
27
+ billingSwitch: {
28
+ options: Array<{
29
+ key: string;
30
+ name: string;
31
+ unit: string;
32
+ discountText: string;
33
+ subTitle?: string;
34
+ }>;
35
+ defaultKey: string;
36
+ };
37
+ plans: Array<any>;
38
+ currency: string;
39
+ pricePlanConfig: PricePlanAppConfig;
40
+ }
41
+
42
+ export function PricePlanInteractive({ data }: { data: PricePlanData }) {
43
+ const [billingKey, setBillingKey] = useState(data.billingSwitch.defaultKey);
44
+ const [tooltip, setTooltip] = useState<{
45
+ show: boolean;
46
+ content: string;
47
+ x: number;
48
+ y: number;
49
+ }>({ show: false, content: '', x: 0, y: 0 });
50
+
51
+ const router = useRouter();
52
+
53
+ useEffect(() => {
54
+ // Progressive enhancement: Add billing switch functionality
55
+ const monthlyButton = document.querySelector('[data-billing-button="monthly"]') as HTMLButtonElement;
56
+ const yearlyButton = document.querySelector('[data-billing-button="yearly"]') as HTMLButtonElement;
57
+
58
+ const handleBillingSwitch = (newBillingKey: string) => {
59
+ setBillingKey(newBillingKey);
60
+ updatePrices(newBillingKey);
61
+ updateDiscountInfo(newBillingKey);
62
+ updateButtonStyles(newBillingKey);
63
+ };
64
+
65
+ if (monthlyButton) {
66
+ monthlyButton.addEventListener('click', () => handleBillingSwitch('monthly'));
67
+ }
68
+ if (yearlyButton) {
69
+ yearlyButton.addEventListener('click', () => handleBillingSwitch('yearly'));
70
+ }
71
+
72
+ // Add tooltip functionality
73
+ data.plans.forEach((plan: any) => {
74
+ plan.features?.forEach((feature: any, i: number) => {
75
+ if (feature?.tooltip) {
76
+ const tooltipTrigger = document.querySelector(`[data-tooltip-trigger="${plan.key}-${i}"]`) as HTMLElement;
77
+ if (tooltipTrigger) {
78
+ const handleMouseEnter = (e: MouseEvent) => {
79
+ setTooltip({
80
+ show: true,
81
+ content: feature.tooltip,
82
+ x: e.clientX,
83
+ y: e.clientY
84
+ });
85
+ };
86
+
87
+ const handleMouseMove = (e: MouseEvent) => {
88
+ setTooltip(prev => ({ ...prev, x: e.clientX, y: e.clientY }));
89
+ };
90
+
91
+ const handleMouseLeave = () => {
92
+ setTooltip(prev => ({ ...prev, show: false }));
93
+ };
94
+
95
+ tooltipTrigger.addEventListener('mouseenter', handleMouseEnter);
96
+ tooltipTrigger.addEventListener('mousemove', handleMouseMove);
97
+ tooltipTrigger.addEventListener('mouseleave', handleMouseLeave);
98
+ }
99
+ }
100
+ });
101
+ });
102
+
103
+ // Add plan button functionality
104
+ data.plans.forEach((plan: any) => {
105
+ const planButton = document.querySelector(`[data-plan-button="${plan.key}"]`) as HTMLButtonElement;
106
+ if (planButton && !plan.button?.disabled) {
107
+ planButton.addEventListener('click', () => {
108
+ router.push('/');
109
+ });
110
+ }
111
+ });
112
+
113
+ // Cleanup
114
+ return () => {
115
+ if (monthlyButton) {
116
+ const newButton = monthlyButton.cloneNode(true);
117
+ monthlyButton.parentNode?.replaceChild(newButton, monthlyButton);
118
+ }
119
+ if (yearlyButton) {
120
+ const newButton = yearlyButton.cloneNode(true);
121
+ yearlyButton.parentNode?.replaceChild(newButton, yearlyButton);
122
+ }
123
+
124
+ // Cleanup tooltip events
125
+ data.plans.forEach((plan: any) => {
126
+ plan.features?.forEach((_feature: any, i: number) => {
127
+ const tooltipTrigger = document.querySelector(`[data-tooltip-trigger="${plan.key}-${i}"]`) as HTMLElement;
128
+ if (tooltipTrigger) {
129
+ const newTrigger = tooltipTrigger.cloneNode(true);
130
+ tooltipTrigger.parentNode?.replaceChild(newTrigger, tooltipTrigger);
131
+ }
132
+ });
133
+ });
134
+ };
135
+ }, [data, router]);
136
+
137
+ const updatePrices = (newBillingKey: string) => {
138
+ const currentBilling = data.pricePlanConfig.billingOptions.find((opt: any) => opt.key === newBillingKey) || data.pricePlanConfig.billingOptions[0];
139
+ const currentBillingDisplay = data.billingSwitch.options.find((opt: any) => opt.key === newBillingKey) || data.billingSwitch.options[0];
140
+
141
+ data.plans.forEach((plan: any) => {
142
+ const priceContainer = document.querySelector(`[data-price-container="${plan.key}"]`) as HTMLElement;
143
+ const priceValue = data.pricePlanConfig.prices[plan.key];
144
+
145
+ if (priceContainer) {
146
+ // Update price display based on new billing
147
+ const priceValueElement = document.querySelector(`[data-price-value="${plan.key}"]`) as HTMLElement;
148
+ const priceUnitElement = document.querySelector(`[data-price-unit="${plan.key}"]`) as HTMLElement;
149
+ const priceOriginalElement = document.querySelector(`[data-price-original="${plan.key}"]`) as HTMLElement;
150
+ const priceDiscountElement = document.querySelector(`[data-price-discount="${plan.key}"]`) as HTMLElement;
151
+ const priceSubtitleElement = document.querySelector(`[data-price-subtitle="${plan.key}"]`) as HTMLElement;
152
+
153
+ if (typeof priceValue !== 'number' || isNaN(priceValue)) {
154
+ // Non-numeric price
155
+ if (priceValueElement) priceValueElement.textContent = String(priceValue);
156
+ if (priceSubtitleElement) priceSubtitleElement.textContent = plan.showBillingSubTitle === false ? '' : (currentBillingDisplay?.subTitle || '');
157
+ } else {
158
+ // Numeric price
159
+ const originValue = Number(priceValue);
160
+ const discount = currentBilling.discount;
161
+ const hasDiscount = discount !== 0;
162
+ const saleValue = originValue * (1 - discount);
163
+ const formatPrice = (v: number) => Number(v.toFixed(2)).toString();
164
+ const showNaN = saleValue < 0;
165
+
166
+ if (priceValueElement) {
167
+ priceValueElement.textContent = `${data.currency}${showNaN ? 'NaN' : (hasDiscount ? formatPrice(saleValue) : formatPrice(originValue))}`;
168
+ }
169
+ if (priceUnitElement) {
170
+ priceUnitElement.textContent = currentBillingDisplay.unit || '';
171
+ }
172
+
173
+ // Handle discount display
174
+ if (hasDiscount) {
175
+ if (priceOriginalElement) {
176
+ priceOriginalElement.textContent = `${data.currency}${showNaN ? 'NaN' : formatPrice(originValue)}`;
177
+ priceOriginalElement.style.display = 'inline';
178
+ }
179
+ if (priceDiscountElement && currentBillingDisplay.discountText) {
180
+ const discountText = currentBillingDisplay.discountText.replace('{percent}', String(Math.round(Math.abs(discount) * 100)));
181
+ priceDiscountElement.textContent = discountText;
182
+ priceDiscountElement.style.display = 'inline';
183
+ }
184
+ } else {
185
+ if (priceOriginalElement) priceOriginalElement.style.display = 'none';
186
+ if (priceDiscountElement) priceDiscountElement.style.display = 'none';
187
+ }
188
+
189
+ if (priceSubtitleElement) {
190
+ priceSubtitleElement.textContent = plan.showBillingSubTitle === false ? '' : (currentBillingDisplay?.subTitle || '');
191
+ }
192
+ }
193
+ }
194
+ });
195
+ };
196
+
197
+ const updateDiscountInfo = (newBillingKey: string) => {
198
+ const discountInfoElement = document.querySelector('[data-discount-info]') as HTMLElement;
199
+ if (discountInfoElement) {
200
+ const opt = data.billingSwitch.options.find((opt: any) => opt.key === newBillingKey);
201
+ const bOpt = data.pricePlanConfig.billingOptions.find((opt: any) => opt.key === newBillingKey);
202
+
203
+ if (opt && bOpt && opt.discountText && bOpt.discount !== 0) {
204
+ const discountText = opt.discountText.replace('{percent}', String(Math.round(Math.abs(bOpt.discount) * 100)));
205
+ discountInfoElement.innerHTML = `
206
+ <span class="px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap">
207
+ ${discountText}
208
+ </span>
209
+ `;
210
+ } else {
211
+ discountInfoElement.innerHTML = '';
212
+ }
213
+ }
214
+ };
215
+
216
+ const updateButtonStyles = (newBillingKey: string) => {
217
+ const monthlyButton = document.querySelector('[data-billing-button="monthly"]') as HTMLElement;
218
+ const yearlyButton = document.querySelector('[data-billing-button="yearly"]') as HTMLElement;
219
+
220
+ if (monthlyButton) {
221
+ if (newBillingKey === 'monthly') {
222
+ monthlyButton.className = cn(
223
+ 'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
224
+ 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
225
+ );
226
+ } else {
227
+ monthlyButton.className = cn(
228
+ 'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
229
+ 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'
230
+ );
231
+ }
232
+ }
233
+
234
+ if (yearlyButton) {
235
+ if (newBillingKey === 'yearly') {
236
+ yearlyButton.className = cn(
237
+ 'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
238
+ 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
239
+ );
240
+ } else {
241
+ yearlyButton.className = cn(
242
+ 'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
243
+ 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'
244
+ );
245
+ }
246
+ }
247
+ };
248
+
249
+ // Tooltip component
250
+ const Tooltip = ({ show, content, x, y }: typeof tooltip) => {
251
+ if (!show) return null;
252
+ const style: React.CSSProperties = {
253
+ position: 'fixed',
254
+ left: Math.max(8, x),
255
+ top: Math.max(8, y),
256
+ zIndex: 9999,
257
+ maxWidth: 200,
258
+ transform: 'translateY(-50%)',
259
+ pointerEvents: 'none',
260
+ whiteSpace: 'pre-line',
261
+ };
262
+ return (
263
+ <div
264
+ style={style}
265
+ className="bg-gray-700 dark:bg-gray-200 text-gray-100 dark:text-gray-800 text-xs leading-relaxed px-3 py-2 rounded-lg shadow-lg border border-gray-300 dark:border-gray-600 backdrop-blur-sm"
266
+ >
267
+ {content}
268
+ </div>
269
+ );
270
+ };
271
+
272
+ return <Tooltip {...tooltip} />;
273
+ }