A11y: Make Annotations and Template Variables list and edit pages responsive (#71791)

This commit is contained in:
Juan Cabanas 2023-07-28 10:09:31 -03:00 committed by GitHub
parent c4731efb62
commit 66cea5aac6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 87 deletions

View File

@ -27,7 +27,6 @@ export const CallToActionCard = ({ message, callToActionElement, footer, classNa
const getStyles = (theme: GrafanaTheme2) => ({
wrapper: css({
label: 'call-to-action-card',
padding: theme.spacing(3),
background: theme.colors.background.secondary,
borderRadius: theme.shape.radius.default,
display: 'flex',
@ -35,6 +34,10 @@ const getStyles = (theme: GrafanaTheme2) => ({
alignItems: 'center',
justifyContent: 'center',
flexGrow: 1,
padding: theme.spacing(3),
[theme.breakpoints.down('sm')]: {
padding: theme.spacing(3, 1),
},
}),
message: css({
marginBottom: theme.spacing(3),

View File

@ -1,8 +1,9 @@
import { css } from '@emotion/css';
import React, { useState } from 'react';
import { arrayUtils, AnnotationQuery } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { Button, DeleteButton, IconButton, VerticalGroup } from '@grafana/ui';
import { Button, DeleteButton, IconButton, useStyles2, VerticalGroup } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { DashboardModel } from '../../state/DashboardModel';
@ -15,6 +16,7 @@ type Props = {
};
export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
const styles = useStyles2(getStyles);
const [annotations, updateAnnotations] = useState(dashboard.annotations.list);
const onMove = (idx: number, direction: number) => {
@ -53,54 +55,56 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
return (
<VerticalGroup>
{annotations.length > 0 && (
<table role="grid" className="filter-table filter-table--hover">
<thead>
<tr>
<th>Query name</th>
<th>Data source</th>
<th colSpan={3}></th>
</tr>
</thead>
<tbody>
{dashboard.annotations.list.map((annotation, idx) => (
<tr key={`${annotation.name}-${idx}`}>
{annotation.builtIn ? (
<td role="gridcell" style={{ width: '90%' }} className="pointer" onClick={() => onEdit(idx)}>
<Button size="sm" fill="text" variant="secondary">
{getAnnotationName(annotation)}
</Button>
</td>
) : (
<td role="gridcell" className="pointer" onClick={() => onEdit(idx)}>
<Button size="sm" fill="text" variant="secondary">
{getAnnotationName(annotation)}
</Button>
</td>
)}
<td role="gridcell" className="pointer" onClick={() => onEdit(idx)}>
{dataSourceSrv.getInstanceSettings(annotation.datasource)?.name || annotation.datasource?.uid}
</td>
<td role="gridcell" style={{ width: '1%' }}>
{idx !== 0 && <IconButton name="arrow-up" onClick={() => onMove(idx, -1)} tooltip="Move up" />}
</td>
<td role="gridcell" style={{ width: '1%' }}>
{dashboard.annotations.list.length > 1 && idx !== dashboard.annotations.list.length - 1 ? (
<IconButton name="arrow-down" onClick={() => onMove(idx, 1)} tooltip="Move down" />
) : null}
</td>
<td role="gridcell" style={{ width: '1%' }}>
{!annotation.builtIn && (
<DeleteButton
size="sm"
onConfirm={() => onDelete(idx)}
aria-label={`Delete query with title "${annotation.name}"`}
/>
)}
</td>
<div className={styles.table}>
<table role="grid" className="filter-table filter-table--hover">
<thead>
<tr>
<th>Query name</th>
<th>Data source</th>
<th colSpan={3}></th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{dashboard.annotations.list.map((annotation, idx) => (
<tr key={`${annotation.name}-${idx}`}>
{annotation.builtIn ? (
<td role="gridcell" style={{ width: '90%' }} className="pointer" onClick={() => onEdit(idx)}>
<Button size="sm" fill="text" variant="secondary">
{getAnnotationName(annotation)}
</Button>
</td>
) : (
<td role="gridcell" className="pointer" onClick={() => onEdit(idx)}>
<Button size="sm" fill="text" variant="secondary">
{getAnnotationName(annotation)}
</Button>
</td>
)}
<td role="gridcell" className="pointer" onClick={() => onEdit(idx)}>
{dataSourceSrv.getInstanceSettings(annotation.datasource)?.name || annotation.datasource?.uid}
</td>
<td role="gridcell" style={{ width: '1%' }}>
{idx !== 0 && <IconButton name="arrow-up" onClick={() => onMove(idx, -1)} tooltip="Move up" />}
</td>
<td role="gridcell" style={{ width: '1%' }}>
{dashboard.annotations.list.length > 1 && idx !== dashboard.annotations.list.length - 1 ? (
<IconButton name="arrow-down" onClick={() => onMove(idx, 1)} tooltip="Move down" />
) : null}
</td>
<td role="gridcell" style={{ width: '1%' }}>
{!annotation.builtIn && (
<DeleteButton
size="sm"
onConfirm={() => onDelete(idx)}
aria-label={`Delete query with title "${annotation.name}"`}
/>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{showEmptyListCTA && (
<EmptyListCTA
@ -127,3 +131,10 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
</VerticalGroup>
);
};
const getStyles = () => ({
table: css`
width: 100%;
overflow-x: scroll;
`,
});

View File

@ -1,10 +1,11 @@
import { css } from '@emotion/css';
import React, { ReactElement } from 'react';
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
import { selectors } from '@grafana/e2e-selectors';
import { Stack } from '@grafana/experimental';
import { reportInteraction } from '@grafana/runtime';
import { Button } from '@grafana/ui';
import { Button, useStyles2 } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { VariablesDependenciesButton } from '../inspect/VariablesDependenciesButton';
@ -35,6 +36,7 @@ export function VariableEditorList({
onDelete,
onDuplicate,
}: Props): ReactElement {
const styles = useStyles2(getStyles);
const onDragEnd = (result: DropResult) => {
if (!result.destination || !result.source) {
return;
@ -51,40 +53,42 @@ export function VariableEditorList({
{variables.length > 0 && (
<Stack direction="column" gap={4}>
<table
className="filter-table filter-table--hover"
aria-label={selectors.pages.Dashboard.Settings.Variables.List.table}
role="grid"
>
<thead>
<tr>
<th>Variable</th>
<th>Definition</th>
<th colSpan={5} />
</tr>
</thead>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="variables-list" direction="vertical">
{(provided) => (
<tbody ref={provided.innerRef} {...provided.droppableProps}>
{variables.map((variable, index) => (
<VariableEditorListRow
index={index}
key={`${variable.name}-${index}`}
variable={variable}
usageTree={usages}
usagesNetwork={usagesNetwork}
onDelete={onDelete}
onDuplicate={onDuplicate}
onEdit={onEdit}
/>
))}
{provided.placeholder}
</tbody>
)}
</Droppable>
</DragDropContext>
</table>
<div className={styles.tableContainer}>
<table
className="filter-table filter-table--hover"
aria-label={selectors.pages.Dashboard.Settings.Variables.List.table}
role="grid"
>
<thead>
<tr>
<th>Variable</th>
<th>Definition</th>
<th colSpan={5} />
</tr>
</thead>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="variables-list" direction="vertical">
{(provided) => (
<tbody ref={provided.innerRef} {...provided.droppableProps}>
{variables.map((variable, index) => (
<VariableEditorListRow
index={index}
key={`${variable.name}-${index}`}
variable={variable}
usageTree={usages}
usagesNetwork={usagesNetwork}
onDelete={onDelete}
onDuplicate={onDuplicate}
onEdit={onEdit}
/>
))}
{provided.placeholder}
</tbody>
)}
</Droppable>
</DragDropContext>
</table>
</div>
<Stack>
<VariablesDependenciesButton variables={variables} />
<Button
@ -130,3 +134,10 @@ function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement {
</div>
);
}
const getStyles = () => ({
tableContainer: css`
overflow: scroll;
width: 100%;
`,
});

View File

@ -61,6 +61,10 @@ export function getStyles(theme: GrafanaTheme2) {
overflow: auto;
padding: ${theme.spacing(0.75, 1)};
width: inherit;
${theme.breakpoints.down('sm')} {
width: 100%;
}
`,
};
}

View File

@ -1,7 +1,8 @@
import React, { PropsWithChildren, useMemo } from 'react';
import React, { PropsWithChildren, useMemo, useState } from 'react';
import { VariableRefresh } from '@grafana/data';
import { Field, RadioButtonGroup } from '@grafana/ui';
import { Field, RadioButtonGroup, useTheme2 } from '@grafana/ui';
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
interface Props {
onChange: (option: VariableRefresh) => void;
@ -14,6 +15,16 @@ const REFRESH_OPTIONS = [
];
export function QueryVariableRefreshSelect({ onChange, refresh }: PropsWithChildren<Props>) {
const theme = useTheme2();
const [isSmallScreen, setIsSmallScreen] = useState(false);
useMediaQueryChange({
breakpoint: theme.breakpoints.values.sm,
onChange: (e) => {
setIsSmallScreen(!e.matches);
},
});
const value = useMemo(
() => REFRESH_OPTIONS.find((o) => o.value === refresh)?.value ?? REFRESH_OPTIONS[0].value,
[refresh]
@ -21,7 +32,12 @@ export function QueryVariableRefreshSelect({ onChange, refresh }: PropsWithChild
return (
<Field label="Refresh" description="When to update the values of this variable">
<RadioButtonGroup options={REFRESH_OPTIONS} onChange={onChange} value={value} />
<RadioButtonGroup
options={REFRESH_OPTIONS}
onChange={onChange}
value={value}
size={isSmallScreen ? 'sm' : 'md'}
/>
</Field>
);
}