mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
A11y: Add focus styles to dashboard toolbar links (#35895)
This commit is contained in:
parent
83860bdcc3
commit
f5bd325354
@ -80,4 +80,22 @@ describe('locationUtil', () => {
|
||||
expect(urlWithoutMaster).toBe('/subUrl/grafana/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSearchParams', () => {
|
||||
beforeEach(() => {
|
||||
locationUtil.initialize({
|
||||
config: {} as any,
|
||||
getVariablesUrlParams: (() => {}) as any,
|
||||
getTimeRangeForUrl: (() => {}) as any,
|
||||
});
|
||||
});
|
||||
|
||||
test('absolute url', () => {
|
||||
const newURL = locationUtil.updateSearchParams(
|
||||
'http://www.domain.com:1234/test?a=1&b=2#hashtag',
|
||||
'?a=newValue&newKey=hello'
|
||||
);
|
||||
expect(newURL).toBe('http://www.domain.com:1234/test?a=newValue&b=2&newKey=hello#hashtag');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -42,6 +42,14 @@ const assureBaseUrl = (url: string): string => {
|
||||
return url;
|
||||
};
|
||||
|
||||
const updateSearchParams = (href: string, searchParams: string) => {
|
||||
const curURL = new URL(href);
|
||||
const urlSearchParams = new URLSearchParams(searchParams);
|
||||
urlSearchParams.forEach((val, key) => curURL.searchParams.set(key, val));
|
||||
|
||||
return curURL.href;
|
||||
};
|
||||
|
||||
interface LocationUtilDependencies {
|
||||
config: GrafanaConfig;
|
||||
getTimeRangeForUrl: () => RawTimeRange;
|
||||
@ -63,6 +71,7 @@ export const locationUtil = {
|
||||
},
|
||||
stripBaseFromUrl,
|
||||
assureBaseUrl,
|
||||
updateSearchParams,
|
||||
getTimeRangeUrlParams: () => {
|
||||
if (!getTimeRangeUrlParams) {
|
||||
return null;
|
||||
|
@ -27,8 +27,8 @@ export const Examples = () => {
|
||||
pageIcon="apps"
|
||||
title="A very long dashboard name"
|
||||
parent="A long folder name"
|
||||
onClickTitle={() => action('Title clicked')}
|
||||
onClickParent={() => action('Parent clicked')}
|
||||
titleHref=""
|
||||
parentHref=""
|
||||
leftItems={[
|
||||
<IconButton name="share-alt" size="lg" key="share" />,
|
||||
<IconButton name="favorite" iconType="mono" size="lg" key="favorite" />,
|
||||
|
@ -7,14 +7,16 @@ import { Icon } from '../Icon/Icon';
|
||||
import { styleMixins } from '../../themes';
|
||||
import { IconButton } from '../IconButton/IconButton';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Link } from '..';
|
||||
import { getFocusStyles } from '../../themes/mixins';
|
||||
|
||||
export interface Props {
|
||||
pageIcon?: IconName;
|
||||
title: string;
|
||||
parent?: string;
|
||||
onGoBack?: () => void;
|
||||
onClickTitle?: () => void;
|
||||
onClickParent?: () => void;
|
||||
titleHref?: string;
|
||||
parentHref?: string;
|
||||
leftItems?: ReactNode[];
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
@ -23,18 +25,7 @@ export interface Props {
|
||||
|
||||
/** @alpha */
|
||||
export const PageToolbar: FC<Props> = React.memo(
|
||||
({
|
||||
title,
|
||||
parent,
|
||||
pageIcon,
|
||||
onGoBack,
|
||||
children,
|
||||
onClickTitle,
|
||||
onClickParent,
|
||||
leftItems,
|
||||
isFullscreen,
|
||||
className,
|
||||
}) => {
|
||||
({ title, parent, pageIcon, onGoBack, children, titleHref, parentHref, leftItems, isFullscreen, className }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
/**
|
||||
@ -56,7 +47,7 @@ export const PageToolbar: FC<Props> = React.memo(
|
||||
<div className={mainStyle}>
|
||||
{pageIcon && !onGoBack && (
|
||||
<div className={styles.pageIcon}>
|
||||
<Icon name={pageIcon} size="lg" />
|
||||
<Icon name={pageIcon} size="lg" aria-hidden />
|
||||
</div>
|
||||
)}
|
||||
{onGoBack && (
|
||||
@ -72,17 +63,24 @@ export const PageToolbar: FC<Props> = React.memo(
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{parent && onClickParent && (
|
||||
<button onClick={onClickParent} className={cx(styles.titleText, styles.parentLink)}>
|
||||
{parent} <span className={styles.parentIcon}>/</span>
|
||||
</button>
|
||||
{parent && parentHref && (
|
||||
<>
|
||||
<Link className={cx(styles.titleText, styles.parentLink, styles.titleLink)} href={parentHref}>
|
||||
{parent} <span className={styles.parentIcon}></span>
|
||||
</Link>
|
||||
{titleHref && (
|
||||
<span className={cx(styles.titleText, styles.titleDivider, styles.parentLink)} aria-hidden>
|
||||
/
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{onClickTitle && (
|
||||
<button onClick={onClickTitle} className={styles.titleText}>
|
||||
{titleHref && (
|
||||
<Link className={cx(styles.titleText, styles.titleLink)} href={titleHref}>
|
||||
{title}
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
{!onClickTitle && <div className={styles.titleText}>{title}</div>}
|
||||
{!titleHref && <div className={styles.titleText}>{title}</div>}
|
||||
{leftItems?.map((child, index) => (
|
||||
<div className={styles.leftActionItem} key={index}>
|
||||
{child}
|
||||
@ -109,21 +107,18 @@ PageToolbar.displayName = 'PageToolbar';
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const { spacing, typography } = theme;
|
||||
|
||||
const titleStyles = `
|
||||
font-size: ${typography.size.lg};
|
||||
padding: ${spacing(0.5, 1, 0.5, 1)};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 240px;
|
||||
const focusStyle = getFocusStyles(theme);
|
||||
const titleStyles = css`
|
||||
font-size: ${typography.size.lg};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 240px;
|
||||
border-radius: 2px;
|
||||
|
||||
// clear default button styles
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
@media ${styleMixins.mediaUp(theme.v1.breakpoints.xl)} {
|
||||
max-width: unset;
|
||||
}
|
||||
@media ${styleMixins.mediaUp(theme.v1.breakpoints.xl)} {
|
||||
max-width: unset;
|
||||
}
|
||||
`;
|
||||
|
||||
return {
|
||||
@ -142,6 +137,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
display: none;
|
||||
@media ${styleMixins.mediaUp(theme.v1.breakpoints.md)} {
|
||||
display: flex;
|
||||
padding-right: ${theme.spacing(1)};
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
@ -154,12 +150,17 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
parentIcon: css`
|
||||
margin-left: ${theme.spacing(0.5)};
|
||||
`,
|
||||
titleText: css`
|
||||
${titleStyles};
|
||||
titleText: titleStyles,
|
||||
titleLink: css`
|
||||
&:focus-visible {
|
||||
${focusStyle}
|
||||
}
|
||||
`,
|
||||
titleDivider: css`
|
||||
padding: ${spacing(0, 0.5, 0, 0.5)};
|
||||
`,
|
||||
parentLink: css`
|
||||
display: none;
|
||||
padding-right: 0;
|
||||
@media ${styleMixins.mediaUp(theme.v1.breakpoints.md)} {
|
||||
display: unset;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
||||
import { DashNavButton } from './DashNavButton';
|
||||
import { DashNavTimeControls } from './DashNavTimeControls';
|
||||
import { ButtonGroup, ModalsController, ToolbarButton, PageToolbar } from '@grafana/ui';
|
||||
import { textUtil } from '@grafana/data';
|
||||
import { locationUtil, textUtil } from '@grafana/data';
|
||||
// State
|
||||
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
|
||||
// Types
|
||||
@ -57,10 +57,6 @@ class DashNav extends PureComponent<Props> {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onFolderNameClick = () => {
|
||||
locationService.partial({ search: 'open', folder: 'current' });
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
locationService.partial({ viewPanel: null });
|
||||
};
|
||||
@ -96,10 +92,6 @@ class DashNav extends PureComponent<Props> {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onDashboardNameClick = () => {
|
||||
locationService.partial({ search: 'open' });
|
||||
};
|
||||
|
||||
addCustomContent(actions: DashNavButtonModel[], buttons: ReactNode[]) {
|
||||
actions.map((action, index) => {
|
||||
const Component = action.component;
|
||||
@ -250,13 +242,16 @@ class DashNav extends PureComponent<Props> {
|
||||
const { isFullscreen, title, folderTitle } = this.props;
|
||||
const onGoBack = isFullscreen ? this.onClose : undefined;
|
||||
|
||||
const titleHref = locationUtil.updateSearchParams(window.location.href, '?search=open');
|
||||
const parentHref = locationUtil.updateSearchParams(window.location.href, '?search=open&folder=current');
|
||||
|
||||
return (
|
||||
<PageToolbar
|
||||
pageIcon={isFullscreen ? undefined : 'apps'}
|
||||
title={title}
|
||||
parent={folderTitle}
|
||||
onClickTitle={this.onDashboardNameClick}
|
||||
onClickParent={this.onFolderNameClick}
|
||||
titleHref={titleHref}
|
||||
parentHref={parentHref}
|
||||
onGoBack={onGoBack}
|
||||
leftItems={this.renderLeftActionsButton()}
|
||||
>
|
||||
|
Loading…
Reference in New Issue
Block a user