Refactor form components

This commit is contained in:
Kazi 2025-08-28 22:28:21 +06:00
parent ee988c7774
commit e48fd206e2
15 changed files with 63 additions and 83 deletions

View File

@ -33,3 +33,8 @@
.form > :has(label):not(:last-of-type) {
border-block-end: 1px solid var(--border-color);
}
.form > [type="submit"] {
display: block;
margin-block-start: var(--spacingXL);
}

View File

@ -0,0 +1,21 @@
import styles from "./Form.module.css";
export function Form(props: React.ComponentProps<"form">) {
return <form {...props} className={`${styles.form} ${props.className}`} />;
}
export function FormContainer({ children }: { children: React.ReactNode }) {
return <div className={`${styles.container} flow`}>{children}</div>;
}
export function FormCard({
as,
children,
}: {
as?: React.ElementType;
children: React.ReactNode;
}) {
const Wrapper = as ?? "section";
return <Wrapper className={styles.card}>{children}</Wrapper>;
}

View File

@ -1,52 +0,0 @@
.container {
--border-color: var(--colorNeutralStroke2);
--flow-space: 2em;
width: 74ch;
margin: 0 auto;
padding-block-end: --spacing;
}
.card {
display: "flex";
flex-direction: "column";
background: var(--colorNeutralCardBackground);
padding: var(--spacingL);
border-radius: var(--borderRadiusLarge);
border-width: 1px;
border-style: solid;
border-color: var(--border-color);
}
.form > :has(label) {
padding-block: 0.5em;
}
.form > :first-child:has(label) {
padding-block-start: 0em;
}
.form > :last-child:has(label) {
padding-block-end: 0em;
}
.form > :has(label):not(:last-of-type) {
border-block-end: 1px solid var(--border-color);
}
.form > [type="submit"] {
display: block;
margin-block-start: var(--spacingXL);
}
.single-field {
display: grid;
grid-template-columns: 1fr auto;
align-items: baseline;
gap: var(--gutter);
}
.field-description {
color: var(--colorNeutralForeground2);
margin-block-start: var(--spacingXS);
}

View File

