@windrun-huaiin/third-ui 10.1.0 → 10.1.1

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.
@@ -34,5 +34,20 @@ export interface CustomHomeHeaderProps extends HomeLayoutProps {
34
34
  * @defaultValue true
35
35
  */
36
36
  floating?: boolean;
37
+ /**
38
+ * Control order of action items on desktop.
39
+ */
40
+ desktopActionsOrder?: DesktopAction[];
41
+ /**
42
+ * Control order of quick actions on the mobile bar.
43
+ */
44
+ mobileBarActionsOrder?: MobileBarAction[];
45
+ /**
46
+ * Control order of utilities inside the mobile dropdown.
47
+ */
48
+ mobileMenuActionsOrder?: MobileMenuAction[];
37
49
  }
38
- export declare function CustomHomeHeader({ nav, i18n, links, githubUrl, themeSwitch, searchToggle, bannerHeight, headerHeight, maxContentWidth, navbarClassName, floating, }: CustomHomeHeaderProps): import("react/jsx-runtime").JSX.Element;
50
+ export type DesktopAction = 'search' | 'theme' | 'i18n' | 'secondary';
51
+ export type MobileBarAction = 'pinned' | 'search' | 'menu';
52
+ export type MobileMenuAction = 'secondary' | 'separator' | 'i18n' | 'theme';
53
+ export declare function CustomHomeHeader({ nav, i18n, links, githubUrl, themeSwitch, searchToggle, bannerHeight, headerHeight, maxContentWidth, navbarClassName, floating, desktopActionsOrder, mobileBarActionsOrder, mobileMenuActionsOrder, }: CustomHomeHeaderProps): import("react/jsx-runtime").JSX.Element;
@@ -18,30 +18,92 @@ var button = require('fumadocs-ui/components/ui/button');
18
18
  var layout = require('fumadocs-ui/contexts/layout');
19
19
  var i18n = require('fumadocs-ui/contexts/i18n');
20
20
 
