mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
Rework site layout into a fixed sidebar
Remove the top header and move its contents into the sidebar, which is now sticky and full height: logo and version at the top, navigation in the middle, and the account menu pinned to the bottom (opening to the right). The sidebar is rebuilt with antd Flex, Typography and Menu so almost all styling comes from antd, reducing the custom css to a single sticky rule. The nav menus also use theme="dark" to match the sider and fix the low contrast active item. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9e727f9a13
commit
262357acef
2 files changed
+65
-134
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import React, { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||
import { Button, Dropdown, Layout, Menu, MenuProps, Tooltip } from "antd";
|
||||
import { Button, Flex, Layout, Menu, MenuProps, Tooltip, Typography } from "antd";
|
||||
import { UserOutlined, DownloadOutlined } from "@ant-design/icons";
|
||||
|
||||
import ErrorBoundary from "./ErrorBoundary";
|
||||
@@ -14,12 +14,13 @@ import { saveJson } from "../util/save_file";
|
||||
import { useAccount } from "../model/account";
|
||||
import { DraggingContext } from "../model/is_dragging";
|
||||
import webUiPackage from "../../package.json";
|
||||
import logo from "../images/logo.png";
|
||||
|
||||
import { ControlConfig } from "@clusterio/lib";
|
||||
|
||||
type MenuItem = Required<MenuProps>["items"][number];
|
||||
|
||||
const { Header, Sider } = Layout;
|
||||
const { Sider } = Layout;
|
||||
|
||||
function isActiveDropzone(element: HTMLElement | null): boolean {
|
||||
if (!element) {
|
||||
@@ -127,6 +128,8 @@ export default function SiteLayout() {
|
||||
}
|
||||
|
||||
return <Layout
|
||||
hasSider
|
||||
className="site-layout"
|
||||
style={{ minHeight: "100vh" }}
|
||||
onDragEnter={() => setDraggingProxy(1)}
|
||||
onDragLeave={() => setDraggingProxy(-1)}
|
||||
@@ -170,70 +173,68 @@ export default function SiteLayout() {
|
||||
}}
|
||||
/>
|
||||
<DraggingContext.Provider value={dragging > 0}>
|
||||
<Header className="header">
|
||||
<div className="site-logo" />
|
||||
<span className="site-name">Clusterio</span>
|
||||
<Tooltip
|
||||
title="View changelog"
|
||||
placement="bottom"
|
||||
align={{ offset: [0, -10] }}
|
||||
>
|
||||
<span
|
||||
className="site-version"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => setChangeLogOpen(true)}
|
||||
>
|
||||
{webUiPackage.version}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="horizontal"
|
||||
defaultSelectedKeys={["1"]}
|
||||
items={[{ label: "Dashboard", key: "1" }]}
|
||||
/>
|
||||
<Dropdown
|
||||
className="account-dropdown-header"
|
||||
placement="bottomRight"
|
||||
trigger={["click"]}
|
||||
menu={accountMenuProps}
|
||||
>
|
||||
<UserOutlined />
|
||||
</Dropdown>
|
||||
</Header>
|
||||
<Layout className="site-layout">
|
||||
<Sider
|
||||
collapsible
|
||||
collapsedWidth={0}
|
||||
breakpoint="md"
|
||||
zeroWidthTriggerStyle={{ top: 6, zIndex: -1 }}
|
||||
width={250}
|
||||
className="site-layout-sider"
|
||||
>
|
||||
<Sider
|
||||
collapsible
|
||||
collapsedWidth={0}
|
||||
breakpoint="md"
|
||||
zeroWidthTriggerStyle={{ top: 6, zIndex: -1 }}
|
||||
width={250}
|
||||
className="site-layout-sider"
|
||||
>
|
||||
<Flex vertical style={{ height: "100%" }}>
|
||||
<Flex align="center" gap="middle" style={{ padding: 16 }}>
|
||||
<img src={logo} width={48} height={48} alt="Clusterio logo" />
|
||||
<Flex vertical>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>Clusterio</Typography.Title>
|
||||
<Tooltip title="View changelog" placement="right">
|
||||
<Typography.Text
|
||||
type="danger"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => setChangeLogOpen(true)}
|
||||
>
|
||||
{webUiPackage.version}
|
||||
</Typography.Text>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
defaultOpenKeys={[...menuGroups.keys()]}
|
||||
selectedKeys={currentSidebarPath ? [currentSidebarPath] : []}
|
||||
style={{ height: "100%", borderRight: 0 }}
|
||||
style={{ flex: 1, overflow: "auto", borderInlineEnd: 0 }}
|
||||
onClick={({ key }) => navigate(key)}
|
||||
items={menuItems}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout className="site-layout-content-container">
|
||||
<Routes>
|
||||
{combinedPages.map(({ path, sidebarPath, content }) => <Route
|
||||
path={path}
|
||||
key={path}
|
||||
element={<Fragment key={path}>
|
||||
<SetSidebar path={sidebarPath ? sidebarPath : path} />
|
||||
<ErrorBoundary Component={ErrorPage}>
|
||||
{content}
|
||||
</ErrorBoundary>
|
||||
</Fragment>}
|
||||
/>)}
|
||||
<Route element={<SetSidebar path={null} />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="vertical"
|
||||
selectable={false}
|
||||
style={{ borderInlineEnd: 0 }}
|
||||
onClick={accountMenuProps.onClick}
|
||||
items={[{
|
||||
key: "account",
|
||||
icon: <UserOutlined />,
|
||||
label: account.name,
|
||||
children: accountMenuProps.items,
|
||||
}]}
|
||||
/>
|
||||
</Flex>
|
||||
</Sider>
|
||||
<Layout className="site-layout-content-container">
|
||||
<Routes>
|
||||
{combinedPages.map(({ path, sidebarPath, content }) => <Route
|
||||
path={path}
|
||||
key={path}
|
||||
element={<Fragment key={path}>
|
||||
<SetSidebar path={sidebarPath ? sidebarPath : path} />
|
||||
<ErrorBoundary Component={ErrorPage}>
|
||||
{content}
|
||||
</ErrorBoundary>
|
||||
</Fragment>}
|
||||
/>)}
|
||||
<Route element={<SetSidebar path={null} />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
</DraggingContext.Provider>
|
||||
</Layout>;
|
||||
|
||||
@@ -56,13 +56,12 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep the sidebar fixed in place while the page content scrolls */
|
||||
.site-layout-sider {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding-left: 250px;
|
||||
padding-right: 64px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
@@ -105,75 +104,6 @@ body {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 112px;
|
||||
font-size: 18pt;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.site-version {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 120px;
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
position: absolute;
|
||||
left: 36px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-image: url("./images/logo.png");
|
||||
background-size: 64px;
|
||||
}
|
||||
|
||||
@media not all and (min-width: 576px) {
|
||||
.header {
|
||||
padding-left: 88px;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.site-version {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.account-dropdown-header {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
font-size: 18px;
|
||||
line-height: 72px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.account-dropdown-header::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: transparent;
|
||||
transition: all 0.3s;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.account-dropdown-header:hover::after {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.error-traceback code {
|
||||
display: block;
|
||||
margin: 0;
|
||||
|
||||
Reference in new issue
Block a user