mirror of
https://github.com/grafana/grafana.git
synced 2025-09-25 20:54:10 +08:00
Navigation: Implement scrolling navbar + fix clickable area of grafana logo (#48045)
* Attach nav item menus to a portal that's a sibling of the chevron to prevent incorrect stacking * add scrollbar to navbar * Make clickable area of grafana logo full size * hide vertical track as well * prettier...
This commit is contained in:
@ -391,7 +391,7 @@ const getCollapsibleStyles = (theme: GrafanaTheme2) => ({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridAutoFlow: 'column',
|
gridAutoFlow: 'column',
|
||||||
gridTemplateColumns: '56px auto',
|
gridTemplateColumns: `${theme.spacing(7)} auto`,
|
||||||
}),
|
}),
|
||||||
collapsibleMenuItem: css({
|
collapsibleMenuItem: css({
|
||||||
height: theme.spacing(6),
|
height: theme.spacing(6),
|
||||||
|
@ -3,7 +3,7 @@ import { useLocation } from 'react-router-dom';
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
|
||||||
import { Icon, IconName, useTheme2 } from '@grafana/ui';
|
import { CustomScrollbar, Icon, IconName, useTheme2 } from '@grafana/ui';
|
||||||
import { config, locationService } from '@grafana/runtime';
|
import { config, locationService } from '@grafana/runtime';
|
||||||
import { getKioskMode } from 'app/core/navigation/kiosk';
|
import { getKioskMode } from 'app/core/navigation/kiosk';
|
||||||
import { KioskMode, StoreState } from 'app/types';
|
import { KioskMode, StoreState } from 'app/types';
|
||||||
@ -13,10 +13,10 @@ import { NavBarMenu } from './NavBarMenu';
|
|||||||
import NavBarItem from './NavBarItem';
|
import NavBarItem from './NavBarItem';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
|
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
|
||||||
import { FocusScope } from '@react-aria/focus';
|
|
||||||
import { NavBarContext } from '../context';
|
import { NavBarContext } from '../context';
|
||||||
import { NavBarToggle } from './NavBarToggle';
|
import { NavBarToggle } from './NavBarToggle';
|
||||||
import { NavBarMenuPortalContainer } from './NavBarMenuPortalContainer';
|
import { NavBarMenuPortalContainer } from './NavBarMenuPortalContainer';
|
||||||
|
import { FocusScope } from '@react-aria/focus';
|
||||||
|
|
||||||
const onOpenSearch = () => {
|
const onOpenSearch = () => {
|
||||||
locationService.partial({ search: 'open' });
|
locationService.partial({ search: 'open' });
|
||||||
@ -90,6 +90,7 @@ export const NavBarNext = React.memo(() => {
|
|||||||
|
|
||||||
<ul className={styles.itemList}>
|
<ul className={styles.itemList}>
|
||||||
<NavBarItemWithoutMenu
|
<NavBarItemWithoutMenu
|
||||||
|
elClassName={styles.grafanaLogoInner}
|
||||||
isActive={isMatchOrChildMatch(homeItem, activeItem)}
|
isActive={isMatchOrChildMatch(homeItem, activeItem)}
|
||||||
label="Home"
|
label="Home"
|
||||||
className={styles.grafanaLogo}
|
className={styles.grafanaLogo}
|
||||||
@ -97,29 +98,36 @@ export const NavBarNext = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<Icon name="grafana" size="xl" />
|
<Icon name="grafana" size="xl" />
|
||||||
</NavBarItemWithoutMenu>
|
</NavBarItemWithoutMenu>
|
||||||
<NavBarItem className={styles.search} isActive={activeItem === searchItem} link={searchItem}>
|
|
||||||
<Icon name="search" size="xl" />
|
|
||||||
</NavBarItem>
|
|
||||||
|
|
||||||
{coreItems.map((link, index) => (
|
<CustomScrollbar hideVerticalTrack hideHorizontalTrack>
|
||||||
<NavBarItem
|
<NavBarItem className={styles.search} isActive={activeItem === searchItem} link={searchItem}>
|
||||||
key={`${link.id}-${index}`}
|
<Icon name="search" size="xl" />
|
||||||
isActive={isMatchOrChildMatch(link, activeItem)}
|
|
||||||
link={{ ...link, subTitle: undefined, onClick: undefined }}
|
|
||||||
>
|
|
||||||
{link.icon && <Icon name={link.icon as IconName} size="xl" />}
|
|
||||||
{link.img && <img src={link.img} alt={`${link.text} logo`} />}
|
|
||||||
</NavBarItem>
|
</NavBarItem>
|
||||||
))}
|
|
||||||
|
|
||||||
{pluginItems.length > 0 &&
|
{coreItems.map((link, index) => (
|
||||||
pluginItems.map((link, index) => (
|
<NavBarItem
|
||||||
<NavBarItem key={`${link.id}-${index}`} isActive={isMatchOrChildMatch(link, activeItem)} link={link}>
|
key={`${link.id}-${index}`}
|
||||||
|
isActive={isMatchOrChildMatch(link, activeItem)}
|
||||||
|
link={{ ...link, subTitle: undefined, onClick: undefined }}
|
||||||
|
>
|
||||||
{link.icon && <Icon name={link.icon as IconName} size="xl" />}
|
{link.icon && <Icon name={link.icon as IconName} size="xl" />}
|
||||||
{link.img && <img src={link.img} alt={`${link.text} logo`} />}
|
{link.img && <img src={link.img} alt={`${link.text} logo`} />}
|
||||||
</NavBarItem>
|
</NavBarItem>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{pluginItems.length > 0 &&
|
||||||
|
pluginItems.map((link, index) => (
|
||||||
|
<NavBarItem
|
||||||
|
key={`${link.id}-${index}`}
|
||||||
|
isActive={isMatchOrChildMatch(link, activeItem)}
|
||||||
|
link={link}
|
||||||
|
>
|
||||||
|
{link.icon && <Icon name={link.icon as IconName} size="xl" />}
|
||||||
|
{link.img && <img src={link.img} alt={`${link.text} logo`} />}
|
||||||
|
</NavBarItem>
|
||||||
|
))}
|
||||||
|
</CustomScrollbar>
|
||||||
|
|
||||||
{configItems.map((link, index) => (
|
{configItems.map((link, index) => (
|
||||||
<NavBarItem
|
<NavBarItem
|
||||||
key={`${link.id}-${index}`}
|
key={`${link.id}-${index}`}
|
||||||
@ -171,7 +179,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
zIndex: theme.zIndex.sidemenu,
|
zIndex: theme.zIndex.sidemenu,
|
||||||
padding: `${theme.spacing(1)} 0`,
|
padding: `${theme.spacing(1)} 0`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: theme.spacing(7),
|
width: `calc(${theme.spacing(7)} + 1px)`,
|
||||||
borderRight: `1px solid ${theme.colors.border.weak}`,
|
borderRight: `1px solid ${theme.colors.border.weak}`,
|
||||||
|
|
||||||
[theme.breakpoints.down('md')]: {
|
[theme.breakpoints.down('md')]: {
|
||||||
@ -207,13 +215,22 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
grafanaLogo: css({
|
grafanaLogo: css({
|
||||||
|
alignItems: 'stretch',
|
||||||
|
display: 'flex',
|
||||||
|
flexShrink: 0,
|
||||||
|
justifyContent: 'stretch',
|
||||||
|
}),
|
||||||
|
grafanaLogoInner: css({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
img: {
|
height: '100%',
|
||||||
height: theme.spacing(3),
|
|
||||||
width: theme.spacing(3),
|
|
||||||
},
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
width: '100%',
|
||||||
|
|
||||||
|
'> div': {
|
||||||
|
height: 'auto',
|
||||||
|
width: 'auto',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
search: css({
|
search: css({
|
||||||
display: 'none',
|
display: 'none',
|
||||||
@ -244,4 +261,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
right: '0px',
|
right: '0px',
|
||||||
transform: `translateX(50%)`,
|
transform: `translateX(50%)`,
|
||||||
}),
|
}),
|
||||||
|
menuPortalContainer: css({
|
||||||
|
zIndex: theme.zIndex.sidemenu,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,9 @@ import { getFooterLinks } from '../Footer/Footer';
|
|||||||
import { HelpModal } from '../help/HelpModal';
|
import { HelpModal } from '../help/HelpModal';
|
||||||
|
|
||||||
export const SEARCH_ITEM_ID = 'search';
|
export const SEARCH_ITEM_ID = 'search';
|
||||||
|
export const NAV_MENU_PORTAL_CONTAINER_ID = 'navbar-menu-portal-container';
|
||||||
|
|
||||||
|
export const getNavMenuPortalContainer = () => document.getElementById(NAV_MENU_PORTAL_CONTAINER_ID) ?? document.body;
|
||||||
|
|
||||||
export const getForcedLoginUrl = (url: string) => {
|
export const getForcedLoginUrl = (url: string) => {
|
||||||
const queryParams = new URLSearchParams(url.split('?')[1]);
|
const queryParams = new URLSearchParams(url.split('?')[1]);
|
||||||
|
Reference in New Issue
Block a user