Value mappings: scroll values when many items exist (#40733)

This commit is contained in:
Ryan McKinley
2021-10-22 18:16:32 +02:00
committed by GitHub
parent 4680a8454f
commit 814a78ba46
5 changed files with 101 additions and 59 deletions
@@ -89,10 +89,25 @@ export function Modal(props: PropsWithChildren<Props>) {
);
}
function ModalButtonRow({ children }: { children: React.ReactNode }) {
function ModalButtonRow({ leftItems, children }: { leftItems?: React.ReactNode; children: React.ReactNode }) {
const theme = useTheme2();
const styles = getModalStyles(theme);
if (leftItems) {
return (
<div className={styles.modalButtonRow}>
<HorizontalGroup justify="space-between">
<HorizontalGroup justify="flex-start" spacing="md">
{leftItems}
</HorizontalGroup>
<HorizontalGroup justify="flex-end" spacing="md">
{children}
</HorizontalGroup>
</HorizontalGroup>
</div>
);
}
return (
<div className={styles.modalButtonRow}>
<HorizontalGroup justify="flex-end" spacing="md">
@@ -121,6 +121,7 @@ describe('When adding and updating range map', () => {
it('should add new range map', async () => {
const onChangeSpy = jest.fn();
setup(onChangeSpy, { value: [] });
screen.getAllByTestId('remove-value-mapping')[0].click();
fireEvent.click(screen.getByLabelText(selectors.components.ValuePicker.button('Add a new mapping')));
const selectComponent = await screen.findByLabelText(selectors.components.ValuePicker.select('Add a new mapping'));
@@ -148,10 +149,11 @@ describe('When adding and updating range map', () => {
});
});
describe('When adding and updating tegex map', () => {
describe('When adding and updating regex map', () => {
it('should add new regex map', async () => {
const onChangeSpy = jest.fn();
setup(onChangeSpy, { value: [] });
screen.getAllByTestId('remove-value-mapping')[0].click();
fireEvent.click(screen.getByLabelText(selectors.components.ValuePicker.button('Add a new mapping')));
const selectComponent = await screen.findByLabelText(selectors.components.ValuePicker.select('Add a new mapping'));
@@ -81,51 +81,65 @@ export function ValueMappingsEditorModal({ value, onChange, onClose }: Props) {
onClose();
};
// Start with an empty row
useEffect(() => {
if (!value?.length) {
onAddValueMapping({ value: MappingType.ValueToText });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<table className={styles.editTable}>
<thead>
<tr>
<th style={{ width: '1%' }}></th>
<th style={{ width: '40%', textAlign: 'left' }} colSpan={2}>
Condition
</th>
<th style={{ textAlign: 'left' }}>Display text</th>
<th style={{ width: '10%' }}>Color</th>
<th style={{ width: '1%' }}></th>
</tr>
</thead>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="sortable-field-mappings" direction="vertical">
{(provided) => (
<tbody ref={provided.innerRef} {...provided.droppableProps}>
{rows.map((row, index) => (
<ValueMappingEditRow
key={index.toString()}
mapping={row}
index={index}
onChange={onChangeMapping}
onRemove={onRemoveRow}
onDuplicate={onDuplicateMapping}
/>
))}
{provided.placeholder}
</tbody>
)}
</Droppable>
</DragDropContext>
</table>
<ValuePicker
label="Add a new mapping"
variant="secondary"
size="md"
icon="plus"
menuPlacement="auto"
isFullWidth={false}
options={mappingTypes}
onChange={onAddValueMapping}
/>
<Modal.ButtonRow>
<div className={styles.tableWrap}>
<table className={styles.editTable}>
<thead>
<tr>
<th style={{ width: '1%' }}></th>
<th style={{ width: '40%', textAlign: 'left' }} colSpan={2}>
Condition
</th>
<th style={{ textAlign: 'left' }}>Display text</th>
<th style={{ width: '10%' }}>Color</th>
<th style={{ width: '1%' }}></th>
</tr>
</thead>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="sortable-field-mappings" direction="vertical">
{(provided) => (
<tbody ref={provided.innerRef} {...provided.droppableProps}>
{rows.map((row, index) => (
<ValueMappingEditRow
key={index.toString()}
mapping={row}
index={index}
onChange={onChangeMapping}
onRemove={onRemoveRow}
onDuplicate={onDuplicateMapping}
/>
))}
{provided.placeholder}
</tbody>
)}
</Droppable>
</DragDropContext>
</table>
</div>
<Modal.ButtonRow
leftItems={
<ValuePicker
label="Add a new mapping"
variant="secondary"
size="md"
icon="plus"
menuPlacement="auto"
minWidth={40}
options={mappingTypes}
onChange={onAddValueMapping}
/>
}
>
<Button variant="secondary" fill="outline" onClick={onClose}>
Cancel
</Button>
@@ -138,6 +152,12 @@ export function ValueMappingsEditorModal({ value, onChange, onClose }: Props) {
}
export const getStyles = (theme: GrafanaTheme2) => ({
tableWrap: css`
max-height: calc(80vh - 170px);
min-height: 40px;
overflow: auto;
`,
editTable: css({
width: '100%',
marginBottom: theme.spacing(2),
@@ -3,9 +3,9 @@ import { IconName } from '../../types';
import { SelectableValue } from '@grafana/data';
import { Button, ButtonVariant } from '../Button';
import { Select } from '../Select/Select';
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
import { ComponentSize } from '../../types/size';
import { selectors } from '@grafana/e2e-selectors';
import { useTheme2 } from '../../themes';
export interface ValuePickerProps<T> {
/** Label to display on the picker button */
@@ -20,6 +20,8 @@ export interface ValuePickerProps<T> {
variant?: ButtonVariant;
/** Size of button */
size?: ComponentSize;
/** Min width for select in grid units */
minWidth?: number;
/** Should the picker cover the full width of its parent */
isFullWidth?: boolean;
/** Control where the menu is rendered */
@@ -32,29 +34,31 @@ export function ValuePicker<T>({
options,
onChange,
variant,
minWidth = 16,
size = 'sm',
isFullWidth = true,
menuPlacement,
}: ValuePickerProps<T>) {
const [isPicking, setIsPicking] = useState(false);
const theme = useTheme2();
const buttonEl = (
<Button
size={size || 'sm'}
icon={icon || 'plus'}
onClick={() => setIsPicking(true)}
variant={variant}
aria-label={selectors.components.ValuePicker.button(label)}
>
{label}
</Button>
);
return (
<>
{!isPicking && (isFullWidth ? <FullWidthButtonContainer>{buttonEl}</FullWidthButtonContainer> : buttonEl)}
{!isPicking && (
<Button
size={size || 'sm'}
icon={icon || 'plus'}
onClick={() => setIsPicking(true)}
variant={variant}
fullWidth={isFullWidth}
aria-label={selectors.components.ValuePicker.button(label)}
>
{label}
</Button>
)}
{isPicking && (
<span>
<span style={{ minWidth: theme.spacing(minWidth), flexGrow: isFullWidth ? 1 : undefined }}>
<Select
menuShouldPortal
placeholder={label}
@@ -260,5 +260,6 @@ function getBorderTopStyles(theme: GrafanaTheme2) {
return css({
borderTop: `1px solid ${theme.colors.border.weak}`,
padding: `${theme.spacing(2)}`,
display: 'flex',
});
}