mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #14897 from grafana/14812/formgroup-component
FormField component
This commit is contained in:
commit
7c22848cf6
@ -0,0 +1,24 @@
|
||||
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();
|
||||
});
|
||||
});
|
25
packages/grafana-ui/src/components/FormField/FormField.tsx
Normal file
25
packages/grafana-ui/src/components/FormField/FormField.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
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 };
|
12
packages/grafana-ui/src/components/FormField/_FormField.scss
Normal file
12
packages/grafana-ui/src/components/FormField/_FormField.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.form-field {
|
||||
margin-bottom: $gf-form-margin;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
|
||||
&--grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// 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>
|
||||
`;
|
42
packages/grafana-ui/src/components/FormLabel/FormLabel.tsx
Normal file
42
packages/grafana-ui/src/components/FormLabel/FormLabel.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { FunctionComponent, ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Tooltip } from '..';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
htmlFor?: string;
|
||||
isFocused?: boolean;
|
||||
isInvalid?: boolean;
|
||||
tooltip?: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export const FormLabel: FunctionComponent<Props> = ({
|
||||
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-invalid': isInvalid,
|
||||
});
|
||||
|
||||
return (
|
||||
<label className={classes} {...rest} htmlFor={htmlFor}>
|
||||
{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>
|
||||
);
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
import React, { SFC, ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
htmlFor?: string;
|
||||
className?: string;
|
||||
isFocused?: boolean;
|
||||
isInvalid?: boolean;
|
||||
}
|
||||
|
||||
export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => {
|
||||
const classes = classNames('gf-form-label', className, {
|
||||
'gf-form-label--is-focused': isFocused,
|
||||
'gf-form-label--is-invalid': isInvalid,
|
||||
});
|
||||
|
||||
return (
|
||||
<label className={classes} {...rest} htmlFor={htmlFor}>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
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';
|
||||
import IndicatorsContainer from './IndicatorsContainer';
|
||||
import NoOptionsMessage from './NoOptionsMessage';
|
||||
import resetSelectStyles from './resetSelectStyles';
|
||||
import { CustomScrollbar } from '@grafana/ui';
|
||||
import { CustomScrollbar } from '..';
|
||||
|
||||
export interface SelectOptionItem {
|
||||
label?: string;
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { ChangeEvent, PureComponent } from 'react';
|
||||
|
||||
import { MappingType, ValueMapping } from '../../types/panel';
|
||||
import { Label } from '../Label/Label';
|
||||
import { Select } from '../Select/Select';
|
||||
import { MappingType, ValueMapping } from '../../types';
|
||||
import { FormField, FormLabel, Select } from '..';
|
||||
|
||||
export interface Props {
|
||||
valueMapping: ValueMapping;
|
||||
@ -32,19 +31,19 @@ export default class MappingRow extends PureComponent<Props, State> {
|
||||
this.state = { ...props.valueMapping };
|
||||
}
|
||||
|
||||
onMappingValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onMappingValueChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ value: event.target.value });
|
||||
};
|
||||
|
||||
onMappingFromChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onMappingFromChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ from: event.target.value });
|
||||
};
|
||||
|
||||
onMappingToChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onMappingToChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ to: event.target.value });
|
||||
};
|
||||
|
||||
onMappingTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onMappingTextChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ text: event.target.value });
|
||||
};
|
||||
|
||||
@ -62,30 +61,28 @@ export default class MappingRow extends PureComponent<Props, State> {
|
||||
if (type === MappingType.RangeToText) {
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<Label width={4}>From</Label>
|
||||
<FormField
|
||||
label="From"
|
||||
labelWidth={4}
|
||||
inputWidth={8}
|
||||
onBlur={this.updateMapping}
|
||||
onChange={this.onMappingFromChange}
|
||||
value={from}
|
||||
/>
|
||||
<FormField
|
||||
label="To"
|
||||
labelWidth={4}
|
||||
inputWidth={8}
|
||||
onBlur={this.updateMapping}
|
||||
onChange={this.onMappingToChange}
|
||||
value={to}
|
||||
/>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<FormLabel width={4}>Text</FormLabel>
|
||||
<input
|
||||
className="gf-form-input width-8"
|
||||
value={from}
|
||||
className="gf-form-input"
|
||||
onBlur={this.updateMapping}
|
||||
onChange={this.onMappingFromChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<Label width={4}>To</Label>
|
||||
<input
|
||||
className="gf-form-input width-8"
|
||||
value={to}
|
||||
onBlur={this.updateMapping}
|
||||
onChange={this.onMappingToChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<Label width={4}>Text</Label>
|
||||
<input
|
||||
className="gf-form-input width-10"
|
||||
value={text}
|
||||
onBlur={this.updateMapping}
|
||||
onChange={this.onMappingTextChange}
|
||||
/>
|
||||
</div>
|
||||
@ -95,17 +92,16 @@ export default class MappingRow extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<Label width={4}>Value</Label>
|
||||
<input
|
||||
className="gf-form-input width-8"
|
||||
onBlur={this.updateMapping}
|
||||
onChange={this.onMappingValueChange}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
label="Value"
|
||||
labelWidth={4}
|
||||
onBlur={this.updateMapping}
|
||||
onChange={this.onMappingValueChange}
|
||||
value={value}
|
||||
inputWidth={8}
|
||||
/>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<Label width={4}>Text</Label>
|
||||
<FormLabel width={4}>Text</FormLabel>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onBlur={this.updateMapping}
|
||||
@ -123,7 +119,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<Label width={5}>Type</Label>
|
||||
<FormLabel width={5}>Type</FormLabel>
|
||||
<Select
|
||||
placeholder="Choose type"
|
||||
isSearchable={false}
|
||||
|
@ -7,3 +7,4 @@
|
||||
@import 'PanelOptionsGrid/PanelOptionsGrid';
|
||||
@import 'ColorPicker/ColorPicker';
|
||||
@import 'ValueMappingsEditor/ValueMappingsEditor';
|
||||
@import "FormField/FormField";
|
||||
|
@ -2,7 +2,6 @@ export { DeleteButton } from './DeleteButton/DeleteButton';
|
||||
export { Tooltip } from './Tooltip/Tooltip';
|
||||
export { Portal } from './Portal/Portal';
|
||||
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
|
||||
export { Label } from './Label/Label';
|
||||
|
||||
// Select
|
||||
export { Select, AsyncSelect, SelectOptionItem } from './Select/Select';
|
||||
@ -10,12 +9,15 @@ export { IndicatorsContainer } from './Select/IndicatorsContainer';
|
||||
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
||||
export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
||||
|
||||
// Forms
|
||||
export { FormLabel } from './FormLabel/FormLabel';
|
||||
export { FormField } from './FormField/FormField';
|
||||
|
||||
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
|
||||
export { ColorPicker } from './ColorPicker/ColorPicker';
|
||||
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
|
||||
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
|
||||
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
|
||||
export { GfFormLabel } from './GfFormLabel/GfFormLabel';
|
||||
export { Graph } from './Graph/Graph';
|
||||
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
|
||||
export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
|
||||
|
@ -1,6 +1,6 @@
|
||||
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 { DashboardSearchHit } from 'app/types';
|
||||
@ -99,12 +99,12 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<Label
|
||||
<FormLabel
|
||||
width={11}
|
||||
tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
|
||||
>
|
||||
Home Dashboard
|
||||
</Label>
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={dashboards.find(dashboard => dashboard.id === homeDashboardId)}
|
||||
getOptionValue={i => i.id}
|
||||
|
@ -10,7 +10,7 @@ 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 { GfFormLabel } from '@grafana/ui';
|
||||
import { FormLabel } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../panel_model';
|
||||
@ -164,7 +164,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
{this.renderOptions()}
|
||||
|
||||
<div className="gf-form">
|
||||
<GfFormLabel>Relative time</GfFormLabel>
|
||||
<FormLabel>Relative time</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { SFC } from 'react';
|
||||
import { Label } from '@grafana/ui';
|
||||
|
||||
import { FormLabel } from '@grafana/ui';
|
||||
import { Switch } from '../../../core/components/Switch/Switch';
|
||||
|
||||
export interface Props {
|
||||
@ -15,14 +14,14 @@ const BasicSettings: SFC<Props> = ({ dataSourceName, isDefault, onDefaultChange,
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form max-width-30" style={{ marginRight: '3px' }}>
|
||||
<Label
|
||||
<FormLabel
|
||||
tooltip={
|
||||
'The name is used when you select the data source in panels. The Default data source is ' +
|
||||
'preselected in new panels.'
|
||||
}
|
||||
>
|
||||
Name
|
||||
</Label>
|
||||
</FormLabel>
|
||||
<input
|
||||
className="gf-form-input max-width-23"
|
||||
type="text"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Label } from '@grafana/ui';
|
||||
import { FormLabel } from '@grafana/ui';
|
||||
|
||||
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
|
||||
import { updateTeam } from './state/actions';
|
||||
@ -51,7 +51,7 @@ export class TeamSettings extends React.Component<Props, State> {
|
||||
<h3 className="page-sub-heading">Team Settings</h3>
|
||||
<form name="teamDetailsForm" className="gf-form-group" onSubmit={this.onUpdate}>
|
||||
<div className="gf-form max-width-30">
|
||||
<Label>Name</Label>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
@ -62,9 +62,9 @@ export class TeamSettings extends React.Component<Props, State> {
|
||||
</div>
|
||||
|
||||
<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
|
||||
</Label>
|
||||
</FormLabel>
|
||||
<input
|
||||
type="email"
|
||||
className="gf-form-input max-width-22"
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { GaugeOptions } from './types';
|
||||
@ -21,14 +21,8 @@ export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<
|
||||
|
||||
return (
|
||||
<PanelOptionsGroup title="Gauge">
|
||||
<div className="gf-form">
|
||||
<Label width={8}>Min value</Label>
|
||||
<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>
|
||||
<FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={minValue} />
|
||||
<FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={maxValue} />
|
||||
<Switch
|
||||
label="Show labels"
|
||||
labelClass="width-8"
|
||||
|
@ -1,6 +1,5 @@
|
||||
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 { GaugeOptions } from './types';
|
||||
|
||||
@ -41,7 +40,7 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO
|
||||
return (
|
||||
<PanelOptionsGroup title="Value">
|
||||
<div className="gf-form">
|
||||
<Label width={labelWidth}>Stat</Label>
|
||||
<FormLabel width={labelWidth}>Stat</FormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={statOptions}
|
||||
@ -50,27 +49,19 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<Label width={labelWidth}>Unit</Label>
|
||||
<FormLabel width={labelWidth}>Unit</FormLabel>
|
||||
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<Label width={labelWidth}>Decimals</Label>
|
||||
<input
|
||||
className="gf-form-input width-12"
|
||||
type="number"
|
||||
placeholder="auto"
|
||||
value={decimals || ''}
|
||||
onChange={this.onDecimalChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<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>
|
||||
<FormField
|
||||
label="Decimals"
|
||||
labelWidth={labelWidth}
|
||||
placeholder="auto"
|
||||
onChange={this.onDecimalChange}
|
||||
value={decimals || ''}
|
||||
type="number"
|
||||
/>
|
||||
<FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
|
||||
<FormField label="Suffix" labelWidth={labelWidth} onChange={this.onSuffixChange} value={suffix || ''} />
|
||||
</PanelOptionsGroup>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user