@ -8,15 +8,15 @@ import type {
SettingState,
StringBoolSetting,
} from "../../../api/urbackupserver";
import styles from "./Mail.module.css";
import { CheckboxFieldUncontrolled } from "../Fields/CheckboxField";
import { CheckboxFieldUncontrolled } from "../Form/CheckboxField";
import { mailFields, mailFormSchema, MailSettingsKey } from "./mailForm";
import { TextFieldUncontrolled } from "../Fields/TextField";
import { TextFieldUncontrolled } from "../Form/TextField";
import { Banner } from "../../../components/Banner/Banner";
import { clearMessages } from "../../../components/Banner/messageStore";
import { useMail } from "./useMail";
import { TestMail } from "./TestMail";
import { Field } from "../Fields/types";
import { Field } from "../Form/types";
import { Form, FormCard, FormContainer } from "../Form/Form";
export function Mail() {
const { settings, handleSubmit, sendTestMail } = useMail();
@ -30,22 +30,22 @@ export function Mail() {
}
return (
<div className={`${styles.container} flow`}>
<FormContainer>
<h1>Mail</h1>
<Banner />
<section className={styles.card}>
<FormCard>
<FormSection
fields={mailFields}
schema={mailFormSchema}
initialFormState={initialFormState}
onSubmit={handleSubmit}
/>
</section>
</FormCard>
<TestMail onSubmit={sendTestMail} />
</div>
</FormContainer>
);
}
@ -94,7 +94,7 @@ function FormSection<T extends string>({
};
return (
<form className={`${styles.form}`} onSubmit={handleSubmit} noValidate>
<Form onSubmit={handleSubmit} noValidate>
{fields.map((f) => {
const initialValue = initialFormState[f.name];
@ -130,7 +130,7 @@ function FormSection<T extends string>({
<Button type="submit" appearance="primary">
Save mail settings
</Button>
</form>
</Form>
);
}

View File

@ -0,0 +1,6 @@
.single-field {
display: grid;
grid-template-columns: 1fr auto;
align-items: baseline;
gap: var(--gutter);
}

View File

@ -6,9 +6,10 @@ import {
clearMessages,
Message,
} from "../../../components/Banner/messageStore";
import styles from "./Mail.module.css";
import styles from "./TestMail.module.css";
import { SingleBanner } from "../../../components/Banner/Banner";
import { MailSettings } from "../../../api/urbackupserver";
import { FormCard } from "../Form/Form";
const TESTMAIL = {
LABEL: "Send test mail to this email address",
@ -78,7 +79,7 @@ export function TestMail({
{feedback && (
<SingleBanner message={feedback} onClick={() => setFeedback(null)} />
)}
<div className={styles.card}>
<FormCard>
<div className="flow flow-xs">
<Label htmlFor={TESTMAIL.ID}>{TESTMAIL.LABEL}</Label>
<form className={styles["single-field"]} onSubmit={handleSubmit}>
@ -87,14 +88,13 @@ export function TestMail({
validationState: "error",
validationMessage,
})}
className={styles.field}
>
<Input id={TESTMAIL.ID} name={TESTMAIL.ID} type="email" />
</Field>
<Button type="submit">Send test mail</Button>
</form>
</div>
</div>
</FormCard>
</section>
);
}

View File

@ -1,8 +1,8 @@
import { z } from "zod/v4-mini";
import { integerValidation, VALIDATION_MESSAGES } from "../Fields/validation";
import { integerValidation, VALIDATION_MESSAGES } from "../Form/validation";
import { type MailSettingsVals } from "../../../api/urbackupserver";
import { BaseField, Field } from "../Fields/types";
import { BaseField, Field } from "../Form/types";
export type MailSettingsKey = keyof MailSettingsVals;

View File

@ -6,19 +6,19 @@ import type {
GeneralSettings,
SettingState,
} from "../../../api/urbackupserver";
import styles from "./SettingsServer.module.css";
import { CheckboxField } from "../Fields/CheckboxField";
import { CheckboxField } from "../Form/CheckboxField";
import {
AdvancedServerFieldNames,
advancedServerFields,
advancedServerFormSchema,
Field,
ServerFieldNames,
serverFields,
serverFormSchema,
} from "./serverForm";
import { TextField } from "../Fields/TextField";
import { TextField } from "../Form/TextField";
import { useSettings } from "../useSettings";
import { Form, FormCard, FormContainer } from "../Form/Form";
import { Field } from "../Form/types";
export function SettingsServer() {
const { settings, updateSettings } = useSettings();
@ -34,16 +34,16 @@ export function SettingsServer() {
}
return (
<div className={`${styles.container} flow`}>
<FormContainer>
<h1>Server</h1>
<section className={styles.card}>
<FormCard>
<FormSection
fields={serverFields}
schema={serverFormSchema}
initialFormState={initialFormState}
updateSettings={updateSettings}
/>
</section>
</FormCard>
{!showAdvancedSettings && (
<div>
@ -56,17 +56,17 @@ export function SettingsServer() {
{showAdvancedSettings && (
<>
<h2>Advanced</h2>
<section className={styles.card}>
<FormCard>
<FormSection
fields={advancedServerFields}
schema={advancedServerFormSchema}
initialFormState={initialFormState}
updateSettings={updateSettings}
/>
</section>
</FormCard>
</>
)}
</div>
</FormContainer>
);
}
@ -84,7 +84,7 @@ function FormSection<T extends string>({
) => void;
}) {
return (
<form className={`${styles.form}`}>
<Form>
{fields.map((f) => {
const initialValue = initialFormState[f.name];
@ -128,7 +128,7 @@ function FormSection<T extends string>({
/>
);
})}
</form>
</Form>
);
}

View File

@ -5,8 +5,8 @@ import {
integerValidation,
requiredStringValidation,
VALIDATION_MESSAGES,
} from "../Fields/validation";
import { BaseField, Field } from "../Fields/types";
} from "../Form/validation";
import { BaseField, Field } from "../Form/types";
const CLEANUP_WINDOW_REGEX =
/^(([mon|mo|tu|tue|tues|di|wed|mi|th|thu|thur|thurs|do|fri|fr|sat|sa|sun|so|1-7]\-?[mon|mo|tu|tue|tues|di|wed|mi|th|thu|thur|thurs|do|fri|fr|sat|sa|sun|so|1-7]?\s*[,]?\s*)+\/([0-9][0-9]?:?[0-9]?[0-9]?\-[0-9][0-9]?:?[0-9]?[0-9]?\s*[,]?\s*)+\s*[;]?\s*)*$/i;

View File

@ -19,7 +19,7 @@ import styles from "./CreateUser.module.css";
import {
requiredStringValidation,
VALIDATION_MESSAGES,
} from "../Fields/validation";
} from "../Form/validation";
import { DismissRegular } from "@fluentui/react-icons";
import { UserAlreadyExistsError, UserRight } from "../../../api/urbackupserver";
import {