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>
129 lines
3.1 KiB
TypeScript
129 lines
3.1 KiB
TypeScript
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;
|