@windrun-huaiin/base-ui 6.0.3 → 7.1.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/components/index.d.cts +1 -28
- package/dist/components/index.d.ts +1 -28
- package/dist/components/index.js +28 -199
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +27 -198
- package/dist/components/index.mjs.map +1 -1
- package/dist/components/server.d.cts +15 -1
- package/dist/components/server.d.ts +15 -1
- package/dist/components/server.js +87 -1
- package/dist/components/server.js.map +1 -1
- package/dist/components/server.mjs +87 -1
- package/dist/components/server.mjs.map +1 -1
- package/dist/ui/index.d.cts +0 -9
- package/dist/ui/index.d.ts +0 -9
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +1 -0
- package/dist/ui/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +0 -4
- package/src/components/script/google-analytics-client.tsx +53 -0
- package/src/components/script/google-analytics-script.tsx +12 -47
- package/src/components/script/microsoft-clarity-client.tsx +21 -0
- package/src/components/script/microsoft-clarity-script.tsx +11 -17
- package/src/components/server.ts +4 -0
- package/src/ui/button.tsx +2 -0
- package/src/ui/language-button.tsx +1 -1
- package/src/components/language-detector.tsx +0 -175
package/package.json
CHANGED
package/src/components/index.ts
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
"use client"
|
2
2
|
export * from './404-page';
|
3
|
-
export * from './language-detector';
|
4
3
|
export * from './language-switcher';
|
5
4
|
|
6
|
-
// Script Components (All Client-side)
|
7
|
-
export * from './script/google-analytics-script';
|
8
|
-
export * from './script/microsoft-clarity-script';
|
9
5
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
"use client";
|
3
|
+
|
4
|
+
import Script from "next/script";
|
5
|
+
|
6
|
+
interface GoogleAnalyticsClientProps {
|
7
|
+
analyticsId: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
export function GoogleAnalyticsClient({ analyticsId }: GoogleAnalyticsClientProps) {
|
11
|
+
return (
|
12
|
+
<>
|
13
|
+
<Script
|
14
|
+
strategy="afterInteractive"
|
15
|
+
src={`https://www.googletagmanager.com/gtag/js?id=${analyticsId}`}
|
16
|
+
/>
|
17
|
+
<Script
|
18
|
+
id="google-analytics"
|
19
|
+
strategy="afterInteractive"
|
20
|
+
dangerouslySetInnerHTML={{
|
21
|
+
__html: `
|
22
|
+
window.dataLayer = window.dataLayer || [];
|
23
|
+
function gtag(){dataLayer.push(arguments);}
|
24
|
+
gtag('js', new Date());
|
25
|
+
gtag('config', '${analyticsId}');
|
26
|
+
`,
|
27
|
+
}}
|
28
|
+
/>
|
29
|
+
</>
|
30
|
+
);
|
31
|
+
}
|
32
|
+
|
33
|
+
export function useGoogleAnalytics() {
|
34
|
+
const trackEvent = (event: string, data?: Record<string, unknown>) => {
|
35
|
+
if (typeof window === "undefined" || !window.gtag) {
|
36
|
+
return;
|
37
|
+
}
|
38
|
+
|
39
|
+
window.gtag("event", event, data);
|
40
|
+
};
|
41
|
+
|
42
|
+
return {
|
43
|
+
trackEvent,
|
44
|
+
};
|
45
|
+
}
|
46
|
+
|
47
|
+
// Add gtag type definition to window
|
48
|
+
declare global {
|
49
|
+
interface Window {
|
50
|
+
dataLayer: any[];
|
51
|
+
gtag: (...args: any[]) => void;
|
52
|
+
}
|
53
|
+
}
|
@@ -1,56 +1,21 @@
|
|
1
|
-
|
2
|
-
"use client";
|
3
|
-
|
4
|
-
import Script from "next/script";
|
5
|
-
|
6
|
-
const googleAnalyticsId = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID!;
|
1
|
+
import { GoogleAnalyticsClient } from './google-analytics-client';
|
7
2
|
|
8
3
|
export function GoogleAnalyticsScript() {
|
9
4
|
// Only load in production environment
|
10
5
|
if (process.env.NODE_ENV !== 'production') {
|
11
|
-
return null
|
6
|
+
return null;
|
12
7
|
}
|
13
|
-
|
14
|
-
return (
|
15
|
-
<>
|
16
|
-
<Script
|
17
|
-
strategy="afterInteractive"
|
18
|
-
src={`https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}`}
|
19
|
-
/>
|
20
|
-
<Script
|
21
|
-
id="google-analytics"
|
22
|
-
strategy="afterInteractive"
|
23
|
-
dangerouslySetInnerHTML={{
|
24
|
-
__html: `
|
25
|
-
window.dataLayer = window.dataLayer || [];
|
26
|
-
function gtag(){dataLayer.push(arguments);}
|
27
|
-
gtag('js', new Date());
|
28
|
-
gtag('config', '${googleAnalyticsId}');
|
29
|
-
`,
|
30
|
-
}}
|
31
|
-
/>
|
32
|
-
</>
|
33
|
-
);
|
34
|
-
}
|
35
|
-
|
36
|
-
export function useGoogleAnalytics() {
|
37
|
-
const trackEvent = (event: string, data?: Record<string, unknown>) => {
|
38
|
-
if (typeof window === "undefined" || !window.gtag) {
|
39
|
-
return;
|
40
|
-
}
|
41
8
|
|
42
|
-
|
43
|
-
|
9
|
+
// Get and validate Google Analytics ID
|
10
|
+
const googleAnalyticsId = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID;
|
11
|
+
|
12
|
+
if (!googleAnalyticsId) {
|
13
|
+
console.warn('NEXT_PUBLIC_GOOGLE_ANALYTICS_ID is not configured');
|
14
|
+
return null;
|
15
|
+
}
|
44
16
|
|
45
|
-
return {
|
46
|
-
trackEvent,
|
47
|
-
};
|
17
|
+
return <GoogleAnalyticsClient analyticsId={googleAnalyticsId} />;
|
48
18
|
}
|
49
19
|
|
50
|
-
//
|
51
|
-
|
52
|
-
interface Window {
|
53
|
-
dataLayer: any[];
|
54
|
-
gtag: (...args: any[]) => void;
|
55
|
-
}
|
56
|
-
}
|
20
|
+
// Re-export the hook for convenience
|
21
|
+
export { useGoogleAnalytics } from './google-analytics-client';
|
@@ -0,0 +1,21 @@
|
|
1
|
+
'use client'
|
2
|
+
|
3
|
+
import Script from 'next/script'
|
4
|
+
|
5
|
+
interface MicrosoftClarityClientProps {
|
6
|
+
clarityId: string;
|
7
|
+
}
|
8
|
+
|
9
|
+
export function MicrosoftClarityClient({ clarityId }: MicrosoftClarityClientProps) {
|
10
|
+
return (
|
11
|
+
<Script id="microsoft-clarity" strategy="afterInteractive">
|
12
|
+
{`
|
13
|
+
(function(c,l,a,r,i,t,y){
|
14
|
+
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
15
|
+
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
16
|
+
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
17
|
+
})(window, document, "clarity", "script", "${clarityId}");
|
18
|
+
`}
|
19
|
+
</Script>
|
20
|
+
)
|
21
|
+
}
|
@@ -1,24 +1,18 @@
|
|
1
|
-
'
|
2
|
-
|
3
|
-
import Script from 'next/script'
|
4
|
-
|
5
|
-
const microsoftClarityId = process.env.NEXT_PUBLIC_MICROSOFT_CLARITY_ID!;
|
1
|
+
import { MicrosoftClarityClient } from './microsoft-clarity-client';
|
6
2
|
|
7
3
|
export function MicrosoftClarityScript() {
|
8
4
|
// Only load in production environment
|
9
5
|
if (process.env.NODE_ENV !== 'production') {
|
10
|
-
return null
|
6
|
+
return null;
|
7
|
+
}
|
8
|
+
|
9
|
+
// Get and validate Microsoft Clarity ID
|
10
|
+
const microsoftClarityId = process.env.NEXT_PUBLIC_MICROSOFT_CLARITY_ID;
|
11
|
+
|
12
|
+
if (!microsoftClarityId) {
|
13
|
+
console.warn('NEXT_PUBLIC_MICROSOFT_CLARITY_ID is not configured');
|
14
|
+
return null;
|
11
15
|
}
|
12
16
|
|
13
|
-
return
|
14
|
-
<Script id="microsoft-clarity" strategy="afterInteractive">
|
15
|
-
{`
|
16
|
-
(function(c,l,a,r,i,t,y){
|
17
|
-
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
18
|
-
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
19
|
-
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
20
|
-
})(window, document, "clarity", "script", "${microsoftClarityId}");
|
21
|
-
`}
|
22
|
-
</Script>
|
23
|
-
)
|
17
|
+
return <MicrosoftClarityClient clarityId={microsoftClarityId} />;
|
24
18
|
}
|
package/src/components/server.ts
CHANGED
package/src/ui/button.tsx
CHANGED
@@ -1,175 +0,0 @@
|
|
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
|
-
}
|