mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Forms migration: Move Input folders (#23313)
* Remove exports * Move folders * Fix errors
This commit is contained in:
parent
56687a08f9
commit
f91c7a81ce
@ -4,7 +4,7 @@ import RCCascader from 'rc-cascader';
|
||||
|
||||
import { Select } from '../Select/Select';
|
||||
import { FormInputSize } from '../Forms/types';
|
||||
import { Input } from '../Forms/Input/Input';
|
||||
import { Input } from '../Input/Input';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
import { onChangeCascader } from './optionMappings';
|
||||
|
@ -3,7 +3,7 @@ import React, { useState } from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { ClipboardButton } from './ClipboardButton';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Input } from '../Forms/Legacy/Input/Input';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
const getKnobs = () => {
|
||||
|
@ -3,7 +3,7 @@ import tinycolor from 'tinycolor2';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import { ColorPickerProps } from './ColorPickerPopover';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Input } from '../Forms/Legacy/Input/Input';
|
||||
|
||||
interface ColorInputState {
|
||||
previousColor: string;
|
||||
|
@ -9,7 +9,7 @@ import { DataSourceSettings } from '@grafana/data';
|
||||
import { HttpSettingsProps } from './types';
|
||||
import { CustomHeadersSettings } from './CustomHeadersSettings';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Input } from '../Forms/Legacy/Input/Input';
|
||||
import { FormField } from '../FormField/FormField';
|
||||
import { FormLabel } from '../FormLabel/FormLabel';
|
||||
import { Switch } from '../Switch/Switch';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { boolean, number, text } from '@storybook/addon-knobs';
|
||||
import { Field } from './Field';
|
||||
import { Input } from './Input/Input';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Switch } from './Switch';
|
||||
import mdx from './Field.mdx';
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { Legend } from './Legend';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { withStoryContainer } from '../../utils/storybook/withStoryContainer';
|
||||
import { Field } from './Field';
|
||||
import { Input } from './Input/Input';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Button } from '../Button';
|
||||
import { Form } from './Form';
|
||||
import { Switch } from './Switch';
|
||||
|
@ -1,110 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { boolean, text, select, number } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../../utils/storybook/withCenteredStory';
|
||||
import { Input } from './Input';
|
||||
import { Button } from '../../Button';
|
||||
import mdx from './Input.mdx';
|
||||
import { getAvailableIcons, IconType } from '../../Icon/types';
|
||||
import { KeyValue } from '@grafana/data';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { Field } from '../Field';
|
||||
|
||||
export default {
|
||||
title: 'Forms/Input',
|
||||
component: Input,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const simple = () => {
|
||||
const prefixSuffixOpts = {
|
||||
None: null,
|
||||
Text: '$',
|
||||
...getAvailableIcons().reduce<KeyValue<string>>((prev, c) => {
|
||||
return {
|
||||
...prev,
|
||||
[`Icon: ${c}`]: `icon-${c}`,
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
|
||||
const BEHAVIOUR_GROUP = 'Behaviour props';
|
||||
// ---
|
||||
const type = select(
|
||||
'Type',
|
||||
{
|
||||
text: 'text',
|
||||
password: 'password',
|
||||
number: 'number',
|
||||
},
|
||||
'text',
|
||||
BEHAVIOUR_GROUP
|
||||
);
|
||||
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
|
||||
const invalid = boolean('Invalid', false, BEHAVIOUR_GROUP);
|
||||
const loading = boolean('Loading', false, BEHAVIOUR_GROUP);
|
||||
|
||||
const VISUAL_GROUP = 'Visual options';
|
||||
// ---
|
||||
const placeholder = text('Placeholder', 'Enter your name here...', VISUAL_GROUP);
|
||||
const before = boolean('Addon before', false, VISUAL_GROUP);
|
||||
const after = boolean('Addon after', false, VISUAL_GROUP);
|
||||
const addonAfter = <Button variant="secondary">Load</Button>;
|
||||
const addonBefore = <div style={{ display: 'flex', alignItems: 'center', padding: '5px' }}>Input</div>;
|
||||
const prefix = select('Prefix', prefixSuffixOpts, null, VISUAL_GROUP);
|
||||
const suffix = select('Suffix', prefixSuffixOpts, null, VISUAL_GROUP);
|
||||
let prefixEl: any = prefix;
|
||||
if (prefix && prefix.match(/icon-/g)) {
|
||||
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconType} />;
|
||||
}
|
||||
let suffixEl: any = suffix;
|
||||
if (suffix && suffix.match(/icon-/g)) {
|
||||
suffixEl = <Icon name={suffix.replace(/icon-/g, '') as IconType} />;
|
||||
}
|
||||
|
||||
const CONTAINER_GROUP = 'Container options';
|
||||
// ---
|
||||
const containerWidth = number(
|
||||
'Container width',
|
||||
300,
|
||||
{
|
||||
range: true,
|
||||
min: 100,
|
||||
max: 500,
|
||||
step: 10,
|
||||
},
|
||||
CONTAINER_GROUP
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ width: containerWidth }}>
|
||||
<Input
|
||||
disabled={disabled}
|
||||
invalid={invalid}
|
||||
prefix={prefixEl}
|
||||
suffix={suffixEl}
|
||||
loading={loading}
|
||||
addonBefore={before && addonBefore}
|
||||
addonAfter={after && addonAfter}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const withFieldValidation = () => {
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Field invalid={value === ''} error={value === '' ? 'This input is required' : ''}>
|
||||
<Input value={value} onChange={e => setValue(e.currentTarget.value)} />
|
||||
</Field>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,260 +0,0 @@
|
||||
import React, { HTMLProps, ReactNode } from 'react';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css, cx } from 'emotion';
|
||||
import { getFocusStyle, inputSizes, sharedInputStyle } from '../commonStyles';
|
||||
import { stylesFactory, useTheme } from '../../../themes';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { useClientRect } from '../../../utils/useClientRect';
|
||||
import { FormInputSize } from '../types';
|
||||
|
||||
export interface Props extends Omit<HTMLProps<HTMLInputElement>, 'prefix' | 'size'> {
|
||||
/** Show an invalid state around the input */
|
||||
invalid?: boolean;
|
||||
/** Show an icon as a prefix in the input */
|
||||
prefix?: JSX.Element | string | null;
|
||||
/** Show an icon as a suffix in the input */
|
||||
suffix?: JSX.Element | string | null;
|
||||
/** Show a loading indicator as a suffix in the input */
|
||||
loading?: boolean;
|
||||
/** Add a component as an addon before the input */
|
||||
addonBefore?: ReactNode;
|
||||
/** Add a component as an addon after the input */
|
||||
addonAfter?: ReactNode;
|
||||
size?: FormInputSize;
|
||||
}
|
||||
|
||||
interface StyleDeps {
|
||||
theme: GrafanaTheme;
|
||||
invalid: boolean;
|
||||
}
|
||||
|
||||
export const getInputStyles = stylesFactory(({ theme, invalid = false }: StyleDeps) => {
|
||||
const colors = theme.colors;
|
||||
const borderRadius = theme.border.radius.sm;
|
||||
const height = theme.spacing.formInputHeight;
|
||||
|
||||
const prefixSuffixStaticWidth = '28px';
|
||||
const prefixSuffix = css`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
font-size: ${theme.typography.size.md};
|
||||
height: 100%;
|
||||
/* Min width specified for prefix/suffix classes used outside React component*/
|
||||
min-width: ${prefixSuffixStaticWidth};
|
||||
`;
|
||||
|
||||
return {
|
||||
// Wraps inputWrapper and addons
|
||||
wrapper: cx(
|
||||
css`
|
||||
label: input-wrapper;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: ${height};
|
||||
border-radius: ${borderRadius};
|
||||
&:hover {
|
||||
> .prefix,
|
||||
.suffix,
|
||||
.input {
|
||||
border-color: ${invalid ? colors.redBase : colors.formInputBorder};
|
||||
}
|
||||
|
||||
// only show number buttons on hover
|
||||
input[type='number'] {
|
||||
-moz-appearance: number-input;
|
||||
-webkit-appearance: number-input;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
input[type='number']::-webkit-inner-spin-button,
|
||||
input[type='number']::-webkit-outer-spin-button {
|
||||
-webkit-appearance: inner-spin-button !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`
|
||||
),
|
||||
// Wraps input and prefix/suffix
|
||||
inputWrapper: css`
|
||||
label: input-inputWrapper;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
/* we want input to be above addons, especially for focused state */
|
||||
z-index: 1;
|
||||
|
||||
/* when input rendered with addon before only*/
|
||||
&:not(:first-child):last-child {
|
||||
> input {
|
||||
border-left: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* when input rendered with addon after only*/
|
||||
&:first-child:not(:last-child) {
|
||||
> input {
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* when rendered with addon before and after */
|
||||
&:not(:first-child):not(:last-child) {
|
||||
> input {
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
/* paddings specified for classes used outside React component */
|
||||
&:not(:first-child) {
|
||||
padding-left: ${prefixSuffixStaticWidth};
|
||||
}
|
||||
&:not(:last-child) {
|
||||
padding-right: ${prefixSuffixStaticWidth};
|
||||
}
|
||||
&[readonly] {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
input: cx(
|
||||
getFocusStyle(theme),
|
||||
sharedInputStyle(theme, invalid),
|
||||
css`
|
||||
label: input-input;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
flex-grow: 1;
|
||||
border-radius: ${borderRadius};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`
|
||||
),
|
||||
inputDisabled: css`
|
||||
background-color: ${colors.formInputBgDisabled};
|
||||
color: ${colors.formInputDisabledText};
|
||||
`,
|
||||
addon: css`
|
||||
label: input-addon;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
|
||||
&:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
> :last-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
> :first-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
> *:focus {
|
||||
/* we want anything that has focus and is an addon to be above input */
|
||||
z-index: 2;
|
||||
}
|
||||
`,
|
||||
prefix: cx(
|
||||
prefixSuffix,
|
||||
css`
|
||||
label: input-prefix;
|
||||
padding-left: ${theme.spacing.sm};
|
||||
padding-right: ${theme.spacing.xs};
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
`
|
||||
),
|
||||
suffix: cx(
|
||||
prefixSuffix,
|
||||
css`
|
||||
label: input-suffix;
|
||||
padding-right: ${theme.spacing.sm};
|
||||
padding-left: ${theme.spacing.xs};
|
||||
border-left: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
right: 0;
|
||||
`
|
||||
),
|
||||
loadingIndicator: css`
|
||||
& + * {
|
||||
margin-left: ${theme.spacing.xs};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
export const Input = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
|
||||
const { className, addonAfter, addonBefore, prefix, suffix, invalid, loading, size = 'auto', ...restProps } = props;
|
||||
/**
|
||||
* Prefix & suffix are positioned absolutely within inputWrapper. We use client rects below to apply correct padding to the input
|
||||
* when prefix/suffix is larger than default (28px = 16px(icon) + 12px(left/right paddings)).
|
||||
* Thanks to that prefix/suffix do not overflow the input element itself.
|
||||
*/
|
||||
const [prefixRect, prefixRef] = useClientRect<HTMLDivElement>();
|
||||
const [suffixRect, suffixRef] = useClientRect<HTMLDivElement>();
|
||||
|
||||
const theme = useTheme();
|
||||
const styles = getInputStyles({ theme, invalid: !!invalid });
|
||||
|
||||
return (
|
||||
<div className={cx(styles.wrapper, inputSizes()[size], className)}>
|
||||
{!!addonBefore && <div className={styles.addon}>{addonBefore}</div>}
|
||||
|
||||
<div className={styles.inputWrapper}>
|
||||
{prefix && (
|
||||
<div className={styles.prefix} ref={prefixRef}>
|
||||
{prefix}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={ref}
|
||||
className={styles.input}
|
||||
{...restProps}
|
||||
style={{
|
||||
paddingLeft: prefixRect ? prefixRect.width : undefined,
|
||||
paddingRight: suffixRect ? suffixRect.width : undefined,
|
||||
}}
|
||||
/>
|
||||
|
||||
{(suffix || loading) && (
|
||||
<div className={styles.suffix} ref={suffixRef}>
|
||||
{loading && <Icon name="spinner" className={cx('fa-spin', styles.loadingIndicator)} />}
|
||||
{suffix}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!!addonAfter && <div className={styles.addon}>{addonAfter}</div>}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Input.displayName = 'Input';
|
@ -0,0 +1,40 @@
|
||||
import React, { useState } from 'react';
|
||||
import { zip, fromPairs } from 'lodash';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { withCenteredStory } from '../../../../utils/storybook/withCenteredStory';
|
||||
import { Input } from './Input';
|
||||
import { text, select } from '@storybook/addon-knobs';
|
||||
import { EventsWithValidation } from '../../../../utils';
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
validation: text('Validation regex (will do a partial match if you do not anchor it)', ''),
|
||||
validationErrorMessage: text('Validation error message', 'Input not valid'),
|
||||
validationEvent: select(
|
||||
'Validation event',
|
||||
fromPairs(zip(Object.keys(EventsWithValidation), Object.values(EventsWithValidation))),
|
||||
EventsWithValidation.onBlur
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const Wrapper = () => {
|
||||
const { validation, validationErrorMessage, validationEvent } = getKnobs();
|
||||
const [value, setValue] = useState('');
|
||||
const validations = {
|
||||
[validationEvent]: [
|
||||
{
|
||||
rule: (value: string) => {
|
||||
return !!value.match(validation);
|
||||
},
|
||||
errorMessage: validationErrorMessage,
|
||||
},
|
||||
],
|
||||
};
|
||||
return <Input value={value} onChange={e => setValue(e.currentTarget.value)} validationEvents={validations} />;
|
||||
};
|
||||
|
||||
const story = storiesOf('General/Input', module);
|
||||
story.addDecorator(withCenteredStory);
|
||||
story.add('input', () => <Wrapper />);
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Input } from './Input';
|
||||
import { EventsWithValidation } from '../../utils';
|
||||
import { ValidationEvents } from '../../types';
|
||||
import { EventsWithValidation } from '../../../../utils';
|
||||
import { ValidationEvents } from '../../../../types';
|
||||
|
||||
const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars';
|
||||
const testBlurValidation: ValidationEvents = {
|
@ -0,0 +1,86 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { validate, EventsWithValidation, hasValidationEvent } from '../../../../utils';
|
||||
import { ValidationEvents, ValidationRule } from '../../../../types';
|
||||
|
||||
export enum LegacyInputStatus {
|
||||
Invalid = 'invalid',
|
||||
Valid = 'valid',
|
||||
}
|
||||
|
||||
interface Props extends React.HTMLProps<HTMLInputElement> {
|
||||
validationEvents?: ValidationEvents;
|
||||
hideErrorMessage?: boolean;
|
||||
inputRef?: React.LegacyRef<HTMLInputElement>;
|
||||
|
||||
// Override event props and append status as argument
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: LegacyInputStatus) => void;
|
||||
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: LegacyInputStatus) => void;
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>, status?: LegacyInputStatus) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export class Input extends PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
state: State = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
get status() {
|
||||
return this.state.error ? LegacyInputStatus.Invalid : LegacyInputStatus.Valid;
|
||||
}
|
||||
|
||||
get isInvalid() {
|
||||
return this.status === LegacyInputStatus.Invalid;
|
||||
}
|
||||
|
||||
validatorAsync = (validationRules: ValidationRule[]) => {
|
||||
return (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const errors = validate(evt.target.value, validationRules);
|
||||
this.setState(prevState => {
|
||||
return { ...prevState, error: errors ? errors[0] : null };
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
populateEventPropsWithStatus = (restProps: any, validationEvents: ValidationEvents | undefined) => {
|
||||
const inputElementProps = { ...restProps };
|
||||
if (!validationEvents) {
|
||||
return inputElementProps;
|
||||
}
|
||||
Object.keys(EventsWithValidation).forEach(eventName => {
|
||||
if (hasValidationEvent(eventName as EventsWithValidation, validationEvents) || restProps[eventName]) {
|
||||
inputElementProps[eventName] = async (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
|
||||
if (hasValidationEvent(eventName as EventsWithValidation, validationEvents)) {
|
||||
await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
|
||||
}
|
||||
if (restProps[eventName]) {
|
||||
restProps[eventName].apply(null, [evt, this.status]);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
return inputElementProps;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { validationEvents, className, hideErrorMessage, inputRef, ...restProps } = this.props;
|
||||
const { error } = this.state;
|
||||
const inputClassName = classNames('gf-form-input', { invalid: this.isInvalid }, className);
|
||||
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
|
||||
|
||||
return (
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<input {...inputElementProps} ref={inputRef} className={inputClassName} />
|
||||
{error && !hideErrorMessage && <span>{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import { getLegendStyles } from './Legend';
|
||||
import { getFieldValidationMessageStyles } from './FieldValidationMessage';
|
||||
import { getButtonStyles, ButtonVariant } from '../Button';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
import { getInputStyles } from './Input/Input';
|
||||
import { getInputStyles } from '../Input/Input';
|
||||
import { getSwitchStyles } from './Switch';
|
||||
import { getCheckboxStyles } from './Checkbox';
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Controller as InputControl } from 'react-hook-form';
|
||||
import { getFormStyles } from './getFormStyles';
|
||||
import { Label } from './Label';
|
||||
// To be removed
|
||||
import { Input } from './Input/Input';
|
||||
import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
|
||||
import { Form } from './Form';
|
||||
import { Field } from './Field';
|
||||
@ -16,8 +14,6 @@ const Forms = {
|
||||
Switch,
|
||||
getFormStyles,
|
||||
Label,
|
||||
// To be removed
|
||||
Input,
|
||||
Form,
|
||||
Field,
|
||||
InputControl,
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
|
||||
import { Forms } from '../index';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Field } from '../Forms/Field';
|
||||
import { Icon } from './Icon';
|
||||
import { getAvailableIcons, IconType } from './types';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
@ -79,13 +80,13 @@ export const simple = () => {
|
||||
width: 100%;
|
||||
`}
|
||||
>
|
||||
<Forms.Field
|
||||
<Field
|
||||
className={css`
|
||||
width: 300px;
|
||||
`}
|
||||
>
|
||||
<Forms.Input onChange={searchIcon} placeholder="Search icons by name" />
|
||||
</Forms.Field>
|
||||
<Input onChange={searchIcon} placeholder="Search icons by name" />
|
||||
</Field>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
|
@ -1,40 +1,110 @@
|
||||
import React, { useState } from 'react';
|
||||
import { zip, fromPairs } from 'lodash';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { boolean, text, select, number } from '@storybook/addon-knobs';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { Input } from './Input';
|
||||
import { text, select } from '@storybook/addon-knobs';
|
||||
import { EventsWithValidation } from '../../utils';
|
||||
import { Button } from '../Button';
|
||||
import mdx from './Input.mdx';
|
||||
import { getAvailableIcons, IconType } from '../Icon/types';
|
||||
import { KeyValue } from '@grafana/data';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Field } from '../Forms/Field';
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
validation: text('Validation regex (will do a partial match if you do not anchor it)', ''),
|
||||
validationErrorMessage: text('Validation error message', 'Input not valid'),
|
||||
validationEvent: select(
|
||||
'Validation event',
|
||||
fromPairs(zip(Object.keys(EventsWithValidation), Object.values(EventsWithValidation))),
|
||||
EventsWithValidation.onBlur
|
||||
),
|
||||
};
|
||||
export default {
|
||||
title: 'Forms/Input',
|
||||
component: Input,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Wrapper = () => {
|
||||
const { validation, validationErrorMessage, validationEvent } = getKnobs();
|
||||
export const simple = () => {
|
||||
const prefixSuffixOpts = {
|
||||
None: null,
|
||||
Text: '$',
|
||||
...getAvailableIcons().reduce<KeyValue<string>>((prev, c) => {
|
||||
return {
|
||||
...prev,
|
||||
[`Icon: ${c}`]: `icon-${c}`,
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
|
||||
const BEHAVIOUR_GROUP = 'Behaviour props';
|
||||
// ---
|
||||
const type = select(
|
||||
'Type',
|
||||
{
|
||||
text: 'text',
|
||||
password: 'password',
|
||||
number: 'number',
|
||||
},
|
||||
'text',
|
||||
BEHAVIOUR_GROUP
|
||||
);
|
||||
const disabled = boolean('Disabled', false, BEHAVIOUR_GROUP);
|
||||
const invalid = boolean('Invalid', false, BEHAVIOUR_GROUP);
|
||||
const loading = boolean('Loading', false, BEHAVIOUR_GROUP);
|
||||
|
||||
const VISUAL_GROUP = 'Visual options';
|
||||
// ---
|
||||
const placeholder = text('Placeholder', 'Enter your name here...', VISUAL_GROUP);
|
||||
const before = boolean('Addon before', false, VISUAL_GROUP);
|
||||
const after = boolean('Addon after', false, VISUAL_GROUP);
|
||||
const addonAfter = <Button variant="secondary">Load</Button>;
|
||||
const addonBefore = <div style={{ display: 'flex', alignItems: 'center', padding: '5px' }}>Input</div>;
|
||||
const prefix = select('Prefix', prefixSuffixOpts, null, VISUAL_GROUP);
|
||||
const suffix = select('Suffix', prefixSuffixOpts, null, VISUAL_GROUP);
|
||||
let prefixEl: any = prefix;
|
||||
if (prefix && prefix.match(/icon-/g)) {
|
||||
prefixEl = <Icon name={prefix.replace(/icon-/g, '') as IconType} />;
|
||||
}
|
||||
let suffixEl: any = suffix;
|
||||
if (suffix && suffix.match(/icon-/g)) {
|
||||
suffixEl = <Icon name={suffix.replace(/icon-/g, '') as IconType} />;
|
||||
}
|
||||
|
||||
const CONTAINER_GROUP = 'Container options';
|
||||
// ---
|
||||
const containerWidth = number(
|
||||
'Container width',
|
||||
300,
|
||||
{
|
||||
range: true,
|
||||
min: 100,
|
||||
max: 500,
|
||||
step: 10,
|
||||
},
|
||||
CONTAINER_GROUP
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ width: containerWidth }}>
|
||||
<Input
|
||||
disabled={disabled}
|
||||
invalid={invalid}
|
||||
prefix={prefixEl}
|
||||
suffix={suffixEl}
|
||||
loading={loading}
|
||||
addonBefore={before && addonBefore}
|
||||
addonAfter={after && addonAfter}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const withFieldValidation = () => {
|
||||
const [value, setValue] = useState('');
|
||||
const validations = {
|
||||
[validationEvent]: [
|
||||
{
|
||||
rule: (value: string) => {
|
||||
return !!value.match(validation);
|
||||
},
|
||||
errorMessage: validationErrorMessage,
|
||||
},
|
||||
],
|
||||
};
|
||||
return <Input value={value} onChange={e => setValue(e.currentTarget.value)} validationEvents={validations} />;
|
||||
};
|
||||
|
||||
const story = storiesOf('General/Input', module);
|
||||
story.addDecorator(withCenteredStory);
|
||||
story.add('input', () => <Wrapper />);
|
||||
return (
|
||||
<div>
|
||||
<Field invalid={value === ''} error={value === '' ? 'This input is required' : ''}>
|
||||
<Input value={value} onChange={e => setValue(e.currentTarget.value)} />
|
||||
</Field>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,86 +1,260 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { validate, EventsWithValidation, hasValidationEvent } from '../../utils';
|
||||
import { ValidationEvents, ValidationRule } from '../../types';
|
||||
import React, { HTMLProps, ReactNode } from 'react';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css, cx } from 'emotion';
|
||||
import { getFocusStyle, inputSizes, sharedInputStyle } from '../Forms/commonStyles';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { useClientRect } from '../../utils/useClientRect';
|
||||
import { FormInputSize } from '../Forms/types';
|
||||
|
||||
export enum LegacyInputStatus {
|
||||
Invalid = 'invalid',
|
||||
Valid = 'valid',
|
||||
export interface Props extends Omit<HTMLProps<HTMLInputElement>, 'prefix' | 'size'> {
|
||||
/** Show an invalid state around the input */
|
||||
invalid?: boolean;
|
||||
/** Show an icon as a prefix in the input */
|
||||
prefix?: JSX.Element | string | null;
|
||||
/** Show an icon as a suffix in the input */
|
||||
suffix?: JSX.Element | string | null;
|
||||
/** Show a loading indicator as a suffix in the input */
|
||||
loading?: boolean;
|
||||
/** Add a component as an addon before the input */
|
||||
addonBefore?: ReactNode;
|
||||
/** Add a component as an addon after the input */
|
||||
addonAfter?: ReactNode;
|
||||
size?: FormInputSize;
|
||||
}
|
||||
|
||||
interface Props extends React.HTMLProps<HTMLInputElement> {
|
||||
validationEvents?: ValidationEvents;
|
||||
hideErrorMessage?: boolean;
|
||||
inputRef?: React.LegacyRef<HTMLInputElement>;
|
||||
|
||||
// Override event props and append status as argument
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: LegacyInputStatus) => void;
|
||||
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: LegacyInputStatus) => void;
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>, status?: LegacyInputStatus) => void;
|
||||
interface StyleDeps {
|
||||
theme: GrafanaTheme;
|
||||
invalid: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
error: string | null;
|
||||
}
|
||||
export const getInputStyles = stylesFactory(({ theme, invalid = false }: StyleDeps) => {
|
||||
const colors = theme.colors;
|
||||
const borderRadius = theme.border.radius.sm;
|
||||
const height = theme.spacing.formInputHeight;
|
||||
|
||||
export class Input extends PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
const prefixSuffixStaticWidth = '28px';
|
||||
const prefixSuffix = css`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
font-size: ${theme.typography.size.md};
|
||||
height: 100%;
|
||||
/* Min width specified for prefix/suffix classes used outside React component*/
|
||||
min-width: ${prefixSuffixStaticWidth};
|
||||
`;
|
||||
|
||||
state: State = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
get status() {
|
||||
return this.state.error ? LegacyInputStatus.Invalid : LegacyInputStatus.Valid;
|
||||
}
|
||||
|
||||
get isInvalid() {
|
||||
return this.status === LegacyInputStatus.Invalid;
|
||||
}
|
||||
|
||||
validatorAsync = (validationRules: ValidationRule[]) => {
|
||||
return (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const errors = validate(evt.target.value, validationRules);
|
||||
this.setState(prevState => {
|
||||
return { ...prevState, error: errors ? errors[0] : null };
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
populateEventPropsWithStatus = (restProps: any, validationEvents: ValidationEvents | undefined) => {
|
||||
const inputElementProps = { ...restProps };
|
||||
if (!validationEvents) {
|
||||
return inputElementProps;
|
||||
}
|
||||
Object.keys(EventsWithValidation).forEach(eventName => {
|
||||
if (hasValidationEvent(eventName as EventsWithValidation, validationEvents) || restProps[eventName]) {
|
||||
inputElementProps[eventName] = async (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
|
||||
if (hasValidationEvent(eventName as EventsWithValidation, validationEvents)) {
|
||||
await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
|
||||
return {
|
||||
// Wraps inputWrapper and addons
|
||||
wrapper: cx(
|
||||
css`
|
||||
label: input-wrapper;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: ${height};
|
||||
border-radius: ${borderRadius};
|
||||
&:hover {
|
||||
> .prefix,
|
||||
.suffix,
|
||||
.input {
|
||||
border-color: ${invalid ? colors.redBase : colors.formInputBorder};
|
||||
}
|
||||
if (restProps[eventName]) {
|
||||
restProps[eventName].apply(null, [evt, this.status]);
|
||||
|
||||
// only show number buttons on hover
|
||||
input[type='number'] {
|
||||
-moz-appearance: number-input;
|
||||
-webkit-appearance: number-input;
|
||||
appearance: textfield;
|
||||
}
|
||||
};
|
||||
|
||||
input[type='number']::-webkit-inner-spin-button,
|
||||
input[type='number']::-webkit-outer-spin-button {
|
||||
-webkit-appearance: inner-spin-button !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`
|
||||
),
|
||||
// Wraps input and prefix/suffix
|
||||
inputWrapper: css`
|
||||
label: input-inputWrapper;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
/* we want input to be above addons, especially for focused state */
|
||||
z-index: 1;
|
||||
|
||||
/* when input rendered with addon before only*/
|
||||
&:not(:first-child):last-child {
|
||||
> input {
|
||||
border-left: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
return inputElementProps;
|
||||
|
||||
/* when input rendered with addon after only*/
|
||||
&:first-child:not(:last-child) {
|
||||
> input {
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* when rendered with addon before and after */
|
||||
&:not(:first-child):not(:last-child) {
|
||||
> input {
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
/* paddings specified for classes used outside React component */
|
||||
&:not(:first-child) {
|
||||
padding-left: ${prefixSuffixStaticWidth};
|
||||
}
|
||||
&:not(:last-child) {
|
||||
padding-right: ${prefixSuffixStaticWidth};
|
||||
}
|
||||
&[readonly] {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
input: cx(
|
||||
getFocusStyle(theme),
|
||||
sharedInputStyle(theme, invalid),
|
||||
css`
|
||||
label: input-input;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
flex-grow: 1;
|
||||
border-radius: ${borderRadius};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`
|
||||
),
|
||||
inputDisabled: css`
|
||||
background-color: ${colors.formInputBgDisabled};
|
||||
color: ${colors.formInputDisabledText};
|
||||
`,
|
||||
addon: css`
|
||||
label: input-addon;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
|
||||
&:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
> :last-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
> :first-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
> *:focus {
|
||||
/* we want anything that has focus and is an addon to be above input */
|
||||
z-index: 2;
|
||||
}
|
||||
`,
|
||||
prefix: cx(
|
||||
prefixSuffix,
|
||||
css`
|
||||
label: input-prefix;
|
||||
padding-left: ${theme.spacing.sm};
|
||||
padding-right: ${theme.spacing.xs};
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
`
|
||||
),
|
||||
suffix: cx(
|
||||
prefixSuffix,
|
||||
css`
|
||||
label: input-suffix;
|
||||
padding-right: ${theme.spacing.sm};
|
||||
padding-left: ${theme.spacing.xs};
|
||||
border-left: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
right: 0;
|
||||
`
|
||||
),
|
||||
loadingIndicator: css`
|
||||
& + * {
|
||||
margin-left: ${theme.spacing.xs};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
render() {
|
||||
const { validationEvents, className, hideErrorMessage, inputRef, ...restProps } = this.props;
|
||||
const { error } = this.state;
|
||||
const inputClassName = classNames('gf-form-input', { invalid: this.isInvalid }, className);
|
||||
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
|
||||
export const Input = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
|
||||
const { className, addonAfter, addonBefore, prefix, suffix, invalid, loading, size = 'auto', ...restProps } = props;
|
||||
/**
|
||||
* Prefix & suffix are positioned absolutely within inputWrapper. We use client rects below to apply correct padding to the input
|
||||
* when prefix/suffix is larger than default (28px = 16px(icon) + 12px(left/right paddings)).
|
||||
* Thanks to that prefix/suffix do not overflow the input element itself.
|
||||
*/
|
||||
const [prefixRect, prefixRef] = useClientRect<HTMLDivElement>();
|
||||
const [suffixRect, suffixRef] = useClientRect<HTMLDivElement>();
|
||||
|
||||
return (
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<input {...inputElementProps} ref={inputRef} className={inputClassName} />
|
||||
{error && !hideErrorMessage && <span>{error}</span>}
|
||||
const theme = useTheme();
|
||||
const styles = getInputStyles({ theme, invalid: !!invalid });
|
||||
|
||||
return (
|
||||
<div className={cx(styles.wrapper, inputSizes()[size], className)}>
|
||||
{!!addonBefore && <div className={styles.addon}>{addonBefore}</div>}
|
||||
|
||||
<div className={styles.inputWrapper}>
|
||||
{prefix && (
|
||||
<div className={styles.prefix} ref={prefixRef}>
|
||||
{prefix}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={ref}
|
||||
className={styles.input}
|
||||
{...restProps}
|
||||
style={{
|
||||
paddingLeft: prefixRect ? prefixRect.width : undefined,
|
||||
paddingRight: suffixRect ? suffixRect.width : undefined,
|
||||
}}
|
||||
/>
|
||||
|
||||
{(suffix || loading) && (
|
||||
<div className={styles.suffix} ref={suffixRef}>
|
||||
{loading && <Icon name="spinner" className={cx('fa-spin', styles.loadingIndicator)} />}
|
||||
{suffix}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{!!addonAfter && <div className={styles.addon}>{addonAfter}</div>}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Input.displayName = 'Input';
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
toFloatOrUndefined,
|
||||
NumberFieldConfigSettings,
|
||||
} from '@grafana/data';
|
||||
import { Input } from '../Forms/Input/Input';
|
||||
import { Input } from '../Input/Input';
|
||||
|
||||
export const NumberValueEditor: React.FC<FieldConfigEditorProps<number, NumberFieldConfigSettings>> = ({
|
||||
value,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FieldConfigEditorProps, StringFieldConfigSettings } from '@grafana/data';
|
||||
import { Input } from '../Forms/Input/Input';
|
||||
import { Input } from '../Input/Input';
|
||||
|
||||
export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFieldConfigSettings>> = ({
|
||||
value,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { getInputStyles } from '../Forms/Input/Input';
|
||||
import { getInputStyles } from '../Input/Input';
|
||||
import { cx, css } from 'emotion';
|
||||
|
||||
export const IndicatorsContainer = React.forwardRef<HTMLDivElement, React.PropsWithChildren<any>>((props, ref) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { getFocusCss, sharedInputStyle } from '../Forms/commonStyles';
|
||||
import { getInputStyles } from '../Forms/Input/Input';
|
||||
import { getInputStyles } from '../Input/Input';
|
||||
import { cx, css } from 'emotion';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
@ -2,7 +2,7 @@ import React, { ChangeEvent, KeyboardEvent, PureComponent } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import { stylesFactory } from '../../themes/stylesFactory';
|
||||
import { Button } from '../Button';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Input } from '../Forms/Legacy/Input/Input';
|
||||
import { TagItem } from './TagItem';
|
||||
|
||||
interface Props {
|
||||
|
@ -3,7 +3,7 @@ import { Threshold, sortThresholds, ThresholdsConfig, ThresholdsMode, Selectable
|
||||
import { colors } from '../../utils';
|
||||
import { getColorFromHexRgbOrName } from '@grafana/data';
|
||||
import { ThemeContext } from '../../themes/ThemeContext';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Input } from '../Forms/Legacy/Input/Input';
|
||||
import { ColorPicker } from '../ColorPicker/ColorPicker';
|
||||
import { css } from 'emotion';
|
||||
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { colors } from '../../utils';
|
||||
import { ThemeContext } from '../../themes/ThemeContext';
|
||||
import { Input } from '../Forms/Input/Input';
|
||||
import { Input } from '../Input/Input';
|
||||
import { ColorPicker } from '../ColorPicker/ColorPicker';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
@ -4,7 +4,7 @@ import { stringToDateTimeType, isValidTimeString } from '../time';
|
||||
import { mapStringsToTimeRange } from './mapper';
|
||||
import { TimePickerCalendar } from './TimePickerCalendar';
|
||||
import Forms from '../../Forms';
|
||||
import { Input } from '../../Forms/Input/Input';
|
||||
import { Input } from '../../Input/Input';
|
||||
import { Button } from '../../Button';
|
||||
|
||||
interface Props {
|
||||
|
@ -2,7 +2,7 @@ import React, { ChangeEvent, PureComponent } from 'react';
|
||||
|
||||
import { FormField } from '../FormField/FormField';
|
||||
import { FormLabel } from '../FormLabel/FormLabel';
|
||||
import { Input } from '../Input/Input';
|
||||
import { Input } from '../Forms/Legacy/Input/Input';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
|
||||
import { MappingType, ValueMapping } from '@grafana/data';
|
||||
|
@ -2,7 +2,7 @@ import React, { ChangeEvent } from 'react';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import { Select } from '../index';
|
||||
import Forms from '../Forms';
|
||||
import { Input } from '../Forms/Input/Input';
|
||||
import { Input } from '../Input/Input';
|
||||
import { MappingType, RangeMap, ValueMap, ValueMapping } from '@grafana/data';
|
||||
import * as styleMixins from '../../themes/mixins';
|
||||
import { useTheme } from '../../themes';
|
||||
|
@ -139,7 +139,7 @@ export { ButtonSelect } from './Select/ButtonSelect';
|
||||
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
|
||||
export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
|
||||
|
||||
export { Input } from './Forms/Input/Input';
|
||||
export { Input } from './Input/Input';
|
||||
|
||||
// Legacy forms
|
||||
|
||||
@ -150,7 +150,7 @@ import { NoOptionsMessage } from './Forms/Legacy/Select/NoOptionsMessage';
|
||||
import { ButtonSelect } from './Forms/Legacy/Select/ButtonSelect';
|
||||
|
||||
//Input
|
||||
import { Input, LegacyInputStatus } from './Input/Input';
|
||||
import { Input, LegacyInputStatus } from './Forms/Legacy/Input/Input';
|
||||
// Export these until Enterprise migrations have been merged
|
||||
// export { Input, InputStatus}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user