mirror of https://github.com/grafana/grafana
Prometheus: Create AutoSizeInput with dynamic width (#45601)
* Autosize input for dynamic width * Update * Refactoring to use measureText util instead * removed react fragment tags * Add tests * Use AutoSize input in legend, step and nested queries vector matcher * Update * Remove unused imports Co-authored-by: Torkel Ödegaard <torkel@grafana.com>pull/45964/head
parent
9b6552c7b4
commit
4ab191b612
@ -0,0 +1,51 @@ |
||||
import React from 'react'; |
||||
import { screen, render, fireEvent } from '@testing-library/react'; |
||||
import { AutoSizeInput } from './AutoSizeInput'; |
||||
|
||||
jest.mock('@grafana/ui', () => { |
||||
const original = jest.requireActual('@grafana/ui'); |
||||
const mockedUi = { ...original }; |
||||
|
||||
// Mocking measureText
|
||||
mockedUi.measureText = (text: string, fontSize: number) => { |
||||
return { width: text.length * fontSize }; |
||||
}; |
||||
|
||||
return mockedUi; |
||||
}); |
||||
|
||||
describe('AutoSizeInput', () => { |
||||
it('should have default minWidth when empty', () => { |
||||
render(<AutoSizeInput />); |
||||
|
||||
const input: HTMLInputElement = screen.getByTestId('autosize-input'); |
||||
const inputWrapper: HTMLDivElement = screen.getByTestId('input-wrapper'); |
||||
|
||||
fireEvent.change(input, { target: { value: '' } }); |
||||
|
||||
expect(input.value).toBe(''); |
||||
expect(getComputedStyle(inputWrapper).width).toBe('80px'); |
||||
}); |
||||
|
||||
it('should have default minWidth for short content', () => { |
||||
render(<AutoSizeInput />); |
||||
|
||||
const input: HTMLInputElement = screen.getByTestId('autosize-input'); |
||||
const inputWrapper: HTMLDivElement = screen.getByTestId('input-wrapper'); |
||||
|
||||
fireEvent.change(input, { target: { value: 'foo' } }); |
||||
|
||||
expect(input.value).toBe('foo'); |
||||
expect(getComputedStyle(inputWrapper).width).toBe('80px'); |
||||
}); |
||||
|
||||
it('should change width for long content', () => { |
||||
render(<AutoSizeInput />); |
||||
|
||||
const input: HTMLInputElement = screen.getByTestId('autosize-input'); |
||||
const inputWrapper: HTMLDivElement = screen.getByTestId('input-wrapper'); |
||||
|
||||
fireEvent.change(input, { target: { value: 'very very long value' } }); |
||||
expect(getComputedStyle(inputWrapper).width).toBe('304px'); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,71 @@ |
||||
import { Input, measureText } from '@grafana/ui'; |
||||
import { Props as InputProps } from '@grafana/ui/src/components/Input/Input'; |
||||
import React, { useEffect } from 'react'; |
||||
export interface Props extends InputProps { |
||||
/** Sets the min-width to a multiple of 8px. Default value is 10*/ |
||||
minWidth?: number; |
||||
/** Sets the max-width to a multiple of 8px.*/ |
||||
maxWidth?: number; |
||||
/** onChange function that will be run on onBlur and onKeyPress with enter*/ |
||||
onCommitChange?: (event: React.FormEvent<HTMLInputElement>) => void; |
||||
} |
||||
|
||||
export const AutoSizeInput = React.forwardRef<HTMLInputElement, Props>((props, ref) => { |
||||
const { defaultValue = '', minWidth = 10, maxWidth, onCommitChange, onKeyDown, onBlur, ...restProps } = props; |
||||
const [value, setValue] = React.useState(defaultValue); |
||||
const [inputWidth, setInputWidth] = React.useState(minWidth); |
||||
|
||||
useEffect(() => { |
||||
setInputWidth(getWidthFor(value.toString(), minWidth, maxWidth)); |
||||
}, [value, maxWidth, minWidth]); |
||||
|
||||
return ( |
||||
<Input |
||||
{...restProps} |
||||
ref={ref} |
||||
value={value.toString()} |
||||
onChange={(event) => { |
||||
setValue(event.currentTarget.value); |
||||
}} |
||||
width={inputWidth} |
||||
onBlur={(event) => { |
||||
if (onCommitChange) { |
||||
onCommitChange(event); |
||||
} |
||||
if (onBlur) { |
||||
onBlur(event); |
||||
} |
||||
}} |
||||
onKeyDown={(event) => { |
||||
if (event.key === 'Enter' && onCommitChange) { |
||||
onCommitChange(event); |
||||
} |
||||
if (onKeyDown) { |
||||
onKeyDown(event); |
||||
} |
||||
}} |
||||
data-testid={'autosize-input'} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
function getWidthFor(value: string, minWidth: number, maxWidth: number | undefined): number { |
||||
if (!value) { |
||||
return minWidth; |
||||
} |
||||
|
||||
const extraSpace = 3; |
||||
const realWidth = measureText(value.toString(), 14).width / 8 + extraSpace; |
||||
|
||||
if (minWidth && realWidth < minWidth) { |
||||
return minWidth; |
||||
} |
||||
|
||||
if (maxWidth && realWidth > maxWidth) { |
||||
return realWidth; |
||||
} |
||||
|
||||
return realWidth; |
||||
} |
||||
|
||||
AutoSizeInput.displayName = 'AutoSizeInput'; |
||||
Loading…
Reference in new issue