Skip to content

Commit f0ea488

Browse files
committed
feat(layout): add back to top button
1 parent d575ff2 commit f0ea488

6 files changed

Lines changed: 71 additions & 6 deletions

File tree

src/components/AppLayout.tsx

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
2-
import { Avatar, Link } from "@mui/material";
2+
import KeyboardArrowUpRoundedIcon from "@mui/icons-material/KeyboardArrowUpRounded";
3+
import { Avatar, Fab, Fade, Link } from "@mui/material";
34
import AppBar from "@mui/material/AppBar";
45
import Chip from "@mui/material/Chip";
56
import IconButton from "@mui/material/IconButton";
@@ -9,12 +10,13 @@ import Tooltip from "@mui/material/Tooltip";
910
import Typography from "@mui/material/Typography";
1011
import { DashboardLayout } from "@toolpad/core/DashboardLayout";
1112
import { PageContainer } from "@toolpad/core/PageContainer";
13+
import { useEffect, useState } from "react";
1214
import { useTranslation } from "react-i18next";
1315
import { Outlet, useLocation, useNavigate } from "react-router-dom";
1416
import AddModal from "@/components/AddModal";
1517
import { SearchBox } from "@/components/SearchBox";
1618
import { Toolbars } from "@/components/Toolbar";
17-
import { saveScrollPosition } from "@/utils/scroll";
19+
import { saveScrollPosition, scrollToTop } from "@/utils/scroll";
1820

1921
/**
2022
* 侧边栏底部信息组件
@@ -122,6 +124,52 @@ const Header = () => (
122124
</AppBar>
123125
);
124126

127+
const BackToTopButton = () => {
128+
const { t } = useTranslation();
129+
const location = useLocation();
130+
const [visible, setVisible] = useState(false);
131+
132+
useEffect(() => {
133+
const container = document.querySelector<HTMLElement>("main");
134+
if (!container) return;
135+
136+
const updateVisible = () => {
137+
setVisible(container.scrollTop > 320);
138+
};
139+
140+
updateVisible();
141+
container.addEventListener("scroll", updateVisible, { passive: true });
142+
143+
return () => {
144+
container.removeEventListener("scroll", updateVisible);
145+
};
146+
}, []);
147+
148+
const label = t("components.AppLayout.backToTop", "返回顶部");
149+
150+
return (
151+
<Fade in={visible} unmountOnExit>
152+
<Tooltip title={label} enterDelay={1000}>
153+
<Fab
154+
color="primary"
155+
size="small"
156+
aria-label={label}
157+
className="print:hidden"
158+
onClick={() => scrollToTop(location.pathname)}
159+
sx={{
160+
position: "fixed",
161+
right: { xs: 16, sm: 24 },
162+
bottom: { xs: 16, sm: 24 },
163+
zIndex: 40,
164+
}}
165+
>
166+
<KeyboardArrowUpRoundedIcon />
167+
</Fab>
168+
</Tooltip>
169+
</Fade>
170+
);
171+
};
172+
125173
/**
126174
* 应用主布局组件
127175
* 集成侧边栏、顶部工具栏、页面容器等,支持自定义标题、国际化和响应式布局。
@@ -157,6 +205,7 @@ export const Layout: React.FC = () => {
157205
) : (
158206
<Outlet />
159207
)}
208+
<BackToTopButton />
160209
</DashboardLayout>
161210
</>
162211
);

src/locales/en-US.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
"ymgalData": "Ymgal Data"
7373
},
7474
"AppLayout": {
75-
"back": "Back"
75+
"back": "Back",
76+
"backToTop": "Back to top"
7677
},
7778
"BulkImportModal": {
7879
"actions": "Actions",

src/locales/ja-JP.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
"ymgalData": "Ymgal データ"
7373
},
7474
"AppLayout": {
75-
"back": "戻る"
75+
"back": "戻る",
76+
"backToTop": "トップへ戻る"
7677
},
7778
"BulkImportModal": {
7879
"actions": "操作",

src/locales/zh-CN.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
"ymgalData": "YMGal 数据"
7373
},
7474
"AppLayout": {
75-
"back": "返回"
75+
"back": "返回",
76+
"backToTop": "返回顶部"
7677
},
7778
"BulkImportModal": {
7879
"actions": "操作",

src/locales/zh-TW.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
"ymgalData": "Ymgal 資料"
7373
},
7474
"AppLayout": {
75-
"back": "返回"
75+
"back": "返回",
76+
"backToTop": "返回頂部"
7677
},
7778
"BulkImportModal": {
7879
"actions": "操作",

src/utils/scroll.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,15 @@ export const saveScrollPosition = (path: string) => {
1010
setScrollPosition(path, container.scrollTop);
1111
}
1212
};
13+
14+
export const scrollToTop = (path: string) => {
15+
const SCROLL_CONTAINER_SELECTOR = "main";
16+
const container = document.querySelector<HTMLElement>(
17+
SCROLL_CONTAINER_SELECTOR,
18+
);
19+
20+
if (!container) return;
21+
22+
setScrollPosition(path, 0);
23+
container.scrollTo({ top: 0, behavior: "smooth" });
24+
};

0 commit comments

Comments
 (0)