Commit 384e11fd by Peter Holmberg

Copied from new timepicker and unified component branch

parent c0eb1402
import React from 'react'; import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Input, EventsWithValidation } from './Input'; import { Input } from './Input';
import { ValidationEvents } from 'app/types'; import { EventsWithValidation } from '../../utils';
import { ValidationEvents } from '../../types';
const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars'; const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars';
const testBlurValidation: ValidationEvents = { const testBlurValidation: ValidationEvents = {
[EventsWithValidation.onBlur]: [ [EventsWithValidation.onBlur]: [
{ {
rule: (value: string) => { rule: (value: string) => {
if (!value || value.length < 3) { return !value || value.length < 3;
return true;
}
return false;
}, },
errorMessage: TEST_ERROR_MESSAGE, errorMessage: TEST_ERROR_MESSAGE,
}, },
......
import React, { PureComponent } from 'react'; import React, { PureComponent, ChangeEvent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { ValidationEvents, ValidationRule } from '../../types/forms'; import { validate, EventsWithValidation, hasValidationEvent } from '../../utils';
import { ValidationEvents, ValidationRule } from '../../types';
export enum InputStatus { export enum InputStatus {
Invalid = 'invalid', Invalid = 'invalid',
Valid = 'valid', Valid = 'valid',
} }
export enum InputTypes {
Text = 'text',
Number = 'number',
Password = 'password',
Email = 'email',
}
export enum EventsWithValidation {
onBlur = 'onBlur',
onFocus = 'onFocus',
onChange = 'onChange',
}
interface Props extends React.HTMLProps<HTMLInputElement> { interface Props extends React.HTMLProps<HTMLInputElement> {
validationEvents?: ValidationEvents; validationEvents?: ValidationEvents;
hideErrorMessage?: boolean; hideErrorMessage?: boolean;
...@@ -27,7 +15,7 @@ interface Props extends React.HTMLProps<HTMLInputElement> { ...@@ -27,7 +15,7 @@ interface Props extends React.HTMLProps<HTMLInputElement> {
// Override event props and append status as argument // Override event props and append status as argument
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void; onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void; onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onChange?: (event: React.FormEvent<HTMLInputElement>, status?: InputStatus) => void; onChange?: (event: React.ChangeEvent<HTMLInputElement>, status?: InputStatus) => void;
} }
export class Input extends PureComponent<Props> { export class Input extends PureComponent<Props> {
...@@ -48,24 +36,24 @@ export class Input extends PureComponent<Props> { ...@@ -48,24 +36,24 @@ export class Input extends PureComponent<Props> {
} }
validatorAsync = (validationRules: ValidationRule[]) => { validatorAsync = (validationRules: ValidationRule[]) => {
return evt => { return (evt: ChangeEvent<HTMLInputElement>) => {
const errors = validate(evt.target.value, validationRules); const errors = validate(evt.target.value, validationRules);
this.setState(prevState => { this.setState(prevState => {
return { return { ...prevState, error: errors ? errors[0] : null };
...prevState,
error: errors ? errors[0] : null,
};
}); });
}; };
}; };
populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => { populateEventPropsWithStatus = (restProps: any, validationEvents: ValidationEvents | undefined) => {
const inputElementProps = { ...restProps }; const inputElementProps = { ...restProps };
Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => { if (!validationEvents) {
if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) { return inputElementProps;
inputElementProps[eventName] = async evt => { }
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 evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
if (hasValidationEvent(eventName, validationEvents)) { if (hasValidationEvent(eventName as EventsWithValidation, validationEvents)) {
await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
} }
if (restProps[eventName]) { if (restProps[eventName]) {
......
...@@ -25,6 +25,7 @@ export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor'; ...@@ -25,6 +25,7 @@ export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
export { Switch } from './Switch/Switch'; export { Switch } from './Switch/Switch';
export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult'; export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
export { UnitPicker } from './UnitPicker/UnitPicker'; export { UnitPicker } from './UnitPicker/UnitPicker';
export { Input, InputStatus } from './Input/Input';
// Visualizations // Visualizations
export { Gauge } from './Gauge/Gauge'; export { Gauge } from './Gauge/Gauge';
......
export enum InputStatus {
Invalid = 'invalid',
Valid = 'valid',
}
export enum InputTypes {
Text = 'text',
Number = 'number',
Password = 'password',
Email = 'email',
}
export enum EventsWithValidation {
onBlur = 'onBlur',
onFocus = 'onFocus',
onChange = 'onChange',
}
export interface ValidationRule {
rule: (valueToValidate: string) => boolean;
errorMessage: string;
}
export interface ValidationEvents {
[eventName: string]: ValidationRule[];
}
...@@ -5,4 +5,4 @@ export * from './plugin'; ...@@ -5,4 +5,4 @@ export * from './plugin';
export * from './datasource'; export * from './datasource';
export * from './theme'; export * from './theme';
export * from './threshold'; export * from './threshold';
export * from './forms'; export * from './input';
import { EventsWithValidation, ValidationEvents, ValidationRule } from '../types'; import { ValidationRule, ValidationEvents } from '../types/input';
export enum EventsWithValidation {
onBlur = 'onBlur',
onFocus = 'onFocus',
onChange = 'onChange',
}
export const validate = (value: string, validationRules: ValidationRule[]) => { export const validate = (value: string, validationRules: ValidationRule[]) => {
const errors = validationRules.reduce((acc, currentRule) => { const errors = validationRules.reduce(
if (!currentRule.rule(value)) { (acc, currRule) => {
return acc.concat(currentRule.errorMessage); if (!currRule.rule(value)) {
} return acc.concat(currRule.errorMessage);
return acc; }
}, []); return acc;
},
[] as string[]
);
return errors.length > 0 ? errors : null; return errors.length > 0 ? errors : null;
}; };
export const hasValidationEvent = (event: EventsWithValidation, validationEvents?: ValidationEvents) => { export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents | undefined) => {
return validationEvents && validationEvents[event]; return validationEvents && validationEvents[event];
}; };
import React, { PureComponent } from 'react';
import classNames from 'classnames';
import { ValidationEvents, ValidationRule } from 'app/types';
import { validate, hasValidationEvent } from 'app/core/utils/validate';
export enum InputStatus {
Invalid = 'invalid',
Valid = 'valid',
}
export enum InputTypes {
Text = 'text',
Number = 'number',
Password = 'password',
Email = 'email',
}
export enum EventsWithValidation {
onBlur = 'onBlur',
onFocus = 'onFocus',
onChange = 'onChange',
}
interface Props extends React.HTMLProps<HTMLInputElement> {
validationEvents?: ValidationEvents;
hideErrorMessage?: boolean;
// Override event props and append status as argument
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onChange?: (event: React.FormEvent<HTMLInputElement>, status?: InputStatus) => void;
}
export class Input extends PureComponent<Props> {
static defaultProps = {
className: '',
};
state = {
error: null,
};
get status() {
return this.state.error ? InputStatus.Invalid : InputStatus.Valid;
}
get isInvalid() {
return this.status === InputStatus.Invalid;
}
validatorAsync = (validationRules: ValidationRule[]) => {
return evt => {
const errors = validate(evt.target.value, validationRules);
this.setState(prevState => {
return {
...prevState,
error: errors ? errors[0] : null,
};
});
};
};
populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => {
const inputElementProps = { ...restProps };
Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => {
if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) {
inputElementProps[eventName] = async evt => {
evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
if (hasValidationEvent(eventName, 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, ...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 className="our-custom-wrapper-class">
<input {...inputElementProps} className={inputClassName} />
{error && !hideErrorMessage && <span>{error}</span>}
</div>
);
}
}
import { ValidationRule, ValidationEvents } from 'app/types';
import { EventsWithValidation } from 'app/core/components/Form/Input';
export const validate = (value: string, validationRules: ValidationRule[]) => {
const errors = validationRules.reduce((acc, currRule) => {
if (!currRule.rule(value)) {
return acc.concat(currRule.errorMessage);
}
return acc;
}, []);
return errors.length > 0 ? errors : null;
};
export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents) => {
return validationEvents && validationEvents[event];
};
...@@ -5,17 +5,12 @@ import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; ...@@ -5,17 +5,12 @@ import React, { PureComponent, ChangeEvent, FocusEvent } from 'react';
import { isValidTimeSpan } from 'app/core/utils/rangeutil'; import { isValidTimeSpan } from 'app/core/utils/rangeutil';
// Components // Components
import { Switch } from '@grafana/ui'; import { DataSourceSelectItem, EventsWithValidation, Input, InputStatus, Switch, ValidationEvents } from '@grafana/ui';
import { Input } from 'app/core/components/Form';
import { EventsWithValidation } from 'app/core/components/Form/Input';
import { InputStatus } from 'app/core/components/Form/Input';
import { DataSourceOption } from './DataSourceOption'; import { DataSourceOption } from './DataSourceOption';
import { FormLabel } from '@grafana/ui'; import { FormLabel } from '@grafana/ui';
// Types // Types
import { PanelModel } from '../state/PanelModel'; import { PanelModel } from '../state';
import { DataSourceSelectItem } from '@grafana/ui/src/types';
import { ValidationEvents } from 'app/types';
const timeRangeValidationEvents: ValidationEvents = { const timeRangeValidationEvents: ValidationEvents = {
[EventsWithValidation.onBlur]: [ [EventsWithValidation.onBlur]: [
......
...@@ -12,6 +12,5 @@ export * from './plugins'; ...@@ -12,6 +12,5 @@ export * from './plugins';
export * from './organization'; export * from './organization';
export * from './appNotifications'; export * from './appNotifications';
export * from './search'; export * from './search';
export * from './form';
export * from './explore'; export * from './explore';
export * from './store'; export * from './store';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment