Skip to content

Commit

Permalink
Add FormSubmitButton that listens on FormState
Browse files Browse the repository at this point in the history
closes #28256

Signed-off-by: jchong <jhchong92@gmail.com>
  • Loading branch information
jhchong92 committed Apr 14, 2024
1 parent 4672366 commit 124164f
Show file tree
Hide file tree
Showing 12 changed files with 69 additions and 30 deletions.
8 changes: 4 additions & 4 deletions js/apps/admin-ui/src/authentication/form/CreateFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";

import { SelectControl } from "ui-shared";
import { FormSubmitButton, SelectControl } from "ui-shared";
import { adminClient } from "../../admin-client";
import { useAlerts } from "../../components/alert/Alerts";
import { FormAccess } from "../../components/form/FormAccess";
Expand All @@ -27,7 +27,7 @@ export default function CreateFlow() {
const { realm } = useRealm();
const { addAlert } = useAlerts();
const form = useForm<AuthenticationFlowRepresentation>();
const { handleSubmit } = form;
const { handleSubmit, formState } = form;

const onSubmit = async (formValues: AuthenticationFlowRepresentation) => {
const flow = { ...formValues, builtIn: false, topLevel: true };
Expand Down Expand Up @@ -76,9 +76,9 @@ export default function CreateFlow() {
}))}
/>
<ActionGroup>
<Button data-testid="create" type="submit">
<FormSubmitButton formState={formState} data-testid="create" type="submit" allowInvalid allowNonDirty>
{t("create")}
</Button>
</FormSubmitButton>
<Button
data-testid="cancel"
variant="link"
Expand Down
11 changes: 5 additions & 6 deletions js/apps/admin-ui/src/client-scopes/details/ScopeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useEffect } from "react";
import { FormProvider, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { SelectControl, TextAreaControl, TextControl } from "ui-shared";
import { FormSubmitButton, SelectControl, TextAreaControl, TextControl } from "ui-shared";

import { getProtocolName } from "../../clients/utils";
import { DefaultSwitchControl } from "../../components/SwitchControl";
Expand All @@ -32,7 +32,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
control,
handleSubmit,
setValue,
formState: { isDirty, isValid },
formState,
} = form;
const { realm } = useRealm();

