mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
A11y: Make Annotations and Template Variables list and edit pages responsive (#71791)
This commit is contained in:
parent
c4731efb62
commit
66cea5aac6
@ -27,7 +27,6 @@ export const CallToActionCard = ({ message, callToActionElement, footer, classNa
|
|||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
wrapper: css({
|
wrapper: css({
|
||||||
label: 'call-to-action-card',
|
label: 'call-to-action-card',
|
||||||
padding: theme.spacing(3),
|
|
||||||
background: theme.colors.background.secondary,
|
background: theme.colors.background.secondary,
|
||||||
borderRadius: theme.shape.radius.default,
|
borderRadius: theme.shape.radius.default,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -35,6 +34,10 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
padding: theme.spacing(3, 1),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
message: css({
|
message: css({
|
||||||
marginBottom: theme.spacing(3),
|
marginBottom: theme.spacing(3),
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { arrayUtils, AnnotationQuery } from '@grafana/data';
|
import { arrayUtils, AnnotationQuery } from '@grafana/data';
|
||||||
import { getDataSourceSrv } from '@grafana/runtime';
|
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 EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
|
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
@ -15,6 +16,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
|
export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
const [annotations, updateAnnotations] = useState(dashboard.annotations.list);
|
const [annotations, updateAnnotations] = useState(dashboard.annotations.list);
|
||||||
|
|
||||||
const onMove = (idx: number, direction: number) => {
|
const onMove = (idx: number, direction: number) => {
|
||||||
@ -53,54 +55,56 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<VerticalGroup>
|
<VerticalGroup>
|
||||||
{annotations.length > 0 && (
|
{annotations.length > 0 && (
|
||||||
<table role="grid" className="filter-table filter-table--hover">
|
<div className={styles.table}>
|
||||||
<thead>
|
<table role="grid" className="filter-table filter-table--hover">
|
||||||
<tr>
|
<thead>
|
||||||
<th>Query name</th>
|
<tr>
|
||||||
<th>Data source</th>
|
<th>Query name</th>
|
||||||
<th colSpan={3}></th>
|
<th>Data source</th>
|
||||||
</tr>
|
<th colSpan={3}></th>
|
||||||
</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>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{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 && (
|
{showEmptyListCTA && (
|
||||||
<EmptyListCTA
|
<EmptyListCTA
|
||||||
@ -127,3 +131,10 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => {
|
|||||||
</VerticalGroup>
|
</VerticalGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStyles = () => ({
|
||||||
|
table: css`
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: scroll;
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Stack } from '@grafana/experimental';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
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 EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
|
|
||||||
import { VariablesDependenciesButton } from '../inspect/VariablesDependenciesButton';
|
import { VariablesDependenciesButton } from '../inspect/VariablesDependenciesButton';
|
||||||
@ -35,6 +36,7 @@ export function VariableEditorList({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onDuplicate,
|
onDuplicate,
|
||||||
}: Props): ReactElement {
|
}: Props): ReactElement {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
const onDragEnd = (result: DropResult) => {
|
const onDragEnd = (result: DropResult) => {
|
||||||
if (!result.destination || !result.source) {
|
if (!result.destination || !result.source) {
|
||||||
return;
|
return;
|
||||||
@ -51,40 +53,42 @@ export function VariableEditorList({
|
|||||||
|
|
||||||
{variables.length > 0 && (
|
{variables.length > 0 && (
|
||||||
<Stack direction="column" gap={4}>
|
<Stack direction="column" gap={4}>
|
||||||
<table
|
<div className={styles.tableContainer}>
|
||||||
className="filter-table filter-table--hover"
|
<table
|
||||||
aria-label={selectors.pages.Dashboard.Settings.Variables.List.table}
|
className="filter-table filter-table--hover"
|
||||||
role="grid"
|
aria-label={selectors.pages.Dashboard.Settings.Variables.List.table}
|
||||||
>
|
role="grid"
|
||||||
<thead>
|
>
|
||||||
<tr>
|
<thead>
|
||||||
<th>Variable</th>
|
<tr>
|
||||||
<th>Definition</th>
|
<th>Variable</th>
|
||||||
<th colSpan={5} />
|
<th>Definition</th>
|
||||||
</tr>
|
<th colSpan={5} />
|
||||||
</thead>
|
</tr>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
</thead>
|
||||||
<Droppable droppableId="variables-list" direction="vertical">
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
{(provided) => (
|
<Droppable droppableId="variables-list" direction="vertical">
|
||||||
<tbody ref={provided.innerRef} {...provided.droppableProps}>
|
{(provided) => (
|
||||||
{variables.map((variable, index) => (
|
<tbody ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
<VariableEditorListRow
|
{variables.map((variable, index) => (
|
||||||
index={index}
|
<VariableEditorListRow
|
||||||
key={`${variable.name}-${index}`}
|
index={index}
|
||||||
variable={variable}
|
key={`${variable.name}-${index}`}
|
||||||
usageTree={usages}
|
variable={variable}
|
||||||
usagesNetwork={usagesNetwork}
|
usageTree={usages}
|
||||||
onDelete={onDelete}
|
usagesNetwork={usagesNetwork}
|
||||||
onDuplicate={onDuplicate}
|
onDelete={onDelete}
|
||||||
onEdit={onEdit}
|
onDuplicate={onDuplicate}
|
||||||
/>
|
onEdit={onEdit}
|
||||||
))}
|
/>
|
||||||
{provided.placeholder}
|
))}
|
||||||
</tbody>
|
{provided.placeholder}
|
||||||
)}
|
</tbody>
|
||||||
</Droppable>
|
)}
|
||||||
</DragDropContext>
|
</Droppable>
|
||||||
</table>
|
</DragDropContext>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<Stack>
|
<Stack>
|
||||||
<VariablesDependenciesButton variables={variables} />
|
<VariablesDependenciesButton variables={variables} />
|
||||||
<Button
|
<Button
|
||||||
@ -130,3 +134,10 @@ function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStyles = () => ({
|
||||||
|
tableContainer: css`
|
||||||
|
overflow: scroll;
|
||||||
|
width: 100%;
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
@ -61,6 +61,10 @@ export function getStyles(theme: GrafanaTheme2) {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: ${theme.spacing(0.75, 1)};
|
padding: ${theme.spacing(0.75, 1)};
|
||||||
width: inherit;
|
width: inherit;
|
||||||
|
|
||||||
|
${theme.breakpoints.down('sm')} {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React, { PropsWithChildren, useMemo } from 'react';
|
import React, { PropsWithChildren, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { VariableRefresh } from '@grafana/data';
|
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 {
|
interface Props {
|
||||||
onChange: (option: VariableRefresh) => void;
|
onChange: (option: VariableRefresh) => void;
|
||||||
@ -14,6 +15,16 @@ const REFRESH_OPTIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function QueryVariableRefreshSelect({ onChange, refresh }: PropsWithChildren<Props>) {
|
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(
|
const value = useMemo(
|
||||||
() => REFRESH_OPTIONS.find((o) => o.value === refresh)?.value ?? REFRESH_OPTIONS[0].value,
|
() => REFRESH_OPTIONS.find((o) => o.value === refresh)?.value ?? REFRESH_OPTIONS[0].value,
|
||||||
[refresh]
|
[refresh]
|
||||||
@ -21,7 +32,12 @@ export function QueryVariableRefreshSelect({ onChange, refresh }: PropsWithChild
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Field label="Refresh" description="When to update the values of this variable">
|
<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>
|
</Field>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user