Form migrations: Last components from Forms namespace (#23556)

* Migrate RadioButtonGroup

* Migrate Label

* Migrate Form

* Migrate Field

* Missing Form import

* Migrate InputControl

* Migrate Checkbox

* Move InputControl and uncomment

* Fix small issues

* inor fix

* Fix import

* Fix stuff
This commit is contained in:
Tobias Skarhed 2020-04-14 18:52:56 +02:00 committed by GitHub
parent a28dfaf177
commit f15593684a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 238 additions and 233 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion';
import { selectThemeVariant, stylesFactory, useTheme } from '../../themes';
import Forms from '../Forms';
import { Label } from '../Forms/Label';
import { Icon } from '../Icon/Icon';
interface FieldConfigItemHeaderTitleProps {
@ -24,7 +24,7 @@ export const FieldConfigItemHeaderTitle: React.FC<FieldConfigItemHeaderTitleProp
return (
<div className={!transparent ? styles.headerWrapper : ''}>
<div className={styles.header}>
<Forms.Label description={description}>{title}</Forms.Label>
<Label description={description}>{title}</Label>
<div className={styles.remove} onClick={() => onRemove()} aria-label="FieldConfigItemHeaderTitle remove button">
<Icon name="trash-alt" />
</div>

View File

@ -1,5 +1,5 @@
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import { Checkbox } from './Checkbox';
import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import { Checkbox } from "./Checkbox";
<Meta title="MDX|Checkbox" component={Checkbox} />
@ -10,9 +10,9 @@ import { Checkbox } from './Checkbox';
```jsx
import { Forms } from '@grafana/ui';
<Forms.Checkbox value={true|false} label={...} description={...} onChange={...} />
<Checkbox value={true|false} label={...} description={...} onChange={...} />
```
### Props
<Props of={Checkbox} />
<Props of={Checkbox} />

View File

@ -1,5 +1,5 @@
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import { Field } from './Field';
import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import { Field } from "./Field";
<Meta title="MDX|Field" component={Field} />
@ -12,11 +12,11 @@ import { Field } from './Field';
```jsx
import { Forms } from '@grafana/ui';
<Forms.Field label={...} description={...}>
<Forms.Input id="userName" onChange={...}/>
</Forms.Field>
<Field label={...} description={...}>
<Input id="userName" onChange={...}/>
</Field>
```
### Props
<Props of={Field} />
<Props of={Field} />

View File

@ -23,18 +23,18 @@ const defaultUser: Partial<UserDTO> = {
// ...
}
<Forms.Form
<Form
defaultValues={defaultUser}
onSubmit={async (user: UserDTO) => await createUser(user)}
>{({register, errors}) => {
return (
<Forms.Field>
<Field>
<Forms.Input name="name" ref={register}/>
<Forms.Input type="email" name="email" ref={register({required: true})}/>
<Button type="submit">Create User</Button>
</Forms.Field>
</Field>
)
}}</Forms.Form>
}}</Form>
```
### Form API
@ -66,28 +66,28 @@ See [Validation](#validation) for examples on validation and validation rules.
#### `errors`
`errors` is an object that contains validation errors of the form. To show error message and invalid input indication in your form, wrap input element with `<Forms.Field ...>` component and pass `invalid` and `error` props to it:
`errors` is an object that contains validation errors of the form. To show error message and invalid input indication in your form, wrap input element with `<Field ...>` component and pass `invalid` and `error` props to it:
```jsx
<Forms.Field label="Name" invalid={!!errors.name} error="Name is required">
<Field label="Name" invalid={!!errors.name} error="Name is required">
<Forms.Input name="name" ref={register({ required: true })} />
</Forms.Field>
</Field>
```
#### `control`
By default `Form` component assumes form elements are uncontrolled (https://reactjs.org/docs/glossary.html#controlled-vs-uncontrolled-components).
There are some components like `RadioButton` or `Select` that are controlled-only and require some extra work. To make
them work with the form, you need to render those using `Forms.InputControl` component:
them work with the form, you need to render those using `InputControl` component:
```jsx
import { Forms } from '@grafana/ui';
import { Form, Field, InputControl } from '@grafana/ui';
// render function
<Forms.Form ...>{({register, errors, control}) => (
<Form ...>{({register, errors, control}) => (
<>
<Field label="RadioButtonExample">
<Forms.InputControl
<InputControl
{/* Render InputControl as controlled input (RadioButtonGroup) */}
as={RadioButtonGroup}
{/* Pass control exposed from Form render prop */}
@ -98,7 +98,7 @@ import { Forms } from '@grafana/ui';
</Field>
<Field label="SelectExample">
<Forms.InputControl
<InputControl
{/* Render InputControl as controlled input (Select) */}
as={Select}
{/* Pass control exposed from Form render prop */}
@ -109,10 +109,10 @@ import { Forms } from '@grafana/ui';
</Field>
</>
)}
</Forms.Form>
</Form>
```
Note that when using `Forms.InputControl`, it expects the name of the prop that handles input change to be called `onChange`.
Note that when using `InputControl`, it expects the name of the prop that handles input change to be called `onChange`.
If the property is named differently for any specific component, additional `onChangeName` prop has to be provided, specifying the name.
Additionally, the `onChange` arguments passed as an array. Check [react-hook-form docs](https://react-hook-form.com/api/#Controller)
for more prop options.
@ -128,7 +128,7 @@ const onSelectChange = ([value]) => {
}
<Field label="SelectExample">
<Forms.InputControl
<InputControl
as={DashboardPicker}
control={control}
name="select"
@ -160,7 +160,7 @@ const defaultValues: FormDto {
isAdmin: false,
}
<Forms.Form defaultValues={defaultValues} ...>{...}</Forms.Form>
<Form defaultValues={defaultValues} ...>{...}</Form>
```
```jsx
@ -176,13 +176,13 @@ const defaultValues: FormDto {
isAdmin: false,
}
<Forms.Form ...>{
<Form ...>{
({register}) => (
<>
<Forms.Input defaultValue={default.name} name="name" ref={register} />
</>
)}
</Forms.Form>
</Form>
```
### Validation
@ -192,10 +192,10 @@ Validation can be performed either synchronously or asynchronously. What's impor
#### Basic required example
```jsx
<Forms.Form ...>{
<Form ...>{
({register, errors}) => (
<>
<Forms.Field invalid={!!errors.name} error={errors.name && 'Name is required'}
<Field invalid={!!errors.name} error={errors.name && 'Name is required'}
<Forms.Input
defaultValue={default.name}
name="name"
@ -203,7 +203,7 @@ Validation can be performed either synchronously or asynchronously. What's impor
/>
</>
)}
</Forms.Form>
</Form>
```
#### Required with synchronous custom validation
@ -211,10 +211,10 @@ Validation can be performed either synchronously or asynchronously. What's impor
One important thing to note is that if you want to provide different error messages for different kind of validation errors you'll need to return a `string` instead of a `boolean`.
```jsx
<Forms.Form ...>{
<Form ...>{
({register, errors}) => (
<>
<Forms.Field invalid={!!errors.name} error={errors.name?.message }
<Field invalid={!!errors.name} error={errors.name?.message }
<Forms.Input
defaultValue={default.name}
name="name"
@ -227,7 +227,7 @@ One important thing to note is that if you want to provide different error messa
/>
</>
)}
</Forms.Form>
</Form>
```
#### Asynchronous validation
@ -252,10 +252,10 @@ validateAsync = (newValue: string) => {
```
```jsx
<Forms.Form ...>{
<Form ...>{
({register, errors}) => (
<>
<Forms.Field invalid={!!errors.name} error={errors.name?.message}
<Field invalid={!!errors.name} error={errors.name?.message}
<Forms.Input
defaultValue={default.name}
name="name"
@ -268,7 +268,7 @@ validateAsync = (newValue: string) => {
/>
</>
)}
</Forms.Form>
</Form>
```
### Props

View File

@ -12,7 +12,7 @@ import { Checkbox } from './Checkbox';
import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
import { Select } from '../Select/Select';
import Forms from './index';
import { InputControl } from '../InputControl';
import mdx from './Form.mdx';
import { ValidateResult } from 'react-hook-form';
import { boolean } from '@storybook/addon-knobs';
@ -103,11 +103,11 @@ const renderForm = (defaultValues?: Partial<FormDTO>) => (
</Field>
<Field label="RadioButton">
<Forms.InputControl name="radio" control={control} options={selectOptions} as={RadioButtonGroup} />
<InputControl name="radio" control={control} options={selectOptions} as={RadioButtonGroup} />
</Field>
<Field label="Select" invalid={!!errors.select} error="Select is required">
<Forms.InputControl
<InputControl
name="select"
control={control}
rules={{

View File

@ -1,5 +1,5 @@
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import { RadioButtonGroup } from './RadioButtonGroup';
import { Meta, Story, Preview, Props } from "@storybook/addon-docs/blocks";
import { RadioButtonGroup } from "./RadioButtonGroup";
<Meta title="MDX|RadioButtonGroup" component={RadioButtonGroup} />
@ -12,9 +12,9 @@ Use `RadioButtonGroup` if there are up to four options available. Otherwise use
### Usage
```jsx
import { Forms } from '@grafana/ui';
import { RadioButtonGroup } from '@grafana/ui';
<Forms.RadioButtonGroup options={...} value={...} onChange={...} />
<RadioButtonGroup options={...} value={...} onChange={...} />
```
#### Disabling options
@ -33,7 +33,7 @@ const options = [
const disabledOptions = ['prometheus', 'elastic'];
<Forms.RadioButtonGroup
<RadioButtonGroup
options={options}
disabledOptions={disabledOptions}
value={...}

View File

@ -1,4 +1,3 @@
import { Controller as InputControl } from 'react-hook-form';
import { getFormStyles } from './getFormStyles';
import { Label } from './Label';
import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
@ -6,7 +5,7 @@ import { Form } from './Form';
import { Field } from './Field';
import { Legend } from './Legend';
import { Checkbox } from './Checkbox';
import { TextArea } from '../TextArea/TextArea';
import { InputControl } from '../InputControl';
const Forms = {
RadioButtonGroup,
@ -17,7 +16,6 @@ const Forms = {
InputControl,
Checkbox,
Legend,
TextArea,
};
export default Forms;

View File

@ -0,0 +1 @@
export { Controller as InputControl } from 'react-hook-form';

View File

@ -3,7 +3,7 @@ import { TIME_FORMAT, TimeZone, isDateTime, TimeRange, DateTime, dateMath } from
import { stringToDateTimeType, isValidTimeString } from '../time';
import { mapStringsToTimeRange } from './mapper';
import { TimePickerCalendar } from './TimePickerCalendar';
import Forms from '../../Forms';
import { Field } from '../../Forms/Field';
import { Input } from '../../Input/Input';
import { Button } from '../../Button';
@ -66,7 +66,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
return (
<>
<Forms.Field label="From" invalid={from.invalid} error={errorMessage}>
<Field label="From" invalid={from.invalid} error={errorMessage}>
<Input
onClick={event => event.stopPropagation()}
onFocus={onFocus}
@ -74,8 +74,8 @@ export const TimeRangeForm: React.FC<Props> = props => {
addonAfter={icon}
value={from.value}
/>
</Forms.Field>
<Forms.Field label="To" invalid={to.invalid} error={errorMessage}>
</Field>
<Field label="To" invalid={to.invalid} error={errorMessage}>
<Input
onClick={event => event.stopPropagation()}
onFocus={onFocus}
@ -83,7 +83,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
addonAfter={icon}
value={to.value}
/>
</Forms.Field>
</Field>
<Button onClick={onApply}>Apply time range</Button>
<TimePickerCalendar

View File

@ -1,7 +1,7 @@
import React, { ChangeEvent } from 'react';
import { HorizontalGroup } from '../Layout/Layout';
import { Select } from '../index';
import Forms from '../Forms';
import { Field } from '../Forms/Field';
import { Input } from '../Input/Input';
import { MappingType, RangeMap, ValueMap, ValueMapping } from '@grafana/data';
import * as styleMixins from '../../themes/mixins';
@ -48,30 +48,30 @@ export const MappingRow: React.FC<Props> = ({ valueMapping, updateValueMapping,
return (
<>
<HorizontalGroup>
<Forms.Field label="From">
<Field label="From">
<Input type="number" defaultValue={(valueMapping as RangeMap).from!} onBlur={onMappingFromChange} />
</Forms.Field>
<Forms.Field label="To">
</Field>
<Field label="To">
<Input type="number" defaultValue={(valueMapping as RangeMap).to} onBlur={onMappingToChange} />
</Forms.Field>
</Field>
</HorizontalGroup>
<Forms.Field label="Text">
<Field label="Text">
<Input defaultValue={valueMapping.text} onBlur={onMappingTextChange} />
</Forms.Field>
</Field>
</>
);
}
return (
<>
<Forms.Field label="Value">
<Field label="Value">
<Input type="number" defaultValue={(valueMapping as ValueMap).value} onBlur={onMappingValueChange} />
</Forms.Field>
</Field>
<Forms.Field label="Text">
<Field label="Text">
<Input defaultValue={valueMapping.text} onBlur={onMappingTextChange} />
</Forms.Field>
</Field>
</>
);
};

View File

@ -124,9 +124,16 @@ export { FieldConfigItemHeaderTitle } from './FieldConfigs/FieldConfigItemHeader
// Next-gen forms
export { default as Forms } from './Forms';
export { Form } from './Forms/Form';
export { InputControl } from './InputControl';
export * from './Button';
export { ValuePicker } from './ValuePicker/ValuePicker';
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
export { getFormStyles } from './Forms/getFormStyles';
export { Label } from './Forms/Label';
export { Field } from './Forms/Field';
export { Legend } from './Forms/Legend';
export { default as resetSelectStyles } from './Select/resetSelectStyles';
export * from './Select/Select';
@ -137,6 +144,7 @@ export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
export { Input } from './Input/Input';
export { Switch } from './Forms/Switch';
export { Checkbox } from './Forms/Checkbox';
export { TextArea } from './TextArea/TextArea';
// Legacy forms

View File

@ -19,7 +19,7 @@ import {
valueMappingsOverrideProcessor,
ThresholdsMode,
} from '@grafana/data';
import { NumberValueEditor, Forms, StringValueEditor, Select } from '../components';
import { NumberValueEditor, RadioButtonGroup, StringValueEditor, Select } from '../components';
import { Switch } from '../components/Forms/Switch';
import { ValueMappingsValueEditor } from '../components/OptionsUI/mappings';
import { ThresholdsValueEditor } from '../components/OptionsUI/thresholds';
@ -248,7 +248,7 @@ export const getStandardOptionEditors = () => {
id: 'radio',
name: 'Radio',
description: 'Allows option selection',
editor: props => <Forms.RadioButtonGroup {...props} options={props.item.settings?.options} fullWidth />,
editor: props => <RadioButtonGroup {...props} options={props.item.settings?.options} fullWidth />,
};
const unit: StandardEditorsRegistryItem<string> = {

View File

@ -1,7 +1,7 @@
import React, { useCallback } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import { Forms, Button, Input } from '@grafana/ui';
import { Form, Button, Input, Field } from '@grafana/ui';
import { NavModel } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { StoreState } from '../../types';
@ -32,22 +32,22 @@ const UserCreatePage: React.FC<UserCreatePageProps> = ({ navModel, updateLocatio
<Page navModel={navModel}>
<Page.Contents>
<h1>Add new user</h1>
<Forms.Form onSubmit={onSubmit} validateOn="onBlur">
<Form onSubmit={onSubmit} validateOn="onBlur">
{({ register, errors }) => {
return (
<>
<Forms.Field label="Name" required invalid={!!errors.name} error={!!errors.name && 'Name is required'}>
<Field label="Name" required invalid={!!errors.name} error={!!errors.name && 'Name is required'}>
<Input name="name" size="md" ref={register({ required: true })} />
</Forms.Field>
</Field>
<Forms.Field label="E-mail">
<Field label="E-mail">
<Input name="email" size="md" ref={register} />
</Forms.Field>
</Field>
<Forms.Field label="Username">
<Field label="Username">
<Input name="login" size="md" ref={register} />
</Forms.Field>
<Forms.Field
</Field>
<Field
label="Password"
required
invalid={!!errors.password}
@ -61,12 +61,12 @@ const UserCreatePage: React.FC<UserCreatePageProps> = ({ navModel, updateLocatio
validate: value => value.trim() !== '' && value.length >= 4,
})}
/>
</Forms.Field>
</Field>
<Button type="submit">Create user</Button>
</>
);
}}
</Forms.Form>
</Form>
</Page.Contents>
</Page>
);

View File

@ -7,9 +7,9 @@ import {
withTheme,
ConfirmButton,
Button,
Forms,
HorizontalGroup,
Container,
Field,
} from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { UserOrg, Organization, OrgRole } from 'app/types';
@ -225,12 +225,12 @@ export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgMod
return (
<Modal className={styles.modal} title="Add to an organization" isOpen={isOpen} onDismiss={this.onCancel}>
<Forms.Field label="Organisation">
<Field label="Organisation">
<OrgPicker onSelected={this.onOrgSelect} />
</Forms.Field>
<Forms.Field label="Role">
</Field>
<Field label="Role">
<OrgRolePicker value={role} onChange={this.onOrgRoleChange} />
</Forms.Field>
</Field>
<Container padding="md">
<HorizontalGroup spacing="md" justify="center">
<Button variant="primary" onClick={this.onAddUserToOrg}>

View File

@ -8,7 +8,7 @@ import {
PanelPlugin,
SelectableValue,
} from '@grafana/data';
import { Forms, fieldMatchersUI, ValuePicker, useTheme } from '@grafana/ui';
import { fieldMatchersUI, ValuePicker, useTheme, Label, Field } from '@grafana/ui';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { OverrideEditor } from './OverrideEditor';
import { css } from 'emotion';
@ -156,13 +156,13 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
: (defaults as any)[item.path];
const label = (
<Forms.Label description={item.description} category={item.category?.slice(1)}>
<Label description={item.description} category={item.category?.slice(1)}>
{item.name}
</Forms.Label>
</Label>
);
return (
<Forms.Field label={label} key={`${item.id}/${item.isCustom}`}>
<Field label={label} key={`${item.id}/${item.isCustom}`}>
<item.editor
item={item}
value={value}
@ -172,7 +172,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(data, scope),
}}
/>
</Forms.Field>
</Field>
);
},
[config]

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data';
import { Button, stylesFactory, Icon, Forms } from '@grafana/ui';
import { Button, stylesFactory, Icon, RadioButtonGroup } from '@grafana/ui';
import { css, cx } from 'emotion';
import config from 'app/core/config';
import AutoSizer from 'react-virtualized-auto-sizer';
@ -209,7 +209,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
{this.renderTemplateVariables(styles)}
<div className="flex-grow-1" />
<div className={styles.toolbarItem}>
<Forms.RadioButtonGroup value={uiState.mode} options={displayModes} onChange={this.onDiplayModeChange} />
<RadioButtonGroup value={uiState.mode} options={displayModes} onChange={this.onDiplayModeChange} />
</div>
<div className={styles.toolbarItem}>
<DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} />

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { PanelOptionsEditorItem, PanelPlugin } from '@grafana/data';
import { set as lodashSet, get as lodashGet } from 'lodash';
import { Forms } from '@grafana/ui';
import { Label, Field } from '@grafana/ui';
import groupBy from 'lodash/groupBy';
import { OptionsGroup } from './OptionsGroup';
@ -33,18 +33,18 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
}
const label = (
<Forms.Label description={e.description} category={e.category?.slice(1)}>
<Label description={e.description} category={e.category?.slice(1)}>
{e.name}
</Forms.Label>
</Label>
);
return (
<Forms.Field label={label} key={`${e.id}/${j}`}>
<Field label={label} key={`${e.id}/${j}`}>
<e.editor
value={lodashGet(options, e.path)}
onChange={value => onOptionChange(e.path, value)}
item={e}
/>
</Forms.Field>
</Field>
);
})
.filter(e => e !== null);

View File

@ -1,7 +1,7 @@
import React, { FC, useMemo } from 'react';
import { PanelModel, DashboardModel } from '../../state';
import { SelectableValue, PanelPlugin, FieldConfigSource, PanelData } from '@grafana/data';
import { Forms, Switch, Select, DataLinksInlineEditor, Input, TextArea } from '@grafana/ui';
import { Switch, Select, DataLinksInlineEditor, Input, TextArea, RadioButtonGroup, Field } from '@grafana/ui';
import { OptionsGroup } from './OptionsGroup';
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { getVariables } from '../../../variables/state/selectors';
@ -41,18 +41,18 @@ export const PanelOptionsTab: FC<Props> = ({
// Fist common panel settings Title, description
elements.push(
<OptionsGroup title="Basic" key="basic settings">
<Forms.Field label="Panel title">
<Field label="Panel title">
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
</Forms.Field>
<Forms.Field label="Description" description="Panel description supports markdown and links.">
</Field>
<Field label="Description" description="Panel description supports markdown and links.">
<TextArea
defaultValue={panel.description}
onBlur={e => onPanelConfigChange('description', e.currentTarget.value)}
/>
</Forms.Field>
<Forms.Field label="Transparent" description="Display panel without a background.">
</Field>
<Field label="Transparent" description="Display panel without a background.">
<Switch value={panel.transparent} onChange={e => onPanelConfigChange('transparent', e.currentTarget.checked)} />
</Forms.Field>
</Field>
</OptionsGroup>
);
@ -101,7 +101,7 @@ export const PanelOptionsTab: FC<Props> = ({
elements.push(
<OptionsGroup title="Panel repeats" key="panel repeats" defaultToClosed={true}>
<Forms.Field
<Field
label="Repeat by variable"
description="Repeat this panel for each value in the selected variable.
This is not visible while in edit mode. You need to go back to dashboard and then update the variable or
@ -112,25 +112,25 @@ export const PanelOptionsTab: FC<Props> = ({
onChange={value => onPanelConfigChange('repeat', value.value)}
options={variableOptions}
/>
</Forms.Field>
</Field>
{panel.repeat && (
<Forms.Field label="Repeat direction">
<Forms.RadioButtonGroup
<Field label="Repeat direction">
<RadioButtonGroup
options={directionOptions}
value={panel.repeatDirection || 'h'}
onChange={value => onPanelConfigChange('repeatDirection', value)}
/>
</Forms.Field>
</Field>
)}
{panel.repeat && panel.repeatDirection === 'h' && (
<Forms.Field label="Max per row">
<Field label="Max per row">
<Select
options={maxPerRowOptions}
value={panel.maxPerRow}
onChange={value => onPanelConfigChange('maxPerRow', value.value)}
/>
</Forms.Field>
</Field>
)}
</OptionsGroup>
);

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Button, Forms, HorizontalGroup, Input, Switch } from '@grafana/ui';
import { Button, HorizontalGroup, Input, Switch, Form, Field, InputControl } from '@grafana/ui';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { SaveDashboardFormProps } from '../types';
@ -62,7 +62,7 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardFormProps & { isNew?: bo
};
return (
<Forms.Form
<Form
defaultValues={defaultValues}
onSubmit={async (data: SaveDashboardAsFormDTO) => {
const clone = getSaveAsDashboardClone(dashboard);
@ -86,7 +86,7 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardFormProps & { isNew?: bo
>
{({ register, control, errors, getValues }) => (
<>
<Forms.Field label="Dashboard name" invalid={!!errors.title} error={errors.title?.message}>
<Field label="Dashboard name" invalid={!!errors.title} error={errors.title?.message}>
<Input
name="title"
ref={register({
@ -95,9 +95,9 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardFormProps & { isNew?: bo
aria-label="Save dashboard title field"
autoFocus
/>
</Forms.Field>
<Forms.Field label="Folder">
<Forms.InputControl
</Field>
<Field label="Folder">
<InputControl
as={FolderPicker}
control={control}
name="$folder"
@ -107,10 +107,10 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardFormProps & { isNew?: bo
enableCreateNew
useNewForms
/>
</Forms.Field>
<Forms.Field label="Copy tags">
</Field>
<Field label="Copy tags">
<Switch name="copyTags" ref={register} />
</Forms.Field>
</Field>
<HorizontalGroup>
<Button type="submit" aria-label="Save dashboard button">
Save
@ -121,6 +121,6 @@ export const SaveDashboardAsForm: React.FC<SaveDashboardFormProps & { isNew?: bo
</HorizontalGroup>
</>
)}
</Forms.Form>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { Forms, Button, HorizontalGroup, TextArea } from '@grafana/ui';
import { Button, HorizontalGroup, TextArea, Form, Checkbox } from '@grafana/ui';
import { e2e } from '@grafana/e2e';
import { SaveDashboardFormProps } from '../types';
@ -15,7 +15,7 @@ export const SaveDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard,
const hasVariableChanged = useMemo(() => dashboard.hasVariableValuesChanged(), [dashboard]);
return (
<Forms.Form
<Form
onSubmit={async (data: SaveDashboardFormDTO) => {
const result = await onSubmit(dashboard.getSaveModelClone(data), data, dashboard);
if (result.status === 'success') {
@ -33,7 +33,7 @@ export const SaveDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard,
<>
<div className="gf-form-group">
{hasTimeChanged && (
<Forms.Checkbox
<Checkbox
label="Save current time range as dashboard default"
name="saveTimerange"
ref={register}
@ -41,7 +41,7 @@ export const SaveDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard,
/>
)}
{hasVariableChanged && (
<Forms.Checkbox
<Checkbox
label="Save current variable values as dashboard default"
name="saveVariables"
ref={register}
@ -63,6 +63,6 @@ export const SaveDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard,
</HorizontalGroup>
</>
)}
</Forms.Form>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import React from 'react';
import { css } from 'emotion';
import { stylesFactory, useTheme, Forms, Select, Button, Switch } from '@grafana/ui';
import { stylesFactory, useTheme, Select, Button, Switch, Field } from '@grafana/ui';
import { GrafanaTheme, AppEvents } from '@grafana/data';
import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
@ -73,7 +73,7 @@ export function RichHistorySettings(props: RichHistorySettingsProps) {
return (
<div className={styles.container}>
<Forms.Field
<Field
label="History time span"
description="Select the period of time for which Grafana will save your query history"
className="space-between"
@ -81,19 +81,19 @@ export function RichHistorySettings(props: RichHistorySettingsProps) {
<div className={styles.input}>
<Select value={selectedOption} options={retentionPeriodOptions} onChange={onChangeRetentionPeriod}></Select>
</div>
</Forms.Field>
<Forms.Field label="Default active tab" description=" " className="space-between">
</Field>
<Field label="Default active tab" description=" " className="space-between">
<div className={styles.switch}>
<Switch value={starredTabAsFirstTab} onChange={toggleStarredTabAsFirstTab}></Switch>
<div className={styles.label}>Change the default active tab from Query history to Starred</div>
</div>
</Forms.Field>
<Forms.Field label="Data source behaviour" description=" " className="space-between">
</Field>
<Field label="Data source behaviour" description=" " className="space-between">
<div className={styles.switch}>
<Switch value={activeDatasourceOnly} onChange={toggleactiveDatasourceOnly}></Switch>
<div className={styles.label}>Only show queries for data source currently active in Explore</div>
</div>
</Forms.Field>
</Field>
<div
className={css`
font-weight: ${theme.typography.weight.bold};

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { NavModel } from '@grafana/data';
import { Forms, Button, Input } from '@grafana/ui';
import { Button, Input, Form, Field } from '@grafana/ui';
import Page from 'app/core/components/Page/Page';
import { createNewFolder } from '../state/actions';
import { getNavModel } from 'app/core/selectors/navModel';
@ -47,10 +47,10 @@ export class NewDashboardsFolder extends PureComponent<Props> {
<Page navModel={this.props.navModel}>
<Page.Contents>
<h3>New Dashboard Folder</h3>
<Forms.Form defaultValues={initialFormModel} onSubmit={this.onSubmit}>
<Form defaultValues={initialFormModel} onSubmit={this.onSubmit}>
{({ register, errors }) => (
<>
<Forms.Field
<Field
label="Folder name"
invalid={!!errors.folderName}
error={errors.folderName && errors.folderName.message}
@ -62,11 +62,11 @@ export class NewDashboardsFolder extends PureComponent<Props> {
validate: async v => await this.validateFolderName(v),
})}
/>
</Forms.Field>
</Field>
<Button type="submit">Create</Button>
</>
)}
</Forms.Form>
</Form>
</Page.Contents>
</Page>
);

View File

@ -2,7 +2,7 @@ import React, { FormEvent, PureComponent } from 'react';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { css } from 'emotion';
import { AppEvents, NavModel } from '@grafana/data';
import { Button, Forms, stylesFactory, Input, TextArea } from '@grafana/ui';
import { Button, stylesFactory, Input, TextArea, Field, Form, Legend } from '@grafana/ui';
import Page from 'app/core/components/Page/Page';
import { ImportDashboardOverview } from './components/ImportDashboardOverview';
import { DashboardFileUpload } from './components/DashboardFileUpload';
@ -80,13 +80,10 @@ class DashboardImportUnConnected extends PureComponent<Props> {
<DashboardFileUpload onFileUpload={this.onFileUpload} />
</div>
<div className={styles.option}>
<Forms.Legend>Import via grafana.com</Forms.Legend>
<Forms.Form onSubmit={this.getGcomDashboard} defaultValues={{ gcomDashboard: '' }}>
<Legend>Import via grafana.com</Legend>
<Form onSubmit={this.getGcomDashboard} defaultValues={{ gcomDashboard: '' }}>
{({ register, errors }) => (
<Forms.Field
invalid={!!errors.gcomDashboard}
error={errors.gcomDashboard && errors.gcomDashboard.message}
>
<Field invalid={!!errors.gcomDashboard} error={errors.gcomDashboard && errors.gcomDashboard.message}>
<Input
size="md"
name="gcomDashboard"
@ -98,19 +95,16 @@ class DashboardImportUnConnected extends PureComponent<Props> {
})}
addonAfter={<Button type="submit">Load</Button>}
/>
</Forms.Field>
</Field>
)}
</Forms.Form>
</Form>
</div>
<div className={styles.option}>
<Forms.Legend>Import via panel json</Forms.Legend>
<Forms.Form onSubmit={this.getDashboardFromJson} defaultValues={{ dashboardJson: '' }}>
<Legend>Import via panel json</Legend>
<Form onSubmit={this.getDashboardFromJson} defaultValues={{ dashboardJson: '' }}>
{({ register, errors }) => (
<>
<Forms.Field
invalid={!!errors.dashboardJson}
error={errors.dashboardJson && errors.dashboardJson.message}
>
<Field invalid={!!errors.dashboardJson} error={errors.dashboardJson && errors.dashboardJson.message}>
<TextArea
name="dashboardJson"
ref={register({
@ -119,11 +113,11 @@ class DashboardImportUnConnected extends PureComponent<Props> {
})}
rows={10}
/>
</Forms.Field>
</Field>
<Button type="submit">Load</Button>
</>
)}
</Forms.Form>
</Form>
</div>
</>
);

View File

@ -1,5 +1,5 @@
import React, { FC, FormEvent } from 'react';
import { Forms, stylesFactory, useTheme } from '@grafana/ui';
import { getFormStyles, stylesFactory, useTheme } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion';
@ -8,7 +8,7 @@ interface Props {
}
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const buttonFormStyle = Forms.getFormStyles(theme, { variant: 'primary', invalid: false, size: 'md' }).button.button;
const buttonFormStyle = getFormStyles(theme, { variant: 'primary', invalid: false, size: 'md' }).button.button;
return {
fileUpload: css`
display: none;

View File

@ -1,5 +1,15 @@
import React, { FC, useEffect, useState } from 'react';
import { Button, Forms, FormAPI, FormsOnSubmit, HorizontalGroup, FormFieldErrors, Input } from '@grafana/ui';
import {
Button,
Forms,
FormAPI,
FormsOnSubmit,
HorizontalGroup,
FormFieldErrors,
Input,
Field,
InputControl,
} from '@grafana/ui';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import DataSourcePicker from 'app/core/components/Select/DataSourcePicker';
import { DashboardInput, DashboardInputs, DataSourceInput, ImportDashboardDTO } from '../state/reducers';
@ -42,7 +52,7 @@ export const ImportDashboardForm: FC<Props> = ({
return (
<>
<Forms.Legend>Options</Forms.Legend>
<Forms.Field label="Name" invalid={!!errors.title} error={errors.title && errors.title.message}>
<Field label="Name" invalid={!!errors.title} error={errors.title && errors.title.message}>
<Input
name="title"
size="md"
@ -52,17 +62,17 @@ export const ImportDashboardForm: FC<Props> = ({
validate: async (v: string) => await validateTitle(v, getValues().folderId),
})}
/>
</Forms.Field>
<Forms.Field label="Folder">
<Forms.InputControl
</Field>
<Field label="Folder">
<InputControl
as={FolderPicker}
name="folderId"
useNewForms
initialFolderId={initialFolderId}
control={control}
/>
</Forms.Field>
<Forms.Field
</Field>
<Field
label="Unique identifier (uid)"
description="The unique identifier (uid) of a dashboard can be used for uniquely identify a dashboard between multiple Grafana installs.
The uid allows having consistent URLs for accessing dashboards so changing the title of a dashboard will not break any
@ -87,18 +97,18 @@ export const ImportDashboardForm: FC<Props> = ({
/>
)}
</>
</Forms.Field>
</Field>
{inputs.dataSources &&
inputs.dataSources.map((input: DataSourceInput, index: number) => {
const dataSourceOption = `dataSources[${index}]`;
return (
<Forms.Field
<Field
label={input.label}
key={dataSourceOption}
invalid={errors.dataSources && !!errors.dataSources[index]}
error={errors.dataSources && errors.dataSources[index] && 'A data source is required'}
>
<Forms.InputControl
<InputControl
as={DataSourcePicker}
name={`${dataSourceOption}`}
datasources={input.options}
@ -106,14 +116,14 @@ export const ImportDashboardForm: FC<Props> = ({
placeholder={input.info}
rules={{ required: true }}
/>
</Forms.Field>
</Field>
);
})}
{inputs.constants &&
inputs.constants.map((input: DashboardInput, index) => {
const constantIndex = `constants[${index}]`;
return (
<Forms.Field
<Field
label={input.label}
error={errors.constants && errors.constants[index] && `${input.label} needs a value`}
invalid={errors.constants && !!errors.constants[index]}
@ -125,7 +135,7 @@ export const ImportDashboardForm: FC<Props> = ({
size="md"
defaultValue={input.value}
/>
</Forms.Field>
</Field>
);
})}
<HorizontalGroup>

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import { dateTime } from '@grafana/data';
import { Forms } from '@grafana/ui';
import { Forms, Form } from '@grafana/ui';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { ImportDashboardForm } from './ImportDashboardForm';
import { resetDashboard, saveDashboard } from '../state/actions';
@ -79,7 +79,7 @@ class ImportDashboardOverviewUnConnected extends PureComponent<Props, State> {
</table>
</div>
)}
<Forms.Form
<Form
onSubmit={this.onSubmit}
defaultValues={{ ...dashboard, constants: [], dataSources: [], folderId }}
validateOnMount
@ -100,7 +100,7 @@ class ImportDashboardOverviewUnConnected extends PureComponent<Props, State> {
initialFolderId={folderId}
/>
)}
</Forms.Form>
</Form>
</>
);
}

View File

@ -1,7 +1,7 @@
import React, { FC } from 'react';
import { getBackendSrv } from '@grafana/runtime';
import Page from 'app/core/components/Page/Page';
import { Forms, Button, Input } from '@grafana/ui';
import { Button, Input, Field, Form } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { StoreState } from 'app/types';
import { hot } from 'react-hot-loader';
@ -49,15 +49,11 @@ export const NewOrgPage: FC<PropsWithState> = ({ navModel }) => {
deployments.{' '}
</p>
<Forms.Form<CreateOrgFormDTO> onSubmit={createOrg}>
<Form<CreateOrgFormDTO> onSubmit={createOrg}>
{({ register, errors }) => {
return (
<>
<Forms.Field
label="Organization name"
invalid={!!errors.name}
error={errors.name && errors.name.message}
>
<Field label="Organization name" invalid={!!errors.name} error={errors.name && errors.name.message}>
<Input
size="md"
placeholder="Org. name"
@ -67,12 +63,12 @@ export const NewOrgPage: FC<PropsWithState> = ({ navModel }) => {
validate: async orgName => await validateOrg(orgName),
})}
/>
</Forms.Field>
</Field>
<Button type="submit">Create</Button>
</>
);
}}
</Forms.Form>
</Form>
</Page.Contents>
</Page>
);

View File

@ -1,5 +1,15 @@
import React, { FC } from 'react';
import { Forms, HorizontalGroup, Button, LinkButton, Input, Switch } from '@grafana/ui';
import {
HorizontalGroup,
Button,
LinkButton,
Input,
Switch,
RadioButtonGroup,
Form,
Field,
InputControl,
} from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { OrgRole } from 'app/types';
import { getBackendSrv } from '@grafana/runtime';
@ -45,26 +55,26 @@ export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
};
return (
<Forms.Form defaultValues={defaultValues} onSubmit={onSubmit}>
<Form defaultValues={defaultValues} onSubmit={onSubmit}>
{({ register, control, errors }) => {
return (
<>
<Forms.Field
<Field
invalid={!!errors.loginOrEmail}
error={!!errors.loginOrEmail && 'Email or Username is required'}
label="Email or Username"
>
<Input size="md" name="loginOrEmail" placeholder="email@example.com" ref={register({ required: true })} />
</Forms.Field>
<Forms.Field invalid={!!errors.name} label="Name">
</Field>
<Field invalid={!!errors.name} label="Name">
<Input size="md" name="name" placeholder="(optional)" ref={register} />
</Forms.Field>
<Forms.Field invalid={!!errors.role} label="Role">
<Forms.InputControl as={Forms.RadioButtonGroup} control={control} options={roles} name="role" />
</Forms.Field>
<Forms.Field invalid={!!errors.sendEmail} label="Send invite email">
</Field>
<Field invalid={!!errors.role} label="Role">
<InputControl as={RadioButtonGroup} control={control} options={roles} name="role" />
</Field>
<Field invalid={!!errors.sendEmail} label="Send invite email">
<Switch name="sendEmail" ref={register} />
</Forms.Field>
</Field>
<HorizontalGroup>
<Button type="submit">Submit</Button>
<LinkButton href={assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary">
@ -74,7 +84,7 @@ export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
</>
);
}}
</Forms.Form>
</Form>
);
};

View File

@ -1,5 +1,5 @@
import React, { FC } from 'react';
import { Forms, Button, LinkButton, Input } from '@grafana/ui';
import { Button, LinkButton, Input, Form, Field } from '@grafana/ui';
import { css } from 'emotion';
import { getConfig } from 'app/core/config';
@ -60,24 +60,24 @@ export const SignupForm: FC<Props> = props => {
};
return (
<Forms.Form defaultValues={defaultValues} onSubmit={onSubmit}>
<Form defaultValues={defaultValues} onSubmit={onSubmit}>
{({ register, errors }) => {
return (
<>
{verifyEmailEnabled && (
<Forms.Field label="Email verification code (sent to your email)">
<Field label="Email verification code (sent to your email)">
<Input name="code" size="md" ref={register} placeholder="Code" />
</Forms.Field>
</Field>
)}
{!autoAssignOrg && (
<Forms.Field label="Org. name">
<Field label="Org. name">
<Input size="md" name="orgName" placeholder="Org. name" ref={register} />
</Forms.Field>
</Field>
)}
<Forms.Field label="Your name">
<Field label="Your name">
<Input size="md" name="name" placeholder="(optional)" ref={register} />
</Forms.Field>
<Forms.Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}>
</Field>
<Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}>
<Input
size="md"
name="email"
@ -91,12 +91,8 @@ export const SignupForm: FC<Props> = props => {
},
})}
/>
</Forms.Field>
<Forms.Field
label="Password"
invalid={!!errors.password}
error={!!errors.password && errors.password.message}
>
</Field>
<Field label="Password" invalid={!!errors.password} error={!!errors.password && errors.password.message}>
<Input
size="md"
name="password"
@ -104,7 +100,7 @@ export const SignupForm: FC<Props> = props => {
placeholder="Password"
ref={register({ required: 'Password is required' })}
/>
</Forms.Field>
</Field>
<Button type="submit">Submit</Button>
<span className={buttonSpacing}>
@ -115,6 +111,6 @@ export const SignupForm: FC<Props> = props => {
</>
);
}}
</Forms.Form>
</Form>
);
};

View File

@ -1,6 +1,6 @@
import React, { FC, memo } from 'react';
import { css } from 'emotion';
import { Forms, stylesFactory } from '@grafana/ui';
import { Checkbox, stylesFactory } from '@grafana/ui';
interface Props {
checked?: boolean;
@ -13,7 +13,7 @@ export const SearchCheckbox: FC<Props> = memo(({ onClick, checked = false, edita
return editable ? (
<div onClick={onClick} className={styles.wrapper}>
<Forms.Checkbox value={checked} />
<Checkbox value={checked} />
</div>
) : null;
});

View File

@ -1,6 +1,6 @@
import React, { FC } from 'react';
import { css } from 'emotion';
import { Button, Select, Forms, stylesFactory, useTheme, HorizontalGroup } from '@grafana/ui';
import { Button, Select, Checkbox, stylesFactory, useTheme, HorizontalGroup } from '@grafana/ui';
import { GrafanaTheme, SelectableValue } from '@grafana/data';
type onSelectChange = (value: SelectableValue) => void;
@ -43,7 +43,7 @@ export const SearchResultsFilter: FC<Props> = ({
return (
<div className={styles.wrapper}>
<Forms.Checkbox value={allChecked} onChange={onSelectAllChanged} />
<Checkbox value={allChecked} onChange={onSelectAllChanged} />
{showActions ? (
<HorizontalGroup spacing="md">
<Button disabled={!canMove} onClick={moveTo} icon="exchange-alt" variant="secondary">

View File

@ -4,7 +4,7 @@ import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux';
import { StoreState } from 'app/types';
import { updateLocation } from 'app/core/actions';
import { UrlQueryValue, getBackendSrv } from '@grafana/runtime';
import { Forms, Button, Input } from '@grafana/ui';
import { Button, Input, Form, Field } from '@grafana/ui';
import { useAsync } from 'react-use';
import Page from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core';
@ -69,10 +69,10 @@ const SingupInvitedPageUnconnected: FC<DispatchProps & ConnectedProps> = ({ code
<br />
Please complete the following and choose a password to accept your invitation and continue:
</div>
<Forms.Form defaultValues={initFormModel} onSubmit={onSubmit}>
<Form defaultValues={initFormModel} onSubmit={onSubmit}>
{({ register, errors }) => (
<>
<Forms.Field invalid={!!errors.email} error={!!errors.email && errors.email.message} label="Email">
<Field invalid={!!errors.email} error={!!errors.email && errors.email.message} label="Email">
<Input
size="md"
placeholder="email@example.com"
@ -85,27 +85,19 @@ const SingupInvitedPageUnconnected: FC<DispatchProps & ConnectedProps> = ({ code
},
})}
/>
</Forms.Field>
<Forms.Field invalid={!!errors.name} error={!!errors.name && errors.name.message} label="Name">
</Field>
<Field invalid={!!errors.name} error={!!errors.name && errors.name.message} label="Name">
<Input size="md" placeholder="Name (optional)" name="name" ref={register} />
</Forms.Field>
<Forms.Field
invalid={!!errors.username}
error={!!errors.username && errors.username.message}
label="Username"
>
</Field>
<Field invalid={!!errors.username} error={!!errors.username && errors.username.message} label="Username">
<Input
size="md"
placeholder="Username"
name="username"
ref={register({ required: 'Username is required' })}
/>
</Forms.Field>
<Forms.Field
invalid={!!errors.password}
error={!!errors.password && errors.password.message}
label="Password"
>
</Field>
<Field invalid={!!errors.password} error={!!errors.password && errors.password.message} label="Password">
<Input
size="md"
type="password"
@ -113,12 +105,12 @@ const SingupInvitedPageUnconnected: FC<DispatchProps & ConnectedProps> = ({ code
name="password"
ref={register({ required: 'Password is required' })}
/>
</Forms.Field>
</Field>
<Button type="submit">Sign Up</Button>
</>
)}
</Forms.Form>
</Form>
</Page.Contents>
</Page>
);