diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b18eeac0350..3f107cb0bb9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -383,6 +383,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/core/components/GraphNG/ @grafana/dataviz-squad /public/app/core/components/TimeSeries/ @grafana/dataviz-squad /public/app/core/components/TimelineChart/ @grafana/dataviz-squad +/public/app/core/components/Form/ @grafana/grafana-frontend-platform /public/app/features/all.ts @grafana/grafana-frontend-platform /public/app/features/admin/ @grafana/identity-access-team diff --git a/public/app/core/components/Form/Form.tsx b/public/app/core/components/Form/Form.tsx new file mode 100644 index 00000000000..91ea061c975 --- /dev/null +++ b/public/app/core/components/Form/Form.tsx @@ -0,0 +1,62 @@ +import { css } from '@emotion/css'; +import React, { HTMLProps, useEffect } from 'react'; +import { + useForm, + Mode, + DefaultValues, + SubmitHandler, + FieldValues, + UseFormReturn, + FieldErrors, + FieldPath, +} from 'react-hook-form'; + +export type FormAPI = Omit, 'handleSubmit'> & { + errors: FieldErrors; +}; + +interface FormProps extends Omit, 'onSubmit' | 'children'> { + validateOn?: Mode; + validateOnMount?: boolean; + validateFieldsOnMount?: FieldPath | Array>; + defaultValues?: DefaultValues; + onSubmit: SubmitHandler; + children: (api: FormAPI) => React.ReactNode; + /** Sets max-width for container. Use it instead of setting individual widths on inputs.*/ + maxWidth?: number | 'none'; +} + +export function Form({ + defaultValues, + onSubmit, + validateOnMount = false, + validateFieldsOnMount, + children, + validateOn = 'onSubmit', + maxWidth = 600, + ...htmlProps +}: FormProps) { + const { handleSubmit, trigger, formState, ...rest } = useForm({ + mode: validateOn, + defaultValues, + }); + + useEffect(() => { + if (validateOnMount) { + trigger(validateFieldsOnMount); + } + }, [trigger, validateFieldsOnMount, validateOnMount]); + + return ( +
+ {children({ errors: formState.errors, formState, trigger, ...rest })} +
+ ); +} diff --git a/public/app/features/manage-dashboards/DashboardImportPage.tsx b/public/app/features/manage-dashboards/DashboardImportPage.tsx index 16266303ca3..2a60019e4ad 100644 --- a/public/app/features/manage-dashboards/DashboardImportPage.tsx +++ b/public/app/features/manage-dashboards/DashboardImportPage.tsx @@ -8,14 +8,11 @@ import { config, reportInteraction } from '@grafana/runtime'; import { Button, Field, - Form, - HorizontalGroup, Input, Spinner, stylesFactory, TextArea, Themeable2, - VerticalGroup, FileDropzone, withTheme2, DropzoneFile, @@ -23,8 +20,10 @@ import { LinkButton, TextLink, Label, + Stack, } from '@grafana/ui'; import appEvents from 'app/core/app_events'; +import { Form } from 'app/core/components/Form/Form'; import { Page } from 'app/core/components/Page/Page'; import { t, Trans } from 'app/core/internationalization'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; @@ -208,14 +207,14 @@ class UnthemedDashboardImport extends PureComponent { placeholder={JSON_PLACEHOLDER} /> - + Cancel - + )} @@ -236,11 +235,11 @@ class UnthemedDashboardImport extends PureComponent { {loadingState === LoadingState.Loading && ( - - + + - - + + )} {[LoadingState.Error, LoadingState.NotStarted].includes(loadingState) && this.renderImportForm()} {loadingState === LoadingState.Done && } diff --git a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx index b49f9c1ca4e..8cb50e12a57 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx @@ -1,18 +1,9 @@ import React, { useEffect, useState } from 'react'; +import { Controller, FieldErrors, UseFormReturn } from 'react-hook-form'; import { selectors } from '@grafana/e2e-selectors'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; -import { - Button, - Field, - FormAPI, - FormFieldErrors, - FormsOnSubmit, - HorizontalGroup, - Input, - InputControl, - Legend, -} from '@grafana/ui'; +import { Button, Field, FormFieldErrors, FormsOnSubmit, Stack, Input, Legend } from '@grafana/ui'; import { OldFolderPicker } from 'app/core/components/Select/OldFolderPicker'; import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; @@ -27,11 +18,11 @@ import { validateTitle, validateUid } from '../utils/validation'; import { ImportDashboardLibraryPanelsList } from './ImportDashboardLibraryPanelsList'; -interface Props extends Pick, 'register' | 'errors' | 'control' | 'getValues' | 'watch'> { +interface Props extends Pick, 'register' | 'control' | 'getValues' | 'watch'> { uidReset: boolean; inputs: DashboardInputs; initialFolderUid: string; - + errors: FieldErrors; onCancel: () => void; onUidReset: () => void; onSubmit: FormsOnSubmit; @@ -80,7 +71,7 @@ export const ImportDashboardForm = ({ /> - ( )} @@ -123,7 +114,7 @@ export const ImportDashboardForm = ({ invalid={errors.dataSources && !!errors.dataSources[index]} error={errors.dataSources && errors.dataSources[index] && 'A data source is required'} > - ( - + - + ); }; diff --git a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx index 5b9273cbe4e..7ead5823447 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx @@ -3,7 +3,8 @@ import { connect, ConnectedProps } from 'react-redux'; import { dateTimeFormat } from '@grafana/data'; import { locationService, reportInteraction } from '@grafana/runtime'; -import { Form, Legend } from '@grafana/ui'; +import { Box, Legend } from '@grafana/ui'; +import { Form } from 'app/core/components/Form/Form'; import { StoreState } from 'app/types'; import { clearLoadedDashboard, importDashboard } from '../state/actions'; @@ -64,7 +65,7 @@ class ImportDashboardOverviewUnConnected extends PureComponent { return ( <> {source === DashboardSource.Gcom && ( -
+
Importing dashboard from{' '} @@ -90,7 +91,7 @@ class ImportDashboardOverviewUnConnected extends PureComponent { -
+
)}