21
- function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitch = {}, searchToggle: searchToggle$1 = {}, bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, navbarClassName, floating = false, }) {
21
+ const DEFAULT_DESKTOP_ACTIONS = [
22
+ 'search',
23
+ 'theme',
24
+ 'i18n',
25
+ 'secondary',
26
+ ];
27
+ const DEFAULT_MOBILE_BAR_ACTIONS = [
28
+ 'pinned',
29
+ 'search',
30
+ 'menu',
31
+ ];
32
+ const DEFAULT_MOBILE_MENU_ACTIONS = [
33
+ 'secondary',
34
+ 'separator',
35
+ 'i18n',
36
+ 'theme',
37
+ ];
38
+ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitch = {}, searchToggle: searchToggle$1 = {}, bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, navbarClassName, floating = false, desktopActionsOrder = DEFAULT_DESKTOP_ACTIONS, mobileBarActionsOrder = DEFAULT_MOBILE_BAR_ACTIONS, mobileMenuActionsOrder = DEFAULT_MOBILE_MENU_ACTIONS, }) {
22
39
  var _a, _b, _c, _d, _e, _f, _g;
23
40
  const finalLinks = React.useMemo(() => shared.getLinks(links, githubUrl), [links, githubUrl]);
24
41
  const navItems = finalLinks.filter((item) => { var _a; return ['nav', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
25
42
  const menuItems = finalLinks.filter((item) => { var _a; return ['menu', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
26
43
  const mobilePinnedItems = navItems.filter((item) => isSecondary(item) && isMobilePinned(item));
27
44
  const filteredMenuItems = menuItems.filter((item) => !isMobilePinned(item));
28
- return (jsxRuntime.jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsxRuntime.jsx(Link, { href: (_a = nav.url) !== null && _a !== void 0 ? _a : '/', className: "inline-flex items-center gap-2.5 font-semibold", children: nav.title }), nav.children, jsxRuntime.jsx("ul", { className: "flex flex-row items-center gap-2 px-6 max-sm:hidden", children: navItems
45
+ const primaryMenuItems = filteredMenuItems.filter((item) => !isSecondary(item));
46
+ const secondaryMenuItems = filteredMenuItems.filter(isSecondary);
47
+ const desktopSecondaryItems = navItems.filter(isSecondary);
48
+ const desktopActionNodes = {
49
+ search: searchToggle$1.enabled !== false
50
+ ? (_b = (_a = searchToggle$1.components) === null || _a === void 0 ? void 0 : _a.lg) !== null && _b !== void 0 ? _b : (jsxRuntime.jsx(searchToggle.LargeSearchToggle, { className: "w-full rounded-full ps-2.5 max-w-[240px]", hideIfDisabled: true }))
51
+ : null,
52
+ theme: themeSwitch.enabled !== false
53
+ ? (_c = themeSwitch.component) !== null && _c !== void 0 ? _c : jsxRuntime.jsx(themeToggle.ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })
54
+ : null,
55
+ i18n: i18n ? (jsxRuntime.jsx(CompactLanguageToggle, { children: jsxRuntime.jsx(server.globalLucideIcons.Languages, { className: "size-5" }) })) : null,
56
+ secondary: desktopSecondaryItems.length ? (jsxRuntime.jsx("ul", { className: "flex flex-row gap-2 items-center empty:hidden", children: desktopSecondaryItems.map((item, i) => (jsxRuntime.jsx(NavbarLinkItem, { item: item, className: cn.cn(item.type === 'icon' && [
57
+ '-mx-1',
58
+ i === 0 && 'ms-0',
59
+ i === desktopSecondaryItems.length - 1 && 'me-0',
60
+ ]) }, i))) })) : null,
61
+ };
62
+ const mobileMenuActionNodes = {
63
+ secondary: secondaryMenuItems.length ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: secondaryMenuItems.map((item, i) => (jsxRuntime.jsx(MenuLinkItem, { item: item, className: "-me-1.5" }, i))) })) : null,
64
+ separator: jsxRuntime.jsx("div", { role: "separator", className: "flex-1" }),
65
+ i18n: i18n ? (jsxRuntime.jsxs(CompactLanguageToggle, { children: [jsxRuntime.jsx(server.globalLucideIcons.Languages, { className: "size-5" }), jsxRuntime.jsx(languageToggle.LanguageToggleText, {}), jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: "size-3 text-fd-muted-foreground" })] })) : null,
66
+ theme: themeSwitch.enabled !== false
67
+ ? (_d = themeSwitch.component) !== null && _d !== void 0 ? _d : jsxRuntime.jsx(themeToggle.ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })
68
+ : null,
69
+ };
70
+ const shouldRenderMobileUtilities = mobileMenuActionsOrder.some((action) => action !== 'separator' && Boolean(mobileMenuActionNodes[action]));
71
+ const renderMobileMenuAction = (action) => {
72
+ if (action === 'separator' && !shouldRenderMobileUtilities)
73
+ return null;
74
+ return mobileMenuActionNodes[action];
75
+ };
76
+ const menuNode = (jsxRuntime.jsxs(Menu, { children: [jsxRuntime.jsx(MenuTrigger, { "aria-label": "Toggle Menu", className: cn.cn(button.buttonVariants({
77
+ size: 'icon',
78
+ color: 'ghost',
79
+ className: 'group [&_svg]:size-5.5',
80
+ })), enableHover: nav.enableHoverToOpen, children: jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: "transition-transform duration-300 group-data-[state=open]:rotate-180" }) }), jsxRuntime.jsxs(MenuContent, { className: "sm:flex-row sm:items-center sm:justify-end", children: [primaryMenuItems.map((item, i) => (jsxRuntime.jsx(MenuLinkItem, { item: item, className: "sm:hidden" }, i))), shouldRenderMobileUtilities ? (jsxRuntime.jsx("div", { className: "-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2", children: mobileMenuActionsOrder.map((action) => {
81
+ const node = renderMobileMenuAction(action);
82
+ if (!node)
83
+ return null;
84
+ return (jsxRuntime.jsx(React.Fragment, { children: node }, `mobile-menu-${action}`));
85
+ }) })) : null] })] }));
86
+ 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;
87
+ const mobileSearchNode = searchToggle$1.enabled !== false
88
+ ? (_f = (_e = searchToggle$1.components) === null || _e === void 0 ? void 0 : _e.sm) !== null && _f !== void 0 ? _f : (jsxRuntime.jsx(searchToggle.SearchToggle, { className: "p-2", hideIfDisabled: true }))
89
+ : null;
90
+ const mobileBarNodes = {
91
+ pinned: mobilePinnedNode,
92
+ search: mobileSearchNode,
93
+ menu: menuNode,
94
+ };
95
+ const getMobileBarNode = (action) => { var _a; return (_a = mobileBarNodes[action]) !== null && _a !== void 0 ? _a : null; };
96
+ return (jsxRuntime.jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsxRuntime.jsx(Link, { href: (_g = nav.url) !== null && _g !== void 0 ? _g : '/', className: "inline-flex items-center gap-2.5 font-semibold", children: nav.title }), nav.children, jsxRuntime.jsx("ul", { className: "flex flex-row items-center gap-2 px-6 max-sm:hidden", children: navItems
29
97
  .filter((item) => !isSecondary(item))
30
- .map((item, i) => (jsxRuntime.jsx(NavbarLinkItem, { item: item, className: "text-sm" }, i))) }), jsxRuntime.jsxs("div", { className: "flex flex-row items-center justify-end gap-1.5 flex-1 max-lg:hidden", children: [searchToggle$1.enabled !== false &&
31
- ((_c = (_b = searchToggle$1.components) === null || _b === void 0 ? void 0 : _b.lg) !== null && _c !== void 0 ? _c : (jsxRuntime.jsx(searchToggle.LargeSearchToggle, { className: "w-full rounded-full ps-2.5 max-w-[240px]", hideIfDisabled: true }))), themeSwitch.enabled !== false &&
32
- ((_d = themeSwitch.component) !== null && _d !== void 0 ? _d : jsxRuntime.jsx(themeToggle.ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })), i18n && (jsxRuntime.jsx(CompactLanguageToggle, { children: jsxRuntime.jsx(server.globalLucideIcons.Languages, { className: "size-5" }) })), jsxRuntime.jsx("ul", { className: "flex flex-row gap-2 items-center empty:hidden", children: navItems.filter(isSecondary).map((item, i) => (jsxRuntime.jsx(NavbarLinkItem, { item: item, className: cn.cn(item.type === 'icon' && [
33
- '-mx-1',
34
- i === 0 && 'ms-0',
35
- i === navItems.length - 1 && 'me-0',
36
- ]) }, i))) })] }), jsxRuntime.jsxs("ul", { className: "flex flex-row items-center ms-auto -me-1.5 lg:hidden", children: [mobilePinnedItems.map((item, i) => (jsxRuntime.jsx(NavbarLinkItem, { item: item, className: "max-sm:-mr-1" }, `mobile-pinned-${i}`))), searchToggle$1.enabled !== false &&
37
- ((_f = (_e = searchToggle$1.components) === null || _e === void 0 ? void 0 : _e.sm) !== null && _f !== void 0 ? _f : (jsxRuntime.jsx(searchToggle.SearchToggle, { className: "p-2", hideIfDisabled: true }))), jsxRuntime.jsxs(Menu, { children: [jsxRuntime.jsx(MenuTrigger, { "aria-label": "Toggle Menu", className: cn.cn(button.buttonVariants({
38
- size: 'icon',
39
- color: 'ghost',
40
- className: 'group [&_svg]:size-5.5',
41
- })), enableHover: nav.enableHoverToOpen, children: jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: "transition-transform duration-300 group-data-[state=open]:rotate-180" }) }), jsxRuntime.jsxs(MenuContent, { className: "sm:flex-row sm:items-center sm:justify-end", children: [filteredMenuItems
42
- .filter((item) => !isSecondary(item))
43
- .map((item, i) => (jsxRuntime.jsx(MenuLinkItem, { item: item, className: "sm:hidden" }, i))), jsxRuntime.jsxs("div", { className: "-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2", children: [filteredMenuItems.filter(isSecondary).map((item, i) => (jsxRuntime.jsx(MenuLinkItem, { item: item, className: "-me-1.5" }, i))), jsxRuntime.jsx("div", { role: "separator", className: "flex-1" }), i18n ? (jsxRuntime.jsxs(CompactLanguageToggle, { children: [jsxRuntime.jsx(server.globalLucideIcons.Languages, { className: "size-5" }), jsxRuntime.jsx(languageToggle.LanguageToggleText, {}), jsxRuntime.jsx(server.globalLucideIcons.ChevronDown, { className: "size-3 text-fd-muted-foreground" })] })) : null, themeSwitch.enabled !== false &&
44
- ((_g = themeSwitch.component) !== null && _g !== void 0 ? _g : (jsxRuntime.jsx(themeToggle.ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })))] })] })] })] })] }));
98
+ .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) => {
99
+ const node = desktopActionNodes[action];
100
+ if (!node)
101
+ return null;
102
+ return (jsxRuntime.jsx(React.Fragment, { children: node }, `desktop-${action}`));
103
+ }) }), jsxRuntime.jsx("ul", { className: "flex flex-row items-center ms-auto -me-1.5 lg:hidden", children: mobileBarActionsOrder.map((action) => {
104
+ const node = getMobileBarNode(action);
105
+ return node ? (jsxRuntime.jsx(React.Fragment, { children: node }, `mobile-bar-${action}`)) : null;
106
+ }) })] }));
45
107
  }
