Commit 7c22848c by Torkel Ödegaard Committed by GitHub

Merge pull request #14897 from grafana/14812/formgroup-component

FormField component
parents af8425da 3ce45e71
import React from 'react';
import { shallow } from 'enzyme';
import { FormField, Props } from './FormField';
const setup = (propOverrides?: object) => {
const props: Props = {
label: 'Test',
labelWidth: 11,
value: 10,
onChange: jest.fn(),
};
Object.assign(props, propOverrides);
return shallow(<FormField {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
});
import React, { InputHTMLAttributes, FunctionComponent } from 'react';
import { FormLabel } from '..';
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
label: string;
labelWidth?: number;
inputWidth?: number;
}
const defaultProps = {
labelWidth: 6,
inputWidth: 12,
};
const FormField: FunctionComponent<Props> = ({ label, labelWidth, inputWidth, ...inputProps }) => {
return (
<div className="form-field">
<FormLabel width={labelWidth}>{label}</FormLabel>
<input type="text" className={`gf-form-input width-${inputWidth}`} {...inputProps} />
</div>
);
};
FormField.defaultProps = defaultProps;
export { FormField };
.form-field {
margin-bottom: $gf-form-margin;
display: flex;
flex-direction: row;
align-items: center;
text-align: left;
position: relative;
&--grow {
flex-grow: 1;
}
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div
className="form-field"
>
<Component
width={11}
>
Test
</Component>
<input
className="gf-form-input width-12"
onChange={[MockFunction]}
type="text"
value={10}
/>
</div>
`;
import React, { SFC, ReactNode } from 'react'; import React, { FunctionComponent, ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Tooltip } from '..';
interface Props { interface Props {
children: ReactNode; children: ReactNode;
htmlFor?: string;
className?: string; className?: string;
htmlFor?: string;
isFocused?: boolean; isFocused?: boolean;
isInvalid?: boolean; isInvalid?: boolean;
tooltip?: string;
width?: number;
} }
export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => { export const FormLabel: FunctionComponent<Props> = ({
const classes = classNames('gf-form-label', className, { children,
isFocused,
isInvalid,
className,
htmlFor,
tooltip,
width,
...rest
}) => {
const classes = classNames(`gf-form-label width-${width ? width : '10'}`, className, {
'gf-form-label--is-focused': isFocused, 'gf-form-label--is-focused': isFocused,
'gf-form-label--is-invalid': isInvalid, 'gf-form-label--is-invalid': isInvalid,
}); });
...@@ -18,6 +30,13 @@ export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, classN ...@@ -18,6 +30,13 @@ export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, classN
return ( return (
<label className={classes} {...rest} htmlFor={htmlFor}> <label className={classes} {...rest} htmlFor={htmlFor}>
{children} {children}
{tooltip && (
<Tooltip placement="auto" content={tooltip}>
<div className="gf-form-help-icon--right-normal">
<i className="gicon gicon-question gicon--has-hover" />
</div>
</Tooltip>
)}
</label> </label>
); );
}; };
import React, { SFC, ReactNode } from 'react';
import { Tooltip } from '../Tooltip/Tooltip';
interface Props {
tooltip?: string;
for?: string;
children: ReactNode;
width?: number;
className?: string;
}
export const Label: SFC<Props> = props => {
return (
<span className={`gf-form-label width-${props.width ? props.width : '10'}`}>
<span>{props.children}</span>
{props.tooltip && (
<Tooltip placement="auto" content={props.tooltip}>
<div className="gf-form-help-icon--right-normal">
<i className="gicon gicon-question gicon--has-hover" />
</div>
</Tooltip>
)}
</span>
);
};
...@@ -16,7 +16,7 @@ import SelectOptionGroup from './SelectOptionGroup'; ...@@ -16,7 +16,7 @@ import SelectOptionGroup from './SelectOptionGroup';
import IndicatorsContainer from './IndicatorsContainer'; import IndicatorsContainer from './IndicatorsContainer';
import NoOptionsMessage from './NoOptionsMessage'; import NoOptionsMessage from './NoOptionsMessage';
import resetSelectStyles from './resetSelectStyles'; import resetSelectStyles from './resetSelectStyles';
import { CustomScrollbar } from '@grafana/ui'; import { CustomScrollbar } from '..';
export interface SelectOptionItem { export interface SelectOptionItem {
label?: string; label?: string;
......
import React, { PureComponent } from 'react'; import React, { ChangeEvent, PureComponent } from 'react';
import { MappingType, ValueMapping } from '../../types/panel'; import { MappingType, ValueMapping } from '../../types';
import { Label } from '../Label/Label'; import { FormField, FormLabel, Select } from '..';
import { Select } from '../Select/Select';
export interface Props { export interface Props {
valueMapping: ValueMapping; valueMapping: ValueMapping;
...@@ -32,19 +31,19 @@ export default class MappingRow extends PureComponent<Props, State> { ...@@ -32,19 +31,19 @@ export default class MappingRow extends PureComponent<Props, State> {
this.state = { ...props.valueMapping }; this.state = { ...props.valueMapping };
} }
onMappingValueChange = (event: React.ChangeEvent<HTMLInputElement>) => { onMappingValueChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ value: event.target.value }); this.setState({ value: event.target.value });
}; };
onMappingFromChange = (event: React.ChangeEvent<HTMLInputElement>) => { onMappingFromChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ from: event.target.value }); this.setState({ from: event.target.value });
}; };
onMappingToChange = (event: React.ChangeEvent<HTMLInputElement>) => { onMappingToChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ to: event.target.value }); this.setState({ to: event.target.value });
}; };
onMappingTextChange = (event: React.ChangeEvent<HTMLInputElement>) => { onMappingTextChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ text: event.target.value }); this.setState({ text: event.target.value });
}; };
...@@ -62,30 +61,28 @@ export default class MappingRow extends PureComponent<Props, State> { ...@@ -62,30 +61,28 @@ export default class MappingRow extends PureComponent<Props, State> {
if (type === MappingType.RangeToText) { if (type === MappingType.RangeToText) {
return ( return (
<> <>
<div className="gf-form"> <FormField
<Label width={4}>From</Label> label="From"
<input labelWidth={4}
className="gf-form-input width-8" inputWidth={8}
value={from}
onBlur={this.updateMapping} onBlur={this.updateMapping}
onChange={this.onMappingFromChange} onChange={this.onMappingFromChange}
value={from}
/> />
</div> <FormField
<div className="gf-form"> label="To"
<Label width={4}>To</Label> labelWidth={4}
<input inputWidth={8}
className="gf-form-input width-8"
value={to}
onBlur={this.updateMapping} onBlur={this.updateMapping}
onChange={this.onMappingToChange} onChange={this.onMappingToChange}
value={to}
/> />
</div> <div className="gf-form gf-form--grow">
<div className="gf-form"> <FormLabel width={4}>Text</FormLabel>
<Label width={4}>Text</Label>
<input <input
className="gf-form-input width-10" className="gf-form-input"
value={text}
onBlur={this.updateMapping} onBlur={this.updateMapping}
value={text}
onChange={this.onMappingTextChange} onChange={this.onMappingTextChange}
/> />
</div> </div>
...@@ -95,17 +92,16 @@ export default class MappingRow extends PureComponent<Props, State> { ...@@ -95,17 +92,16 @@ export default class MappingRow extends PureComponent<Props, State> {
return ( return (
<> <>
<div className="gf-form"> <FormField
<Label width={4}>Value</Label> label="Value"
<input labelWidth={4}
className="gf-form-input width-8"
onBlur={this.updateMapping} onBlur={this.updateMapping}
onChange={this.onMappingValueChange} onChange={this.onMappingValueChange}
value={value} value={value}
inputWidth={8}
/> />
</div>
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">
<Label width={4}>Text</Label> <FormLabel width={4}>Text</FormLabel>
<input <input
className="gf-form-input" className="gf-form-input"
onBlur={this.updateMapping} onBlur={this.updateMapping}
...@@ -123,7 +119,7 @@ export default class MappingRow extends PureComponent<Props, State> { ...@@ -123,7 +119,7 @@ export default class MappingRow extends PureComponent<Props, State> {
return ( return (
<div className="gf-form-inline"> <div className="gf-form-inline">
<div className="gf-form"> <div className="gf-form">
<Label width={5}>Type</Label> <FormLabel width={5}>Type</FormLabel>
<Select <Select
placeholder="Choose type" placeholder="Choose type"
isSearchable={false} isSearchable={false}
......
...@@ -7,3 +7,4 @@ ...@@ -7,3 +7,4 @@
@import 'PanelOptionsGrid/PanelOptionsGrid'; @import 'PanelOptionsGrid/PanelOptionsGrid';
@import 'ColorPicker/ColorPicker'; @import 'ColorPicker/ColorPicker';
@import 'ValueMappingsEditor/ValueMappingsEditor'; @import 'ValueMappingsEditor/ValueMappingsEditor';
@import "FormField/FormField";
...@@ -2,7 +2,6 @@ export { DeleteButton } from './DeleteButton/DeleteButton'; ...@@ -2,7 +2,6 @@ export { DeleteButton } from './DeleteButton/DeleteButton';
export { Tooltip } from './Tooltip/Tooltip'; export { Tooltip } from './Tooltip/Tooltip';
export { Portal } from './Portal/Portal'; export { Portal } from './Portal/Portal';
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar'; export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
export { Label } from './Label/Label';
// Select // Select
export { Select, AsyncSelect, SelectOptionItem } from './Select/Select'; export { Select, AsyncSelect, SelectOptionItem } from './Select/Select';
...@@ -10,12 +9,15 @@ export { IndicatorsContainer } from './Select/IndicatorsContainer'; ...@@ -10,12 +9,15 @@ export { IndicatorsContainer } from './Select/IndicatorsContainer';
export { NoOptionsMessage } from './Select/NoOptionsMessage'; export { NoOptionsMessage } from './Select/NoOptionsMessage';
export { default as resetSelectStyles } from './Select/resetSelectStyles'; export { default as resetSelectStyles } from './Select/resetSelectStyles';
// Forms
export { FormLabel } from './FormLabel/FormLabel';
export { FormField } from './FormField/FormField';
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder'; export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
export { ColorPicker } from './ColorPicker/ColorPicker'; export { ColorPicker } from './ColorPicker/ColorPicker';
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover'; export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker'; export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor'; export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
export { GfFormLabel } from './GfFormLabel/GfFormLabel';
export { Graph } from './Graph/Graph'; export { Graph } from './Graph/Graph';
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid'; export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Select, Label } from '@grafana/ui';
import { FormLabel, Select } from '@grafana/ui';
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv'; import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
import { DashboardSearchHit } from 'app/types'; import { DashboardSearchHit } from 'app/types';
...@@ -99,12 +99,12 @@ export class SharedPreferences extends PureComponent<Props, State> { ...@@ -99,12 +99,12 @@ export class SharedPreferences extends PureComponent<Props, State> {
/> />
</div> </div>
<div className="gf-form"> <div className="gf-form">
<Label <FormLabel
width={11} width={11}
tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box." tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
> >
Home Dashboard Home Dashboard
</Label> </FormLabel>
<Select <Select
value={dashboards.find(dashboard => dashboard.id === homeDashboardId)} value={dashboards.find(dashboard => dashboard.id === homeDashboardId)}
getOptionValue={i => i.id} getOptionValue={i => i.id}
......
...@@ -10,7 +10,7 @@ import { Input } from 'app/core/components/Form'; ...@@ -10,7 +10,7 @@ import { Input } from 'app/core/components/Form';
import { EventsWithValidation } from 'app/core/components/Form/Input'; import { EventsWithValidation } from 'app/core/components/Form/Input';
import { InputStatus } from 'app/core/components/Form/Input'; import { InputStatus } from 'app/core/components/Form/Input';
import DataSourceOption from './DataSourceOption'; import DataSourceOption from './DataSourceOption';
import { GfFormLabel } from '@grafana/ui'; import { FormLabel } from '@grafana/ui';
// Types // Types
import { PanelModel } from '../panel_model'; import { PanelModel } from '../panel_model';
...@@ -164,7 +164,7 @@ export class QueryOptions extends PureComponent<Props, State> { ...@@ -164,7 +164,7 @@ export class QueryOptions extends PureComponent<Props, State> {
{this.renderOptions()} {this.renderOptions()}
<div className="gf-form"> <div className="gf-form">
<GfFormLabel>Relative time</GfFormLabel> <FormLabel>Relative time</FormLabel>
<Input <Input
type="text" type="text"
className="width-6" className="width-6"
......
import React, { SFC } from 'react'; import React, { SFC } from 'react';
import { Label } from '@grafana/ui'; import { FormLabel } from '@grafana/ui';
import { Switch } from '../../../core/components/Switch/Switch'; import { Switch } from '../../../core/components/Switch/Switch';
export interface Props { export interface Props {
...@@ -15,14 +14,14 @@ const BasicSettings: SFC<Props> = ({ dataSourceName, isDefault, onDefaultChange, ...@@ -15,14 +14,14 @@ const BasicSettings: SFC<Props> = ({ dataSourceName, isDefault, onDefaultChange,
<div className="gf-form-group"> <div className="gf-form-group">
<div className="gf-form-inline"> <div className="gf-form-inline">
<div className="gf-form max-width-30" style={{ marginRight: '3px' }}> <div className="gf-form max-width-30" style={{ marginRight: '3px' }}>
<Label <FormLabel
tooltip={ tooltip={
'The name is used when you select the data source in panels. The Default data source is ' + 'The name is used when you select the data source in panels. The Default data source is ' +
'preselected in new panels.' 'preselected in new panels.'
} }
> >
Name Name
</Label> </FormLabel>
<input <input
className="gf-form-input max-width-23" className="gf-form-input max-width-23"
type="text" type="text"
......
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Label } from '@grafana/ui'; import { FormLabel } from '@grafana/ui';
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences'; import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
import { updateTeam } from './state/actions'; import { updateTeam } from './state/actions';
...@@ -51,7 +51,7 @@ export class TeamSettings extends React.Component<Props, State> { ...@@ -51,7 +51,7 @@ export class TeamSettings extends React.Component<Props, State> {
<h3 className="page-sub-heading">Team Settings</h3> <h3 className="page-sub-heading">Team Settings</h3>
<form name="teamDetailsForm" className="gf-form-group" onSubmit={this.onUpdate}> <form name="teamDetailsForm" className="gf-form-group" onSubmit={this.onUpdate}>
<div className="gf-form max-width-30"> <div className="gf-form max-width-30">
<Label>Name</Label> <FormLabel>Name</FormLabel>
<input <input
type="text" type="text"
required required
...@@ -62,9 +62,9 @@ export class TeamSettings extends React.Component<Props, State> { ...@@ -62,9 +62,9 @@ export class TeamSettings extends React.Component<Props, State> {
</div> </div>
<div className="gf-form max-width-30"> <div className="gf-form max-width-30">
<Label tooltip="This is optional and is primarily used to set the team profile avatar (via gravatar service)"> <FormLabel tooltip="This is optional and is primarily used to set the team profile avatar (via gravatar service)">
Email Email
</Label> </FormLabel>
<input <input
type="email" type="email"
className="gf-form-input max-width-22" className="gf-form-input max-width-22"
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { PanelOptionsProps, PanelOptionsGroup, Label } from '@grafana/ui'; import { FormField, PanelOptionsProps, PanelOptionsGroup } from '@grafana/ui';
import { Switch } from 'app/core/components/Switch/Switch'; import { Switch } from 'app/core/components/Switch/Switch';
import { GaugeOptions } from './types'; import { GaugeOptions } from './types';
...@@ -21,14 +21,8 @@ export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps< ...@@ -21,14 +21,8 @@ export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<
return ( return (
<PanelOptionsGroup title="Gauge"> <PanelOptionsGroup title="Gauge">
<div className="gf-form"> <FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={minValue} />
<Label width={8}>Min value</Label> <FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={maxValue} />
<input type="text" className="gf-form-input width-12" onChange={this.onMinValueChange} value={minValue} />
</div>
<div className="gf-form">
<Label width={8}>Max value</Label>
<input type="text" className="gf-form-input width-12" onChange={this.onMaxValueChange} value={maxValue} />
</div>
<Switch <Switch
label="Show labels" label="Show labels"
labelClass="width-8" labelClass="width-8"
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { PanelOptionsProps, PanelOptionsGroup, Label, Select } from '@grafana/ui'; import { FormField, FormLabel, PanelOptionsProps, PanelOptionsGroup, Select } from '@grafana/ui';
import UnitPicker from 'app/core/components/Select/UnitPicker'; import UnitPicker from 'app/core/components/Select/UnitPicker';
import { GaugeOptions } from './types'; import { GaugeOptions } from './types';
...@@ -41,7 +40,7 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO ...@@ -41,7 +40,7 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO
return ( return (
<PanelOptionsGroup title="Value"> <PanelOptionsGroup title="Value">
<div className="gf-form"> <div className="gf-form">
<Label width={labelWidth}>Stat</Label> <FormLabel width={labelWidth}>Stat</FormLabel>
<Select <Select
width={12} width={12}
options={statOptions} options={statOptions}
...@@ -50,27 +49,19 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO ...@@ -50,27 +49,19 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO
/> />
</div> </div>
<div className="gf-form"> <div className="gf-form">
<Label width={labelWidth}>Unit</Label> <FormLabel width={labelWidth}>Unit</FormLabel>
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} /> <UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
</div> </div>
<div className="gf-form"> <FormField
<Label width={labelWidth}>Decimals</Label> label="Decimals"
<input labelWidth={labelWidth}
className="gf-form-input width-12"
type="number"
placeholder="auto" placeholder="auto"
value={decimals || ''}
onChange={this.onDecimalChange} onChange={this.onDecimalChange}
value={decimals || ''}
type="number"
/> />
</div> <FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
<div className="gf-form"> <FormField label="Suffix" labelWidth={labelWidth} onChange={this.onSuffixChange} value={suffix || ''} />
<Label width={labelWidth}>Prefix</Label>
<input className="gf-form-input width-12" type="text" value={prefix || ''} onChange={this.onPrefixChange} />
</div>
<div className="gf-form">
<Label width={labelWidth}>Suffix</Label>
<input className="gf-form-input width-12" type="text" value={suffix || ''} onChange={this.onSuffixChange} />
</div>
</PanelOptionsGroup> </PanelOptionsGroup>
); );
} }
......
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