mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PanelMenu: Fixes panel submenu not being accessible for panels close to the right edge of the screen (#28666)
* Dropdowns: Trying to fix dropdown menus * Dropdowns: Trying to fix dropdown menus * removed now unnessary wrapper ref * Upodates * Remove export
This commit is contained in:
@@ -44,7 +44,7 @@ export const DashboardLinks: FC<Props> = ({ dashboard, links }) => {
|
||||
|
||||
const linkElement = (
|
||||
<a
|
||||
className="gf-form-label"
|
||||
className="gf-form-label gf-form-label--dashlink"
|
||||
href={sanitizeUrl(linkInfo.href)}
|
||||
target={link.targetBlank ? '_blank' : '_self'}
|
||||
aria-label={selectors.components.DashboardLinks.link}
|
||||
|
||||
@@ -19,7 +19,6 @@ interface State {
|
||||
|
||||
export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
state: State = { resolvedLinks: [] };
|
||||
wrapperRef = createRef<HTMLDivElement>();
|
||||
listItemRef = createRef<HTMLUListElement>();
|
||||
|
||||
componentDidMount() {
|
||||
@@ -45,7 +44,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
const { link } = this.props;
|
||||
|
||||
return (
|
||||
<div className="gf-form" key={key} aria-label={selector} ref={this.wrapperRef}>
|
||||
<div className="gf-form" key={key} aria-label={selector}>
|
||||
{link.tooltip && <Tooltip content={link.tooltip}>{linkElement}</Tooltip>}
|
||||
{!link.tooltip && <>{linkElement}</>}
|
||||
</div>
|
||||
@@ -62,7 +61,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
resolvedLinks.map((resolvedLink, index) => {
|
||||
const linkElement = (
|
||||
<a
|
||||
className="gf-form-label"
|
||||
className="gf-form-label gf-form-label--dashlink"
|
||||
href={resolvedLink.url}
|
||||
target={link.targetBlank ? '_blank' : '_self'}
|
||||
aria-label={selectors.components.DashboardLinks.link}
|
||||
@@ -81,32 +80,26 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
getDropdownLocationCssClass = (): string => {
|
||||
const [pullLeftCssClass, pullRightCssClass] = ['pull-left', 'pull-right'];
|
||||
const wrapper = this.wrapperRef.current;
|
||||
const list = this.listItemRef.current;
|
||||
if (!wrapper || !list) {
|
||||
return pullRightCssClass;
|
||||
}
|
||||
return wrapper.offsetLeft > list.offsetWidth - wrapper.offsetWidth ? pullRightCssClass : pullLeftCssClass;
|
||||
};
|
||||
|
||||
renderDropdown = () => {
|
||||
renderDropdown() {
|
||||
const { link, linkInfo } = this.props;
|
||||
const { resolvedLinks } = this.state;
|
||||
|
||||
const linkElement = (
|
||||
<>
|
||||
<a
|
||||
className="gf-form-label pointer"
|
||||
className="gf-form-label gf-form-label--dashlink"
|
||||
onClick={this.searchForDashboards}
|
||||
data-placement="bottom"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<Icon name="bars" />
|
||||
<Icon name="bars" style={{ marginRight: '4px' }} />
|
||||
<span>{linkInfo.title}</span>
|
||||
</a>
|
||||
<ul className={'dropdown-menu ' + this.getDropdownLocationCssClass()} role="menu" ref={this.listItemRef}>
|
||||
<ul
|
||||
className={`dropdown-menu ${getDropdownLocationCssClass(this.listItemRef.current)}`}
|
||||
role="menu"
|
||||
ref={this.listItemRef}
|
||||
>
|
||||
{resolvedLinks.length > 0 &&
|
||||
resolvedLinks.map((resolvedLink, index) => {
|
||||
return (
|
||||
@@ -126,7 +119,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
);
|
||||
|
||||
return this.renderElement(linkElement, 'dashlinks-dropdown', selectors.components.DashboardLinks.dropDown);
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.link.asDropdown) {
|
||||
@@ -174,3 +167,22 @@ export function resolveLinks(
|
||||
return { id, title, url };
|
||||
});
|
||||
}
|
||||
|
||||
function getDropdownLocationCssClass(element: HTMLElement | null) {
|
||||
if (!element) {
|
||||
return 'invisible';
|
||||
}
|
||||
|
||||
const wrapperPos = element.parentElement!.getBoundingClientRect();
|
||||
const pos = element.getBoundingClientRect();
|
||||
|
||||
if (pos.width === 0) {
|
||||
return 'invisible';
|
||||
}
|
||||
|
||||
if (wrapperPos.left + pos.width + 10 > window.innerWidth) {
|
||||
return 'pull-left';
|
||||
} else {
|
||||
return 'pull-right';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { PanelMenuItem } from '@grafana/data';
|
||||
import { Icon, IconName, useTheme } from '@grafana/ui';
|
||||
@@ -9,6 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
|
||||
const [ref, setRef] = useState<HTMLLIElement | null>(null);
|
||||
const isSubMenu = props.type === 'submenu';
|
||||
const isDivider = props.type === 'divider';
|
||||
const theme = useTheme();
|
||||
@@ -24,10 +25,11 @@ export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
|
||||
right: ${theme.spacing.xs};
|
||||
color: ${theme.colors.textWeak};
|
||||
`;
|
||||
|
||||
return isDivider ? (
|
||||
<li className="divider" />
|
||||
) : (
|
||||
<li className={isSubMenu ? 'dropdown-submenu' : undefined}>
|
||||
<li className={isSubMenu ? `dropdown-submenu ${getDropdownLocationCssClass(ref)}` : undefined} ref={setRef}>
|
||||
<a onClick={props.onClick} href={props.href}>
|
||||
{props.iconClassName && <Icon name={props.iconClassName as IconName} className={menuIconClassName} />}
|
||||
<span className="dropdown-item-text" aria-label={selectors.components.Panels.Panel.headerItems(props.text)}>
|
||||
@@ -44,3 +46,22 @@ export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
function getDropdownLocationCssClass(element: HTMLElement | null) {
|
||||
if (!element) {
|
||||
return 'invisible';
|
||||
}
|
||||
|
||||
const wrapperPos = element.parentElement!.getBoundingClientRect();
|
||||
const pos = element.getBoundingClientRect();
|
||||
|
||||
if (pos.width === 0) {
|
||||
return 'invisible';
|
||||
}
|
||||
|
||||
if (wrapperPos.right + pos.width + 10 > window.innerWidth) {
|
||||
return 'pull-left';
|
||||
} else {
|
||||
return 'pull-right';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
text-align: left;
|
||||
|
||||
// Aligns the dropdown menu to right
|
||||
&.pull-right {
|
||||
&.pull-left {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
@@ -238,6 +238,7 @@
|
||||
.dropdown-submenu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Default dropdowns
|
||||
.dropdown-submenu > .dropdown-menu {
|
||||
top: 0;
|
||||
@@ -277,16 +278,21 @@
|
||||
.dropdown-submenu.pull-left {
|
||||
// Undo the float
|
||||
// Yes, this is awkward since .pull-left adds a float, but it sticks to our conventions elsewhere.
|
||||
float: none;
|
||||
float: none !important;
|
||||
|
||||
// Positioning the submenu
|
||||
> .dropdown-menu {
|
||||
left: -100%;
|
||||
margin-left: 10px;
|
||||
width: 100%;
|
||||
margin-left: 2px;
|
||||
@include border-radius(6px 0 6px 6px);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-submenu.pull-right {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
// Tweak nav headers
|
||||
// -----------------
|
||||
// Increase padding from 15px to 20px on sides
|
||||
|
||||
@@ -148,6 +148,11 @@ $input-border: 1px solid $input-border-color;
|
||||
border: $panel-border;
|
||||
}
|
||||
|
||||
&--dashlink {
|
||||
background: $panel-bg;
|
||||
border: $panel-border;
|
||||
}
|
||||
|
||||
&--justify-left {
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user