feat: update sidebar navigation to add social buttons

This commit is contained in:
shiyu
2025-08-25 13:33:24 +08:00
parent a1ffdaeb03
commit a0361db82a
3 changed files with 132 additions and 76 deletions
+130 -73
View File
@@ -1,9 +1,9 @@
import { Layout, Menu, theme, Button } from 'antd'; import { Layout, Menu, theme, Button, Modal } from 'antd';
import { navGroups } from './nav.ts'; import { navGroups } from './nav.ts';
import type { NavItem, NavGroup } from './nav.ts'; import type { NavItem, NavGroup } from './nav.ts';
import { memo } from 'react'; import { memo, useState } from 'react';
import { useSystemStatus } from '../contexts/SystemContext.tsx'; import { useSystemStatus } from '../contexts/SystemContext.tsx';
import { MenuFoldOutlined } from '@ant-design/icons'; import { GithubOutlined, MenuFoldOutlined, SendOutlined, WechatOutlined } from '@ant-design/icons';
import '../styles/sider-menu.css'; import '../styles/sider-menu.css';
const { Sider } = Layout; const { Sider } = Layout;
@@ -18,79 +18,136 @@ export interface SideNavProps {
const SideNav = memo(function SideNav({ collapsed, activeKey, onChange, onToggle }: SideNavProps) { const SideNav = memo(function SideNav({ collapsed, activeKey, onChange, onToggle }: SideNavProps) {
const status = useSystemStatus(); const status = useSystemStatus();
const { token } = theme.useToken(); const { token } = theme.useToken();
const [isModalOpen, setIsModalOpen] = useState(false);
return ( return (
<Sider <>
collapsedWidth={60} <Sider
collapsible collapsedWidth={60}
trigger={null} collapsible
collapsed={collapsed} trigger={null}
width={208} collapsed={collapsed}
style={{ background: token.colorBgContainer, borderRight: `1px solid ${token.colorBorderSecondary}` }} width={208}
> style={{
<div style={{ background: token.colorBgContainer,
height: 56, borderRight: `1px solid ${token.colorBorderSecondary}`,
display: 'flex', display: 'flex',
alignItems: 'center', flexDirection: 'column'
justifyContent: collapsed ? 'center' : 'space-between', }}
padding: '0 14px', >
fontWeight: 600, <div style={{
fontSize: 18, height: 56,
letterSpacing: .5 display: 'flex',
}}> alignItems: 'center',
<div style={{ display: 'flex', alignItems: 'center' }}> justifyContent: collapsed ? 'center' : 'space-between',
<img padding: '0 14px',
src={status?.logo} fontWeight: 600,
alt="Foxel" fontSize: 18,
style={{ letterSpacing: .5,
width: 24, flexShrink: 0
height: 24, }}>
objectFit: 'contain', <div style={{ display: 'flex', alignItems: 'center' }}>
marginRight: collapsed ? 0 : 8, <img
...(status?.logo?.endsWith('.svg') && { filter: 'brightness(0) saturate(100%)' }) src={status?.logo}
}} alt="Foxel"
/> style={{
{!collapsed && <span style={{ fontWeight: 700 }}>{status?.title}</span>} width: 24,
</div> height: 24,
{/* 展开时显示收缩按钮 */} objectFit: 'contain',
{!collapsed && ( marginRight: collapsed ? 0 : 8,
<Button ...(status?.logo?.endsWith('.svg') && { filter: 'brightness(0) saturate(100%)' })
type="text" }}
icon={<MenuFoldOutlined />}
onClick={onToggle}
style={{ fontSize: 18 }}
/>
)}
</div>
{/* 分组渲染 */}
<div style={{ overflowY: 'auto', height: 'calc(100% - 56px)', padding: '4px 4px 8px' }}>
{navGroups.map((group: NavGroup) => (
<div key={group.key} style={{ marginBottom: 12 }}>
{group.title && (
<div
style={{
fontSize: 11,
fontWeight: 600,
letterSpacing: .5,
padding: '6px 10px 4px',
color: token.colorTextTertiary,
textTransform: 'uppercase'
}}
>{group.title}</div>
)}
<Menu
mode="inline"
selectable
inlineIndent={12}
selectedKeys={[activeKey]}
onClick={(e) => onChange(e.key)}
items={group.children.map((i: NavItem) => ({ key: i.key, icon: i.icon, label: i.label }))}
style={{ borderInline: 'none', background: 'transparent' }}
className="sider-menu-group foxel-sider-menu"
/> />
{!collapsed && <span style={{ fontWeight: 700 }}>{status?.title}</span>}
</div> </div>
))} {/* 展开时显示收缩按钮 */}
</div> {!collapsed && (
</Sider> <Button
type="text"
icon={<MenuFoldOutlined />}
onClick={onToggle}
style={{ fontSize: 18 }}
/>
)}
</div>
{/* 分组渲染 */}
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '4px 4px 8px' }}>
{navGroups.map((group: NavGroup) => (
<div key={group.key} style={{ marginBottom: 12 }}>
{group.title && (
<div
style={{
fontSize: 11,
fontWeight: 600,
letterSpacing: .5,
padding: '6px 10px 4px',
color: token.colorTextTertiary,
textTransform: 'uppercase'
}}
>{group.title}</div>
)}
<Menu
mode="inline"
selectable
inlineIndent={12}
selectedKeys={[activeKey]}
onClick={(e) => onChange(e.key)}
items={group.children.map((i: NavItem) => ({ key: i.key, icon: i.icon, label: i.label }))}
style={{ borderInline: 'none', background: 'transparent' }}
className="sider-menu-group foxel-sider-menu"
/>
</div>
))}
</div>
<div
style={{
bottom: '10px',
position: 'absolute',
width: '100%',
padding: '12px 8px',
display: 'flex',
justifyContent: 'center',
gap: 8,
flexShrink: 0,
borderTop: `1px solid ${token.colorBorderSecondary}`
}}
>
<Button
shape="circle"
icon={<GithubOutlined />}
href="https://github.com/DrizzleTime/Foxel"
target="_blank"
/>
<Button
shape="circle"
icon={<WechatOutlined />}
onClick={() => setIsModalOpen(true)}
/>
<Button
shape="circle"
icon={<SendOutlined />}
href="https://t.me/+thDsBfyqJxZkNTU1"
target="_blank"
/>
</div>
</Sider>
<Modal
open={isModalOpen}
onCancel={() => setIsModalOpen(false)}
title="加入社区"
footer={null}
width={320}
>
<div style={{ textAlign: 'center', padding: '12px 0' }}>
<img src="https://foxel.cc/image/wechat.png" width={200} alt="wechat" />
<div style={{ marginTop: 12, color: token.colorTextSecondary }}>
</div>
<div style={{ marginTop: 8, fontSize: 12, color: token.colorTextTertiary }}>
drizzle2001
</div>
</div>
</Modal>
</>
); );
}); });
@@ -18,7 +18,6 @@ interface HeaderProps {
} }
export const Header: React.FC<HeaderProps> = ({ export const Header: React.FC<HeaderProps> = ({
navKey,
path, path,
loading, loading,
uploading, uploading,
@@ -95,7 +94,7 @@ export const Header: React.FC<HeaderProps> = ({
<Flex align="center" justify="space-between" style={{ padding: '10px 16px', borderBottom: `1px solid ${token.colorBorderSecondary}`, gap: 12 }}> <Flex align="center" justify="space-between" style={{ padding: '10px 16px', borderBottom: `1px solid ${token.colorBorderSecondary}`, gap: 12 }}>
<Flex align="center" gap={8} style={{ flexWrap: 'wrap', flex: 1, overflow: 'hidden' }}> <Flex align="center" gap={8} style={{ flexWrap: 'wrap', flex: 1, overflow: 'hidden' }}>
<Button size="small" icon={<ArrowUpOutlined />} onClick={onGoUp} disabled={path === '/'} /> <Button size="small" icon={<ArrowUpOutlined />} onClick={onGoUp} disabled={path === '/'} />
<Typography.Text strong>{navKey}</Typography.Text> <Typography.Text strong></Typography.Text>
<Divider type="vertical" /> <Divider type="vertical" />
{renderBreadcrumb()} {renderBreadcrumb()}
</Flex> </Flex>
@@ -66,7 +66,7 @@ export const ShareModal = memo(function ShareModal({ entries, path, open, onOk,
message.success('已复制到剪贴板'); message.success('已复制到剪贴板');
}; };
const shareUrl = createdShare ? `${window.location.origin}/s/${createdShare.token}` : ''; const shareUrl = createdShare ? `${window.location.origin}/share/${createdShare.token}` : '';
const renderForm = () => ( const renderForm = () => (
<Form form={form} layout="vertical" initialValues={{ name: defaultName, accessType: 'public', expiresInDays: 7 }}> <Form form={form} layout="vertical" initialValues={{ name: defaultName, accessType: 'public', expiresInDays: 7 }}>