46
108
  function CustomNavbar(_a) {
47
109
  var { bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, className, style, floating = false } = _a, props = tslib_es6.__rest(_a, ["bannerHeight", "headerHeight", "maxContentWidth", "className", "style", "floating"]);
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { __rest } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
3
- import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
4
- import { useMemo, useState, Fragment } from 'react';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { useMemo, Fragment as Fragment$1, useState } from 'react';
5
5
  import { cva } from '../../node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.mjs';
6
6
  import Link from 'fumadocs-core/link';
7
7
  import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
@@ -10,36 +10,98 @@ import { cn } from 'fumadocs-ui/utils/cn';
10
10
  import { LargeSearchToggle, SearchToggle } from 'fumadocs-ui/components/layout/search-toggle';
11
11
  import { ThemeToggle } from 'fumadocs-ui/components/layout/theme-toggle';
12
12
  import { LanguageToggleText } from 'fumadocs-ui/components/layout/language-toggle';
13
- import { NavigationMenuItem, NavigationMenu, NavigationMenuList, NavigationMenuViewport, NavigationMenuLink, NavigationMenuTrigger, NavigationMenuContent } from 'fumadocs-ui/components/ui/navigation-menu';
13
+ import { NavigationMenuItem, NavigationMenuLink, NavigationMenuTrigger, NavigationMenuContent, NavigationMenu, NavigationMenuList, NavigationMenuViewport } from 'fumadocs-ui/components/ui/navigation-menu';
14
14
  import { Popover, PopoverTrigger, PopoverContent } from 'fumadocs-ui/components/ui/popover';
15
15
  import { buttonVariants } from 'fumadocs-ui/components/ui/button';
16
16
  import { useNav } from 'fumadocs-ui/contexts/layout';
17
17
  import { useI18n } from 'fumadocs-ui/contexts/i18n';
18
18
 
19
- function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitch = {}, searchToggle = {}, bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, navbarClassName, floating = false, }) {
19
+ const DEFAULT_DESKTOP_ACTIONS = [
20
+ 'search',
21
+ 'theme',
22
+ 'i18n',
23
+ 'secondary',
24
+ ];
25
+ const DEFAULT_MOBILE_BAR_ACTIONS = [
26
+ 'pinned',
27
+ 'search',
28
+ 'menu',
29
+ ];
30
+ const DEFAULT_MOBILE_MENU_ACTIONS = [
31
+ 'secondary',
32
+ 'separator',
33
+ 'i18n',
34
+ 'theme',
35
+ ];
36
+ function CustomHomeHeader({ nav = {}, i18n = false, links, githubUrl, themeSwitch = {}, 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, }) {
20
37
  var _a, _b, _c, _d, _e, _f, _g;
21
38
  const finalLinks = useMemo(() => getLinks(links, githubUrl), [links, githubUrl]);
22
39
  const navItems = finalLinks.filter((item) => { var _a; return ['nav', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
23
40
  const menuItems = finalLinks.filter((item) => { var _a; return ['menu', 'all'].includes((_a = item.on) !== null && _a !== void 0 ? _a : 'all'); });
24
41
  const mobilePinnedItems = navItems.filter((item) => isSecondary(item) && isMobilePinned(item));
25
42
  const filteredMenuItems = menuItems.filter((item) => !isMobilePinned(item));
26
- return (jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsx(Link, { href: (_a = nav.url) !== null && _a !== void 0 ? _a : '/', className: "inline-flex items-center gap-2.5 font-semibold", children: nav.title }), nav.children, jsx("ul", { className: "flex flex-row items-center gap-2 px-6 max-sm:hidden", children: navItems
43
+ const primaryMenuItems = filteredMenuItems.filter((item) => !isSecondary(item));
44
+ const secondaryMenuItems = filteredMenuItems.filter(isSecondary);
45
+ const desktopSecondaryItems = navItems.filter(isSecondary);
46
+ const desktopActionNodes = {
47
+ search: searchToggle.enabled !== false
48
+ ? (_b = (_a = searchToggle.components) === null || _a === void 0 ? void 0 : _a.lg) !== null && _b !== void 0 ? _b : (jsx(LargeSearchToggle, { className: "w-full rounded-full ps-2.5 max-w-[240px]", hideIfDisabled: true }))
49
+ : null,
50
+ theme: themeSwitch.enabled !== false
51
+ ? (_c = themeSwitch.component) !== null && _c !== void 0 ? _c : jsx(ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })
52
+ : null,
53
+ i18n: i18n ? (jsx(CompactLanguageToggle, { children: jsx(globalLucideIcons.Languages, { className: "size-5" }) })) : null,
54
+ secondary: desktopSecondaryItems.length ? (jsx("ul", { className: "flex flex-row gap-2 items-center empty:hidden", children: desktopSecondaryItems.map((item, i) => (jsx(NavbarLinkItem, { item: item, className: cn(item.type === 'icon' && [
55
+ '-mx-1',
56
+ i === 0 && 'ms-0',
57
+ i === desktopSecondaryItems.length - 1 && 'me-0',
58
+ ]) }, i))) })) : null,
59
+ };
60
+ const mobileMenuActionNodes = {
61
+ secondary: secondaryMenuItems.length ? (jsx(Fragment, { children: secondaryMenuItems.map((item, i) => (jsx(MenuLinkItem, { item: item, className: "-me-1.5" }, i))) })) : null,
62
+ separator: jsx("div", { role: "separator", className: "flex-1" }),
63
+ i18n: i18n ? (jsxs(CompactLanguageToggle, { children: [jsx(globalLucideIcons.Languages, { className: "size-5" }), jsx(LanguageToggleText, {}), jsx(globalLucideIcons.ChevronDown, { className: "size-3 text-fd-muted-foreground" })] })) : null,
64
+ theme: themeSwitch.enabled !== false
65
+ ? (_d = themeSwitch.component) !== null && _d !== void 0 ? _d : jsx(ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })
66
+ : null,
67
+ };
68
+ const shouldRenderMobileUtilities = mobileMenuActionsOrder.some((action) => action !== 'separator' && Boolean(mobileMenuActionNodes[action]));
69
+ const renderMobileMenuAction = (action) => {
70
+ if (action === 'separator' && !shouldRenderMobileUtilities)
71
+ return null;
72
+ return mobileMenuActionNodes[action];
73
+ };
74
+ const menuNode = (jsxs(Menu, { children: [jsx(MenuTrigger, { "aria-label": "Toggle Menu", className: cn(buttonVariants({
75
+ size: 'icon',
76
+ color: 'ghost',
77
+ className: 'group [&_svg]:size-5.5',
78
+ })), enableHover: nav.enableHoverToOpen, children: jsx(globalLucideIcons.ChevronDown, { className: "transition-transform duration-300 group-data-[state=open]:rotate-180" }) }), jsxs(MenuContent, { className: "sm:flex-row sm:items-center sm:justify-end", children: [primaryMenuItems.map((item, i) => (jsx(MenuLinkItem, { item: item, className: "sm:hidden" }, i))), shouldRenderMobileUtilities ? (jsx("div", { className: "-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2", children: mobileMenuActionsOrder.map((action) => {
79
+ const node = renderMobileMenuAction(action);
80
+ if (!node)
81
+ return null;
82
+ return (jsx(Fragment$1, { children: node }, `mobile-menu-${action}`));
83
+ }) })) : null] })] }));
84
+ 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;
85
+ const mobileSearchNode = searchToggle.enabled !== false
86
+ ? (_f = (_e = searchToggle.components) === null || _e === void 0 ? void 0 : _e.sm) !== null && _f !== void 0 ? _f : (jsx(SearchToggle, { className: "p-2", hideIfDisabled: true }))
87
+ : null;
88
+ const mobileBarNodes = {
89
+ pinned: mobilePinnedNode,
90
+ search: mobileSearchNode,
91
+ menu: menuNode,
92
+ };
93
+ const getMobileBarNode = (action) => { var _a; return (_a = mobileBarNodes[action]) !== null && _a !== void 0 ? _a : null; };
94
+ return (jsxs(CustomNavbar, { bannerHeight: bannerHeight, headerHeight: headerHeight, maxContentWidth: maxContentWidth, className: navbarClassName, floating: floating, children: [jsx(Link, { href: (_g = nav.url) !== null && _g !== void 0 ? _g : '/', className: "inline-flex items-center gap-2.5 font-semibold", children: nav.title }), nav.children, jsx("ul", { className: "flex flex-row items-center gap-2 px-6 max-sm:hidden", children: navItems
27
95
  .filter((item) => !isSecondary(item))
28
- .map((item, i) => (jsx(NavbarLinkItem, { item: item, className: "text-sm" }, i))) }), jsxs("div", { className: "flex flex-row items-center justify-end gap-1.5 flex-1 max-lg:hidden", children: [searchToggle.enabled !== false &&
29
- ((_c = (_b = searchToggle.components) === null || _b === void 0 ? void 0 : _b.lg) !== null && _c !== void 0 ? _c : (jsx(LargeSearchToggle, { className: "w-full rounded-full ps-2.5 max-w-[240px]", hideIfDisabled: true }))), themeSwitch.enabled !== false &&
30
- ((_d = themeSwitch.component) !== null && _d !== void 0 ? _d : jsx(ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })), i18n && (jsx(CompactLanguageToggle, { children: jsx(globalLucideIcons.Languages, { className: "size-5" }) })), jsx("ul", { className: "flex flex-row gap-2 items-center empty:hidden", children: navItems.filter(isSecondary).map((item, i) => (jsx(NavbarLinkItem, { item: item, className: cn(item.type === 'icon' && [
31
- '-mx-1',
32
- i === 0 && 'ms-0',
33
- i === navItems.length - 1 && 'me-0',
34
- ]) }, i))) })] }), jsxs("ul", { className: "flex flex-row items-center ms-auto -me-1.5 lg:hidden", children: [mobilePinnedItems.map((item, i) => (jsx(NavbarLinkItem, { item: item, className: "max-sm:-mr-1" }, `mobile-pinned-${i}`))), searchToggle.enabled !== false &&
35
- ((_f = (_e = searchToggle.components) === null || _e === void 0 ? void 0 : _e.sm) !== null && _f !== void 0 ? _f : (jsx(SearchToggle, { className: "p-2", hideIfDisabled: true }))), jsxs(Menu, { children: [jsx(MenuTrigger, { "aria-label": "Toggle Menu", className: cn(buttonVariants({
36
- size: 'icon',
37
- color: 'ghost',
38
- className: 'group [&_svg]:size-5.5',
39
- })), enableHover: nav.enableHoverToOpen, children: jsx(globalLucideIcons.ChevronDown, { className: "transition-transform duration-300 group-data-[state=open]:rotate-180" }) }), jsxs(MenuContent, { className: "sm:flex-row sm:items-center sm:justify-end", children: [filteredMenuItems
40
- .filter((item) => !isSecondary(item))
41
- .map((item, i) => (jsx(MenuLinkItem, { item: item, className: "sm:hidden" }, i))), jsxs("div", { className: "-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2", children: [filteredMenuItems.filter(isSecondary).map((item, i) => (jsx(MenuLinkItem, { item: item, className: "-me-1.5" }, i))), jsx("div", { role: "separator", className: "flex-1" }), i18n ? (jsxs(CompactLanguageToggle, { children: [jsx(globalLucideIcons.Languages, { className: "size-5" }), jsx(LanguageToggleText, {}), jsx(globalLucideIcons.ChevronDown, { className: "size-3 text-fd-muted-foreground" })] })) : null, themeSwitch.enabled !== false &&
42
- ((_g = themeSwitch.component) !== null && _g !== void 0 ? _g : (jsx(ThemeToggle, { mode: themeSwitch === null || themeSwitch === void 0 ? void 0 : themeSwitch.mode })))] })] })] })] })] }));
96
+ .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) => {
97
+ const node = desktopActionNodes[action];
98
+ if (!node)
99
+ return null;
100
+ return (jsx(Fragment$1, { children: node }, `desktop-${action}`));
101
+ }) }), jsx("ul", { className: "flex flex-row items-center ms-auto -me-1.5 lg:hidden", children: mobileBarActionsOrder.map((action) => {
102
+ const node = getMobileBarNode(action);
103
+ return node ? (jsx(Fragment$1, { children: node }, `mobile-bar-${action}`)) : null;
104
+ }) })] }));
43
105
  }
