refactor: scroll to first error when submitting a form
This commit is contained in:
parent
505f60a8a1
commit
d34bd53275
5 changed files with 35 additions and 4 deletions
10
app/client/hooks/use-scroll-to-form-error.ts
Normal file
10
app/client/hooks/use-scroll-to-form-error.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
export function useScrollToFormError() {
|
||||||
|
return useCallback(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const firstError = document.querySelector("[data-slot='form-message']");
|
||||||
|
firstError?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
}, 50);
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
|
||||||
import { Form } from "~/client/components/ui/form";
|
import { Form } from "~/client/components/ui/form";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import type { BackupSchedule, Volume } from "~/client/lib/types";
|
import type { BackupSchedule, Volume } from "~/client/lib/types";
|
||||||
|
|
@ -30,6 +31,8 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
defaultValues: backupScheduleToFormValues(initialValues),
|
defaultValues: backupScheduleToFormValues(initialValues),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const scrollToFirstError = useScrollToFormError();
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(data: InternalFormValues) => {
|
(data: InternalFormValues) => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -100,7 +103,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(handleSubmit)}
|
onSubmit={form.handleSubmit(handleSubmit, scrollToFirstError)}
|
||||||
className="grid gap-4 xl:grid-cols-[minmax(0,2.3fr)_minmax(320px,1fr)]"
|
className="grid gap-4 xl:grid-cols-[minmax(0,2.3fr)_minmax(320px,1fr)]"
|
||||||
id={formId}
|
id={formId}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import { Input } from "~/client/components/ui/input";
|
import { Input } from "~/client/components/ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||||
import { notificationConfigSchemaBase } from "~/schemas/notifications";
|
import { notificationConfigSchemaBase } from "~/schemas/notifications";
|
||||||
|
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
|
||||||
import {
|
import {
|
||||||
CustomForm,
|
CustomForm,
|
||||||
DiscordForm,
|
DiscordForm,
|
||||||
|
|
@ -116,11 +117,16 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
|
||||||
});
|
});
|
||||||
|
|
||||||
const { watch } = form;
|
const { watch } = form;
|
||||||
|
const scrollToFirstError = useScrollToFormError();
|
||||||
const watchedType = watch("type");
|
const watchedType = watch("type");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form id={formId} onSubmit={form.handleSubmit(onSubmit)} className={cn("space-y-4", className)}>
|
<form
|
||||||
|
id={formId}
|
||||||
|
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
|
||||||
|
className={cn("space-y-4", className)}
|
||||||
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { SecretInput } from "../../../components/ui/secret-input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../components/ui/select";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
|
||||||
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
||||||
|
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
|
||||||
import { COMPRESSION_MODES, repositoryConfigSchemaBase } from "~/schemas/restic";
|
import { COMPRESSION_MODES, repositoryConfigSchemaBase } from "~/schemas/restic";
|
||||||
import { Checkbox } from "../../../components/ui/checkbox";
|
import { Checkbox } from "../../../components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
|
|
@ -96,10 +97,15 @@ export const CreateRepositoryForm = ({
|
||||||
const [passwordMode, setPasswordMode] = useState<"default" | "custom">("default");
|
const [passwordMode, setPasswordMode] = useState<"default" | "custom">("default");
|
||||||
|
|
||||||
const { capabilities } = useSystemInfo();
|
const { capabilities } = useSystemInfo();
|
||||||
|
const scrollToFirstError = useScrollToFormError();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form id={formId} onSubmit={form.handleSubmit(onSubmit)} className={cn("space-y-4", className)}>
|
<form
|
||||||
|
id={formId}
|
||||||
|
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
|
||||||
|
className={cn("space-y-4", className)}
|
||||||
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import { volumeConfigSchemaBase } from "~/schemas/volumes";
|
||||||
import { testConnectionMutation } from "../../../api-client/@tanstack/react-query.gen";
|
import { testConnectionMutation } from "../../../api-client/@tanstack/react-query.gen";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
|
||||||
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
import { useSystemInfo } from "~/client/hooks/use-system-info";
|
||||||
|
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
|
||||||
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm, SFTPForm } from "./volume-forms";
|
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm, SFTPForm } from "./volume-forms";
|
||||||
|
|
||||||
export const formSchema = type({
|
export const formSchema = type({
|
||||||
|
|
@ -65,6 +66,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
||||||
const { getValues, watch } = form;
|
const { getValues, watch } = form;
|
||||||
|
|
||||||
const { capabilities } = useSystemInfo();
|
const { capabilities } = useSystemInfo();
|
||||||
|
const scrollToFirstError = useScrollToFormError();
|
||||||
const watchedBackend = watch("backend");
|
const watchedBackend = watch("backend");
|
||||||
|
|
||||||
const [testMessage, setTestMessage] = useState<{ success: boolean; message: string } | null>(null);
|
const [testMessage, setTestMessage] = useState<{ success: boolean; message: string } | null>(null);
|
||||||
|
|
@ -102,7 +104,11 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form id={formId} onSubmit={form.handleSubmit(onSubmit)} className={cn("space-y-4", className)}>
|
<form
|
||||||
|
id={formId}
|
||||||
|
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
|
||||||
|
className={cn("space-y-4", className)}
|
||||||
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue