Commit 5a83fc57 by Torkel Ödegaard Committed by GitHub

PanelMenu: Fixes panel submenu not being accessible for panels close to the…

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
parent 6dbf1f83
...@@ -44,7 +44,7 @@ export const DashboardLinks: FC<Props> = ({ dashboard, links }) => { ...@@ -44,7 +44,7 @@ export const DashboardLinks: FC<Props> = ({ dashboard, links }) => {
const linkElement = ( const linkElement = (
<a <a
className="gf-form-label" className="gf-form-label gf-form-label--dashlink"
href={sanitizeUrl(linkInfo.href)} href={sanitizeUrl(linkInfo.href)}
target={link.targetBlank ? '_blank' : '_self'} target={link.targetBlank ? '_blank' : '_self'}
aria-label={selectors.components.DashboardLinks.link} aria-label={selectors.components.DashboardLinks.link}
......
...@@ -19,7 +19,6 @@ interface State { ...@@ -19,7 +19,6 @@ interface State {
export class DashboardLinksDashboard extends PureComponent<Props, State> { export class DashboardLinksDashboard extends PureComponent<Props, State> {
state: State = { resolvedLinks: [] }; state: State = { resolvedLinks: [] };
wrapperRef = createRef<HTMLDivElement>();
listItemRef = createRef<HTMLUListElement>(); listItemRef = createRef<HTMLUListElement>();
componentDidMount() { componentDidMount() {
...@@ -45,7 +44,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> { ...@@ -45,7 +44,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
const { link } = this.props; const { link } = this.props;
return ( 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 && <Tooltip content={link.tooltip}>{linkElement}</Tooltip>}
{!link.tooltip && <>{linkElement}</>} {!link.tooltip && <>{linkElement}</>}
</div> </div>
...@@ -62,7 +61,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> { ...@@ -62,7 +61,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
resolvedLinks.map((resolvedLink, index) => { resolvedLinks.map((resolvedLink, index) => {
const linkElement = ( const linkElement = (
<a <a
className="gf-form-label" className="gf-form-label gf-form-label--dashlink"
href={resolvedLink.url} href={resolvedLink.url}
target={link.targetBlank ? '_blank' : '_self'} target={link.targetBlank ? '_blank' : '_self'}
aria-label={selectors.components.DashboardLinks.link} aria-label={selectors.components.DashboardLinks.link}
...@@ -81,32 +80,26 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> { ...@@ -81,32 +80,26 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
); );
}; };
getDropdownLocationCssClass = (): string => { renderDropdown() {
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 = () => {
const { link, linkInfo } = this.props; const { link, linkInfo } = this.props;
const { resolvedLinks } = this.state; const { resolvedLinks } = this.state;
const linkElement = ( const linkElement = (
<> <>
<a <a
className="gf-form-label pointer" className="gf-form-label gf-form-label--dashlink"
onClick={this.searchForDashboards} onClick={this.searchForDashboards}
data-placement="bottom" data-placement="bottom"
data-toggle="dropdown" data-toggle="dropdown"
> >
<Icon name="bars" /> <Icon name="bars" style={{ marginRight: '4px' }} />
<span>{linkInfo.title}</span> <span>{linkInfo.title}</span>
</a> </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.length > 0 &&
resolvedLinks.map((resolvedLink, index) => { resolvedLinks.map((resolvedLink, index) => {
return ( return (
...@@ -126,7 +119,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> { ...@@ -126,7 +119,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
); );
return this.renderElement(linkElement, 'dashlinks-dropdown', selectors.components.DashboardLinks.dropDown); return this.renderElement(linkElement, 'dashlinks-dropdown', selectors.components.DashboardLinks.dropDown);
}; }
render() { render() {
if (this.props.link.asDropdown) { if (this.props.link.asDropdown) {
...@@ -174,3 +167,22 @@ export function resolveLinks( ...@@ -174,3 +167,22 @@ export function resolveLinks(
return { id, title, url }; 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';
}
}
import React, { FC } from 'react'; import React, { FC, useState } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { PanelMenuItem } from '@grafana/data'; import { PanelMenuItem } from '@grafana/data';
import { Icon, IconName, useTheme } from '@grafana/ui'; import { Icon, IconName, useTheme } from '@grafana/ui';
...@@ -9,6 +9,7 @@ interface Props { ...@@ -9,6 +9,7 @@ interface Props {
} }
export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => { export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
const [ref, setRef] = useState<HTMLLIElement | null>(null);
const isSubMenu = props.type === 'submenu'; const isSubMenu = props.type === 'submenu';
const isDivider = props.type === 'divider'; const isDivider = props.type === 'divider';
const theme = useTheme(); const theme = useTheme();
...@@ -24,10 +25,11 @@ export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => { ...@@ -24,10 +25,11 @@ export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
right: ${theme.spacing.xs}; right: ${theme.spacing.xs};
color: ${theme.colors.textWeak}; color: ${theme.colors.textWeak};
`; `;
return isDivider ? ( return isDivider ? (
<li className="divider" /> <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}> <a onClick={props.onClick} href={props.href}>
{props.iconClassName && <Icon name={props.iconClassName as IconName} className={menuIconClassName} />} {props.iconClassName && <Icon name={props.iconClassName as IconName} className={menuIconClassName} />}
<span className="dropdown-item-text" aria-label={selectors.components.Panels.Panel.headerItems(props.text)}> <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 => { ...@@ -44,3 +46,22 @@ export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
</li> </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 @@ ...@@ -59,7 +59,7 @@
text-align: left; text-align: left;
// Aligns the dropdown menu to right // Aligns the dropdown menu to right
&.pull-right { &.pull-left {
right: 0; right: 0;
left: auto; left: auto;
} }
...@@ -238,6 +238,7 @@ ...@@ -238,6 +238,7 @@
.dropdown-submenu { .dropdown-submenu {
position: relative; position: relative;
} }
// Default dropdowns // Default dropdowns
.dropdown-submenu > .dropdown-menu { .dropdown-submenu > .dropdown-menu {
top: 0; top: 0;
...@@ -277,16 +278,21 @@ ...@@ -277,16 +278,21 @@
.dropdown-submenu.pull-left { .dropdown-submenu.pull-left {
// Undo the float // Undo the float
// Yes, this is awkward since .pull-left adds a float, but it sticks to our conventions elsewhere. // 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 // Positioning the submenu
> .dropdown-menu { > .dropdown-menu {
left: -100%; left: -100%;
margin-left: 10px; width: 100%;
margin-left: 2px;
@include border-radius(6px 0 6px 6px); @include border-radius(6px 0 6px 6px);
} }
} }
.dropdown-submenu.pull-right {
float: none !important;
}
// Tweak nav headers // Tweak nav headers
// ----------------- // -----------------
// Increase padding from 15px to 20px on sides // Increase padding from 15px to 20px on sides
......
...@@ -148,6 +148,11 @@ $input-border: 1px solid $input-border-color; ...@@ -148,6 +148,11 @@ $input-border: 1px solid $input-border-color;
border: $panel-border; border: $panel-border;
} }
&--dashlink {
background: $panel-bg;
border: $panel-border;
}
&--justify-left { &--justify-left {
justify-content: left; justify-content: left;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment