@windrun-huaiin/third-ui 31.0.1 → 31.2.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 +1 -1
- package/dist/fuma/base/custom-header.js +16 -8
- package/dist/fuma/base/custom-header.mjs +16 -8
- package/dist/fuma/base/docs-root-provider.d.ts +2 -5
- package/dist/fuma/base/docs-root-provider.js +4 -7
- package/dist/fuma/base/docs-root-provider.mjs +4 -7
- package/dist/fuma/base/header-theme-switch.d.ts +2 -2
- package/dist/fuma/base/header-theme-switch.js +2 -10
- package/dist/fuma/base/header-theme-switch.mjs +2 -10
- package/dist/fuma/base/site-docs-layout.d.ts +1 -0
- package/dist/fuma/base/site-docs-layout.js +2 -1
- package/dist/fuma/base/site-docs-layout.mjs +2 -1
- package/dist/fuma/base/site-docs-theme-switch.d.ts +2 -0
- package/dist/fuma/base/site-docs-theme-switch.js +16 -0
- package/dist/fuma/base/site-docs-theme-switch.mjs +14 -0
- package/dist/fuma/base/site-layout-shared.d.ts +4 -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-context.d.ts +8 -0
- package/dist/fuma/base/site-theme-context.js +19 -0
- package/dist/fuma/base/site-theme-context.mjs +16 -0
- package/dist/fuma/base/site-theme-provider.d.ts +8 -0
- package/dist/fuma/base/site-theme-provider.js +34 -0
- package/dist/fuma/base/site-theme-provider.mjs +32 -0
- package/package.json +3 -3
- package/src/fuma/base/custom-header.tsx +28 -6
- package/src/fuma/base/custom-home-layout.tsx +12 -12
- package/src/fuma/base/docs-root-provider.tsx +9 -28
- package/src/fuma/base/header-theme-switch.tsx +3 -32
- package/src/fuma/base/site-docs-layout.tsx +7 -1
- package/src/fuma/base/site-docs-theme-switch.tsx +15 -0
- package/src/fuma/base/site-layout-shared.tsx +9 -2
- package/src/fuma/base/site-theme-context.tsx +30 -0
- package/src/fuma/base/site-theme-provider.tsx +51 -0
|
@@ -50,4 +50,4 @@ export interface CustomHomeHeaderProps extends HomeLayoutProps {
|
|
|
50
50
|
export type DesktopAction = 'search' | 'theme' | 'i18n' | 'secondary' | 'github';
|
|
51
51
|
export type MobileBarAction = 'pinned' | 'search' | 'menu';
|
|
52
52
|
export type MobileMenuAction = 'secondary' | 'github' | 'separator' | 'i18n' | 'theme';
|
|
53
|
-
export declare function CustomHomeHeader({ nav, i18n, links, githubUrl,
|
|
53
|
+
export declare function CustomHomeHeader({ nav, i18n, links, githubUrl, searchToggle, bannerHeight, headerHeight, maxContentWidth, navbarClassName, floating, desktopActionsOrder, mobileBarActionsOrder, mobileMenuActionsOrder, }: CustomHomeHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -15,6 +15,7 @@ var popover = require('fumadocs-ui/components/ui/popover');
|
|
|
15
15
|
var button = require('fumadocs-ui/components/ui/button');
|
|
16
16
|
var i18n = require('fumadocs-ui/contexts/i18n');
|
|
17
17
|
var headerThemeSwitch = require('./header-theme-switch.js');
|
|
18
|
+
var siteThemeContext = require('./site-theme-context.js');
|
|
18
19
|
|
|
19
20
|
const PrefetchLinkItem = shared.LinkItem;
|
|
20
21
|
const DEFAULT_DESKTOP_ACTIONS = [
|
|
@@ -34,8 +35,9 @@ const DEFAULT_MOBILE_MENU_ACTIONS = [
|
|
|
34
35
|
'i18n',
|
|
35
36
|
'theme',
|
|
36
37
|
];
|
|
37
|
-
function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl,
|
|
38
|
-
var _a, _b, _c, _d, _e
|
|
38
|
+
function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, searchToggle = {}, bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, navbarClassName, floating = false, desktopActionsOrder = DEFAULT_DESKTOP_ACTIONS, mobileBarActionsOrder = DEFAULT_MOBILE_BAR_ACTIONS, mobileMenuActionsOrder = DEFAULT_MOBILE_MENU_ACTIONS, }) {
|
|
39
|
+
var _a, _b, _c, _d, _e;
|
|
40
|
+
const themeMode = siteThemeContext.useSiteThemeMode();
|
|
39
41
|
const finalLinks = React.useMemo(() => shared.resolveLinkItems({ links, githubUrl }), [links, githubUrl]);
|
|
40
42
|
const navItems = finalLinks.filter((item) => { var _a; return ['nav', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
|
|
41
43
|
const menuItems = finalLinks.filter((item) => { var _a; return ['menu', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
|
|
@@ -55,8 +57,8 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
55
57
|
search: searchToggle.enabled !== false
|
|
56
58
|
? (_b = (_a = searchToggle.components) === null || _a === void 0 ? void 0 : _a.lg) !== null && _b !== void 0 ? _b : null
|
|
57
59
|
: null,
|
|
58
|
-
theme:
|
|
59
|
-
? (
|
|
60
|
+
theme: shouldShowThemeSwitch(themeMode)
|
|
61
|
+
? (jsxRuntime.jsx(headerThemeSwitch.HeaderThemeSwitch, { mode: normalizeThemeSwitchMode() }))
|
|
60
62
|
: null,
|
|
61
63
|
i18n: i18n ? (jsxRuntime.jsx(CompactLanguageToggle, { children: jsxRuntime.jsx(icons.LanguagesIcon, { className: "size-5" }) })) : null,
|
|
62
64
|
secondary: desktopSecondaryDisplayItems.length ? (jsxRuntime.jsx("ul", { className: "flex flex-row gap-2 items-center empty:hidden", children: desktopSecondaryDisplayItems.map((item, i) => (jsxRuntime.jsx(NavbarLinkItem, { item: item, className: utils.cn(item.type === 'icon' && [
|
|
@@ -78,8 +80,8 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
78
80
|
github: githubMobileMenuItem ? (jsxRuntime.jsx(MenuLinkItem, { item: githubMobileMenuItem, className: "-me-1.5" })) : null,
|
|
79
81
|
separator: jsxRuntime.jsx("div", { role: "separator", className: "flex-1" }),
|
|
80
82
|
i18n: i18n ? (jsxRuntime.jsxs(CompactLanguageToggle, { children: [jsxRuntime.jsx(icons.LanguagesIcon, { className: "size-5" }), jsxRuntime.jsx(languageSelect.LanguageSelectText, {}), jsxRuntime.jsx(icons.ChevronDownIcon, { className: "size-3 text-fd-muted-foreground" })] })) : null,
|
|
81
|
-
theme:
|
|
82
|
-
? (
|
|
83
|
+
theme: shouldShowThemeSwitch(themeMode)
|
|
84
|
+
? (jsxRuntime.jsx(headerThemeSwitch.HeaderThemeSwitch, { mode: normalizeThemeSwitchMode() }))
|
|
83
85
|
: null,
|
|
84
86
|
};
|
|
85
87
|
const shouldRenderMobileUtilities = mobileMenuActionsOrder.some((action) => action !== 'separator' && Boolean(mobileMenuActionNodes[action]));
|
|
@@ -100,7 +102,7 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
100
102
|
}) })) : null] })] }));
|
|
101
103
|
const mobilePinnedNode = mobilePinnedItems.length > 0 ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: mobilePinnedItems.map((item, i) => (jsxRuntime.jsx(NavbarLinkItem, { item: item, className: "max-sm:-mr-1" }, `mobile-pinned-${i}`))) })) : null;
|
|
102
104
|
const mobileSearchNode = searchToggle.enabled !== false
|
|
103
|
-
? (
|
|
105
|
+
? (_d = (_c = searchToggle.components) === null || _c === void 0 ? void 0 : _c.sm) !== null && _d !== void 0 ? _d : null
|
|
104
106
|
: null;
|
|
105
107
|
const mobileBarNodes = {
|
|
106
108
|
pinned: mobilePinnedNode,
|
|
@@ -108,7 +110,7 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
108
110
|
menu: menuNode,
|
|
109
111
|
};
|
|
110
112
|
const getMobileBarNode = (action) => { var _a; return (_a = mobileBarNodes[action]) !== null && _a !== void 0 ? _a : null; };
|
|
111
|
-
return (jsxRuntime.jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsxRuntime.jsx(Link, { href: (
|
|
113
|
+
return (jsxRuntime.jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsxRuntime.jsx(Link, { href: (_e = nav.url) !== null && _e !== void 0 ? _e : '/', prefetch: false, className: "inline-flex items-center gap-2.5 font-semibold", children: renderNavTitle(nav.title) }), nav.children, jsxRuntime.jsx("ul", { className: "flex flex-row items-center gap-2 px-6 max-sm:hidden", children: navItems
|
|
112
114
|
.filter((item) => !isSecondary(item))
|
|
113
115
|
.map((item, i) => (jsxRuntime.jsx(NavbarLinkItem, { item: item, className: "text-sm" }, i))) }), jsxRuntime.jsx("div", { className: "flex flex-row items-center justify-end gap-1.5 flex-1 max-lg:hidden", children: desktopActionsOrder.map((action) => {
|
|
114
116
|
const node = desktopActionNodes[action];
|
|
@@ -120,6 +122,12 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
120
122
|
return node ? (jsxRuntime.jsx(React.Fragment, { children: node }, `mobile-bar-${action}`)) : null;
|
|
121
123
|
}) })] }));
|
|
122
124
|
}
|
|
125
|
+
function shouldShowThemeSwitch(mode) {
|
|
126
|
+
return mode === 'light-dark-system' || mode == null;
|
|
127
|
+
}
|
|
128
|
+
function normalizeThemeSwitchMode(mode) {
|
|
129
|
+
return 'light-dark-system';
|
|
130
|
+
}
|
|
123
131
|
function CustomNavbar(_a) {
|
|
124
132
|
var { bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, className, style, floating = false } = _a, props = tslib.__rest(_a, ["bannerHeight", "headerHeight", "maxContentWidth", "className", "style", "floating"]);
|
|
125
133
|
const [value, setValue] = React.useState('');
|
|
@@ -13,6 +13,7 @@ import { Popover, PopoverTrigger, PopoverContent } from 'fumadocs-ui/components/
|
|
|
13
13
|
import { buttonVariants } from 'fumadocs-ui/components/ui/button';
|
|
14
14
|
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
|
15
15
|
import { HeaderThemeSwitch } from './header-theme-switch.mjs';
|
|
16
|
+
import { useSiteThemeMode } from './site-theme-context.mjs';
|
|
16
17
|
|
|
17
18
|
const PrefetchLinkItem = LinkItem;
|
|
18
19
|
const DEFAULT_DESKTOP_ACTIONS = [
|
|
@@ -32,8 +33,9 @@ const DEFAULT_MOBILE_MENU_ACTIONS = [
|
|
|
32
33
|
'i18n',
|
|
33
34
|
'theme',
|
|
34
35
|
];
|
|
35
|
-
function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl,
|
|
36
|
-
var _a, _b, _c, _d, _e
|
|
36
|
+
function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, searchToggle = {}, bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, navbarClassName, floating = false, desktopActionsOrder = DEFAULT_DESKTOP_ACTIONS, mobileBarActionsOrder = DEFAULT_MOBILE_BAR_ACTIONS, mobileMenuActionsOrder = DEFAULT_MOBILE_MENU_ACTIONS, }) {
|
|
37
|
+
var _a, _b, _c, _d, _e;
|
|
38
|
+
const themeMode = useSiteThemeMode();
|
|
37
39
|
const finalLinks = useMemo(() => resolveLinkItems({ links, githubUrl }), [links, githubUrl]);
|
|
38
40
|
const navItems = finalLinks.filter((item) => { var _a; return ['nav', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
|
|
39
41
|
const menuItems = finalLinks.filter((item) => { var _a; return ['menu', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
|
|
@@ -53,8 +55,8 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
53
55
|
search: searchToggle.enabled !== false
|
|
54
56
|
? (_b = (_a = searchToggle.components) === null || _a === void 0 ? void 0 : _a.lg) !== null && _b !== void 0 ? _b : null
|
|
55
57
|
: null,
|
|
56
|
-
theme:
|
|
57
|
-
? (
|
|
58
|
+
theme: shouldShowThemeSwitch(themeMode)
|
|
59
|
+
? (jsx(HeaderThemeSwitch, { mode: normalizeThemeSwitchMode() }))
|
|
58
60
|
: null,
|
|
59
61
|
i18n: i18n ? (jsx(CompactLanguageToggle, { children: jsx(LanguagesIcon, { className: "size-5" }) })) : null,
|
|
60
62
|
secondary: desktopSecondaryDisplayItems.length ? (jsx("ul", { className: "flex flex-row gap-2 items-center empty:hidden", children: desktopSecondaryDisplayItems.map((item, i) => (jsx(NavbarLinkItem, { item: item, className: cn(item.type === 'icon' && [
|
|
@@ -76,8 +78,8 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
76
78
|
github: githubMobileMenuItem ? (jsx(MenuLinkItem, { item: githubMobileMenuItem, className: "-me-1.5" })) : null,
|
|
77
79
|
separator: jsx("div", { role: "separator", className: "flex-1" }),
|
|
78
80
|
i18n: i18n ? (jsxs(CompactLanguageToggle, { children: [jsx(LanguagesIcon, { className: "size-5" }), jsx(LanguageSelectText, {}), jsx(ChevronDownIcon, { className: "size-3 text-fd-muted-foreground" })] })) : null,
|
|
79
|
-
theme:
|
|
80
|
-
? (
|
|
81
|
+
theme: shouldShowThemeSwitch(themeMode)
|
|
82
|
+
? (jsx(HeaderThemeSwitch, { mode: normalizeThemeSwitchMode() }))
|
|
81
83
|
: null,
|
|
82
84
|
};
|
|
83
85
|
const shouldRenderMobileUtilities = mobileMenuActionsOrder.some((action) => action !== 'separator' && Boolean(mobileMenuActionNodes[action]));
|
|
@@ -98,7 +100,7 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
98
100
|
}) })) : null] })] }));
|
|
99
101
|
const mobilePinnedNode = mobilePinnedItems.length > 0 ? (jsx(Fragment, { children: mobilePinnedItems.map((item, i) => (jsx(NavbarLinkItem, { item: item, className: "max-sm:-mr-1" }, `mobile-pinned-${i}`))) })) : null;
|
|
100
102
|
const mobileSearchNode = searchToggle.enabled !== false
|
|
101
|
-
? (
|
|
103
|
+
? (_d = (_c = searchToggle.components) === null || _c === void 0 ? void 0 : _c.sm) !== null && _d !== void 0 ? _d : null
|
|
102
104
|
: null;
|
|
103
105
|
const mobileBarNodes = {
|
|
104
106
|
pinned: mobilePinnedNode,
|
|
@@ -106,7 +108,7 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
106
108
|
menu: menuNode,
|
|
107
109
|
};
|
|
108
110
|
const getMobileBarNode = (action) => { var _a; return (_a = mobileBarNodes[action]) !== null && _a !== void 0 ? _a : null; };
|
|
109
|
-
return (jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsx(Link, { href: (
|
|
111
|
+
return (jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsx(Link, { href: (_e = nav.url) !== null && _e !== void 0 ? _e : '/', prefetch: false, className: "inline-flex items-center gap-2.5 font-semibold", children: renderNavTitle(nav.title) }), nav.children, jsx("ul", { className: "flex flex-row items-center gap-2 px-6 max-sm:hidden", children: navItems
|
|
110
112
|
.filter((item) => !isSecondary(item))
|
|
111
113
|
.map((item, i) => (jsx(NavbarLinkItem, { item: item, className: "text-sm" }, i))) }), jsx("div", { className: "flex flex-row items-center justify-end gap-1.5 flex-1 max-lg:hidden", children: desktopActionsOrder.map((action) => {
|
|
112
114
|
const node = desktopActionNodes[action];
|
|
@@ -118,6 +120,12 @@ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitc
|
|
|
118
120
|
return node ? (jsx(Fragment$1, { children: node }, `mobile-bar-${action}`)) : null;
|
|
119
121
|
}) })] }));
|
|
120
122
|
}
|
|
123
|
+
function shouldShowThemeSwitch(mode) {
|
|
124
|
+
return mode === 'light-dark-system' || mode == null;
|
|
125
|
+
}
|
|
126
|
+
function normalizeThemeSwitchMode(mode) {
|
|
127
|
+
return 'light-dark-system';
|
|
128
|
+
}
|
|
121
129
|
function CustomNavbar(_a) {
|
|
122
130
|
var { bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, className, style, floating = false } = _a, props = __rest(_a, ["bannerHeight", "headerHeight", "maxContentWidth", "className", "style", "floating"]);
|
|
123
131
|
const [value, setValue] = useState('');
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import type { ComponentProps, ReactNode } from 'react';
|
|
2
2
|
import { NextProvider } from 'fumadocs-core/framework/next';
|
|
3
3
|
import { type I18nProviderProps } from 'fumadocs-ui/contexts/i18n';
|
|
4
|
-
import {
|
|
4
|
+
import type { SiteThemeProviderProps } from './site-theme-provider';
|
|
5
5
|
type NextProviderComponents = {
|
|
6
6
|
Link?: ComponentProps<typeof NextProvider>['Link'];
|
|
7
7
|
Image?: ComponentProps<typeof NextProvider>['Image'];
|
|
8
8
|
};
|
|
9
|
-
type ThemeOptions = ThemeProviderProps & {
|
|
10
|
-
enabled?: boolean;
|
|
11
|
-
};
|
|
12
9
|
export interface DocsRootProviderProps {
|
|
13
10
|
i18n: Omit<I18nProviderProps, 'children'>;
|
|
14
|
-
theme?:
|
|
11
|
+
theme?: Omit<SiteThemeProviderProps, 'children'>;
|
|
15
12
|
components?: NextProviderComponents;
|
|
16
13
|
children: ReactNode;
|
|
17
14
|
}
|
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var next = require('fumadocs-core/framework/next');
|
|
5
5
|
var i18n = require('fumadocs-ui/contexts/i18n');
|
|
6
|
-
var
|
|
6
|
+
var siteThemeContext = require('./site-theme-context.js');
|
|
7
7
|
|
|
8
8
|
function DocsRootProvider({ i18n: i18n$1, theme = {}, components, children, }) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
body = (jsxRuntime.jsx(i18n.I18nProvider, Object.assign({}, i18n$1, { children: body })));
|
|
14
|
-
return (jsxRuntime.jsx(next.NextProvider, { Link: components === null || components === void 0 ? void 0 : components.Link, Image: components === null || components === void 0 ? void 0 : components.Image, children: body }));
|
|
9
|
+
var _a;
|
|
10
|
+
const themeMode = (_a = theme.mode) !== null && _a !== void 0 ? _a : 'light-dark-system';
|
|
11
|
+
return (jsxRuntime.jsx(next.NextProvider, { Link: components === null || components === void 0 ? void 0 : components.Link, Image: components === null || components === void 0 ? void 0 : components.Image, children: jsxRuntime.jsx(i18n.I18nProvider, Object.assign({}, i18n$1, { children: jsxRuntime.jsx(siteThemeContext.SiteThemeRootProvider, Object.assign({}, theme, { mode: themeMode, children: children })) })) }));
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
exports.DocsRootProvider = DocsRootProvider;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { NextProvider } from 'fumadocs-core/framework/next';
|
|
3
3
|
import { I18nProvider } from 'fumadocs-ui/contexts/i18n';
|
|
4
|
-
import {
|
|
4
|
+
import { SiteThemeRootProvider } from './site-theme-context.mjs';
|
|
5
5
|
|
|
6
6
|
function DocsRootProvider({ i18n, theme = {}, components, children, }) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
body = (jsx(I18nProvider, Object.assign({}, i18n, { children: body })));
|
|
12
|
-
return (jsx(NextProvider, { Link: components === null || components === void 0 ? void 0 : components.Link, Image: components === null || components === void 0 ? void 0 : components.Image, children: body }));
|
|
7
|
+
var _a;
|
|
8
|
+
const themeMode = (_a = theme.mode) !== null && _a !== void 0 ? _a : 'light-dark-system';
|
|
9
|
+
return (jsx(NextProvider, { Link: components === null || components === void 0 ? void 0 : components.Link, Image: components === null || components === void 0 ? void 0 : components.Image, children: jsx(I18nProvider, Object.assign({}, i18n, { children: jsx(SiteThemeRootProvider, Object.assign({}, theme, { mode: themeMode, children: children })) })) }));
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
export { DocsRootProvider };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ComponentProps } from 'react';
|
|
2
2
|
export interface HeaderThemeSwitchProps extends ComponentProps<'div'> {
|
|
3
|
-
mode?: 'light-dark
|
|
3
|
+
mode?: 'light-dark-system';
|
|
4
4
|
}
|
|
5
|
-
export declare function HeaderThemeSwitch({ className,
|
|
5
|
+
export declare function HeaderThemeSwitch({ className, ...props }: HeaderThemeSwitchProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -19,22 +19,14 @@ const itemVariants = classVarianceAuthority.cva('inline-flex size-6.5 items-cent
|
|
|
19
19
|
});
|
|
20
20
|
const full = [['light', icons.SunIcon], ['dark', icons.MoonIcon], ['system', icons.AirplayIcon]];
|
|
21
21
|
function HeaderThemeSwitch(_a) {
|
|
22
|
-
var { className
|
|
23
|
-
const { setTheme, theme
|
|
22
|
+
var { className } = _a, props = tslib.__rest(_a, ["className"]);
|
|
23
|
+
const { setTheme, theme } = nextThemes.useTheme();
|
|
24
24
|
const [mounted, setMounted] = React.useState(false);
|
|
25
25
|
React.useEffect(() => {
|
|
26
26
|
setMounted(true);
|
|
27
27
|
}, []);
|
|
28
28
|
const container = utils.cn('inline-flex items-center rounded-full border p-1 overflow-hidden *:rounded-full', className);
|
|
29
29
|
const iconClassName = 'size-3.5 text-neutral-600 dark:text-neutral-300';
|
|
30
|
-
if (mode === 'light-dark') {
|
|
31
|
-
const value = mounted ? resolvedTheme : null;
|
|
32
|
-
return (jsxRuntime.jsx("button", { type: "button", className: container, "aria-label": "Toggle Theme", onClick: () => setTheme(value === 'light' ? 'dark' : 'light'), "data-theme-toggle": "", children: full.map(([key, Icon]) => {
|
|
33
|
-
if (key === 'system')
|
|
34
|
-
return null;
|
|
35
|
-
return (jsxRuntime.jsx(Icon, { fill: "currentColor", className: utils.cn(itemVariants({ active: value === key }), iconClassName) }, key));
|
|
36
|
-
}) }));
|
|
37
|
-
}
|
|
38
30
|
const value = mounted ? theme : null;
|
|
39
31
|
return (jsxRuntime.jsx("div", Object.assign({ className: container, "data-theme-toggle": "" }, props, { children: full.map(([key, Icon]) => (jsxRuntime.jsx("button", { type: "button", "aria-label": key, className: utils.cn(itemVariants({ active: value === key })), onClick: () => setTheme(key), children: jsxRuntime.jsx(Icon, { className: iconClassName, fill: "currentColor" }) }, key))) })));
|
|
40
32
|
}
|
|
@@ -17,22 +17,14 @@ const itemVariants = cva('inline-flex size-6.5 items-center justify-center round
|
|
|
17
17
|
});
|
|
18
18
|
const full = [['light', SunIcon], ['dark', MoonIcon], ['system', AirplayIcon]];
|
|
19
19
|
function HeaderThemeSwitch(_a) {
|
|
20
|
-
var { className
|
|
21
|
-
const { setTheme, theme
|
|
20
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
21
|
+
const { setTheme, theme } = useTheme();
|
|
22
22
|
const [mounted, setMounted] = useState(false);
|
|
23
23
|
useEffect(() => {
|
|
24
24
|
setMounted(true);
|
|
25
25
|
}, []);
|
|
26
26
|
const container = cn('inline-flex items-center rounded-full border p-1 overflow-hidden *:rounded-full', className);
|
|
27
27
|
const iconClassName = 'size-3.5 text-neutral-600 dark:text-neutral-300';
|
|
28
|
-
if (mode === 'light-dark') {
|
|
29
|
-
const value = mounted ? resolvedTheme : null;
|
|
30
|
-
return (jsx("button", { type: "button", className: container, "aria-label": "Toggle Theme", onClick: () => setTheme(value === 'light' ? 'dark' : 'light'), "data-theme-toggle": "", children: full.map(([key, Icon]) => {
|
|
31
|
-
if (key === 'system')
|
|
32
|
-
return null;
|
|
33
|
-
return (jsx(Icon, { fill: "currentColor", className: cn(itemVariants({ active: value === key }), iconClassName) }, key));
|
|
34
|
-
}) }));
|
|
35
|
-
}
|
|
36
28
|
const value = mounted ? theme : null;
|
|
37
29
|
return (jsx("div", Object.assign({ className: container, "data-theme-toggle": "" }, props, { children: full.map(([key, Icon]) => (jsx("button", { type: "button", "aria-label": key, className: cn(itemVariants({ active: value === key })), onClick: () => setTheme(key), children: jsx(Icon, { className: iconClassName, fill: "currentColor" }) }, key))) })));
|
|
38
30
|
}
|
|
@@ -4,6 +4,7 @@ import { type SiteBaseLayoutConfig } from './site-layout-shared';
|
|
|
4
4
|
export interface SiteDocsLayoutConfig extends SiteBaseLayoutConfig {
|
|
5
5
|
tree: DocsLayoutProps['tree'];
|
|
6
6
|
sidebar?: DocsLayoutProps['sidebar'];
|
|
7
|
+
slots?: DocsLayoutProps['slots'];
|
|
7
8
|
}
|
|
8
9
|
export declare function SiteDocsLayout({ config, children, }: {
|
|
9
10
|
config: SiteDocsLayoutConfig;
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var docs = require('fumadocs-ui/layouts/docs');
|
|
5
5
|
var siteLayoutShared = require('./site-layout-shared.js');
|
|
6
|
+
var siteDocsThemeSwitch = require('./site-docs-theme-switch.js');
|
|
6
7
|
|
|
7
8
|
function toDocsLayoutOptions(config) {
|
|
8
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config.nav ? { nav: config.nav } : {})), (config.i18n ? { i18n: config.i18n } : {})), (config.githubUrl ? { githubUrl: config.githubUrl } : {})), (config.links ? { links: siteLayoutShared.normalizeNavItems(config.links) } : {})), (config.searchToggle ? { searchToggle: config.searchToggle } : {})), (config.
|
|
9
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config.nav ? { nav: config.nav } : {})), (config.i18n ? { i18n: config.i18n } : {})), (config.githubUrl ? { githubUrl: config.githubUrl } : {})), (config.links ? { links: siteLayoutShared.normalizeNavItems(config.links) } : {})), (config.searchToggle ? { searchToggle: config.searchToggle } : {})), { slots: Object.assign(Object.assign({}, config.slots), { themeSwitch: siteDocsThemeSwitch.SiteDocsThemeSwitch }) }), (config.sidebar ? { sidebar: config.sidebar } : {})), { tree: config.tree });
|
|
9
10
|
}
|
|
10
11
|
function SiteDocsLayout({ config, children, }) {
|
|
11
12
|
const options = toDocsLayoutOptions(config);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
3
3
|
import { normalizeNavItems } from './site-layout-shared.mjs';
|
|
4
|
+
import { SiteDocsThemeSwitch } from './site-docs-theme-switch.mjs';
|
|
4
5
|
|
|
5
6
|
function toDocsLayoutOptions(config) {
|
|
6
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config.nav ? { nav: config.nav } : {})), (config.i18n ? { i18n: config.i18n } : {})), (config.githubUrl ? { githubUrl: config.githubUrl } : {})), (config.links ? { links: normalizeNavItems(config.links) } : {})), (config.searchToggle ? { searchToggle: config.searchToggle } : {})), (config.
|
|
7
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config.nav ? { nav: config.nav } : {})), (config.i18n ? { i18n: config.i18n } : {})), (config.githubUrl ? { githubUrl: config.githubUrl } : {})), (config.links ? { links: normalizeNavItems(config.links) } : {})), (config.searchToggle ? { searchToggle: config.searchToggle } : {})), { slots: Object.assign(Object.assign({}, config.slots), { themeSwitch: SiteDocsThemeSwitch }) }), (config.sidebar ? { sidebar: config.sidebar } : {})), { tree: config.tree });
|
|
7
8
|
}
|
|
8
9
|
function SiteDocsLayout({ config, children, }) {
|
|
9
10
|
const options = toDocsLayoutOptions(config);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var headerThemeSwitch = require('./header-theme-switch.js');
|
|
6
|
+
var siteThemeContext = require('./site-theme-context.js');
|
|
7
|
+
|
|
8
|
+
function SiteDocsThemeSwitch(props) {
|
|
9
|
+
const themeMode = siteThemeContext.useSiteThemeMode();
|
|
10
|
+
if (themeMode !== 'light-dark-system') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return jsxRuntime.jsx(headerThemeSwitch.HeaderThemeSwitch, Object.assign({}, props, { mode: "light-dark-system" }));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
exports.SiteDocsThemeSwitch = SiteDocsThemeSwitch;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { HeaderThemeSwitch } from './header-theme-switch.mjs';
|
|
4
|
+
import { useSiteThemeMode } from './site-theme-context.mjs';
|
|
5
|
+
|
|
6
|
+
function SiteDocsThemeSwitch(props) {
|
|
7
|
+
const themeMode = useSiteThemeMode();
|
|
8
|
+
if (themeMode !== 'light-dark-system') {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return jsx(HeaderThemeSwitch, Object.assign({}, props, { mode: "light-dark-system" }));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { SiteDocsThemeSwitch };
|
|
@@ -47,7 +47,10 @@ export interface SiteBaseLayoutConfig {
|
|
|
47
47
|
githubUrl?: string;
|
|
48
48
|
links?: SiteNavItemConfig[];
|
|
49
49
|
searchToggle?: HomeLayoutProps['searchToggle'];
|
|
50
|
-
|
|
50
|
+
}
|
|
51
|
+
export type SiteThemeSwitchMode = 'light-dark-system' | 'light-only' | 'dark-only';
|
|
52
|
+
export interface SiteThemeSwitchConfig {
|
|
53
|
+
mode?: SiteThemeSwitchMode;
|
|
51
54
|
}
|
|
52
55
|
export interface SiteMenuLeafConfig {
|
|
53
56
|
text: ReactNode;
|
|
@@ -38,7 +38,7 @@ function createSiteBaseLayoutConfig(options) {
|
|
|
38
38
|
} }, (options.i18n ? { i18n: options.i18n } : {})), (options.githubUrl ? { githubUrl: options.githubUrl } : {}));
|
|
39
39
|
}
|
|
40
40
|
function toHomeLayoutOptions(config) {
|
|
41
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(
|
|
41
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config.nav ? { nav: config.nav } : {})), (config.i18n ? { i18n: config.i18n } : {})), (config.githubUrl ? { githubUrl: config.githubUrl } : {})), (config.links ? { links: normalizeNavItems(config.links) } : {})), (config.searchToggle ? { searchToggle: config.searchToggle } : {}));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
exports.createSiteBaseLayoutConfig = createSiteBaseLayoutConfig;
|
|
@@ -36,7 +36,7 @@ function createSiteBaseLayoutConfig(options) {
|
|
|
36
36
|
} }, (options.i18n ? { i18n: options.i18n } : {})), (options.githubUrl ? { githubUrl: options.githubUrl } : {}));
|
|
37
37
|
}
|
|
38
38
|
function toHomeLayoutOptions(config) {
|
|
39
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(
|
|
39
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config.nav ? { nav: config.nav } : {})), (config.i18n ? { i18n: config.i18n } : {})), (config.githubUrl ? { githubUrl: config.githubUrl } : {})), (config.links ? { links: normalizeNavItems(config.links) } : {})), (config.searchToggle ? { searchToggle: config.searchToggle } : {}));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export { createSiteBaseLayoutConfig, createSiteNavGroup, createSiteNavLink, normalizeNavItems, toHomeLayoutOptions };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { SiteThemeSwitchMode } from './site-layout-shared';
|
|
3
|
+
import { type SiteThemeProviderProps } from './site-theme-provider';
|
|
4
|
+
export declare function useSiteThemeMode(): SiteThemeSwitchMode;
|
|
5
|
+
export interface SiteThemeRootProviderProps extends Omit<SiteThemeProviderProps, 'children'> {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function SiteThemeRootProvider({ mode, children, ...props }: SiteThemeRootProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tslib = require('tslib');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var React = require('react');
|
|
7
|
+
var siteThemeProvider = require('./site-theme-provider.js');
|
|
8
|
+
|
|
9
|
+
const SiteThemeModeContext = React.createContext('light-dark-system');
|
|
10
|
+
function useSiteThemeMode() {
|
|
11
|
+
return React.useContext(SiteThemeModeContext);
|
|
12
|
+
}
|
|
13
|
+
function SiteThemeRootProvider(_a) {
|
|
14
|
+
var { mode = 'light-dark-system', children } = _a, props = tslib.__rest(_a, ["mode", "children"]);
|
|
15
|
+
return (jsxRuntime.jsx(SiteThemeModeContext.Provider, { value: mode, children: jsxRuntime.jsx(siteThemeProvider.SiteThemeProvider, Object.assign({}, props, { mode: mode, children: children })) }));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.SiteThemeRootProvider = SiteThemeRootProvider;
|
|
19
|
+
exports.useSiteThemeMode = useSiteThemeMode;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { __rest } from 'tslib';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { createContext, useContext } from 'react';
|
|
5
|
+
import { SiteThemeProvider } from './site-theme-provider.mjs';
|
|
6
|
+
|
|
7
|
+
const SiteThemeModeContext = createContext('light-dark-system');
|
|
8
|
+
function useSiteThemeMode() {
|
|
9
|
+
return useContext(SiteThemeModeContext);
|
|
10
|
+
}
|
|
11
|
+
function SiteThemeRootProvider(_a) {
|
|
12
|
+
var { mode = 'light-dark-system', children } = _a, props = __rest(_a, ["mode", "children"]);
|
|
13
|
+
return (jsx(SiteThemeModeContext.Provider, { value: mode, children: jsx(SiteThemeProvider, Object.assign({}, props, { mode: mode, children: children })) }));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { SiteThemeRootProvider, useSiteThemeMode };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type ThemeProviderProps } from 'next-themes';
|
|
3
|
+
import type { SiteThemeSwitchMode } from './site-layout-shared';
|
|
4
|
+
export interface SiteThemeProviderProps extends Omit<ThemeProviderProps, 'children'> {
|
|
5
|
+
mode?: SiteThemeSwitchMode;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function SiteThemeProvider({ mode, children, ...props }: SiteThemeProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tslib = require('tslib');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var nextThemes = require('next-themes');
|
|
7
|
+
|
|
8
|
+
function SiteThemeProvider(_a) {
|
|
9
|
+
var { mode = 'light-dark-system', children } = _a, props = tslib.__rest(_a, ["mode", "children"]);
|
|
10
|
+
return (jsxRuntime.jsx(nextThemes.ThemeProvider, Object.assign({ attribute: "class", disableTransitionOnChange: true }, resolveThemeProviderProps(mode), props, { children: children })));
|
|
11
|
+
}
|
|
12
|
+
function resolveThemeProviderProps(mode) {
|
|
13
|
+
if (mode === 'light-only') {
|
|
14
|
+
return {
|
|
15
|
+
forcedTheme: 'light',
|
|
16
|
+
enableSystem: false,
|
|
17
|
+
defaultTheme: 'light',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (mode === 'dark-only') {
|
|
21
|
+
return {
|
|
22
|
+
forcedTheme: 'dark',
|
|
23
|
+
enableSystem: false,
|
|
24
|
+
defaultTheme: 'dark',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
enableSystem: true,
|
|
29
|
+
defaultTheme: 'system',
|
|
30
|
+
forcedTheme: undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exports.SiteThemeProvider = SiteThemeProvider;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { __rest } from 'tslib';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { ThemeProvider } from 'next-themes';
|
|
5
|
+
|
|
6
|
+
function SiteThemeProvider(_a) {
|
|
7
|
+
var { mode = 'light-dark-system', children } = _a, props = __rest(_a, ["mode", "children"]);
|
|
8
|
+
return (jsx(ThemeProvider, Object.assign({ attribute: "class", disableTransitionOnChange: true }, resolveThemeProviderProps(mode), props, { children: children })));
|
|
9
|
+
}
|
|
10
|
+
function resolveThemeProviderProps(mode) {
|
|
11
|
+
if (mode === 'light-only') {
|
|
12
|
+
return {
|
|
13
|
+
forcedTheme: 'light',
|
|
14
|
+
enableSystem: false,
|
|
15
|
+
defaultTheme: 'light',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (mode === 'dark-only') {
|
|
19
|
+
return {
|
|
20
|
+
forcedTheme: 'dark',
|
|
21
|
+
enableSystem: false,
|
|
22
|
+
defaultTheme: 'dark',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
enableSystem: true,
|
|
27
|
+
defaultTheme: 'system',
|
|
28
|
+
forcedTheme: undefined,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { SiteThemeProvider };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "31.0
|
|
3
|
+
"version": "31.2.0",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./clerk": {
|
|
@@ -243,9 +243,9 @@
|
|
|
243
243
|
"tslib": "^2.8.1",
|
|
244
244
|
"unified": "^11.0.5",
|
|
245
245
|
"zod": "^4.3.6",
|
|
246
|
+
"@windrun-huaiin/lib": "^31.0.1",
|
|
246
247
|
"@windrun-huaiin/base-ui": "^31.0.0",
|
|
247
|
-
"@windrun-huaiin/contracts": "^31.0.0"
|
|
248
|
-
"@windrun-huaiin/lib": "^31.0.0"
|
|
248
|
+
"@windrun-huaiin/contracts": "^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
|
|
38
|
+
import { useSiteThemeMode } from './site-theme-context';
|
|
39
|
+
import type {
|
|
40
|
+
ExtendedLinkItem,
|
|
41
|
+
SiteThemeSwitchMode,
|
|
42
|
+
} from './site-layout-shared';
|
|
39
43
|
|
|
40
44
|
export type NavbarCSSVars = CSSProperties & {
|
|
41
45
|
'--fd-banner-height'?: string;
|
|
@@ -131,7 +135,6 @@ export function CustomHomeHeader({
|
|
|
131
135
|
i18n = false,
|
|
132
136
|
links,
|
|
133
137
|
githubUrl,
|
|
134
|
-
themeSwitch = {},
|
|
135
138
|
searchToggle = {},
|
|
136
139
|
bannerHeight = 0,
|
|
137
140
|
headerHeight = 2.5,
|
|
@@ -142,6 +145,7 @@ export function CustomHomeHeader({
|
|
|
142
145
|
mobileBarActionsOrder = DEFAULT_MOBILE_BAR_ACTIONS,
|
|
143
146
|
mobileMenuActionsOrder = DEFAULT_MOBILE_MENU_ACTIONS,
|
|
144
147
|
}: CustomHomeHeaderProps) {
|
|
148
|
+
const themeMode = useSiteThemeMode();
|
|
145
149
|
const finalLinks = useMemo(
|
|
146
150
|
() => resolveLinkItems({ links, githubUrl }),
|
|
147
151
|
[links, githubUrl],
|
|
@@ -175,8 +179,12 @@ export function CustomHomeHeader({
|
|
|
175
179
|
? searchToggle.components?.lg ?? null
|
|
176
180
|
: null,
|
|
177
181
|
theme:
|
|
178
|
-
|
|
179
|
-
?
|
|
182
|
+
shouldShowThemeSwitch(themeMode)
|
|
183
|
+
? (
|
|
184
|
+
<HeaderThemeSwitch
|
|
185
|
+
mode={normalizeThemeSwitchMode(themeMode)}
|
|
186
|
+
/>
|
|
187
|
+
)
|
|
180
188
|
: null,
|
|
181
189
|
i18n: i18n ? (
|
|
182
190
|
<CompactLanguageToggle>
|
|
@@ -238,8 +246,12 @@ export function CustomHomeHeader({
|
|
|
238
246
|
</CompactLanguageToggle>
|
|
239
247
|
) : null,
|
|
240
248
|
theme:
|
|
241
|
-
|
|
242
|
-
?
|
|
249
|
+
shouldShowThemeSwitch(themeMode)
|
|
250
|
+
? (
|
|
251
|
+
<HeaderThemeSwitch
|
|
252
|
+
mode={normalizeThemeSwitchMode(themeMode)}
|
|
253
|
+
/>
|
|
254
|
+
)
|
|
243
255
|
: null,
|
|
244
256
|
};
|
|
245
257
|
const shouldRenderMobileUtilities = mobileMenuActionsOrder.some(
|
|
@@ -356,6 +368,16 @@ export function CustomHomeHeader({
|
|
|
356
368
|
);
|
|
357
369
|
}
|
|
358
370
|
|
|
371
|
+
function shouldShowThemeSwitch(mode?: SiteThemeSwitchMode): boolean {
|
|
372
|
+
return mode === 'light-dark-system' || mode == null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function normalizeThemeSwitchMode(
|
|
376
|
+
mode?: SiteThemeSwitchMode,
|
|
377
|
+
): 'light-dark-system' {
|
|
378
|
+
return 'light-dark-system';
|
|
379
|
+
}
|
|
380
|
+
|
|
359
381
|
interface CustomNavbarProps extends ComponentProps<'div'> {
|
|
360
382
|
bannerHeight?: number;
|
|
361
383
|
headerHeight?: number;
|
|
@@ -159,18 +159,18 @@ export function CustomHomeLayout({
|
|
|
159
159
|
/>
|
|
160
160
|
)}
|
|
161
161
|
<HomeLayout
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
{...homeLayoutProps}
|
|
163
|
+
nav={{
|
|
164
|
+
...navOptions,
|
|
165
|
+
component: header,
|
|
166
|
+
}}
|
|
167
|
+
className='bg-neutral-100 dark:bg-neutral-900'
|
|
168
|
+
style={layoutStyle}
|
|
169
|
+
>
|
|
170
|
+
{children}
|
|
171
|
+
{showFooter ? footer ?? <Footer locale={locale} localePrefixAsNeeded={localePrefixAsNeeded} defaultLocale={defaultLocale} /> : null}
|
|
172
|
+
{showGoToTop ? goToTop ?? <GoToTop /> : null}
|
|
173
|
+
</HomeLayout>
|
|
174
174
|
</>
|
|
175
175
|
);
|
|
176
176
|
}
|
|
@@ -1,20 +1,17 @@
|
|
|
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 {
|
|
4
|
+
import { SiteThemeRootProvider } from './site-theme-context';
|
|
5
|
+
import type { SiteThemeProviderProps } from './site-theme-provider';
|
|
5
6
|
|
|
6
7
|
type NextProviderComponents = {
|
|
7
8
|
Link?: ComponentProps<typeof NextProvider>['Link'];
|
|
8
9
|
Image?: ComponentProps<typeof NextProvider>['Image'];
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
type ThemeOptions = ThemeProviderProps & {
|
|
12
|
-
enabled?: boolean;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
12
|
export interface DocsRootProviderProps {
|
|
16
13
|
i18n: Omit<I18nProviderProps, 'children'>;
|
|
17
|
-
theme?:
|
|
14
|
+
theme?: Omit<SiteThemeProviderProps, 'children'>;
|
|
18
15
|
components?: NextProviderComponents;
|
|
19
16
|
children: ReactNode;
|
|
20
17
|
}
|
|
@@ -25,34 +22,18 @@ export function DocsRootProvider({
|
|
|
25
22
|
components,
|
|
26
23
|
children,
|
|
27
24
|
}: DocsRootProviderProps) {
|
|
28
|
-
|
|
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
|
-
);
|
|
25
|
+
const themeMode = theme.mode ?? 'light-dark-system';
|
|
49
26
|
|
|
50
27
|
return (
|
|
51
28
|
<NextProvider
|
|
52
29
|
Link={components?.Link}
|
|
53
30
|
Image={components?.Image}
|
|
54
31
|
>
|
|
55
|
-
{
|
|
32
|
+
<I18nProvider {...i18n}>
|
|
33
|
+
<SiteThemeRootProvider {...theme} mode={themeMode}>
|
|
34
|
+
{children}
|
|
35
|
+
</SiteThemeRootProvider>
|
|
36
|
+
</I18nProvider>
|
|
56
37
|
</NextProvider>
|
|
57
38
|
);
|
|
58
39
|
}
|
|
@@ -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,15 +19,14 @@ 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-dark
|
|
22
|
+
mode?: 'light-dark-system';
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function HeaderThemeSwitch({
|
|
25
26
|
className,
|
|
26
|
-
mode = 'light-dark',
|
|
27
27
|
...props
|
|
28
28
|
}: HeaderThemeSwitchProps) {
|
|
29
|
-
const { setTheme, theme
|
|
29
|
+
const { setTheme, theme } = useTheme();
|
|
30
30
|
const [mounted, setMounted] = useState(false);
|
|
31
31
|
|
|
32
32
|
useEffect(() => {
|
|
@@ -39,35 +39,6 @@ export function HeaderThemeSwitch({
|
|
|
39
39
|
);
|
|
40
40
|
const iconClassName = 'size-3.5 text-neutral-600 dark:text-neutral-300';
|
|
41
41
|
|
|
42
|
-
if (mode === 'light-dark') {
|
|
43
|
-
const value = mounted ? resolvedTheme : null;
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<button
|
|
47
|
-
type="button"
|
|
48
|
-
className={container}
|
|
49
|
-
aria-label="Toggle Theme"
|
|
50
|
-
onClick={() => setTheme(value === 'light' ? 'dark' : 'light')}
|
|
51
|
-
data-theme-toggle=""
|
|
52
|
-
>
|
|
53
|
-
{full.map(([key, Icon]) => {
|
|
54
|
-
if (key === 'system') return null;
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<Icon
|
|
58
|
-
key={key}
|
|
59
|
-
fill="currentColor"
|
|
60
|
-
className={cn(
|
|
61
|
-
itemVariants({ active: value === key }),
|
|
62
|
-
iconClassName,
|
|
63
|
-
)}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
})}
|
|
67
|
-
</button>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
42
|
const value = mounted ? theme : null;
|
|
72
43
|
|
|
73
44
|
return (
|
|
@@ -4,10 +4,12 @@ import {
|
|
|
4
4
|
normalizeNavItems,
|
|
5
5
|
type SiteBaseLayoutConfig,
|
|
6
6
|
} from './site-layout-shared';
|
|
7
|
+
import { SiteDocsThemeSwitch } from './site-docs-theme-switch';
|
|
7
8
|
|
|
8
9
|
export interface SiteDocsLayoutConfig extends SiteBaseLayoutConfig {
|
|
9
10
|
tree: DocsLayoutProps['tree'];
|
|
10
11
|
sidebar?: DocsLayoutProps['sidebar'];
|
|
12
|
+
slots?: DocsLayoutProps['slots'];
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
function toDocsLayoutOptions(config: SiteDocsLayoutConfig): DocsLayoutProps {
|
|
@@ -17,7 +19,10 @@ function toDocsLayoutOptions(config: SiteDocsLayoutConfig): DocsLayoutProps {
|
|
|
17
19
|
...(config.githubUrl ? { githubUrl: config.githubUrl } : {}),
|
|
18
20
|
...(config.links ? { links: normalizeNavItems(config.links) } : {}),
|
|
19
21
|
...(config.searchToggle ? { searchToggle: config.searchToggle } : {}),
|
|
20
|
-
|
|
22
|
+
slots: {
|
|
23
|
+
...config.slots,
|
|
24
|
+
themeSwitch: SiteDocsThemeSwitch,
|
|
25
|
+
},
|
|
21
26
|
...(config.sidebar ? { sidebar: config.sidebar } : {}),
|
|
22
27
|
tree: config.tree,
|
|
23
28
|
};
|
|
@@ -31,5 +36,6 @@ export function SiteDocsLayout({
|
|
|
31
36
|
children: ReactNode;
|
|
32
37
|
}) {
|
|
33
38
|
const options = toDocsLayoutOptions(config);
|
|
39
|
+
|
|
34
40
|
return <DocsLayout {...options}>{children}</DocsLayout>;
|
|
35
41
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from 'react';
|
|
4
|
+
import { HeaderThemeSwitch } from './header-theme-switch';
|
|
5
|
+
import { useSiteThemeMode } from './site-theme-context';
|
|
6
|
+
|
|
7
|
+
export function SiteDocsThemeSwitch(props: ComponentProps<'div'>) {
|
|
8
|
+
const themeMode = useSiteThemeMode();
|
|
9
|
+
|
|
10
|
+
if (themeMode !== 'light-dark-system') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return <HeaderThemeSwitch {...props} mode="light-dark-system" />;
|
|
15
|
+
}
|
|
@@ -58,7 +58,15 @@ export interface SiteBaseLayoutConfig {
|
|
|
58
58
|
githubUrl?: string;
|
|
59
59
|
links?: SiteNavItemConfig[];
|
|
60
60
|
searchToggle?: HomeLayoutProps['searchToggle'];
|
|
61
|
-
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type SiteThemeSwitchMode =
|
|
64
|
+
| 'light-dark-system'
|
|
65
|
+
| 'light-only'
|
|
66
|
+
| 'dark-only';
|
|
67
|
+
|
|
68
|
+
export interface SiteThemeSwitchConfig {
|
|
69
|
+
mode?: SiteThemeSwitchMode;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
export interface SiteMenuLeafConfig {
|
|
@@ -185,6 +193,5 @@ export function toHomeLayoutOptions(config: SiteBaseLayoutConfig): HomeLayoutPro
|
|
|
185
193
|
...(config.githubUrl ? { githubUrl: config.githubUrl } : {}),
|
|
186
194
|
...(config.links ? { links: normalizeNavItems(config.links) } : {}),
|
|
187
195
|
...(config.searchToggle ? { searchToggle: config.searchToggle } : {}),
|
|
188
|
-
...(config.themeSwitch ? { themeSwitch: config.themeSwitch } : {}),
|
|
189
196
|
};
|
|
190
197
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, type ReactNode, useContext } from 'react';
|
|
4
|
+
import type { SiteThemeSwitchMode } from './site-layout-shared';
|
|
5
|
+
import { SiteThemeProvider, type SiteThemeProviderProps } from './site-theme-provider';
|
|
6
|
+
|
|
7
|
+
const SiteThemeModeContext = createContext<SiteThemeSwitchMode>('light-dark-system');
|
|
8
|
+
|
|
9
|
+
export function useSiteThemeMode(): SiteThemeSwitchMode {
|
|
10
|
+
return useContext(SiteThemeModeContext);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SiteThemeRootProviderProps
|
|
14
|
+
extends Omit<SiteThemeProviderProps, 'children'> {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function SiteThemeRootProvider({
|
|
19
|
+
mode = 'light-dark-system',
|
|
20
|
+
children,
|
|
21
|
+
...props
|
|
22
|
+
}: SiteThemeRootProviderProps) {
|
|
23
|
+
return (
|
|
24
|
+
<SiteThemeModeContext.Provider value={mode}>
|
|
25
|
+
<SiteThemeProvider {...props} mode={mode}>
|
|
26
|
+
{children}
|
|
27
|
+
</SiteThemeProvider>
|
|
28
|
+
</SiteThemeModeContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
return {
|
|
47
|
+
enableSystem: true,
|
|
48
|
+
defaultTheme: 'system',
|
|
49
|
+
forcedTheme: undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|