Skip to content

Commit 7c3dbeb

Browse files
authored
♻️ Justify global layout: header+footer+content(sider+innercontent)
2 parents 785b35c + 4319b46 commit 7c3dbeb

File tree

7 files changed

+216
-182
lines changed

7 files changed

+216
-182
lines changed

frontend/app/[locale]/chat/internal/ChatTopNavContent.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use client";
22

33
import { useConfig } from "@/hooks/useConfig";
4-
import { useResponsiveTextSize } from "@/hooks/useResponsiveTextSize";
54
import { extractColorsFromUri } from "@/lib/avatar";
65
import { useRouter } from "next/navigation";
76
import { useTranslation } from "react-i18next";
@@ -15,12 +14,7 @@ export function ChatTopNavContent() {
1514
const { appConfig, getAppAvatarUrl } = useConfig();
1615
const sidebarAvatarUrl = getAppAvatarUrl(16);
1716

18-
// Calculate container width for responsive text - smaller for top navbar
19-
const containerWidth = 100;
20-
const { textRef, fontSize } = useResponsiveTextSize(
21-
appConfig.appName,
22-
containerWidth
23-
);
17+
// Static font-size for top navbar (no responsive sizing required)
2418

2519
const colors = extractColorsFromUri(appConfig.avatarUri || "");
2620
const mainColor = colors.mainColor || "273746";
@@ -39,7 +33,6 @@ export function ChatTopNavContent() {
3933
/>
4034
</div>
4135
<span
42-
ref={textRef}
4336
className="font-bold truncate bg-clip-text text-transparent"
4437
style={{
4538
fontSize: '16px',

frontend/components/navigation/Footer.tsx

Lines changed: 0 additions & 70 deletions
This file was deleted.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { Layout, } from "antd";
5+
import { useTranslation } from "react-i18next";
6+
import Link from "next/link";
7+
import { APP_VERSION } from "@/const/constants";
8+
import { FOOTER_CONFIG } from "@/const/layoutConstants";
9+
import { versionService } from "@/services/versionService";
10+
import log from "@/lib/logger";
11+
12+
13+
/**
14+
* Footer component with copyright, version, and links
15+
* Displays at the bottom of the page
16+
*/
17+
export function FooterLayout() {
18+
const { t } = useTranslation("common");
19+
const [appVersion, setAppVersion] = useState<string>("");
20+
21+
// Get app version on mount
22+
useEffect(() => {
23+
const fetchAppVersion = async () => {
24+
try {
25+
const version = await versionService.getAppVersion();
26+
setAppVersion(version);
27+
} catch (error) {
28+
log.error("Failed to fetch app version:", error);
29+
setAppVersion(APP_VERSION); // Fallback
30+
}
31+
};
32+
33+
fetchAppVersion();
34+
}, []);
35+
36+
return (
37+
<div className="py-[9px] px-4 w-full flex items-center justify-between border-t border-b">
38+
<div className="flex items-center gap-8">
39+
<span className="text-sm text-slate-900 dark:text-white">
40+
{t("page.copyright", { year: new Date().getFullYear() })}
41+
<span className="ml-1">· {appVersion || APP_VERSION}</span>
42+
</span>
43+
</div>
44+
<div className="flex items-center gap-6">
45+
<Link
46+
href="https://github.com/nexent-hub/nexent?tab=License-1-ov-file#readme"
47+
className="text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white"
48+
>
49+
{t("page.termsOfUse")}
50+
</Link>
51+
<Link
52+
href="http://nexent.tech/contact"
53+
className="text-sm text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white transition-colors"
54+
>
55+
{t("page.contactUs")}
56+
</Link>
57+
<Link
58+
href="http://nexent.tech/about"
59+
className="text-sm text-slate-600 dark:text-slate-300 dark:hover:text-white transition-colors"
60+
>
61+
{t("page.aboutUs")}
62+
</Link>
63+
</div>
64+
</div>
65+
);
66+
}
67+
Lines changed: 136 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
"use client";
22

3-
import { Layout } from "antd";
3+
import { Layout, Button } from "antd";
44
import { TopNavbar } from "./TopNavbar";
55
import { SideNavigation } from "./SideNavigation";
6-
import { Footer } from "./Footer";
7-
import { HEADER_CONFIG, FOOTER_CONFIG } from "@/const/layoutConstants";
6+
import { FooterLayout } from "./FooterLayout";
7+
import { HEADER_CONFIG, FOOTER_CONFIG, SIDER_CONFIG } from "@/const/layoutConstants";
88
import React from "react";
9+
import { useState, useEffect } from "react";
910

10-
const { Content } = Layout;
11+
import {
12+
ChevronLeft,
13+
ChevronRight,
14+
} from "lucide-react";
1115

16+
const { Header, Sider, Content, Footer } = Layout;
1217
interface NavigationLayoutProps {
1318
children: React.ReactNode;
1419
onAuthRequired?: () => void;
@@ -49,78 +54,141 @@ export function NavigationLayout({
4954
// Use RESERVED_HEIGHT for layout calculations (actual space occupied)
5055
const headerReservedHeight = parseInt(HEADER_CONFIG.RESERVED_HEIGHT);
5156
const footerReservedHeight = parseInt(FOOTER_CONFIG.RESERVED_HEIGHT);
52-
5357
const contentMinHeight = showFooter
5458
? `calc(100vh - ${headerReservedHeight}px - ${footerReservedHeight}px)`
5559
: `calc(100vh - ${headerReservedHeight}px)`;
60+
61+
const [collapsed, setCollapsed] = useState(false);
5662

57-
return (
58-
<div className={`${contentMode === "fullscreen" ? "h-screen" : "min-h-screen"} flex flex-col ${contentMode === "fullscreen" ? "bg-white dark:bg-slate-900" : "bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800"} overflow-hidden`}>
59-
{/* Top navigation bar */}
60-
<TopNavbar
61-
additionalTitle={topNavbarAdditionalTitle}
62-
additionalRightContent={topNavbarAdditionalRightContent}
63-
/>
63+
const layoutStyle: React.CSSProperties = {
64+
height: "100vh", // Key: fill the viewport height
65+
width: "100vw", // Key: fill the viewport width
66+
overflow: "hidden", // Key: prevent the outermost scrollbar
67+
backgroundColor: "#fff"
68+
};
69+
70+
const siderStyle: React.CSSProperties = {
71+
textAlign: "start",
72+
display: "flex", // use column layout to allow inner scrollable area
73+
flexDirection: "column",
74+
alignItems: "stretch",
75+
justifyContent: "flex-start",
76+
position: "fixed",
77+
top: headerReservedHeight,
78+
bottom: showFooter? footerReservedHeight: 0,
79+
left: 0,
80+
backgroundColor: "#fff",
81+
overflow: "visible",
82+
// Ensure the sider (and its toggle) sits above main content
83+
zIndex: 998,
84+
};
85+
86+
const siderInnerStyle: React.CSSProperties = {
87+
height: "100%",
88+
overflowY: "auto",
89+
overflowX: "hidden",
90+
WebkitOverflowScrolling: "touch",
91+
display: "flex",
92+
flexDirection: "column",
93+
};
94+
95+
const headerStyle: React.CSSProperties = {
96+
textAlign: "center",
97+
height: headerReservedHeight, // Fixed height 64px
98+
backgroundColor: "#fff",
99+
lineHeight: "64px",
100+
paddingInline: 0,
101+
flexShrink: 0, // Prevent shrinking
102+
};
64103

65-
{/* Layout with sidebar and content */}
66-
<Layout
67-
className="flex-1 bg-transparent"
68-
style={{
69-
marginTop: 0,
70-
marginBottom: 0,
71-
...(contentMode === "fullscreen"
72-
? { height: contentMinHeight }
73-
: { minHeight: contentMinHeight }
74-
),
75-
}}
76-
>
77-
{/* Side navigation */}
78-
<SideNavigation
79-
onAuthRequired={onAuthRequired}
80-
onAdminRequired={onAdminRequired}
81-
onViewChange={onViewChange}
82-
currentView={currentView}
83-
/>
104+
const footerStyle: React.CSSProperties = {
105+
textAlign: "center",
106+
height: showFooter? footerReservedHeight: 0, // Fixed height 64px
107+
lineHeight: showFooter? footerReservedHeight: 0,
108+
padding: 0,
109+
flexShrink: 0, // 防止被挤压
110+
backgroundColor: "#fff",
111+
};
84112

85-
{/* Main content area */}
86-
<Content
87-
className={
88-
contentMode === "centered"
89-
? "flex-1 flex items-center justify-center overflow-hidden"
90-
: contentMode === "fullscreen"
91-
? "flex-1 overflow-hidden"
92-
: "flex-1 overflow-auto"
93-
}
94-
style={{
95-
paddingTop: contentMode === "fullscreen" ? `${headerReservedHeight}px` : `${headerReservedHeight}px`,
96-
paddingBottom: contentMode === "fullscreen" ? (showFooter ? `${footerReservedHeight}px` : 0) : (showFooter ? `${footerReservedHeight}px` : 0)
97-
}}
98-
>
99-
{contentMode === "centered" ? (
100-
<div className="w-full h-full flex items-center justify-center p-4">
101-
{children}
113+
const contentStyle: React.CSSProperties = {
114+
// Key settings:
115+
overflowY: "auto", // If content overflows vertically, scroll inside Content only
116+
overflowX: "hidden", // Hide horizontal scrollbar (optional)
117+
display: "flex", // (optional) used to center content for demo
118+
alignItems: "center", // (optional)
119+
justifyContent: "center", // (optional)
120+
position: "relative", // Allow absolute-positioned children
121+
marginLeft: collapsed
122+
? `${SIDER_CONFIG.COLLAPSED_WIDTH}px`
123+
: `${SIDER_CONFIG.EXPANDED_WIDTH}px`,
124+
backgroundColor: "#fff"
125+
};
126+
127+
return (
128+
<Layout style={layoutStyle}>
129+
<Header style={headerStyle}>
130+
<TopNavbar
131+
additionalTitle={topNavbarAdditionalTitle}
132+
additionalRightContent={topNavbarAdditionalRightContent}
133+
/>
134+
</Header>
135+
136+
<Layout >
137+
<Sider
138+
style={siderStyle}
139+
width={SIDER_CONFIG.EXPANDED_WIDTH}
140+
collapsed={collapsed}
141+
trigger={null}
142+
breakpoint="lg"
143+
collapsedWidth={SIDER_CONFIG.COLLAPSED_WIDTH}
144+
className="dark:bg-slate-900/95 border-r border-slate-200 dark:border-slate-700 backdrop-blur-sm shadow-sm"
145+
>
146+
{/* Side navigation - wrapped to allow internal vertical scrolling when needed */}
147+
<div style={siderInnerStyle}>
148+
<SideNavigation
149+
onAuthRequired={onAuthRequired}
150+
onAdminRequired={onAdminRequired}
151+
onViewChange={onViewChange}
152+
currentView={currentView}
153+
collapsed={collapsed}
154+
/>
102155
</div>
103-
) : (
104-
children
105-
)}
106-
</Content>
107-
</Layout>
156+
<Button
157+
type="primary"
158+
shape="circle"
159+
size="small"
160+
onClick={() => setCollapsed(!collapsed)}
161+
style={{
162+
position: "absolute",
163+
top: "50%",
164+
transform: "translateY(-50%)",
165+
right: "-12px",
166+
transition: "right 0.2s ease, left 0.2s ease",
167+
// Place toggle above most content; Sider already has high z-index
168+
zIndex: 999,
169+
}}
170+
icon={collapsed ? <ChevronRight className="w-3 h-3" /> : <ChevronLeft className="w-3 h-3" />}
171+
/>
172+
</Sider>
173+
{/* Main content area */}
174+
<Content style={contentStyle}
175+
>
176+
{contentMode === "centered" ? (
177+
<div className="w-full h-full flex items-center justify-center">
178+
{children}
179+
</div>
180+
) : (
181+
children
182+
)}
183+
</Content>
184+
</Layout>
108185

109-
{/* Fixed footer at bottom */}
110-
{showFooter && (
111-
<div
112-
style={{
113-
position: "fixed",
114-
bottom: 0,
115-
left: 0,
116-
right: 0,
117-
zIndex: 10,
118-
}}
119-
>
120-
<Footer />
121-
</div>
122-
)}
123-
</div>
186+
{ showFooter &&
187+
<Footer style={footerStyle}>
188+
<FooterLayout />
189+
</Footer>
190+
}
191+
</Layout>
124192
);
125193
}
126194

0 commit comments

Comments
 (0)