Expand Down Expand Up @@ -190,13 +190,12 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
min={0}
/>
<ActionGroup>
<Button
<FormSubmitButton
formState={formState}
variant="primary"
type="submit"
isDisabled={!isDirty || !isValid}
>
{t("save")}
</Button>
</FormSubmitButton>
<Button
variant="link"
component={(props) => (
Expand Down
8 changes: 4 additions & 4 deletions js/apps/admin-ui/src/clients/import/ImportForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { TextControl } from "ui-shared";
import { FormSubmitButton, TextControl } from "ui-shared";

import { adminClient } from "../../admin-client";
import { useAlerts } from "../../components/alert/Alerts";
Expand All @@ -38,7 +38,7 @@ export default function ImportForm() {
const navigate = useNavigate();
const { realm } = useRealm();
const form = useForm<FormFields>();
const { handleSubmit, setValue } = form;
const { handleSubmit, setValue, formState } = form;
const [imported, setImported] = useState<ClientRepresentation>({});

const { addAlert, addError } = useAlerts();
Expand Down Expand Up @@ -118,9 +118,9 @@ export default function ImportForm() {
<TextControl name="protocol" label={t("type")} readOnly />
<CapabilityConfig unWrap={true} />
<ActionGroup>
<Button variant="primary" type="submit">
<FormSubmitButton formState={formState} variant="primary" allowInvalid allowNonDirty>
{t("save")}
</Button>
</FormSubmitButton>
<Button
variant="link"
component={(props) => (
Expand Down
1 change: 1 addition & 0 deletions js/apps/admin-ui/src/clients/roles/CreateClientRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default function CreateClientRole() {
return (
<FormProvider {...form}>
<RoleForm
form={form}
onSubmit={onSubmit}
cancelLink={toClient({
realm,
Expand Down
10 changes: 6 additions & 4 deletions js/apps/admin-ui/src/components/role-form/RoleForm.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { ActionGroup, Button, PageSection } from "@patternfly/react-core";
import { SubmitHandler, useFormContext, useWatch } from "react-hook-form";
import { SubmitHandler, UseFormReturn, useFormContext, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link, To } from "react-router-dom";
import { TextAreaControl, TextControl } from "ui-shared";
import { FormSubmitButton, TextAreaControl, TextControl } from "ui-shared";

import { FormAccess } from "../form/FormAccess";
import { AttributeForm } from "../key-value-form/AttributeForm";
import { ViewHeader } from "../view-header/ViewHeader";

export type RoleFormProps = {
form: UseFormReturn;
onSubmit: SubmitHandler<AttributeForm>;
cancelLink: To;
role: "manage-realm" | "manage-clients";
editMode: boolean;
};

export const RoleForm = ({
form: { formState },
onSubmit,
cancelLink,
role,
Expand Down Expand Up @@ -65,9 +67,9 @@ export const RoleForm = ({
isDisabled={roleName?.includes("default-roles") ?? false}
/>
<ActionGroup>
<Button data-testid="save" type="submit" variant="primary">
<FormSubmitButton formState={formState} data-testid="save" variant="primary">
{t("save")}
</Button>
</FormSubmitButton>
<Button
data-testid="cancel"
variant="link"
Expand Down
10 changes: 5 additions & 5 deletions js/apps/admin-ui/src/groups/GroupsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@patternfly/react-core";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { TextControl } from "ui-shared";
import { FormSubmitButton, TextControl } from "ui-shared";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";

Expand All @@ -32,7 +32,7 @@ export const GroupsModal = ({
const form = useForm({
defaultValues: { name: rename?.name },
});
const { handleSubmit } = form;
const { handleSubmit, formState } = form;

const submitForm = async (group: GroupRepresentation) => {
group.name = group.name?.trim();
Expand Down Expand Up @@ -69,15 +69,15 @@ export const GroupsModal = ({
isOpen={true}
onClose={handleModalToggle}
actions={[
<Button
<FormSubmitButton
formState={formState}
data-testid={`${rename ? "rename" : "create"}Group`}
key="confirm"
variant="primary"
type="submit"
form="group-form"
>
{t(rename ? "rename" : "create")}
</Button>,
</FormSubmitButton>,
<Button
id="modal-cancel"
data-testid="cancel"
Expand Down
1 change: 1 addition & 0 deletions js/apps/admin-ui/src/realm-roles/CreateRealmRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default function CreateRealmRole() {
return (
<FormProvider {...form}>
<RoleForm
form={form}
onSubmit={onSubmit}
cancelLink={toRealmRoles({ realm })}
role="manage-realm"
Expand Down
1 change: 1 addition & 0 deletions js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ export default function RealmRoleTabs() {
{...detailsTab}
>
<RoleForm
form={form}
onSubmit={onSubmit}
role={clientRoleMatch ? "manage-clients" : "manage-realm"}
cancelLink={
Expand Down
8 changes: 4 additions & 4 deletions js/apps/admin-ui/src/realm/add/NewRealmForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { TextControl } from "ui-shared";
import { FormSubmitButton, TextControl } from "ui-shared";

import { adminClient } from "../../admin-client";
import { DefaultSwitchControl } from "../../components/SwitchControl";
Expand All @@ -29,7 +29,7 @@ export default function NewRealmForm() {
mode: "onChange",
});

const { handleSubmit, setValue } = form;
const { handleSubmit, setValue, formState } = form;

const handleFileChange = (obj?: object) => {
const defaultRealm = { id: "", realm: "", enabled: true };
Expand Down Expand Up @@ -80,9 +80,9 @@ export default function NewRealmForm() {
defaultValue={true}
/>
<ActionGroup>
<Button variant="primary" type="submit">
<FormSubmitButton variant="primary" formState={formState} allowInvalid allowNonDirty>
{t("create")}
</Button>
</FormSubmitButton>
<Button variant="link" onClick={() => navigate(-1)}>
{t("cancel")}
</Button>
Expand Down
7 changes: 4 additions & 3 deletions js/apps/admin-ui/src/user/UserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const UserForm = ({
watch,
control,
reset,
formState: { errors },
formState: { errors, isSubmitting },
} = form;
const watchUsernameInput = watch("username");
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
Expand Down Expand Up @@ -328,9 +328,10 @@ export const UserForm = ({
<Button
data-testid={!user?.id ? "create-user" : "save-user"}
isDisabled={
!user?.id &&
(!user?.id &&
!watchUsernameInput &&
realm.registrationEmailAsUsername === false
realm.registrationEmailAsUsername === false) ||
isSubmitting
}
variant="primary"
type="submit"
Expand Down
33 changes: 33 additions & 0 deletions js/libs/ui-shared/src/buttons/FormSubmitButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Button, ButtonProps } from "@patternfly/react-core";
import { PropsWithChildren } from "react";
import { FieldValues, FormState } from "react-hook-form";

export type FormSubmitButtonProps = ButtonProps & {
formState: FormState<FieldValues>;
allowNonDirty?: boolean
allowInvalid?: boolean
};

const isSubmittable = (formState: FormState<FieldValues>, allowNonDirty: boolean, allowInvalid: boolean) => {
return (
(formState.isValid || allowInvalid) &&
(formState.isDirty || allowNonDirty) &&
!formState.isLoading &&
!formState.isValidating &&
!formState.isSubmitting
);
};

export const FormSubmitButton = ({
formState,
allowInvalid = false,
allowNonDirty = false,
children,
...rest
}: PropsWithChildren<FormSubmitButtonProps>) => {
return (
<Button {...rest} type="submit" isDisabled={!isSubmittable(formState, allowNonDirty, allowInvalid)}>
{children}
</Button>
);
};
1 change: 1 addition & 0 deletions js/libs/ui-shared/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export {
export { IconMapper } from "./icons/IconMapper";
export { FormPanel } from "./scroll-form/FormPanel";
export { ScrollForm, mainPageContentId } from "./scroll-form/ScrollForm";
export { FormSubmitButton } from "./buttons/FormSubmitButton";
export { UserProfileFields } from "./user-profile/UserProfileFields";
export {
beerify,
Expand Down

0 comments on commit 124164f

Please sign in to comment.