feat(layout): move navigation menu from sidebar to top header
Switch ProLayout to top layout with a custom horizontal HeaderMenu (dropdowns for grouped items) and relocate the user/help/version actions into the header right area. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent
99f9ae19cb
commit
8d99dcd681
@@ -0,0 +1,128 @@
|
||||
import { IconFont } from '@gpustack/core-ui';
|
||||
import { Link, useLocation, useNavigate } from '@umijs/max';
|
||||
import { Menu } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
interface MenuItem {
|
||||
icon?: string;
|
||||
selectedIcon?: string;
|
||||
defaultIcon?: string;
|
||||
children?: MenuItem[];
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface HeaderMenuProps {
|
||||
menuData: MenuItem[];
|
||||
initialState?: Global.InitialStateType;
|
||||
}
|
||||
|
||||
const useStyles = createStyles(({ css }) => {
|
||||
return {
|
||||
headerMenu: css`
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
line-height: inherit;
|
||||
|
||||
&.ant-menu-horizontal {
|
||||
border-bottom: none;
|
||||
}
|
||||
&.ant-menu-horizontal > .ant-menu-item::after,
|
||||
&.ant-menu-horizontal > .ant-menu-submenu::after {
|
||||
display: none;
|
||||
}
|
||||
.ant-menu-title-content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.anticon {
|
||||
font-size: 16px;
|
||||
}
|
||||
`
|
||||
};
|
||||
});
|
||||
|
||||
const isItemSelected = (item: MenuItem, pathname: string) => {
|
||||
return (
|
||||
pathname === item.path ||
|
||||
(Array.isArray(item.subMenu) && item.subMenu.includes(pathname))
|
||||
);
|
||||
};
|
||||
|
||||
const HeaderMenu: React.FC<HeaderMenuProps> = (props) => {
|
||||
const { menuData } = props;
|
||||
const { styles } = useStyles();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const buildLeaf = (item: MenuItem) => {
|
||||
const selected = isItemSelected(item, location.pathname);
|
||||
return {
|
||||
key: item.path as string,
|
||||
label: (
|
||||
<Link
|
||||
prefetch="intent"
|
||||
to={(item.path as string).replace('/*', '')}
|
||||
target={item.target}
|
||||
>
|
||||
<span className="flex-center gap-8">
|
||||
<IconFont
|
||||
type={selected ? item.selectedIcon || '' : item.defaultIcon || ''}
|
||||
/>
|
||||
<span>{item.name}</span>
|
||||
</span>
|
||||
</Link>
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
const items = useMemo(() => {
|
||||
return menuData.map((item) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
return {
|
||||
key: item.key,
|
||||
label: item.name,
|
||||
children: item.children.map((child) => buildLeaf(child))
|
||||
};
|
||||
}
|
||||
return buildLeaf(item);
|
||||
});
|
||||
}, [menuData, location.pathname]);
|
||||
|
||||
const selectedKeys = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
for (const item of menuData) {
|
||||
const leaves =
|
||||
item.children && item.children.length > 0 ? item.children : [item];
|
||||
for (const leaf of leaves) {
|
||||
if (isItemSelected(leaf, location.pathname)) {
|
||||
keys.push(leaf.path as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}, [menuData, location.pathname]);
|
||||
|
||||
const handleClick = ({ key }: { key: string }) => {
|
||||
if (key.startsWith('/')) {
|
||||
navigate(key.replace('/*', ''));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className={styles.headerMenu}
|
||||
mode="horizontal"
|
||||
selectedKeys={selectedKeys}
|
||||
items={items}
|
||||
onClick={handleClick}
|
||||
triggerSubMenuAction="hover"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderMenu;
|
||||
Reference in New Issue
Block a user