AutoSizeInput: Move to @grafana/ui (#48811)

* AutoSizeInput: Move to @grafana/ui

* Update packages/grafana-ui/src/components/AutoSizeInput/AutoSizeInput.mdx

Co-authored-by: JitaC <70489351+achatterjee-grafana@users.noreply.github.com>

* Update packages/grafana-ui/src/components/AutoSizeInput/AutoSizeInput.mdx

Co-authored-by: JitaC <70489351+achatterjee-grafana@users.noreply.github.com>

* Update packages/grafana-ui/src/components/AutoSizeInput/AutoSizeInput.mdx

Co-authored-by: JitaC <70489351+achatterjee-grafana@users.noreply.github.com>

* Fix linter error

* Update packages/grafana-ui/src/components/AutoSizeInput/AutoSizeInput.mdx

Co-authored-by: JitaC <70489351+achatterjee-grafana@users.noreply.github.com>

* Update packages/grafana-ui/src/components/AutoSizeInput/AutoSizeInput.mdx

Co-authored-by: JitaC <70489351+achatterjee-grafana@users.noreply.github.com>

* Move AutoSizeInput to Input folder

* Use iconOptions in storybook

Co-authored-by: JitaC <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:
Ivana Huckova
2022-05-30 10:46:33 +02:00
committed by GitHub
co-authored by JitaC
parent 47a3ddd968
commit 5f6b23e45a
11 changed files with 151 additions and 20 deletions
@@ -0,0 +1,37 @@
import { Props, Preview } from '@storybook/addon-docs/blocks';
import { AutoSizeInput } from './AutoSizeInput';
import { Field } from '../Forms/Field';
import { Icon } from '../Icon/Icon';
# AutoSizeInput
You can use it or regular text input. When used, AutoSizeInput resizes itself to the current content. For an array of data or tree-structured data, consider using `Select` or `Cascader` respectively.
## Prefix and suffix
To add more context to the input, you can add either text or an icon before or after the input using the `prefix` and `suffix`. Here are some examples for you to try in the canvas!
```jsx
<AutoSizeInput prefix={<Icon name="search" />} />
```
<Preview>
<AutoSizeInput prefix={<Icon name="search" />} />
</Preview>
## Usage in forms with Field
Use `AutoSizeInput`with the`Field`component to get labels and descriptions. Also, you can use`AutoSizeInput`with the`required`attribute for validation. See the`Field` component for more information.
```jsx
<Field label="Important information" description="This information is very important, so you really need to fill it in">
<AutoSizeInput name="importantInput" required />
</Field>
```
<Preview>
<Field label="Important information!" description="Please enter the relevant information.">
<AutoSizeInput name="importantInput" required />
</Field>
</Preview>
<Props of={AutoSizeInput} />
@@ -0,0 +1,100 @@
import { Story, Meta } from '@storybook/react';
import React from 'react';
import { Icon, Button, AutoSizeInput } from '@grafana/ui';
import { IconName } from '../../types';
import { iconOptions } from '../../utils/storybook/knobs';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import mdx from './AutoSizeInput.mdx';
const icons: { [key: string]: string | undefined } = { ...iconOptions };
Object.keys(icons).forEach((key) => {
icons[key] = `icon-${icons[key]}`;
});
const prefixSuffixOpts = {
Text: '$',
...icons,
};
export default {
title: 'Forms/Input/AutoSizeInput',
component: AutoSizeInput,
decorators: [withCenteredStory],
parameters: {
docs: {
page: mdx,
},
controls: {
exclude: ['prefix', 'suffix', 'addonBefore', 'addonAfter'],
},
},
args: {
type: 'text',
width: 40,
prefixVisible: '',
suffixVisible: '',
invalid: false,
loading: false,
},
argTypes: {
prefixVisible: {
control: {
type: 'select',
options: prefixSuffixOpts,
},
},
suffixVisible: {
control: {
type: 'select',
options: prefixSuffixOpts,
},
},
type: {
control: {
type: 'select',
options: ['text', 'number', 'password'],
},
},
minWidth: { control: { type: 'range', min: 10, max: 200, step: 10 } },
},
} as Meta;
export const Simple: Story = (args) => {
const addonAfter = <Button variant="secondary">Load</Button>;
const addonBefore = <div style={{ display: 'flex', alignItems: 'center', padding: '5px' }}>AutoSizeInput</div>;
const prefix = args.prefixVisible;
const suffix = args.suffixVisible;
let prefixEl: any = prefix;
if (prefix && prefix.match(/icon-/g)) {
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconName} />;
}
let suffixEl: any = suffix;
if (suffix && suffix.match(/icon-/g)) {
suffixEl = <Icon name={suffix.replace(/icon-/g, '') as IconName} />;
}
return (
<AutoSizeInput
disabled={args.disabled}
prefix={prefixEl}
invalid={args.invalid}
width={args.width}
suffix={suffixEl}
loading={args.loading}
addonBefore={args.before && addonBefore}
addonAfter={args.after && addonAfter}
type={args.type}
placeholder={args.placeholder}
minWidth={args.minWidth}
/>
);
};
Simple.args = {
disabled: false,
before: false,
after: false,
placeholder: 'Enter your name here...',
};
@@ -0,0 +1,49 @@
import { screen, render, fireEvent } from '@testing-library/react';
import React from 'react';
import { AutoSizeInput } from './AutoSizeInput';
jest.mock('../../utils/measureText', () => {
// Mocking measureText
const measureText = (text: string, fontSize: number) => {
return { width: text.length * fontSize };
};
return { measureText };
});
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,74 @@
import React, { useEffect } from 'react';
import { measureText } from '../../utils/measureText';
import { Input, Props as InputProps } from './Input';
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';
@@ -218,6 +218,7 @@ export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
export { RadioButtonList } from './Forms/RadioButtonList/RadioButtonList';
export { Input, getInputStyles } from './Input/Input';
export { AutoSizeInput } from './Input/AutoSizeInput';
export { FilterInput } from './FilterInput/FilterInput';
export { FormInputSize } from './Forms/types';