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

View File

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

View File

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

View File

@ -23,18 +23,18 @@ const defaultUser: Partial<UserDTO> = {
// ... // ...
} }
<Forms.Form <Form
defaultValues={defaultUser} defaultValues={defaultUser}
onSubmit={async (user: UserDTO) => await createUser(user)} onSubmit={async (user: UserDTO) => await createUser(user)}
>{({register, errors}) => { >{({register, errors}) => {
return ( return (
<Forms.Field> <Field>
<Forms.Input name="name" ref={register}/> <Forms.Input name="name" ref={register}/>
<Forms.Input type="email" name="email" ref={register({required: true})}/> <Forms.Input type="email" name="email" ref={register({required: true})}/>
<Button type="submit">Create User</Button> <Button type="submit">Create User</Button>
</Forms.Field> </Field>
) )
}}</Forms.Form> }}</Form>
``` ```
### Form API ### Form API
@ -66,28 +66,28 @@ See [Validation](#validation) for examples on validation and validation rules.
#### `errors` #### `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 ```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.Input name="name" ref={register({ required: true })} />
</Forms.Field> </Field>
``` ```
#### `control` #### `control`
By default `Form` component assumes form elements are uncontrolled (https://reactjs.org/docs/glossary.html#controlled-vs-uncontrolled-components). 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 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 ```jsx
import { Forms } from '@grafana/ui'; import { Form, Field, InputControl } from '@grafana/ui';
// render function // render function
<Forms.Form ...>{({register, errors, control}) => ( <Form ...>{({register, errors, control}) => (
<> <>
<Field label="RadioButtonExample"> <Field label="RadioButtonExample">
<Forms.InputControl <InputControl
{/* Render InputControl as controlled input (RadioButtonGroup) */} {/* Render InputControl as controlled input (RadioButtonGroup) */}
as={RadioButtonGroup} as={RadioButtonGroup}
{/* Pass control exposed from Form render prop */} {/* Pass control exposed from Form render prop */}
@ -98,7 +98,7 @@ import { Forms } from '@grafana/ui';
</Field> </Field>
<Field label="SelectExample"> <Field label="SelectExample">
<Forms.InputControl <InputControl
{/* Render InputControl as controlled input (Select) */} {/* Render InputControl as controlled input (Select) */}
as={Select} as={Select}
{/* Pass control exposed from Form render prop */} {/* Pass control exposed from Form render prop */}
@ -109,10 +109,10 @@ import { Forms } from '@grafana/ui';
</Field> </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. 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) Additionally, the `onChange` arguments passed as an array. Check [react-hook-form docs](https://react-hook-form.com/api/#Controller)
for more prop options. for more prop options.
@ -128,7 +128,7 @@ const onSelectChange = ([value]) => {
} }
<Field label="SelectExample"> <Field label="SelectExample">
<Forms.InputControl <InputControl
as={DashboardPicker} as={DashboardPicker}
control={control} control={control}
name="select" name="select"
@ -160,7 +160,7 @@ const defaultValues: FormDto {
isAdmin: false, isAdmin: false,
} }
<Forms.Form defaultValues={defaultValues} ...>{...}</Forms.Form> <Form defaultValues={defaultValues} ...>{...}</Form>
``` ```
```jsx ```jsx
@ -176,13 +176,13 @@ const defaultValues: FormDto {
isAdmin: false, isAdmin: false,
} }
<Forms.Form ...>{ <Form ...>{
({register}) => ( ({register}) => (
<> <>
<Forms.Input defaultValue={default.name} name="name" ref={register} /> <Forms.Input defaultValue={default.name} name="name" ref={register} />
</> </>
)} )}
</Forms.Form> </Form>
``` ```
### Validation ### Validation
@ -192,10 +192,10 @@ Validation can be performed either synchronously or asynchronously. What's impor
#### Basic required example #### Basic required example
```jsx ```jsx
<Forms.Form ...>{ <Form ...>{
({register, errors}) => ( ({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 <Forms.Input
defaultValue={default.name} defaultValue={default.name}
name="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 #### 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`. 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 ```jsx
<Forms.Form ...>{ <Form ...>{
({register, errors}) => ( ({register, errors}) => (
<> <>
<Forms.Field invalid={!!errors.name} error={errors.name?.message } <Field invalid={!!errors.name} error={errors.name?.message }
<Forms.Input <Forms.Input
defaultValue={default.name} defaultValue={default.name}
name="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 #### Asynchronous validation
@ -252,10 +252,10 @@ validateAsync = (newValue: string) => {
``` ```
```jsx ```jsx
<Forms.Form ...>{ <Form ...>{
({register, errors}) => ( ({register, errors}) => (
<> <>
<Forms.Field invalid={!!errors.name} error={errors.name?.message} <Field invalid={!!errors.name} error={errors.name?.message}
<Forms.Input <Forms.Input
defaultValue={default.name} defaultValue={default.name}
name="name" name="name"
@ -268,7 +268,7 @@ validateAsync = (newValue: string) => {
/> />
</> </>
)} )}
</Forms.Form> </Form>
``` ```
### Props ### Props

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { Controller as InputControl } from 'react-hook-form';
import { getFormStyles } from './getFormStyles'; import { getFormStyles } from './getFormStyles';
import { Label } from './Label'; import { Label } from './Label';
import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup'; import { RadioButtonGroup } from './RadioButtonGroup/RadioButtonGroup';
@ -6,7 +5,7 @@ import { Form } from './Form';
import { Field } from './Field'; import { Field } from './Field';
import { Legend } from './Legend'; import { Legend } from './Legend';
import { Checkbox } from './Checkbox'; import { Checkbox } from './Checkbox';
import { TextArea } from '../TextArea/TextArea'; import { InputControl } from '../InputControl';
const Forms = { const Forms = {
RadioButtonGroup, RadioButtonGroup,
@ -17,7 +16,6 @@ const Forms = {
InputControl, InputControl,
Checkbox, Checkbox,
Legend, Legend,
TextArea,
}; };
export default Forms; 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 { stringToDateTimeType, isValidTimeString } from '../time';
import { mapStringsToTimeRange } from './mapper'; import { mapStringsToTimeRange } from './mapper';
import { TimePickerCalendar } from './TimePickerCalendar'; import { TimePickerCalendar } from './TimePickerCalendar';
import Forms from '../../Forms'; import { Field } from '../../Forms/Field';
import { Input } from '../../Input/Input'; import { Input } from '../../Input/Input';
import { Button } from '../../Button'; import { Button } from '../../Button';
@ -66,7 +66,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
return ( return (
<> <>
<Forms.Field label="From" invalid={from.invalid} error={errorMessage}> <Field label="From" invalid={from.invalid} error={errorMessage}>
<Input <Input
onClick={event => event.stopPropagation()} onClick={event => event.stopPropagation()}
onFocus={onFocus} onFocus={onFocus}
@ -74,8 +74,8 @@ export const TimeRangeForm: React.FC<Props> = props => {
addonAfter={icon} addonAfter={icon}
value={from.value} value={from.value}
/> />
</Forms.Field> </Field>
<Forms.Field label="To" invalid={to.invalid} error={errorMessage}> <Field label="To" invalid={to.invalid} error={errorMessage}>
<Input <Input
onClick={event => event.stopPropagation()} onClick={event => event.stopPropagation()}
onFocus={onFocus} onFocus={onFocus}
@ -83,7 +83,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
addonAfter={icon} addonAfter={icon}
value={to.value} value={to.value}
/> />
</Forms.Field> </Field>
<Button onClick={onApply}>Apply time range</Button> <Button onClick={onApply}>Apply time range</Button>
<TimePickerCalendar <TimePickerCalendar

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin } from '@grafana/data'; 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 { css, cx } from 'emotion';
import config from 'app/core/config'; import config from 'app/core/config';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
@ -209,7 +209,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
{this.renderTemplateVariables(styles)} {this.renderTemplateVariables(styles)}
<div className="flex-grow-1" /> <div className="flex-grow-1" />
<div className={styles.toolbarItem}> <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>
<div className={styles.toolbarItem}> <div className={styles.toolbarItem}>
<DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} /> <DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import React, { FC, FormEvent } from 'react'; 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 { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion'; import { css } from 'emotion';
@ -8,7 +8,7 @@ interface Props {
} }
const getStyles = stylesFactory((theme: GrafanaTheme) => { 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 { return {
fileUpload: css` fileUpload: css`
display: none; display: none;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,15 @@
import React, { FC } from 'react'; 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 { getConfig } from 'app/core/config';
import { OrgRole } from 'app/types'; import { OrgRole } from 'app/types';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
@ -45,26 +55,26 @@ export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
}; };
return ( return (
<Forms.Form defaultValues={defaultValues} onSubmit={onSubmit}> <Form defaultValues={defaultValues} onSubmit={onSubmit}>
{({ register, control, errors }) => { {({ register, control, errors }) => {
return ( return (
<> <>
<Forms.Field <Field
invalid={!!errors.loginOrEmail} invalid={!!errors.loginOrEmail}
error={!!errors.loginOrEmail && 'Email or Username is required'} error={!!errors.loginOrEmail && 'Email or Username is required'}
label="Email or Username" label="Email or Username"
> >
<Input size="md" name="loginOrEmail" placeholder="email@example.com" ref={register({ required: true })} /> <Input size="md" name="loginOrEmail" placeholder="email@example.com" ref={register({ required: true })} />
</Forms.Field> </Field>
<Forms.Field invalid={!!errors.name} label="Name"> <Field invalid={!!errors.name} label="Name">
<Input size="md" name="name" placeholder="(optional)" ref={register} /> <Input size="md" name="name" placeholder="(optional)" ref={register} />
</Forms.Field> </Field>
<Forms.Field invalid={!!errors.role} label="Role"> <Field invalid={!!errors.role} label="Role">
<Forms.InputControl as={Forms.RadioButtonGroup} control={control} options={roles} name="role" /> <InputControl as={RadioButtonGroup} control={control} options={roles} name="role" />
</Forms.Field> </Field>
<Forms.Field invalid={!!errors.sendEmail} label="Send invite email"> <Field invalid={!!errors.sendEmail} label="Send invite email">
<Switch name="sendEmail" ref={register} /> <Switch name="sendEmail" ref={register} />
</Forms.Field> </Field>
<HorizontalGroup> <HorizontalGroup>
<Button type="submit">Submit</Button> <Button type="submit">Submit</Button>
<LinkButton href={assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary"> <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 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 { css } from 'emotion';
import { getConfig } from 'app/core/config'; import { getConfig } from 'app/core/config';
@ -60,24 +60,24 @@ export const SignupForm: FC<Props> = props => {
}; };
return ( return (
<Forms.Form defaultValues={defaultValues} onSubmit={onSubmit}> <Form defaultValues={defaultValues} onSubmit={onSubmit}>
{({ register, errors }) => { {({ register, errors }) => {
return ( return (
<> <>
{verifyEmailEnabled && ( {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" /> <Input name="code" size="md" ref={register} placeholder="Code" />
</Forms.Field> </Field>
)} )}
{!autoAssignOrg && ( {!autoAssignOrg && (
<Forms.Field label="Org. name"> <Field label="Org. name">
<Input size="md" name="orgName" placeholder="Org. name" ref={register} /> <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} /> <Input size="md" name="name" placeholder="(optional)" ref={register} />
</Forms.Field> </Field>
<Forms.Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}> <Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}>
<Input <Input
size="md" size="md"
name="email" name="email"
@ -91,12 +91,8 @@ export const SignupForm: FC<Props> = props => {
}, },
})} })}
/> />
</Forms.Field> </Field>
<Forms.Field <Field label="Password" invalid={!!errors.password} error={!!errors.password && errors.password.message}>
label="Password"
invalid={!!errors.password}
error={!!errors.password && errors.password.message}
>
<Input <Input
size="md" size="md"
name="password" name="password"
@ -104,7 +100,7 @@ export const SignupForm: FC<Props> = props => {
placeholder="Password" placeholder="Password"
ref={register({ required: 'Password is required' })} ref={register({ required: 'Password is required' })}
/> />
</Forms.Field> </Field>
<Button type="submit">Submit</Button> <Button type="submit">Submit</Button>
<span className={buttonSpacing}> <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 React, { FC, memo } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { Forms, stylesFactory } from '@grafana/ui'; import { Checkbox, stylesFactory } from '@grafana/ui';
interface Props { interface Props {
checked?: boolean; checked?: boolean;
@ -13,7 +13,7 @@ export const SearchCheckbox: FC<Props> = memo(({ onClick, checked = false, edita
return editable ? ( return editable ? (
<div onClick={onClick} className={styles.wrapper}> <div onClick={onClick} className={styles.wrapper}>
<Forms.Checkbox value={checked} /> <Checkbox value={checked} />
</div> </div>
) : null; ) : null;
}); });

View File

@ -1,6 +1,6 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { css } from 'emotion'; 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'; import { GrafanaTheme, SelectableValue } from '@grafana/data';
type onSelectChange = (value: SelectableValue) => void; type onSelectChange = (value: SelectableValue) => void;
@ -43,7 +43,7 @@ export const SearchResultsFilter: FC<Props> = ({
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<Forms.Checkbox value={allChecked} onChange={onSelectAllChanged} /> <Checkbox value={allChecked} onChange={onSelectAllChanged} />
{showActions ? ( {showActions ? (
<HorizontalGroup spacing="md"> <HorizontalGroup spacing="md">
<Button disabled={!canMove} onClick={moveTo} icon="exchange-alt" variant="secondary"> <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 { StoreState } from 'app/types';
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { UrlQueryValue, getBackendSrv } from '@grafana/runtime'; 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 { useAsync } from 'react-use';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
@ -69,10 +69,10 @@ const SingupInvitedPageUnconnected: FC<DispatchProps & ConnectedProps> = ({ code
<br /> <br />
Please complete the following and choose a password to accept your invitation and continue: Please complete the following and choose a password to accept your invitation and continue:
</div> </div>
<Forms.Form defaultValues={initFormModel} onSubmit={onSubmit}> <Form defaultValues={initFormModel} onSubmit={onSubmit}>
{({ register, errors }) => ( {({ 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 <Input
size="md" size="md"
placeholder="email@example.com" placeholder="email@example.com"
@ -85,27 +85,19 @@ const SingupInvitedPageUnconnected: FC<DispatchProps & ConnectedProps> = ({ code
}, },
})} })}
/> />
</Forms.Field> </Field>
<Forms.Field invalid={!!errors.name} error={!!errors.name && errors.name.message} label="Name"> <Field invalid={!!errors.name} error={!!errors.name && errors.name.message} label="Name">
<Input size="md" placeholder="Name (optional)" name="name" ref={register} /> <Input size="md" placeholder="Name (optional)" name="name" ref={register} />
</Forms.Field> </Field>
<Forms.Field <Field invalid={!!errors.username} error={!!errors.username && errors.username.message} label="Username">
invalid={!!errors.username}
error={!!errors.username && errors.username.message}
label="Username"
>
<Input <Input
size="md" size="md"
placeholder="Username" placeholder="Username"
name="username" name="username"
ref={register({ required: 'Username is required' })} ref={register({ required: 'Username is required' })}
/> />
</Forms.Field> </Field>
<Forms.Field <Field invalid={!!errors.password} error={!!errors.password && errors.password.message} label="Password">
invalid={!!errors.password}
error={!!errors.password && errors.password.message}
label="Password"
>
<Input <Input
size="md" size="md"
type="password" type="password"
@ -113,12 +105,12 @@ const SingupInvitedPageUnconnected: FC<DispatchProps & ConnectedProps> = ({ code
name="password" name="password"
ref={register({ required: 'Password is required' })} ref={register({ required: 'Password is required' })}
/> />
</Forms.Field> </Field>
<Button type="submit">Sign Up</Button> <Button type="submit">Sign Up</Button>
</> </>
)} )}
</Forms.Form> </Form>
</Page.Contents> </Page.Contents>
</Page> </Page>
); );