@windrun-huaiin/third-ui 31.0.0 → 31.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/fuma/base/custom-header.d.ts +3 -1
- package/dist/fuma/base/custom-header.js +13 -7
- package/dist/fuma/base/custom-header.mjs +13 -7
- package/dist/fuma/base/custom-home-layout.d.ts +6 -1
- package/dist/fuma/base/custom-home-layout.js +6 -3
- package/dist/fuma/base/custom-home-layout.mjs +6 -3
- package/dist/fuma/base/docs-root-provider.d.ts +1 -6
- package/dist/fuma/base/docs-root-provider.js +2 -8
- package/dist/fuma/base/docs-root-provider.mjs +2 -8
- package/dist/fuma/base/header-theme-switch.d.ts +2 -1
- package/dist/fuma/base/site-docs-layout.d.ts +1 -0
- package/dist/fuma/base/site-docs-layout.js +22 -2
- package/dist/fuma/base/site-docs-layout.mjs +22 -2
- package/dist/fuma/base/site-home-layout.js +2 -2
- package/dist/fuma/base/site-home-layout.mjs +2 -2
- package/dist/fuma/base/site-layout-shared.d.ts +5 -1
- package/dist/fuma/base/site-layout-shared.js +1 -1
- package/dist/fuma/base/site-layout-shared.mjs +1 -1
- package/dist/fuma/base/site-theme-provider.d.ts +8 -0
- package/dist/fuma/base/site-theme-provider.js +41 -0
- package/dist/fuma/base/site-theme-provider.mjs +39 -0
- package/dist/fuma/mdx/fuma-github-info.d.ts +1 -2
- package/dist/fuma/mdx/fuma-github-info.js +3 -6
- package/dist/fuma/mdx/fuma-github-info.mjs +3 -6
- package/dist/main/credit/credit-overview-nav-client.d.ts +12 -0
- package/dist/main/credit/credit-overview-nav-client.js +65 -0
- package/dist/main/credit/credit-overview-nav-client.mjs +63 -0
- package/dist/main/credit/index.d.ts +2 -0
- package/dist/main/credit/index.js +2 -0
- package/dist/main/credit/index.mjs +1 -0
- package/package.json +3 -3
- package/src/fuma/base/custom-header.tsx +30 -6
- package/src/fuma/base/custom-home-layout.tsx +24 -12
- package/src/fuma/base/docs-root-provider.tsx +3 -30
- package/src/fuma/base/header-theme-switch.tsx +2 -1
- package/src/fuma/base/site-docs-layout.tsx +23 -2
- package/src/fuma/base/site-home-layout.tsx +1 -0
- package/src/fuma/base/site-layout-shared.tsx +11 -2
- package/src/fuma/base/site-theme-provider.tsx +59 -0
- package/src/fuma/mdx/fuma-github-info.tsx +3 -8
- package/src/main/credit/credit-overview-nav-client.tsx +95 -0
- package/src/main/credit/index.ts +5 -0
|
@@ -5,7 +5,7 @@ import { useState, useEffect } from 'react';
|
|
|
5
5
|
import { ExternalLinkIcon, StarIcon } from '@windrun-huaiin/base-ui/icons';
|
|
6
6
|
|
|
7
7
|
// Loading state component
|
|
8
|
-
function GitHubInfoSkeleton({
|
|
8
|
+
function GitHubInfoSkeleton({ className }) {
|
|
9
9
|
return (jsxs("div", { className: `flex flex-col gap-1.5 p-2 rounded-lg text-sm text-fd-foreground/80 lg:flex-row lg:items-center animate-pulse ${className}`, children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("div", { className: "size-3.5 bg-fd-muted rounded" }), jsx("div", { className: "h-4 bg-fd-muted rounded w-20" })] }), jsx("div", { className: "h-3 bg-fd-muted rounded w-8" })] }));
|
|
10
10
|
}
|
|
11
11
|
// Error state component - graceful fallback
|
|
@@ -44,7 +44,7 @@ function humanizeNumber(num) {
|
|
|
44
44
|
* - 🎨 Three states: loading, success, error
|
|
45
45
|
* - 💯 Not affected by network issues causing page crashes
|
|
46
46
|
*/
|
|
47
|
-
function FumaGithubInfo({ owner, repo,
|
|
47
|
+
function FumaGithubInfo({ owner, repo, className }) {
|
|
48
48
|
const [data, setData] = useState(null);
|
|
49
49
|
const [loading, setLoading] = useState(true);
|
|
50
50
|
const [error, setError] = useState(null);
|
|
@@ -59,9 +59,6 @@ function FumaGithubInfo({ owner, repo, token, className }) {
|
|
|
59
59
|
const headers = new Headers({
|
|
60
60
|
'Accept': 'application/vnd.github.v3+json',
|
|
61
61
|
});
|
|
62
|
-
if (token) {
|
|
63
|
-
headers.set('Authorization', `Bearer ${token}`);
|
|
64
|
-
}
|
|
65
62
|
const response = yield fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
66
63
|
signal: controller.signal,
|
|
67
64
|
headers,
|
|
@@ -95,7 +92,7 @@ function FumaGithubInfo({ owner, repo, token, className }) {
|
|
|
95
92
|
}
|
|
96
93
|
});
|
|
97
94
|
fetchRepoData();
|
|
98
|
-
}, [owner, repo
|
|
95
|
+
}, [owner, repo]);
|
|
99
96
|
// Loading state
|
|
100
97
|
if (loading) {
|
|
101
98
|
return jsx(GitHubInfoSkeleton, { owner: owner, repo: repo, className: className });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CreditOverviewTranslations } from './credit-overview-client';
|
|
2
|
+
import type { CreditOverviewData } from './types';
|
|
3
|
+
export interface CreditOverviewPayload {
|
|
4
|
+
data: CreditOverviewData;
|
|
5
|
+
totalLabel: string;
|
|
6
|
+
translations: CreditOverviewTranslations;
|
|
7
|
+
}
|
|
8
|
+
export interface CreditOverviewNavClientProps {
|
|
9
|
+
locale: string;
|
|
10
|
+
endpoint: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function CreditOverviewNavClient({ locale, endpoint, }: CreditOverviewNavClientProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tslib = require('tslib');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var nextjs = require('@clerk/nextjs');
|
|
7
|
+
var React = require('react');
|
|
8
|
+
var creditNavButton = require('./credit-nav-button.js');
|
|
9
|
+
var creditOverviewClient = require('./credit-overview-client.js');
|
|
10
|
+
|
|
11
|
+
function buildCreditOverviewUrl(endpoint, locale) {
|
|
12
|
+
const url = new URL(endpoint, window.location.origin);
|
|
13
|
+
url.searchParams.set('locale', locale);
|
|
14
|
+
return url.toString();
|
|
15
|
+
}
|
|
16
|
+
function CreditOverviewNavClient({ locale, endpoint, }) {
|
|
17
|
+
const { isLoaded, isSignedIn, userId } = nextjs.useAuth();
|
|
18
|
+
const [payload, setPayload] = React.useState(null);
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
if (!isLoaded) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!isSignedIn) {
|
|
24
|
+
setPayload(null);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
function loadCreditOverview() {
|
|
29
|
+
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
try {
|
|
31
|
+
const response = yield fetch(buildCreditOverviewUrl(endpoint, locale), {
|
|
32
|
+
credentials: 'same-origin',
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
if (!controller.signal.aborted) {
|
|
37
|
+
setPayload(null);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const nextPayload = (yield response.json());
|
|
42
|
+
if (!controller.signal.aborted) {
|
|
43
|
+
setPayload(nextPayload);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (!controller.signal.aborted) {
|
|
48
|
+
setPayload(null);
|
|
49
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
loadCreditOverview();
|
|
55
|
+
return () => {
|
|
56
|
+
controller.abort();
|
|
57
|
+
};
|
|
58
|
+
}, [endpoint, isLoaded, isSignedIn, locale, userId]);
|
|
59
|
+
if (!payload) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return (jsxRuntime.jsx(creditNavButton.CreditNavButton, { locale: locale, totalBalance: payload.data.totalBalance, totalLabel: payload.totalLabel, children: jsxRuntime.jsx(creditOverviewClient.CreditOverviewClient, { locale: locale, data: payload.data, translations: payload.translations }) }));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
exports.CreditOverviewNavClient = CreditOverviewNavClient;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { __awaiter } from 'tslib';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { useAuth } from '@clerk/nextjs';
|
|
5
|
+
import { useState, useEffect } from 'react';
|
|
6
|
+
import { CreditNavButton } from './credit-nav-button.mjs';
|
|
7
|
+
import { CreditOverviewClient } from './credit-overview-client.mjs';
|
|
8
|
+
|
|
9
|
+
function buildCreditOverviewUrl(endpoint, locale) {
|
|
10
|
+
const url = new URL(endpoint, window.location.origin);
|
|
11
|
+
url.searchParams.set('locale', locale);
|
|
12
|
+
return url.toString();
|
|
13
|
+
}
|
|
14
|
+
function CreditOverviewNavClient({ locale, endpoint, }) {
|
|
15
|
+
const { isLoaded, isSignedIn, userId } = useAuth();
|
|
16
|
+
const [payload, setPayload] = useState(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!isLoaded) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!isSignedIn) {
|
|
22
|
+
setPayload(null);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
function loadCreditOverview() {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
try {
|
|
29
|
+
const response = yield fetch(buildCreditOverviewUrl(endpoint, locale), {
|
|
30
|
+
credentials: 'same-origin',
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
if (!controller.signal.aborted) {
|
|
35
|
+
setPayload(null);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const nextPayload = (yield response.json());
|
|
40
|
+
if (!controller.signal.aborted) {
|
|
41
|
+
setPayload(nextPayload);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (!controller.signal.aborted) {
|
|
46
|
+
setPayload(null);
|
|
47
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
loadCreditOverview();
|
|
53
|
+
return () => {
|
|
54
|
+
controller.abort();
|
|
55
|
+
};
|
|
56
|
+
}, [endpoint, isLoaded, isSignedIn, locale, userId]);
|
|
57
|
+
if (!payload) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return (jsx(CreditNavButton, { locale: locale, totalBalance: payload.data.totalBalance, totalLabel: payload.totalLabel, children: jsx(CreditOverviewClient, { locale: locale, data: payload.data, translations: payload.translations }) }));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { CreditOverviewNavClient };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { CreditOverviewClient } from './credit-overview-client';
|
|
2
|
+
export { CreditOverviewNavClient } from './credit-overview-nav-client';
|
|
2
3
|
export { CreditNavButton } from './credit-nav-button';
|
|
3
4
|
export type { CreditOverviewTranslations } from './credit-overview-client';
|
|
5
|
+
export type { CreditOverviewNavClientProps, CreditOverviewPayload, } from './credit-overview-nav-client';
|
|
4
6
|
export type { CreditOverviewData, CreditBucket, CreditBucketStatus, SubscriptionInfo, } from './types';
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var creditOverviewClient = require('./credit-overview-client.js');
|
|
5
|
+
var creditOverviewNavClient = require('./credit-overview-nav-client.js');
|
|
5
6
|
var creditNavButton = require('./credit-nav-button.js');
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
exports.CreditOverviewClient = creditOverviewClient.CreditOverviewClient;
|
|
11
|
+
exports.CreditOverviewNavClient = creditOverviewNavClient.CreditOverviewNavClient;
|
|
10
12
|
exports.CreditNavButton = creditNavButton.CreditNavButton;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.1.0",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./clerk": {
|
|
@@ -244,8 +244,8 @@
|
|
|
244
244
|
"unified": "^11.0.5",
|
|
245
245
|
"zod": "^4.3.6",
|
|
246
246
|
"@windrun-huaiin/base-ui": "^31.0.0",
|
|
247
|
-
"@windrun-huaiin/
|
|
248
|
-
"@windrun-huaiin/
|
|
247
|
+
"@windrun-huaiin/contracts": "^31.0.0",
|
|
248
|
+
"@windrun-huaiin/lib": "^31.0.0"
|
|
249
249
|
},
|
|
250
250
|
"peerDependencies": {
|
|
251
251
|
"clsx": "^2.1.1",
|
|
@@ -35,7 +35,11 @@ import { Popover, PopoverContent, PopoverTrigger } from 'fumadocs-ui/components/
|
|
|
35
35
|
import { buttonVariants } from 'fumadocs-ui/components/ui/button';
|
|
36
36
|
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
|
37
37
|
import { HeaderThemeSwitch } from './header-theme-switch';
|
|
38
|
-
import type {
|
|
38
|
+
import type {
|
|
39
|
+
ExtendedLinkItem,
|
|
40
|
+
SiteThemeSwitchConfig,
|
|
41
|
+
SiteThemeSwitchMode,
|
|
42
|
+
} from './site-layout-shared';
|
|
39
43
|
|
|
40
44
|
export type NavbarCSSVars = CSSProperties & {
|
|
41
45
|
'--fd-banner-height'?: string;
|
|
@@ -47,7 +51,8 @@ const PrefetchLinkItem = LinkItem as (
|
|
|
47
51
|
props: ComponentProps<typeof LinkItem> & { prefetch?: boolean },
|
|
48
52
|
) => ReactNode;
|
|
49
53
|
|
|
50
|
-
export interface CustomHomeHeaderProps
|
|
54
|
+
export interface CustomHomeHeaderProps
|
|
55
|
+
extends Omit<HomeLayoutProps, 'themeSwitch'> {
|
|
51
56
|
/**
|
|
52
57
|
* Banner height in rem units
|
|
53
58
|
*
|
|
@@ -92,6 +97,7 @@ export interface CustomHomeHeaderProps extends HomeLayoutProps {
|
|
|
92
97
|
* Control order of utilities inside the mobile dropdown.
|
|
93
98
|
*/
|
|
94
99
|
mobileMenuActionsOrder?: MobileMenuAction[];
|
|
100
|
+
themeSwitch?: SiteThemeSwitchConfig;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
export type DesktopAction =
|
|
@@ -175,8 +181,12 @@ export function CustomHomeHeader({
|
|
|
175
181
|
? searchToggle.components?.lg ?? null
|
|
176
182
|
: null,
|
|
177
183
|
theme:
|
|
178
|
-
themeSwitch
|
|
179
|
-
?
|
|
184
|
+
shouldShowThemeSwitch(themeSwitch?.mode)
|
|
185
|
+
? (
|
|
186
|
+
<HeaderThemeSwitch
|
|
187
|
+
mode={normalizeThemeSwitchMode(themeSwitch?.mode)}
|
|
188
|
+
/>
|
|
189
|
+
)
|
|
180
190
|
: null,
|
|
181
191
|
i18n: i18n ? (
|
|
182
192
|
<CompactLanguageToggle>
|
|
@@ -238,8 +248,12 @@ export function CustomHomeHeader({
|
|
|
238
248
|
</CompactLanguageToggle>
|
|
239
249
|
) : null,
|
|
240
250
|
theme:
|
|
241
|
-
themeSwitch
|
|
242
|
-
?
|
|
251
|
+
shouldShowThemeSwitch(themeSwitch?.mode)
|
|
252
|
+
? (
|
|
253
|
+
<HeaderThemeSwitch
|
|
254
|
+
mode={normalizeThemeSwitchMode(themeSwitch?.mode)}
|
|
255
|
+
/>
|
|
256
|
+
)
|
|
243
257
|
: null,
|
|
244
258
|
};
|
|
245
259
|
const shouldRenderMobileUtilities = mobileMenuActionsOrder.some(
|
|
@@ -356,6 +370,16 @@ export function CustomHomeHeader({
|
|
|
356
370
|
);
|
|
357
371
|
}
|
|
358
372
|
|
|
373
|
+
function shouldShowThemeSwitch(mode?: SiteThemeSwitchMode): boolean {
|
|
374
|
+
return mode === 'light-dark' || mode === 'light-dark-system' || mode == null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function normalizeThemeSwitchMode(
|
|
378
|
+
mode?: SiteThemeSwitchMode,
|
|
379
|
+
): 'light-dark' | 'light-dark-system' {
|
|
380
|
+
return mode === 'light-dark' ? 'light-dark' : 'light-dark-system';
|
|
381
|
+
}
|
|
382
|
+
|
|
359
383
|
interface CustomNavbarProps extends ComponentProps<'div'> {
|
|
360
384
|
bannerHeight?: number;
|
|
361
385
|
headerHeight?: number;
|
|
@@ -3,13 +3,16 @@ import { HomeLayout, type HomeLayoutProps } from 'fumadocs-ui/layouts/home';
|
|
|
3
3
|
import { FumaBannerSuit } from '../fuma-banner-suit';
|
|
4
4
|
import { Footer } from '../../main/footer';
|
|
5
5
|
import { GoToTop } from '../../main/go-to-top';
|
|
6
|
+
import { SiteThemeProvider } from './site-theme-provider';
|
|
6
7
|
import {
|
|
7
8
|
NavbarCSSVars,
|
|
8
9
|
CustomHomeHeader,
|
|
9
10
|
type DesktopAction,
|
|
10
11
|
type MobileBarAction,
|
|
11
12
|
type MobileMenuAction,
|
|
13
|
+
type CustomHomeHeaderProps,
|
|
12
14
|
} from './custom-header';
|
|
15
|
+
import type { SiteThemeSwitchConfig } from './site-layout-shared';
|
|
13
16
|
|
|
14
17
|
// - Set bannerHeight/headerHeight to the rem values expected by the project. Use bannerHeight = 0 when there is no banner.
|
|
15
18
|
// - layoutStyle passes the variables to HomeLayout's main element, offsetting content without has-banner/no-banner classes.
|
|
@@ -91,6 +94,10 @@ export interface CustomHomeLayoutProps {
|
|
|
91
94
|
* The default locale for the application (default: 'en')
|
|
92
95
|
*/
|
|
93
96
|
defaultLocale?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Theme mode for this layout group.
|
|
99
|
+
*/
|
|
100
|
+
themeSwitch?: SiteThemeSwitchConfig;
|
|
94
101
|
children?: ReactNode;
|
|
95
102
|
}
|
|
96
103
|
|
|
@@ -119,6 +126,7 @@ export function CustomHomeLayout({
|
|
|
119
126
|
actionOrders,
|
|
120
127
|
localePrefixAsNeeded = true,
|
|
121
128
|
defaultLocale = 'en',
|
|
129
|
+
themeSwitch,
|
|
122
130
|
}: CustomHomeLayoutProps) {
|
|
123
131
|
const resolvedBannerHeight = bannerHeight ?? (showBanner ? 3 : 0.5);
|
|
124
132
|
const resolvedPaddingTop =
|
|
@@ -139,6 +147,7 @@ export function CustomHomeLayout({
|
|
|
139
147
|
<CustomHomeHeader
|
|
140
148
|
{...homeLayoutProps}
|
|
141
149
|
nav={navOptions}
|
|
150
|
+
themeSwitch={themeSwitch}
|
|
142
151
|
bannerHeight={resolvedBannerHeight}
|
|
143
152
|
headerHeight={headerHeight}
|
|
144
153
|
navbarClassName={navbarClassName}
|
|
@@ -148,6 +157,7 @@ export function CustomHomeLayout({
|
|
|
148
157
|
mobileMenuActionsOrder={actionOrders?.mobileMenu}
|
|
149
158
|
/>
|
|
150
159
|
);
|
|
160
|
+
const themeMode = themeSwitch?.mode ?? 'light-dark-system';
|
|
151
161
|
|
|
152
162
|
return (
|
|
153
163
|
<>
|
|
@@ -158,19 +168,21 @@ export function CustomHomeLayout({
|
|
|
158
168
|
floating={floatingNav}
|
|
159
169
|
/>
|
|
160
170
|
)}
|
|
171
|
+
<SiteThemeProvider mode={themeMode}>
|
|
161
172
|
<HomeLayout
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
{...homeLayoutProps}
|
|
174
|
+
nav={{
|
|
175
|
+
...navOptions,
|
|
176
|
+
component: header,
|
|
177
|
+
}}
|
|
178
|
+
className='bg-neutral-100 dark:bg-neutral-900'
|
|
179
|
+
style={layoutStyle}
|
|
180
|
+
>
|
|
181
|
+
{children}
|
|
182
|
+
{showFooter ? footer ?? <Footer locale={locale} localePrefixAsNeeded={localePrefixAsNeeded} defaultLocale={defaultLocale} /> : null}
|
|
183
|
+
{showGoToTop ? goToTop ?? <GoToTop /> : null}
|
|
184
|
+
</HomeLayout>
|
|
185
|
+
</SiteThemeProvider>
|
|
174
186
|
</>
|
|
175
187
|
);
|
|
176
188
|
}
|
|
@@ -1,58 +1,31 @@
|
|
|
1
1
|
import type { ComponentProps, ReactNode } from 'react';
|
|
2
2
|
import { NextProvider } from 'fumadocs-core/framework/next';
|
|
3
3
|
import { I18nProvider, type I18nProviderProps } from 'fumadocs-ui/contexts/i18n';
|
|
4
|
-
import { ThemeProvider, type ThemeProviderProps } from 'next-themes';
|
|
5
4
|
|
|
6
5
|
type NextProviderComponents = {
|
|
7
6
|
Link?: ComponentProps<typeof NextProvider>['Link'];
|
|
8
7
|
Image?: ComponentProps<typeof NextProvider>['Image'];
|
|
9
8
|
};
|
|
10
9
|
|
|
11
|
-
type ThemeOptions = ThemeProviderProps & {
|
|
12
|
-
enabled?: boolean;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
10
|
export interface DocsRootProviderProps {
|
|
16
11
|
i18n: Omit<I18nProviderProps, 'children'>;
|
|
17
|
-
theme?: ThemeOptions;
|
|
18
12
|
components?: NextProviderComponents;
|
|
19
13
|
children: ReactNode;
|
|
20
14
|
}
|
|
21
15
|
|
|
22
16
|
export function DocsRootProvider({
|
|
23
17
|
i18n,
|
|
24
|
-
theme = {},
|
|
25
18
|
components,
|
|
26
19
|
children,
|
|
27
20
|
}: DocsRootProviderProps) {
|
|
28
|
-
let body = children;
|
|
29
|
-
|
|
30
|
-
if (theme.enabled !== false) {
|
|
31
|
-
body = (
|
|
32
|
-
<ThemeProvider
|
|
33
|
-
attribute="class"
|
|
34
|
-
defaultTheme="system"
|
|
35
|
-
enableSystem
|
|
36
|
-
disableTransitionOnChange
|
|
37
|
-
{...theme}
|
|
38
|
-
>
|
|
39
|
-
{body}
|
|
40
|
-
</ThemeProvider>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
body = (
|
|
45
|
-
<I18nProvider {...i18n}>
|
|
46
|
-
{body}
|
|
47
|
-
</I18nProvider>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
21
|
return (
|
|
51
22
|
<NextProvider
|
|
52
23
|
Link={components?.Link}
|
|
53
24
|
Image={components?.Image}
|
|
54
25
|
>
|
|
55
|
-
{
|
|
26
|
+
<I18nProvider {...i18n}>
|
|
27
|
+
{children}
|
|
28
|
+
</I18nProvider>
|
|
56
29
|
</NextProvider>
|
|
57
30
|
);
|
|
58
31
|
}
|
|
@@ -5,6 +5,7 @@ import { AirplayIcon, MoonIcon, SunIcon } from '@windrun-huaiin/base-ui/icons';
|
|
|
5
5
|
import { useTheme } from 'next-themes';
|
|
6
6
|
import { type ComponentProps, useEffect, useState } from 'react';
|
|
7
7
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
8
|
+
import type { SiteThemeSwitchMode } from './site-layout-shared';
|
|
8
9
|
|
|
9
10
|
const itemVariants = cva('inline-flex size-6.5 items-center justify-center rounded-full p-1.5', {
|
|
10
11
|
variants: {
|
|
@@ -18,7 +19,7 @@ const itemVariants = cva('inline-flex size-6.5 items-center justify-center round
|
|
|
18
19
|
const full = [['light', SunIcon] as const, ['dark', MoonIcon] as const, ['system', AirplayIcon] as const];
|
|
19
20
|
|
|
20
21
|
export interface HeaderThemeSwitchProps extends ComponentProps<'div'> {
|
|
21
|
-
mode?: 'light-
|
|
22
|
+
mode?: Exclude<SiteThemeSwitchMode, 'light-only' | 'dark-only'>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function HeaderThemeSwitch({
|
|
@@ -4,20 +4,36 @@ import {
|
|
|
4
4
|
normalizeNavItems,
|
|
5
5
|
type SiteBaseLayoutConfig,
|
|
6
6
|
} from './site-layout-shared';
|
|
7
|
+
import { SiteThemeProvider } from './site-theme-provider';
|
|
7
8
|
|
|
8
9
|
export interface SiteDocsLayoutConfig extends SiteBaseLayoutConfig {
|
|
9
10
|
tree: DocsLayoutProps['tree'];
|
|
10
11
|
sidebar?: DocsLayoutProps['sidebar'];
|
|
12
|
+
themeProvider?: boolean;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
function toDocsLayoutOptions(config: SiteDocsLayoutConfig): DocsLayoutProps {
|
|
16
|
+
const themeMode = config.themeSwitch?.mode ?? 'light-dark-system';
|
|
17
|
+
const shouldShowThemeSwitch =
|
|
18
|
+
config.themeProvider !== false &&
|
|
19
|
+
(themeMode === 'light-dark' || themeMode === 'light-dark-system');
|
|
14
20
|
return {
|
|
15
21
|
...(config.nav ? { nav: config.nav } : {}),
|
|
16
22
|
...(config.i18n ? { i18n: config.i18n } : {}),
|
|
17
23
|
...(config.githubUrl ? { githubUrl: config.githubUrl } : {}),
|
|
18
24
|
...(config.links ? { links: normalizeNavItems(config.links) } : {}),
|
|
19
25
|
...(config.searchToggle ? { searchToggle: config.searchToggle } : {}),
|
|
20
|
-
...(
|
|
26
|
+
...(shouldShowThemeSwitch
|
|
27
|
+
? {
|
|
28
|
+
themeSwitch: {
|
|
29
|
+
mode: themeMode,
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
: {
|
|
33
|
+
themeSwitch: {
|
|
34
|
+
enabled: false,
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
21
37
|
...(config.sidebar ? { sidebar: config.sidebar } : {}),
|
|
22
38
|
tree: config.tree,
|
|
23
39
|
};
|
|
@@ -31,5 +47,10 @@ export function SiteDocsLayout({
|
|
|
31
47
|
children: ReactNode;
|
|
32
48
|
}) {
|
|
33
49
|
const options = toDocsLayoutOptions(config);
|
|
34
|
-
|
|
50
|
+
const themeMode = config.themeSwitch?.mode ?? 'light-dark-system';
|
|
51
|
+
const body = <DocsLayout {...options}>{children}</DocsLayout>;
|
|
52
|
+
|
|
53
|
+
if (config.themeProvider === false) return body;
|
|
54
|
+
|
|
55
|
+
return <SiteThemeProvider mode={themeMode}>{body}</SiteThemeProvider>;
|
|
35
56
|
}
|
|
@@ -72,6 +72,7 @@ export function SiteHomeLayout({
|
|
|
72
72
|
...(showBanner != null ? { showBanner } : {}),
|
|
73
73
|
...(showFooter != null ? { showFooter } : {}),
|
|
74
74
|
...(showGoToTop != null ? { showGoToTop } : {}),
|
|
75
|
+
...(config.themeSwitch ? { themeSwitch: config.themeSwitch } : {}),
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
return <CustomHomeLayout {...layoutProps}>{children}</CustomHomeLayout>;
|
|
@@ -58,7 +58,17 @@ export interface SiteBaseLayoutConfig {
|
|
|
58
58
|
githubUrl?: string;
|
|
59
59
|
links?: SiteNavItemConfig[];
|
|
60
60
|
searchToggle?: HomeLayoutProps['searchToggle'];
|
|
61
|
-
themeSwitch?:
|
|
61
|
+
themeSwitch?: SiteThemeSwitchConfig;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type SiteThemeSwitchMode =
|
|
65
|
+
| 'light-dark-system'
|
|
66
|
+
| 'light-dark'
|
|
67
|
+
| 'light-only'
|
|
68
|
+
| 'dark-only';
|
|
69
|
+
|
|
70
|
+
export interface SiteThemeSwitchConfig {
|
|
71
|
+
mode?: SiteThemeSwitchMode;
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
export interface SiteMenuLeafConfig {
|
|
@@ -185,6 +195,5 @@ export function toHomeLayoutOptions(config: SiteBaseLayoutConfig): HomeLayoutPro
|
|
|
185
195
|
...(config.githubUrl ? { githubUrl: config.githubUrl } : {}),
|
|
186
196
|
...(config.links ? { links: normalizeNavItems(config.links) } : {}),
|
|
187
197
|
...(config.searchToggle ? { searchToggle: config.searchToggle } : {}),
|
|
188
|
-
...(config.themeSwitch ? { themeSwitch: config.themeSwitch } : {}),
|
|
189
198
|
};
|
|
190
199
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { ThemeProvider, type ThemeProviderProps } from 'next-themes';
|
|
5
|
+
import type { SiteThemeSwitchMode } from './site-layout-shared';
|
|
6
|
+
|
|
7
|
+
export interface SiteThemeProviderProps extends Omit<ThemeProviderProps, 'children'> {
|
|
8
|
+
mode?: SiteThemeSwitchMode;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SiteThemeProvider({
|
|
13
|
+
mode = 'light-dark-system',
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}: SiteThemeProviderProps) {
|
|
17
|
+
return (
|
|
18
|
+
<ThemeProvider
|
|
19
|
+
attribute="class"
|
|
20
|
+
disableTransitionOnChange
|
|
21
|
+
{...resolveThemeProviderProps(mode)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</ThemeProvider>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveThemeProviderProps(mode: SiteThemeSwitchMode): ThemeProviderProps {
|
|
30
|
+
if (mode === 'light-only') {
|
|
31
|
+
return {
|
|
32
|
+
forcedTheme: 'light',
|
|
33
|
+
enableSystem: false,
|
|
34
|
+
defaultTheme: 'light',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (mode === 'dark-only') {
|
|
39
|
+
return {
|
|
40
|
+
forcedTheme: 'dark',
|
|
41
|
+
enableSystem: false,
|
|
42
|
+
defaultTheme: 'dark',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (mode === 'light-dark') {
|
|
47
|
+
return {
|
|
48
|
+
enableSystem: false,
|
|
49
|
+
defaultTheme: 'light',
|
|
50
|
+
forcedTheme: undefined,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
enableSystem: true,
|
|
56
|
+
defaultTheme: 'system',
|
|
57
|
+
forcedTheme: undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -6,7 +6,6 @@ import { ExternalLinkIcon, StarIcon } from '@windrun-huaiin/base-ui/icons';
|
|
|
6
6
|
interface FumaGithubInfoProps {
|
|
7
7
|
owner: string;
|
|
8
8
|
repo: string;
|
|
9
|
-
token?: string;
|
|
10
9
|
className?: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
@@ -16,7 +15,7 @@ interface GitHubRepoData {
|
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
// Loading state component
|
|
19
|
-
function GitHubInfoSkeleton({
|
|
18
|
+
function GitHubInfoSkeleton({ className }: Pick<FumaGithubInfoProps, 'owner' | 'repo' | 'className'>) {
|
|
20
19
|
return (
|
|
21
20
|
<div className={`flex flex-col gap-1.5 p-2 rounded-lg text-sm text-fd-foreground/80 lg:flex-row lg:items-center animate-pulse ${className}`}>
|
|
22
21
|
<div className="flex items-center gap-2">
|
|
@@ -114,7 +113,7 @@ function humanizeNumber(num: number): string {
|
|
|
114
113
|
* - 🎨 Three states: loading, success, error
|
|
115
114
|
* - 💯 Not affected by network issues causing page crashes
|
|
116
115
|
*/
|
|
117
|
-
export function FumaGithubInfo({ owner, repo,
|
|
116
|
+
export function FumaGithubInfo({ owner, repo, className }: FumaGithubInfoProps) {
|
|
118
117
|
const [data, setData] = useState<GitHubRepoData | null>(null);
|
|
119
118
|
const [loading, setLoading] = useState(true);
|
|
120
119
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -133,10 +132,6 @@ export function FumaGithubInfo({ owner, repo, token, className }: FumaGithubInfo
|
|
|
133
132
|
'Accept': 'application/vnd.github.v3+json',
|
|
134
133
|
});
|
|
135
134
|
|
|
136
|
-
if (token) {
|
|
137
|
-
headers.set('Authorization', `Bearer ${token}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
135
|
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
141
136
|
signal: controller.signal,
|
|
142
137
|
headers,
|
|
@@ -170,7 +165,7 @@ export function FumaGithubInfo({ owner, repo, token, className }: FumaGithubInfo
|
|
|
170
165
|
};
|
|
171
166
|
|
|
172
167
|
fetchRepoData();
|
|
173
|
-
}, [owner, repo
|
|
168
|
+
}, [owner, repo]);
|
|
174
169
|
|
|
175
170
|
// Loading state
|
|
176
171
|
if (loading) {
|