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) => ({
|
||||
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),
|
||||
|
@ -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;
|
||||
`,
|
||||
});
|
||||
|
@ -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%;
|
||||
`,
|
||||
});
|
||||
|
@ -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%;
|
||||
}
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user