@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.
- package/dist/clerk/index.d.mts +2 -21
- package/dist/clerk/index.d.ts +2 -21
- package/dist/clerk/index.js +5 -2884
- package/dist/clerk/index.js.map +1 -1
- package/dist/clerk/index.mjs +3 -2872
- package/dist/clerk/index.mjs.map +1 -1
- package/dist/clerk/server.d.mts +28 -0
- package/dist/clerk/server.d.ts +28 -0
- package/dist/clerk/server.js +3025 -0
- package/dist/clerk/server.js.map +1 -0
- package/dist/clerk/server.mjs +2991 -0
- package/dist/clerk/server.mjs.map +1 -0
- package/dist/fuma/mdx/index.d.mts +1 -12
- package/dist/fuma/mdx/index.d.ts +1 -12
- package/dist/fuma/mdx/index.js +49 -263
- package/dist/fuma/mdx/index.js.map +1 -1
- package/dist/fuma/mdx/index.mjs +50 -262
- package/dist/fuma/mdx/index.mjs.map +1 -1
- package/dist/fuma/server.d.mts +15 -2
- package/dist/fuma/server.d.ts +15 -2
- package/dist/fuma/server.js +234 -49
- package/dist/fuma/server.js.map +1 -1
- package/dist/fuma/server.mjs +231 -48
- package/dist/fuma/server.mjs.map +1 -1
- package/dist/lib/server.d.mts +509 -465
- package/dist/lib/server.d.ts +509 -465
- package/dist/main/index.d.mts +5 -56
- package/dist/main/index.d.ts +5 -56
- package/dist/main/index.js +646 -1322
- package/dist/main/index.js.map +1 -1
- package/dist/main/index.mjs +675 -1342
- package/dist/main/index.mjs.map +1 -1
- package/dist/main/server.d.mts +64 -0
- package/dist/main/server.d.ts +64 -0
- package/dist/main/server.js +4166 -0
- package/dist/main/server.js.map +1 -0
- package/dist/main/server.mjs +4128 -0
- package/dist/main/server.mjs.map +1 -0
- package/package.json +12 -2
- package/src/clerk/clerk-organization-client.tsx +50 -0
- package/src/clerk/clerk-organization.tsx +21 -38
- package/src/clerk/clerk-page-generator.tsx +0 -2
- package/src/clerk/clerk-provider-client.tsx +1 -1
- package/src/clerk/clerk-user-client.tsx +64 -0
- package/src/clerk/clerk-user.tsx +29 -58
- package/src/clerk/index.ts +1 -4
- package/src/clerk/server.ts +3 -0
- package/src/fuma/{mdx/fuma-banner-suit.tsx → fuma-banner-suit.tsx} +3 -6
- package/src/fuma/mdx/banner.tsx +0 -1
- package/src/fuma/mdx/index.ts +0 -2
- package/src/fuma/mdx/mermaid.tsx +3 -1
- package/src/fuma/mdx/toc-footer-wrapper.tsx +1 -0
- package/src/fuma/mdx/zia-file.tsx +0 -1
- package/src/fuma/server.ts +3 -1
- package/src/fuma/{mdx/site-x.tsx → site-x.tsx} +4 -5
- package/src/main/cta.tsx +33 -10
- package/src/main/faq-interactive.tsx +68 -0
- package/src/main/faq.tsx +62 -38
- package/src/main/features.tsx +40 -11
- package/src/main/footer.tsx +27 -16
- package/src/main/gallery-interactive.tsx +171 -0
- package/src/main/gallery.tsx +54 -101
- package/src/main/index.ts +1 -10
- package/src/main/language-detector.tsx +175 -0
- package/src/main/price-plan-interactive.tsx +273 -0
- package/src/main/price-plan.tsx +112 -129
- package/src/main/seo-content.tsx +46 -13
- package/src/main/server.ts +10 -0
- package/src/main/tips.tsx +48 -22
- package/src/main/usage.tsx +43 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -11,11 +11,21 @@
|
|
|
11
11
|
"import": "./dist/clerk/index.mjs",
|
|
12
12
|
"require": "./dist/clerk/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./clerk/server": {
|
|
15
|
+
"types": "./dist/clerk/server.d.ts",
|
|
16
|
+
"import": "./dist/clerk/server.mjs",
|
|
17
|
+
"require": "./dist/clerk/server.js"
|
|
18
|
+
},
|
|
14
19
|
"./main": {
|
|
15
20
|
"types": "./dist/main/index.d.ts",
|
|
16
21
|
"import": "./dist/main/index.mjs",
|
|
17
22
|
"require": "./dist/main/index.js"
|
|
18
23
|
},
|
|
24
|
+
"./main/server": {
|
|
25
|
+
"types": "./dist/main/server.d.ts",
|
|
26
|
+
"import": "./dist/main/server.mjs",
|
|
27
|
+
"require": "./dist/main/server.js"
|
|
28
|
+
},
|
|
19
29
|
"./fuma/server": {
|
|
20
30
|
"types": "./dist/fuma/server.d.ts",
|
|
21
31
|
"import": "./dist/fuma/server.mjs",
|
|
@@ -54,7 +64,7 @@
|
|
|
54
64
|
"mermaid": "^11.6.0",
|
|
55
65
|
"react-medium-image-zoom": "^5.2.14",
|
|
56
66
|
"zod": "^3.22.4",
|
|
57
|
-
"@windrun-huaiin/base-ui": "^
|
|
67
|
+
"@windrun-huaiin/base-ui": "^7.0.0"
|
|
58
68
|
},
|
|
59
69
|
"peerDependencies": {
|
|
60
70
|
"react": "19.1.0",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { OrganizationSwitcher } from '@clerk/nextjs';
|
|
4
|
+
import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
|
|
5
|
+
|
|
6
|
+
interface ClerkOrganizationData {
|
|
7
|
+
homepage: string;
|
|
8
|
+
terms: string;
|
|
9
|
+
privacy: string;
|
|
10
|
+
locale: string;
|
|
11
|
+
className: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ClerkOrganizationClient({ data }: { data: ClerkOrganizationData }) {
|
|
15
|
+
return (
|
|
16
|
+
<div className={` ms-3 me-2 flex items-center h-10 rounded-full border shadow-lg ${data.className}`}>
|
|
17
|
+
<div className="flex items-center gap-x-4 w-full min-w-40">
|
|
18
|
+
<OrganizationSwitcher
|
|
19
|
+
appearance={{
|
|
20
|
+
elements: {
|
|
21
|
+
organizationSwitcherTrigger:
|
|
22
|
+
"w-40 h-10 border !rounded-full bg-transparent flex items-center justify-between box-border",
|
|
23
|
+
organizationSwitcherTriggerIcon: "",
|
|
24
|
+
userButtonAvatarBox: "w-8 h-8 border rounded-full",
|
|
25
|
+
},
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<OrganizationSwitcher.OrganizationProfilePage
|
|
29
|
+
label={data.homepage}
|
|
30
|
+
url="/"
|
|
31
|
+
labelIcon={<icons.D8 />}
|
|
32
|
+
/>
|
|
33
|
+
<OrganizationSwitcher.OrganizationProfilePage
|
|
34
|
+
labelIcon={<icons.ReceiptText />}
|
|
35
|
+
label={data.terms}
|
|
36
|
+
url={`/${data.locale}/legal/terms`}
|
|
37
|
+
>
|
|
38
|
+
</OrganizationSwitcher.OrganizationProfilePage>
|
|
39
|
+
|
|
40
|
+
<OrganizationSwitcher.OrganizationProfilePage
|
|
41
|
+
labelIcon={<icons.ShieldUser />}
|
|
42
|
+
label={data.privacy}
|
|
43
|
+
url={`/${data.locale}/legal/privacy`}
|
|
44
|
+
>
|
|
45
|
+
</OrganizationSwitcher.OrganizationProfilePage>
|
|
46
|
+
</OrganizationSwitcher>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,49 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
import { getTranslations } from 'next-intl/server';
|
|
2
|
+
import { ClerkOrganizationClient } from './clerk-organization-client';
|
|
2
3
|
|
|
3
|
-
import { OrganizationSwitcher } from '@clerk/nextjs';
|
|
4
|
-
import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
|
|
5
4
|
interface ClerkOrganizationProps {
|
|
6
5
|
className?: string;
|
|
7
6
|
locale: string;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
interface ClerkOrganizationData {
|
|
10
|
+
homepage: string;
|
|
11
|
+
terms: string;
|
|
12
|
+
privacy: string;
|
|
13
|
+
locale: string;
|
|
14
|
+
className: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function ClerkOrganization({
|
|
11
18
|
locale,
|
|
12
19
|
className = '',
|
|
13
20
|
}: ClerkOrganizationProps) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
userButtonAvatarBox: "w-8 h-8 border rounded-full",
|
|
24
|
-
},
|
|
25
|
-
}}
|
|
26
|
-
>
|
|
27
|
-
<OrganizationSwitcher.OrganizationProfilePage
|
|
28
|
-
label="Homepage"
|
|
29
|
-
url="/"
|
|
30
|
-
labelIcon={<icons.D8 />}
|
|
31
|
-
/>
|
|
32
|
-
<OrganizationSwitcher.OrganizationProfilePage
|
|
33
|
-
labelIcon={<icons.ReceiptText />}
|
|
34
|
-
label="服务"
|
|
35
|
-
url={`/${locale}/legal/terms`}
|
|
36
|
-
>
|
|
37
|
-
</OrganizationSwitcher.OrganizationProfilePage>
|
|
21
|
+
const t = await getTranslations({ locale, namespace: 'footer' });
|
|
22
|
+
|
|
23
|
+
const data: ClerkOrganizationData = {
|
|
24
|
+
homepage: 'Homepage',
|
|
25
|
+
terms: t('terms'),
|
|
26
|
+
privacy: t('privacy'),
|
|
27
|
+
locale,
|
|
28
|
+
className
|
|
29
|
+
};
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
labelIcon={<icons.ShieldUser />}
|
|
41
|
-
label="隐私"
|
|
42
|
-
url={`/${locale}/legal/privacy`}
|
|
43
|
-
>
|
|
44
|
-
</OrganizationSwitcher.OrganizationProfilePage>
|
|
45
|
-
</OrganizationSwitcher>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
31
|
+
return <ClerkOrganizationClient data={data} />;
|
|
49
32
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
|
|
4
|
+
import { ClerkLoaded, ClerkLoading, SignedIn, SignedOut, SignInButton, SignUpButton, UserButton } from "@clerk/nextjs";
|
|
5
|
+
|
|
6
|
+
interface ClerkUserData {
|
|
7
|
+
signIn: string;
|
|
8
|
+
signUp: string;
|
|
9
|
+
terms: string;
|
|
10
|
+
privacy: string;
|
|
11
|
+
locale: string;
|
|
12
|
+
clerkAuthInModal: boolean;
|
|
13
|
+
showSignUp: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ClerkUserClient({ data }: { data: ClerkUserData }) {
|
|
17
|
+
return (
|
|
18
|
+
<div className="ms-1.5 flex items-center gap-2 h-10 me-3">
|
|
19
|
+
<ClerkLoading>
|
|
20
|
+
<div className="w-20 h-9 px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-sm"></div>
|
|
21
|
+
</ClerkLoading>
|
|
22
|
+
<ClerkLoaded>
|
|
23
|
+
<SignedOut>
|
|
24
|
+
<SignInButton mode={data.clerkAuthInModal ? 'modal' : 'redirect'}>
|
|
25
|
+
<button className="w-20 h-9 px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-sm">
|
|
26
|
+
{data.signIn}
|
|
27
|
+
</button>
|
|
28
|
+
</SignInButton>
|
|
29
|
+
{data.showSignUp && (
|
|
30
|
+
<SignUpButton mode={data.clerkAuthInModal ? 'modal' : 'redirect'}>
|
|
31
|
+
<button className="w-20 h-9 px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-sm">
|
|
32
|
+
{data.signUp}
|
|
33
|
+
</button>
|
|
34
|
+
</SignUpButton>
|
|
35
|
+
)}
|
|
36
|
+
</SignedOut>
|
|
37
|
+
<SignedIn>
|
|
38
|
+
<UserButton
|
|
39
|
+
appearance={{
|
|
40
|
+
elements: {
|
|
41
|
+
userButtonAvatarBox: "w-8 h-8 border",
|
|
42
|
+
}
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<UserButton.MenuItems>
|
|
46
|
+
<UserButton.Action label="manageAccount" />
|
|
47
|
+
{<UserButton.Link
|
|
48
|
+
labelIcon={<icons.ReceiptText className="size-4 fill-none stroke-[var(--clerk-icon-stroke-color)]" />}
|
|
49
|
+
label={data.terms}
|
|
50
|
+
href={`/${data.locale}/legal/terms`}>
|
|
51
|
+
</UserButton.Link>}
|
|
52
|
+
{<UserButton.Link
|
|
53
|
+
labelIcon={<icons.ShieldUser className="size-4 fill-none stroke-[var(--clerk-icon-stroke-color)]" />}
|
|
54
|
+
label={data.privacy}
|
|
55
|
+
href={`/${data.locale}/legal/privacy`}>
|
|
56
|
+
</UserButton.Link>}
|
|
57
|
+
<UserButton.Action label="signOut" />
|
|
58
|
+
</UserButton.MenuItems>
|
|
59
|
+
</UserButton>
|
|
60
|
+
</SignedIn>
|
|
61
|
+
</ClerkLoaded>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
package/src/clerk/clerk-user.tsx
CHANGED
|
@@ -1,69 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
|
|
4
|
-
import { ClerkLoaded, ClerkLoading, SignedIn, SignedOut, SignInButton, SignUpButton, UserButton } from "@clerk/nextjs";
|
|
5
|
-
import { useTranslations } from 'next-intl';
|
|
6
|
-
import { type JSX } from 'react';
|
|
1
|
+
import { getTranslations } from 'next-intl/server';
|
|
2
|
+
import { ClerkUserClient } from './clerk-user-client';
|
|
7
3
|
|
|
8
4
|
interface ClerkUserProps {
|
|
9
5
|
locale: string;
|
|
10
|
-
// default as true,
|
|
6
|
+
// default as true, 'cause Clerk direct is not well, so just use model for sign-in/sign-up
|
|
11
7
|
clerkAuthInModal?: boolean;
|
|
12
8
|
showSignUp?: boolean;
|
|
13
9
|
}
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
interface ClerkUserData {
|
|
12
|
+
signIn: string;
|
|
13
|
+
signUp: string;
|
|
14
|
+
terms: string;
|
|
15
|
+
privacy: string;
|
|
16
|
+
locale: string;
|
|
17
|
+
clerkAuthInModal: boolean;
|
|
18
|
+
showSignUp: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function ClerkUser({
|
|
16
22
|
locale,
|
|
17
23
|
clerkAuthInModal = true,
|
|
18
24
|
showSignUp = true
|
|
19
|
-
}: ClerkUserProps)
|
|
20
|
-
const t =
|
|
21
|
-
const t2 =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{showSignUp && (
|
|
35
|
-
<SignUpButton mode={clerkAuthInModal ? 'modal' : 'redirect'}>
|
|
36
|
-
<button className="w-20 h-9 px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-sm">
|
|
37
|
-
{t('signUp')}
|
|
38
|
-
</button>
|
|
39
|
-
</SignUpButton>
|
|
40
|
-
)}
|
|
41
|
-
</SignedOut>
|
|
42
|
-
<SignedIn>
|
|
43
|
-
<UserButton
|
|
44
|
-
appearance={{
|
|
45
|
-
elements: {
|
|
46
|
-
userButtonAvatarBox: "w-8 h-8 border",
|
|
47
|
-
}
|
|
48
|
-
}}
|
|
49
|
-
>
|
|
50
|
-
<UserButton.MenuItems>
|
|
51
|
-
<UserButton.Action label="manageAccount" />
|
|
52
|
-
{<UserButton.Link
|
|
53
|
-
labelIcon={<icons.ReceiptText className="size-4 fill-none stroke-[var(--clerk-icon-stroke-color)]" />}
|
|
54
|
-
label={t2('terms')}
|
|
55
|
-
href={`/${locale}/legal/terms`}>
|
|
56
|
-
</UserButton.Link>}
|
|
57
|
-
{<UserButton.Link
|
|
58
|
-
labelIcon={<icons.ShieldUser className="size-4 fill-none stroke-[var(--clerk-icon-stroke-color)]" />}
|
|
59
|
-
label={t2('privacy')}
|
|
60
|
-
href={`/${locale}/legal/privacy`}>
|
|
61
|
-
</UserButton.Link>}
|
|
62
|
-
<UserButton.Action label="signOut" />
|
|
63
|
-
</UserButton.MenuItems>
|
|
64
|
-
</UserButton>
|
|
65
|
-
</SignedIn>
|
|
66
|
-
</ClerkLoaded>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
25
|
+
}: ClerkUserProps) {
|
|
26
|
+
const t = await getTranslations({ locale, namespace: 'clerk' });
|
|
27
|
+
const t2 = await getTranslations({ locale, namespace: 'footer' });
|
|
28
|
+
|
|
29
|
+
const data: ClerkUserData = {
|
|
30
|
+
signIn: t('signIn'),
|
|
31
|
+
signUp: t('signUp'),
|
|
32
|
+
terms: t2('terms'),
|
|
33
|
+
privacy: t2('privacy'),
|
|
34
|
+
locale,
|
|
35
|
+
clerkAuthInModal,
|
|
36
|
+
showSignUp
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return <ClerkUserClient data={data} />;
|
|
69
40
|
}
|
package/src/clerk/index.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useTranslations } from 'next-intl';
|
|
1
|
+
import { getTranslations } from 'next-intl/server';
|
|
4
2
|
import { Banner } from '@third-ui/fuma/mdx/banner';
|
|
5
3
|
|
|
6
|
-
export function FumaBannerSuit({ showBanner }: { showBanner: boolean }) {
|
|
7
|
-
const t =
|
|
4
|
+
export async function FumaBannerSuit({ locale, showBanner }: { locale: string, showBanner: boolean }) {
|
|
5
|
+
const t = await getTranslations({ locale, namespace: 'home' });
|
|
8
6
|
const heightValue = showBanner ? 3 : 0.5;
|
|
9
7
|
const height= `${heightValue}rem`;
|
|
10
8
|
return (
|
|
11
9
|
<>
|
|
12
|
-
{/* 设置 header 的 top 位置为 Banner 的底部,避免间隙 */}
|
|
13
10
|
{showBanner ? (
|
|
14
11
|
<Banner variant="rainbow" changeLayout={true} height={heightValue}>
|
|
15
12
|
<p className="text-xl">{t('banner')}</p>
|
package/src/fuma/mdx/banner.tsx
CHANGED
|
@@ -53,7 +53,6 @@ export function Banner({
|
|
|
53
53
|
const [open, setOpen] = useState(true);
|
|
54
54
|
const globalKey = id ? `nd-banner-${id}` : null;
|
|
55
55
|
const bannerHeight = `${height}rem`;
|
|
56
|
-
const headerStartHeight = `${height + 5.5}rem`;
|
|
57
56
|
|
|
58
57
|
useEffect(() => {
|
|
59
58
|
if (globalKey) setOpen(localStorage.getItem(globalKey) !== 'true');
|
package/src/fuma/mdx/index.ts
CHANGED
|
@@ -7,8 +7,6 @@ export * from './image-grid';
|
|
|
7
7
|
export * from './zia-card';
|
|
8
8
|
export * from './gradient-button';
|
|
9
9
|
export * from './toc-base';
|
|
10
|
-
export * from './fuma-banner-suit';
|
|
11
10
|
export * from './fuma-github-info';
|
|
12
|
-
export * from './site-x';
|
|
13
11
|
export * from './zia-file';
|
|
14
12
|
export * from './toc-footer-wrapper';
|
package/src/fuma/mdx/mermaid.tsx
CHANGED
|
@@ -85,8 +85,10 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, enableP
|
|
|
85
85
|
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
86
86
|
setScale((prev) => clamp(prev + delta, 0.25, 10));
|
|
87
87
|
} else {
|
|
88
|
+
// two-finger pan on touchpad: support both horizontal (deltaX) and vertical (deltaY)
|
|
89
|
+
e.preventDefault();
|
|
88
90
|
e.stopPropagation();
|
|
89
|
-
setTranslate((prev) => ({ x: prev.x, y: prev.y - e.deltaY }));
|
|
91
|
+
setTranslate((prev) => ({ x: prev.x - e.deltaX, y: prev.y - e.deltaY }));
|
|
90
92
|
}
|
|
91
93
|
}, []);
|
|
92
94
|
|
|
@@ -11,7 +11,6 @@ import Link from 'next/link';
|
|
|
11
11
|
|
|
12
12
|
const itemVariants = 'flex flex-row items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-fd-accent hover:text-fd-accent-foreground [&_svg]:size-4';
|
|
13
13
|
|
|
14
|
-
// 注解样式:色彩突出且主题适配
|
|
15
14
|
const anotionClass = 'ms-2 px-2 py-0.5 rounded text-xs font-semibold bg-fd-accent/80 text-fd-accent-foreground dark:bg-white/20 dark:text-white';
|
|
16
15
|
|
|
17
16
|
export interface ZiaFileProps extends HTMLAttributes<HTMLDivElement> {
|
package/src/fuma/server.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { getTranslations } from 'next-intl/server';
|
|
3
2
|
import { cn } from '@lib/utils';
|
|
4
3
|
import type { HTMLAttributes } from 'react';
|
|
5
|
-
import { useTranslations } from 'next-intl';
|
|
6
4
|
|
|
7
5
|
export type SiteXProps = Omit<HTMLAttributes<HTMLSpanElement>, 'type'> & {
|
|
6
|
+
locale: string;
|
|
8
7
|
type: 'site' | 'email';
|
|
9
8
|
namespace?: string;
|
|
10
9
|
tKey?: string;
|
|
11
10
|
};
|
|
12
11
|
|
|
13
|
-
export function SiteX({ type, namespace, tKey, className, ...props }: SiteXProps) {
|
|
12
|
+
export async function SiteX({ locale, type, namespace, tKey, className, ...props }: SiteXProps) {
|
|
14
13
|
// 默认命名空间和key
|
|
15
14
|
let ns = namespace;
|
|
16
15
|
let key = tKey;
|
|
@@ -20,7 +19,7 @@ export function SiteX({ type, namespace, tKey, className, ...props }: SiteXProps
|
|
|
20
19
|
if (!key) {
|
|
21
20
|
key = type === 'site' ? 'title' : 'email';
|
|
22
21
|
}
|
|
23
|
-
const t =
|
|
22
|
+
const t = await getTranslations({ locale, namespace: ns });
|
|
24
23
|
const text = t(key, { defaultValue: type === 'site' ? 'Site----' : '----@example.com' });
|
|
25
24
|
|
|
26
25
|
if (type === 'site') {
|
package/src/main/cta.tsx
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { getTranslations } from 'next-intl/server';
|
|
3
2
|
import { GradientButton } from "@third-ui/fuma/mdx/gradient-button";
|
|
4
|
-
import { useTranslations } from 'next-intl';
|
|
5
3
|
import { cn } from '@lib/utils';
|
|
6
4
|
import { richText } from '@third-ui/main/rich-text-expert';
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
interface CTAData {
|
|
7
|
+
title: string;
|
|
8
|
+
eyesOn: string;
|
|
9
|
+
description1: string;
|
|
10
|
+
description2: string;
|
|
11
|
+
button: string;
|
|
12
|
+
url: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function CTA({
|
|
16
|
+
locale,
|
|
17
|
+
sectionClassName
|
|
18
|
+
}: {
|
|
19
|
+
locale: string;
|
|
20
|
+
sectionClassName?: string;
|
|
21
|
+
}) {
|
|
22
|
+
const t = await getTranslations({ locale, namespace: 'cta' });
|
|
23
|
+
|
|
24
|
+
const data: CTAData = {
|
|
25
|
+
title: t('title'),
|
|
26
|
+
eyesOn: t('eyesOn'),
|
|
27
|
+
description1: richText(t, 'description1'),
|
|
28
|
+
description2: t('description2'),
|
|
29
|
+
button: t('button'),
|
|
30
|
+
url: t('url')
|
|
31
|
+
};
|
|
32
|
+
|
|
10
33
|
return (
|
|
11
34
|
<section id="cta" className={cn("px-16 py-20 mx-16 md:mx-32 scroll-mt-20", sectionClassName)}>
|
|
12
35
|
<div className="
|
|
@@ -16,16 +39,16 @@ export function CTA({ sectionClassName }: { sectionClassName?: string }) {
|
|
|
16
39
|
bg-[length:200%_auto] animate-cta-gradient-wave
|
|
17
40
|
">
|
|
18
41
|
<h2 className="text-3xl md:text-4xl font-bold mb-6">
|
|
19
|
-
{
|
|
42
|
+
{data.title} <span className="text-purple-400">{data.eyesOn}</span>?
|
|
20
43
|
</h2>
|
|
21
44
|
<p className="text-2xl mx-auto mb-8">
|
|
22
|
-
{
|
|
45
|
+
{data.description1}
|
|
23
46
|
<br />
|
|
24
|
-
<span className="text-purple-400">{
|
|
47
|
+
<span className="text-purple-400">{data.description2}</span>
|
|
25
48
|
</p>
|
|
26
49
|
<GradientButton
|
|
27
|
-
title={
|
|
28
|
-
href={
|
|
50
|
+
title={data.button}
|
|
51
|
+
href={data.url}
|
|
29
52
|
align="center"
|
|
30
53
|
/>
|
|
31
54
|
</div>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
interface FAQData {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
items: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
question: string;
|
|
11
|
+
answer: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function FAQInteractive({ data }: { data: FAQData }) {
|
|
16
|
+
const [openStates, setOpenStates] = useState<Record<string, boolean>>({});
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// Progressive enhancement: Add interactivity to existing DOM elements
|
|
20
|
+
data.items.forEach((item) => {
|
|
21
|
+
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`) as HTMLButtonElement;
|
|
22
|
+
const contentDiv = document.querySelector(`[data-faq-content="${item.id}"]`) as HTMLDivElement;
|
|
23
|
+
const iconSvg = document.querySelector(`[data-faq-icon="${item.id}"]`) as SVGElement;
|
|
24
|
+
|
|
25
|
+
if (toggleButton && contentDiv && iconSvg) {
|
|
26
|
+
const handleClick = () => {
|
|
27
|
+
const isOpen = openStates[item.id] || false;
|
|
28
|
+
const newOpenState = !isOpen;
|
|
29
|
+
|
|
30
|
+
// Update state
|
|
31
|
+
setOpenStates(prev => ({
|
|
32
|
+
...prev,
|
|
33
|
+
[item.id]: newOpenState
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Update DOM
|
|
37
|
+
if (newOpenState) {
|
|
38
|
+
contentDiv.classList.remove('hidden');
|
|
39
|
+
toggleButton.setAttribute('aria-expanded', 'true');
|
|
40
|
+
iconSvg.style.transform = 'rotate(90deg)';
|
|
41
|
+
} else {
|
|
42
|
+
contentDiv.classList.add('hidden');
|
|
43
|
+
toggleButton.setAttribute('aria-expanded', 'false');
|
|
44
|
+
iconSvg.style.transform = 'rotate(0deg)';
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
toggleButton.addEventListener('click', handleClick);
|
|
49
|
+
|
|
50
|
+
// Cleanup function will be handled by the effect cleanup
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Cleanup event listeners
|
|
55
|
+
return () => {
|
|
56
|
+
data.items.forEach((item) => {
|
|
57
|
+
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`) as HTMLButtonElement;
|
|
58
|
+
if (toggleButton) {
|
|
59
|
+
// Remove all event listeners by cloning the element
|
|
60
|
+
const newButton = toggleButton.cloneNode(true);
|
|
61
|
+
toggleButton.parentNode?.replaceChild(newButton, toggleButton);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
}, [data, openStates]);
|
|
66
|
+
|
|
67
|
+
return null; // Progressive enhancement - no additional DOM rendering
|
|
68
|
+
}
|