44
106
  function CustomNavbar(_a) {
45
107
  var { bannerHeight = 0, headerHeight = 2.5, maxContentWidth = 1400, className, style, floating = false } = _a, props = __rest(_a, ["bannerHeight", "headerHeight", "maxContentWidth", "className", "style", "floating"]);
@@ -96,10 +158,10 @@ function NavbarLinkItem(_a) {
96
158
  const children = item.items.map((child, j) => {
97
159
  var _a, _b;
98
160
  if (child.type === 'custom') {
99
- return jsx(Fragment, { children: child.children }, j);
161
+ return jsx(Fragment$1, { children: child.children }, j);
100
162
  }
101
163
  const _c = (_a = child.menu) !== null && _a !== void 0 ? _a : {}, { banner = child.icon ? (jsx("div", { className: "w-fit rounded-md border bg-fd-muted p-1 [&_svg]:size-4", children: child.icon })) : null } = _c, rest = __rest(_c, ["banner"]);
102
- return (jsx(NavigationMenuLink, { asChild: true, children: jsx(Link, Object.assign({ href: child.url, external: child.external }, rest, { className: cn('flex flex-col gap-2 rounded-lg border bg-fd-card p-3 transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground', rest.className), children: (_b = rest.children) !== null && _b !== void 0 ? _b : (jsxs(Fragment$1, { children: [banner, jsx("p", { className: "text-[15px] font-medium", children: child.text }), jsx("p", { className: "text-sm text-fd-muted-foreground empty:hidden", children: child.description })] })) })) }, `${j}-${child.url}`));
164
+ return (jsx(NavigationMenuLink, { asChild: true, children: jsx(Link, Object.assign({ href: child.url, external: child.external }, rest, { className: cn('flex flex-col gap-2 rounded-lg border bg-fd-card p-3 transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground', rest.className), children: (_b = rest.children) !== null && _b !== void 0 ? _b : (jsxs(Fragment, { children: [banner, jsx("p", { className: "text-[15px] font-medium", children: child.text }), jsx("p", { className: "text-sm text-fd-muted-foreground empty:hidden", children: child.description })] })) })) }, `${j}-${child.url}`));
103
165
  });
104
166
  return (jsxs(NavigationMenuItem, { children: [jsx(NavigationMenuTrigger, Object.assign({}, props, { className: cn(navItemVariants(), 'rounded-md', props.className), children: item.url ? (jsx(Link, { href: item.url, external: item.external, children: item.text })) : (item.text) })), jsx(NavigationMenuContent, { className: "grid grid-cols-1 gap-2 p-4 md:grid-cols-2 lg:grid-cols-3", children: children })] }));
105
167
  }
@@ -112,7 +174,7 @@ function MenuLinkItem(_a) {
112
174
  if (item.type === 'custom')
113
175
  return jsx("div", { className: cn('grid', props.className), children: item.children });
114
176
  if (item.type === 'menu') {
115
- const header = (jsxs(Fragment$1, { children: [item.icon, item.text] }));
177
+ const header = (jsxs(Fragment, { children: [item.icon, item.text] }));
116
178
  return (jsxs("div", { className: cn('mb-4 flex flex-col', props.className), children: [jsx("p", { className: "mb-1 text-sm text-fd-muted-foreground", children: item.url ? (jsx(NavigationMenuLink, { asChild: true, children: jsx(Link, { href: item.url, external: item.external, children: header }) })) : (header) }), item.items.map((child, i) => (jsx(MenuLinkItem, { item: child }, i)))] }));
117
179
  }
118
180
  return (jsx(NavigationMenuLink, { asChild: true, children: jsxs(BaseLinkItem, { item: item, className: cn({
@@ -1,5 +1,6 @@
1
1
  import type { CSSProperties, ReactNode } from 'react';
2
2
  import { type HomeLayoutProps } from 'fumadocs-ui/layouts/home';
3
+ import { type DesktopAction, type MobileBarAction, type MobileMenuAction } from './custom-header';
3
4
  export interface CustomHomeLayoutProps {
4
5
  locale: string;
5
6
  options: HomeLayoutProps;
@@ -63,6 +64,15 @@ export interface CustomHomeLayoutProps {
63
64
  * Additional styles merged on top of the computed layout style.
64
65
  */
65
66
  style?: CSSProperties;
67
+ /**
68
+ * Customize the order of header action items.
69
+ */
70
+ actionOrders?: HeaderActionOrders;
66
71
  children?: ReactNode;
67
72
  }
68
- export declare function CustomHomeLayout({ locale, options, children, showBanner, bannerHeight, headerHeight, headerPaddingTop, navbarClassName, banner, footer, goToTop, showFooter, showGoToTop, style, floatingNav, }: CustomHomeLayoutProps): import("react/jsx-runtime").JSX.Element;
73
+ export interface HeaderActionOrders {
74
+ desktop?: DesktopAction[];
75
+ mobileBar?: MobileBarAction[];
76
+ mobileMenu?: MobileMenuAction[];
77
+ }
78
+ export declare function CustomHomeLayout({ locale, options, children, showBanner, bannerHeight, headerHeight, headerPaddingTop, navbarClassName, banner, footer, goToTop, showFooter, showGoToTop, style, floatingNav, actionOrders, }: CustomHomeLayoutProps): import("react/jsx-runtime").JSX.Element;
@@ -8,7 +8,7 @@ var footer = require('../../main/footer.js');
8
8
  var goToTop = require('../../main/go-to-top.js');
9
9
  var customHeader = require('./custom-header.js');
10
10
 
11
- function CustomHomeLayout({ locale, options, children, showBanner = false, bannerHeight, headerHeight = 2.5, headerPaddingTop, navbarClassName, banner, footer: footer$1, goToTop: goToTop$1, showFooter = true, showGoToTop = true, style, floatingNav = false, }) {
11
+ function CustomHomeLayout({ locale, options, children, showBanner = false, bannerHeight, headerHeight = 2.5, headerPaddingTop, navbarClassName, banner, footer: footer$1, goToTop: goToTop$1, showFooter = true, showGoToTop = true, style, floatingNav = false, actionOrders, }) {
12
12
  const resolvedBannerHeight = bannerHeight !== null && bannerHeight !== void 0 ? bannerHeight : (showBanner ? 3 : 0.5);
13
13
  const resolvedPaddingTop = headerPaddingTop !== null && headerPaddingTop !== void 0 ? headerPaddingTop : (showBanner ? 0 : 0.5);
14
14
  const layoutStyle = Object.assign({ '--fd-banner-height': `${resolvedBannerHeight}rem`, '--fd-nav-height': `${headerHeight}rem`, paddingTop: floatingNav
@@ -16,7 +16,7 @@ function CustomHomeLayout({ locale, options, children, showBanner = false, banne
16
16
  : `${resolvedPaddingTop}rem` }, style);
17
17
  const { nav } = options, homeLayoutProps = tslib_es6.__rest(options, ["nav"]);
18
18
  const navOptions = nav !== null && nav !== void 0 ? nav : {};
19
- const header = (jsxRuntime.jsx(customHeader.CustomHomeHeader, Object.assign({}, homeLayoutProps, { nav: navOptions, bannerHeight: resolvedBannerHeight, headerHeight: headerHeight, navbarClassName: navbarClassName, floating: floatingNav })));
19
+ const header = (jsxRuntime.jsx(customHeader.CustomHomeHeader, Object.assign({}, homeLayoutProps, { nav: navOptions, bannerHeight: resolvedBannerHeight, headerHeight: headerHeight, navbarClassName: navbarClassName, floating: floatingNav, desktopActionsOrder: actionOrders === null || actionOrders === void 0 ? void 0 : actionOrders.desktop, mobileBarActionsOrder: actionOrders === null || actionOrders === void 0 ? void 0 : actionOrders.mobileBar, mobileMenuActionsOrder: actionOrders === null || actionOrders === void 0 ? void 0 : actionOrders.mobileMenu })));
20
20
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [banner !== null && banner !== void 0 ? banner : (jsxRuntime.jsx(fumaBannerSuit.FumaBannerSuit, { locale: locale, showBanner: showBanner, floating: floatingNav })), jsxRuntime.jsxs(home.HomeLayout, Object.assign({}, homeLayoutProps, { nav: Object.assign(Object.assign({}, navOptions), { component: header }), className: 'bg-neutral-100 dark:bg-neutral-900', style: layoutStyle, children: [children, showFooter ? footer$1 !== null && footer$1 !== void 0 ? footer$1 : jsxRuntime.jsx(footer.Footer, { locale: locale }) : null, showGoToTop ? goToTop$1 !== null && goToTop$1 !== void 0 ? goToTop$1 : jsxRuntime.jsx(goToTop.GoToTop, {}) : null] }))] }));
21
21
  }
22
22
 
@@ -6,7 +6,7 @@ import { Footer } from '../../main/footer.mjs';
6
6
  import { GoToTop } from '../../main/go-to-top.mjs';
7
7
  import { CustomHomeHeader } from './custom-header.mjs';
8
8
 
9
- function CustomHomeLayout({ locale, options, children, showBanner = false, bannerHeight, headerHeight = 2.5, headerPaddingTop, navbarClassName, banner, footer, goToTop, showFooter = true, showGoToTop = true, style, floatingNav = false, }) {
9
+ function CustomHomeLayout({ locale, options, children, showBanner = false, bannerHeight, headerHeight = 2.5, headerPaddingTop, navbarClassName, banner, footer, goToTop, showFooter = true, showGoToTop = true, style, floatingNav = false, actionOrders, }) {
10
10
  const resolvedBannerHeight = bannerHeight !== null && bannerHeight !== void 0 ? bannerHeight : (showBanner ? 3 : 0.5);
11
11
  const resolvedPaddingTop = headerPaddingTop !== null && headerPaddingTop !== void 0 ? headerPaddingTop : (showBanner ? 0 : 0.5);
12
12
  const layoutStyle = Object.assign({ '--fd-banner-height': `${resolvedBannerHeight}rem`, '--fd-nav-height': `${headerHeight}rem`, paddingTop: floatingNav
@@ -14,7 +14,7 @@ function CustomHomeLayout({ locale, options, children, showBanner = false, banne
14
14
  : `${resolvedPaddingTop}rem` }, style);
15
15
  const { nav } = options, homeLayoutProps = __rest(options, ["nav"]);
16
16
  const navOptions = nav !== null && nav !== void 0 ? nav : {};
17
- const header = (jsx(CustomHomeHeader, Object.assign({}, homeLayoutProps, { nav: navOptions, bannerHeight: resolvedBannerHeight, headerHeight: headerHeight, navbarClassName: navbarClassName, floating: floatingNav })));
17
+ const header = (jsx(CustomHomeHeader, Object.assign({}, homeLayoutProps, { nav: navOptions, bannerHeight: resolvedBannerHeight, headerHeight: headerHeight, navbarClassName: navbarClassName, floating: floatingNav, desktopActionsOrder: actionOrders === null || actionOrders === void 0 ? void 0 : actionOrders.desktop, mobileBarActionsOrder: actionOrders === null || actionOrders === void 0 ? void 0 : actionOrders.mobileBar, mobileMenuActionsOrder: actionOrders === null || actionOrders === void 0 ? void 0 : actionOrders.mobileMenu })));
18
18
  return (jsxs(Fragment, { children: [banner !== null && banner !== void 0 ? banner : (jsx(FumaBannerSuit, { locale: locale, showBanner: showBanner, floating: floatingNav })), jsxs(HomeLayout, Object.assign({}, homeLayoutProps, { nav: Object.assign(Object.assign({}, navOptions), { component: header }), className: 'bg-neutral-100 dark:bg-neutral-900', style: layoutStyle, children: [children, showFooter ? footer !== null && footer !== void 0 ? footer : jsx(Footer, { locale: locale }) : null, showGoToTop ? goToTop !== null && goToTop !== void 0 ? goToTop : jsx(GoToTop, {}) : null] }))] }));
19
19
  }
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "10.1.0",
3
+ "version": "10.1.1",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -81,8 +81,8 @@
81
81
  "react-medium-image-zoom": "^5.2.14",
82
82
  "swiper": "^12.0.3",
83
83
  "zod": "^4.1.12",
84
- "@windrun-huaiin/lib": "^10.1.0",
85
- "@windrun-huaiin/base-ui": "^10.1.0"
84
+ "@windrun-huaiin/base-ui": "^10.1.0",
85
+ "@windrun-huaiin/lib": "^10.1.0"
86
86
  },
87
87
  "peerDependencies": {
88
88
  "clsx": "^2.1.1",
@@ -16,7 +16,7 @@ interface ClerkUserData {
16
16
 
17
17
  export function ClerkUserClient({ data }: { data: ClerkUserData }) {
18
18
  return (
19
- <div className="flex items-center gap-2 h-10 mx-3 md:ml-1 md:mr-2">
19
+ <div className="flex items-center gap-2 h-10 mr-3 sm:mr-2">
20
20
  <ClerkLoading>
21
21
  <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>
22
22
  </ClerkLoading>
@@ -4,6 +4,7 @@ import {
4
4
  type ComponentProps,
5
5
  type CSSProperties,
6
6
  Fragment,
7
+ type ReactNode,
7
8
  useMemo,
8
9
  useState,
9
10
  } from 'react';
@@ -79,8 +80,52 @@ export interface CustomHomeHeaderProps extends HomeLayoutProps {
79
80
  * @defaultValue true
80
81
  */
81
82
  floating?: boolean;
83
+ /**
84
+ * Control order of action items on desktop.
85
+ */
86
+ desktopActionsOrder?: DesktopAction[];
87
+ /**
88
+ * Control order of quick actions on the mobile bar.
89
+ */
90
+ mobileBarActionsOrder?: MobileBarAction[];
91
+ /**
92
+ * Control order of utilities inside the mobile dropdown.
93
+ */
94
+ mobileMenuActionsOrder?: MobileMenuAction[];
82
95
  }
83
96
 
97
+ export type DesktopAction =
98
+ | 'search'
99
+ | 'theme'
100
+ | 'i18n'
101
+ | 'secondary'
102
+ | 'github';
103
+ export type MobileBarAction = 'pinned' | 'search' | 'menu';
104
+ export type MobileMenuAction =
105
+ | 'secondary'
106
+ | 'github'
107
+ | 'separator'
108
+ | 'i18n'
109
+ | 'theme';
110
+
111
+ const DEFAULT_DESKTOP_ACTIONS: DesktopAction[] = [
112
+ 'search',
113
+ 'theme',
114
+ 'i18n',
115
+ 'secondary',
116
+ ];
117
+ const DEFAULT_MOBILE_BAR_ACTIONS: MobileBarAction[] = [
118
+ 'pinned',
119
+ 'search',
120
+ 'menu',
121
+ ];
122
+ const DEFAULT_MOBILE_MENU_ACTIONS: MobileMenuAction[] = [
123
+ 'secondary',
124
+ 'separator',
125
+ 'i18n',
126
+ 'theme',
127
+ ];
128
+
84
129
  export function CustomHomeHeader({
85
130
  nav = {},
86
131
  i18n = false,
@@ -93,6 +138,9 @@ export function CustomHomeHeader({
93
138
  maxContentWidth = 1400,
94
139
  navbarClassName,
95
140
  floating = false,
141
+ desktopActionsOrder = DEFAULT_DESKTOP_ACTIONS,
142
+ mobileBarActionsOrder = DEFAULT_MOBILE_BAR_ACTIONS,
143
+ mobileMenuActionsOrder = DEFAULT_MOBILE_MENU_ACTIONS,
96
144
  }: CustomHomeHeaderProps) {
97
145
  const finalLinks = useMemo(
98
146
  () => getLinks(links, githubUrl),
@@ -108,10 +156,167 @@ export function CustomHomeHeader({
108
156
  const mobilePinnedItems = navItems.filter(
109
157
  (item) => isSecondary(item) && isMobilePinned(item),
110
158
  );
111
- const filteredMenuItems = menuItems.filter(
112
- (item) => !isMobilePinned(item),
159
+ const filteredMenuItems = menuItems.filter((item) => !isMobilePinned(item));
160
+ const primaryMenuItems = filteredMenuItems.filter((item) => !isSecondary(item));
161
+ const secondaryMenuItems = filteredMenuItems.filter(isSecondary);
162
+ const desktopSecondaryItems = navItems.filter(isSecondary);
163
+ const desktopActionsIncludeGithub = desktopActionsOrder.includes('github');
164
+ const githubDesktopItem = desktopActionsIncludeGithub
165
+ ? desktopSecondaryItems.find((item) => isGithubItem(item, githubUrl))
166
+ : undefined;
167
+ const desktopSecondaryDisplayItems =
168
+ desktopActionsIncludeGithub && githubDesktopItem
169
+ ? desktopSecondaryItems.filter((item) => !isGithubItem(item, githubUrl))
170
+ : desktopSecondaryItems;
171
+
172
+ const desktopActionNodes: Record<DesktopAction, ReactNode> = {
173
+ search:
174
+ searchToggle.enabled !== false
175
+ ? searchToggle.components?.lg ?? (
176
+ <LargeSearchToggle
177
+ className="w-full rounded-full ps-2.5 max-w-[240px]"
178
+ hideIfDisabled
179
+ />
180
+ )
181
+ : null,
182
+ theme:
183
+ themeSwitch.enabled !== false
184
+ ? themeSwitch.component ?? <ThemeToggle mode={themeSwitch?.mode} />
185
+ : null,
186
+ i18n: i18n ? (
187
+ <CompactLanguageToggle>
188
+ <icons.Languages className="size-5" />
189
+ </CompactLanguageToggle>
190
+ ) : null,
191
+ secondary: desktopSecondaryDisplayItems.length ? (
192
+ <ul className="flex flex-row gap-2 items-center empty:hidden">
193
+ {desktopSecondaryDisplayItems.map((item, i) => (
194
+ <NavbarLinkItem
195
+ key={i}
196
+ item={item}
197
+ className={cn(
198
+ item.type === 'icon' && [
199
+ '-mx-1',
200
+ i === 0 && 'ms-0',
201
+ i === desktopSecondaryDisplayItems.length - 1 && 'me-0',
202
+ ],
203
+ )}
204
+ />
205
+ ))}
206
+ </ul>
207
+ ) : null,
208
+ github: githubDesktopItem ? (
209
+ <NavbarLinkItem
210
+ item={githubDesktopItem}
211
+ className={cn(githubDesktopItem.type === 'icon' && '-mx-1')}
212
+ />
213
+ ) : null,
214
+ };
215
+
216
+ const mobileMenuActionsIncludeGithub =
217
+ mobileMenuActionsOrder.includes('github');
218
+ const githubMobileMenuItem = mobileMenuActionsIncludeGithub
219
+ ? secondaryMenuItems.find((item) => isGithubItem(item, githubUrl))
220
+ : undefined;
221
+ const secondaryMenuDisplayItems =
222
+ mobileMenuActionsIncludeGithub && githubMobileMenuItem
223
+ ? secondaryMenuItems.filter((item) => !isGithubItem(item, githubUrl))
224
+ : secondaryMenuItems;
225
+
226
+ const mobileMenuActionNodes: Record<MobileMenuAction, ReactNode> = {
227
+ secondary: secondaryMenuDisplayItems.length ? (
228
+ <>
229
+ {secondaryMenuDisplayItems.map((item, i) => (
230
+ <MenuLinkItem key={i} item={item} className="-me-1.5" />
231
+ ))}
232
+ </>
233
+ ) : null,
234
+ github: githubMobileMenuItem ? (
235
+ <MenuLinkItem item={githubMobileMenuItem} className="-me-1.5" />
236
+ ) : null,
237
+ separator: <div role="separator" className="flex-1" />,
238
+ i18n: i18n ? (
239
+ <CompactLanguageToggle>
240
+ <icons.Languages className="size-5" />
241
+ <LanguageToggleText />
242
+ <icons.ChevronDown className="size-3 text-fd-muted-foreground" />
243
+ </CompactLanguageToggle>
244
+ ) : null,
245
+ theme:
246
+ themeSwitch.enabled !== false
247
+ ? themeSwitch.component ?? <ThemeToggle mode={themeSwitch?.mode} />
248
+ : null,
249
+ };
250
+ const shouldRenderMobileUtilities = mobileMenuActionsOrder.some(
251
+ (action) => action !== 'separator' && Boolean(mobileMenuActionNodes[action]),
252
+ );
253
+ const renderMobileMenuAction = (action: MobileMenuAction) => {
254
+ if (action === 'separator' && !shouldRenderMobileUtilities) return null;
255
+ return mobileMenuActionNodes[action];
256
+ };
257
+
258
+ const menuNode = (
259
+ <Menu>
260
+ <MenuTrigger
261
+ aria-label="Toggle Menu"
262
+ className={cn(
263
+ buttonVariants({
264
+ size: 'icon',
265
+ color: 'ghost',
266
+ className: 'group [&_svg]:size-5.5',
267
+ }),
268
+ )}
269
+ enableHover={nav.enableHoverToOpen}
270
+ >
271
+ <icons.ChevronDown className="transition-transform duration-300 group-data-[state=open]:rotate-180" />
272
+ </MenuTrigger>
273
+ <MenuContent className="sm:flex-row sm:items-center sm:justify-end">
274
+ {primaryMenuItems.map((item, i) => (
275
+ <MenuLinkItem key={i} item={item} className="sm:hidden" />
276
+ ))}
277
+ {shouldRenderMobileUtilities ? (
278
+ <div className="-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2">
279
+ {mobileMenuActionsOrder.map((action) => {
280
+ const node = renderMobileMenuAction(action);
281
+ if (!node) return null;
282
+ return (
283
+ <Fragment key={`mobile-menu-${action}`}>
284
+ {node}
285
+ </Fragment>
286
+ );
287
+ })}
288
+ </div>
289
+ ) : null}
290
+ </MenuContent>
291
+ </Menu>
113
292
  );
114
293
 
294
+ const mobilePinnedNode =
295
+ mobilePinnedItems.length > 0 ? (
296
+ <>
297
+ {mobilePinnedItems.map((item, i) => (
298
+ <NavbarLinkItem
299
+ key={`mobile-pinned-${i}`}
300
+ item={item}
301
+ className="max-sm:-mr-1"
302
+ />
303
+ ))}
304
+ </>
305
+ ) : null;
306
+ const mobileSearchNode =
307
+ searchToggle.enabled !== false
308
+ ? searchToggle.components?.sm ?? (
309
+ <SearchToggle className="p-2" hideIfDisabled />
310
+ )
311
+ : null;
312
+ const mobileBarNodes: Record<MobileBarAction, ReactNode> = {
313
+ pinned: mobilePinnedNode,
314
+ search: mobileSearchNode,
315
+ menu: menuNode,
316
+ };
317
+ const getMobileBarNode = (action: MobileBarAction) =>
318
+ mobileBarNodes[action] ?? null;
319
+
115
320
  return (
116
321
  <CustomNavbar
117
322
  bannerHeight={bannerHeight}
@@ -135,87 +340,23 @@ export function CustomHomeHeader({
135
340
  ))}
136
341
  </ul>
137
342
  <div className="flex flex-row items-center justify-end gap-1.5 flex-1 max-lg:hidden">
138
- {searchToggle.enabled !== false &&
139
- (searchToggle.components?.lg ?? (
140
- <LargeSearchToggle
141
- className="w-full rounded-full ps-2.5 max-w-[240px]"
142
- hideIfDisabled
143
- />
144
- ))}
145
- {themeSwitch.enabled !== false &&
146
- (themeSwitch.component ?? <ThemeToggle mode={themeSwitch?.mode} />)}
147
- {i18n && (
148
- <CompactLanguageToggle>
149
- <icons.Languages className="size-5" />
150
- </CompactLanguageToggle>
151
- )}
152
- <ul className="flex flex-row gap-2 items-center empty:hidden">
153
- {navItems.filter(isSecondary).map((item, i) => (
154
- <NavbarLinkItem
155
- key={i}
156
- item={item}
157
- className={cn(
158
- item.type === 'icon' && [
159
- '-mx-1',
160
- i === 0 && 'ms-0',
161
- i === navItems.length - 1 && 'me-0',
162
- ],
163
- )}
164
- />
165
- ))}
166
- </ul>
343
+ {desktopActionsOrder.map((action) => {
344
+ const node = desktopActionNodes[action];
345
+ if (!node) return null;
346
+ return (
347
+ <Fragment key={`desktop-${action}`}>
348
+ {node}
349
+ </Fragment>
350
+ );
351
+ })}
167
352
  </div>
168
353
  <ul className="flex flex-row items-center ms-auto -me-1.5 lg:hidden">
169
- {mobilePinnedItems.map((item, i) => (
170
- <NavbarLinkItem
171
- key={`mobile-pinned-${i}`}
172
- item={item}
173
- className="max-sm:-mr-1"
174
- />
175
- ))}
176
- {searchToggle.enabled !== false &&
177
- (searchToggle.components?.sm ?? (
178
- <SearchToggle className="p-2" hideIfDisabled />
179
- ))}
180
- <Menu>
181
- <MenuTrigger
182
- aria-label="Toggle Menu"
183
- className={cn(
184
- buttonVariants({
185
- size: 'icon',
186
- color: 'ghost',
187
- className: 'group [&_svg]:size-5.5',
188
- }),
189
- )}
190
- enableHover={nav.enableHoverToOpen}
191
- >
192
- <icons.ChevronDown className="transition-transform duration-300 group-data-[state=open]:rotate-180" />
193
- </MenuTrigger>
194
- <MenuContent className="sm:flex-row sm:items-center sm:justify-end">
195
- {filteredMenuItems
196
- .filter((item) => !isSecondary(item))
197
- .map((item, i) => (
198
- <MenuLinkItem key={i} item={item} className="sm:hidden" />
199
- ))}
200
- <div className="-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2">
201
- {filteredMenuItems.filter(isSecondary).map((item, i) => (
202
- <MenuLinkItem key={i} item={item} className="-me-1.5" />
203
- ))}
204
- <div role="separator" className="flex-1" />
205
- {i18n ? (
206
- <CompactLanguageToggle>
207
- <icons.Languages className="size-5" />
208
- <LanguageToggleText />
209
- <icons.ChevronDown className="size-3 text-fd-muted-foreground" />
210
- </CompactLanguageToggle>
211
- ) : null}
212
- {themeSwitch.enabled !== false &&
213
- (themeSwitch.component ?? (
214
- <ThemeToggle mode={themeSwitch?.mode} />
215
- ))}
216
- </div>
217
- </MenuContent>
218
- </Menu>
354
+ {mobileBarActionsOrder.map((action) => {
355
+ const node = getMobileBarNode(action);
356
+ return node ? (
357
+ <Fragment key={`mobile-bar-${action}`}>{node}</Fragment>
358
+ ) : null;
359
+ })}
219
360
  </ul>
220
361
  </CustomNavbar>
221
362
  );
@@ -525,7 +666,7 @@ function CompactLanguageToggle({
525
666
  className={cn(
526
667
  buttonVariants({
527
668
  color: 'ghost',
528
- className: 'gap-1.5 py-1.5 pl-2 pr-0.5',
669
+ className: 'gap-1.5 py-1.5 px-1',
529
670
  }),
530
671
  props.className,
531
672
  )}
@@ -563,6 +704,12 @@ function CompactLanguageToggle({
563
704
  );
564
705
  }
565
706
 
707
+ function isGithubItem(item: LinkItemType, githubUrl?: string): boolean {
708
+ return Boolean(
709
+ githubUrl && item.type === 'icon' && item.url === githubUrl,
710
+ );
711
+ }
712
+
566
713
  function isSecondary(item: LinkItemType): boolean {
567
714
  if ('secondary' in item && item.secondary != null) return item.secondary;
568
715
 
@@ -3,7 +3,13 @@ import { HomeLayout, type HomeLayoutProps } from 'fumadocs-ui/layouts/home';
3
3
  import { FumaBannerSuit } from '@third-ui/fuma/fuma-banner-suit';
4
4
  import { Footer } from '@third-ui/main/footer';
5
5
  import { GoToTop } from '@third-ui/main/go-to-top';
6
- import { NavbarCSSVars, CustomHomeHeader } from './custom-header';
6
+ import {
7
+ NavbarCSSVars,
8
+ CustomHomeHeader,
9
+ type DesktopAction,
10
+ type MobileBarAction,
11
+ type MobileMenuAction,
12
+ } from './custom-header';
7
13
 
8
14
  // - bannerHeight/headerHeight 换成你项目期望的 rem 值即可(如果没有 Banner 就把 bannerHeight 设成 0)。
9
15
  // - layoutStyle 同时把变量传给 HomeLayout 的 main 元素,这样内容整体会往下错开,不需要 has-banner/no-banner class。
@@ -73,9 +79,19 @@ export interface CustomHomeLayoutProps {
73
79
  * Additional styles merged on top of the computed layout style.
74
80
  */
75
81
  style?: CSSProperties;
82
+ /**
83
+ * Customize the order of header action items.
84
+ */
85
+ actionOrders?: HeaderActionOrders;
76
86
  children?: ReactNode;
77
87
  }
78
88
 
89
+ export interface HeaderActionOrders {
90
+ desktop?: DesktopAction[];
91
+ mobileBar?: MobileBarAction[];
92
+ mobileMenu?: MobileMenuAction[];
93
+ }
94
+
79
95
  export function CustomHomeLayout({
80
96
  locale,
81
97
  options,
@@ -92,6 +108,7 @@ export function CustomHomeLayout({
92
108
  showGoToTop = true,
93
109
  style,
94
110
  floatingNav = false,
111
+ actionOrders,
95
112
  }: CustomHomeLayoutProps) {
96
113
  const resolvedBannerHeight = bannerHeight ?? (showBanner ? 3 : 0.5);
97
114
  const resolvedPaddingTop =
@@ -116,6 +133,9 @@ export function CustomHomeLayout({
116
133
  headerHeight={headerHeight}
117
134
  navbarClassName={navbarClassName}
118
135
  floating={floatingNav}
136
+ desktopActionsOrder={actionOrders?.desktop}
137
+ mobileBarActionsOrder={actionOrders?.mobileBar}
138
+ mobileMenuActionsOrder={actionOrders?.mobileMenu}
119
139
  />
120
140
  );
121
141
 
@@ -181,7 +181,7 @@ export function CreditNavButton({
181
181
  type="button"
182
182
  aria-label={`${formattedBalance} ${totalLabel}`}
183
183
  className={cn(
184
- 'group ml-1 mr-0 inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white pl-2 pr-4 py-1.5 text-sm font-semibold text-slate-700 shadow-sm transition-all dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100',
184
+ 'group mx-2 sm:mx-1 inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white pl-2 pr-4 py-1.5 text-sm font-semibold text-slate-700 shadow-sm transition-all dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100',
185
185
  'hover:border-transparent hover:bg-linear-to-r hover:from-purple-500/90 hover:to-pink-500/90 hover:text-white hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:hover:from-purple-500 dark:hover:to-pink-500',
186
186
  )}
187
187
  ref={triggerRef}