create bound components for text input and radio input

This commit is contained in:
Alex Holliday committed 2026-08-05 10:51:48 -07:00
1 parent 623db938f5
commit 6e1e5dd1a5
6 files changed
+1088 -1061

No files matched your search

@@ -0,0 +1,46 @@
import {
Controller,
useFormContext,
type FieldPath,
type FieldValues,
} from "react-hook-form";
import { TextField } from "@/Components/inputs";
interface FormNumberFieldProps<T extends FieldValues> extends Omit<
React.ComponentProps<typeof TextField>,
"name" | "error" | "helperText" | "type"
> {
name: FieldPath<T>;
emptyValue?: number; // what an empty input writes to the form; 0 keeps today's sentinel
}
export const FormNumberField = <T extends FieldValues>({
name,
emptyValue = 0,
...rest
}: FormNumberFieldProps<T>) => {
const { control } = useFormContext<T>();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState }) => (
<TextField
{...field}
type="number"
value={
field.value === undefined || field.value === emptyValue ? "" : field.value
}
onChange={(e) => {
const val = e.target.value;
field.onChange(val === "" ? emptyValue : Number(val));
}}
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
fullWidth
{...rest}
/>
)}
/>
);
};
@@ -0,0 +1,50 @@
import { RadioGroup, useTheme } from "@mui/material";
import FormControl from "@mui/material/FormControl";
import {
Controller,
useFormContext,
type FieldPath,
type FieldValues,
} from "react-hook-form";
import { LAYOUT } from "@/Utils/Theme/constants";
import { RadioWithDescription } from "@/Components/inputs/Radio";
export interface FormRadioOption {
value: string;
label: string;
description: string;
}
interface FormRadioGroupProps<T extends FieldValues> {
name: FieldPath<T>;
options: FormRadioOption[];
}
export const FormRadioGroup = <T extends FieldValues>({
name,
options,
}: FormRadioGroupProps<T>) => {
const { control } = useFormContext<T>();
const theme = useTheme();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState }) => (
<FormControl error={!!fieldState.error}>
<RadioGroup
{...field}
sx={{ gap: theme.spacing(LAYOUT.MD) }}
>
{options.map((option) => (
<RadioWithDescription
key={option.value}
value={option.value}
label={option.label}
description={option.description}
/>
))}
</RadioGroup>
</FormControl>
)}
/>
);
};
@@ -0,0 +1,39 @@
import {
Controller,
useFormContext,
type FieldPath,
type FieldValues,
} from "react-hook-form";
import { TextField } from "@/Components/inputs";
interface FormTextFieldProps<T extends FieldValues> extends Omit<
React.ComponentProps<typeof TextField>,
"name" | "error" | "helperText"
> {
name: FieldPath<T>;
}
export const FormTextField = <T extends FieldValues>({
name,
...rest
}: FormTextFieldProps<T>) => {
const { control } = useFormContext<T>();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState }) => (
<TextField
{...field}
type="text"
value={field.value ?? ""}
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
fullWidth
{...rest}
/>
)}
/>
);
};
File diff suppressed because it is too large. Load diff
+28
View File
@@ -20,6 +20,34 @@ export const MonitorTypes = [
] as const;
export type MonitorType = (typeof MonitorTypes)[number];
// The subset offered by the CreateMonitor type selector, in display order.
// pagespeed/hardware are set by their own pages; unknown is never selectable.
export const SelectableMonitorTypes = [
"http",
"ping",
"docker",
"port",
"game",
"grpc",
"websocket",
"dns",
] as const;
export type SelectableMonitorType = (typeof SelectableMonitorTypes)[number];
// Irregular i18n key fragments shared by the type selector's label
// (pages.common.monitors.monitorTypes.<fragment>) and description
// (pages.createMonitor.form.type.<fragment>Description) keys.
export const monitorTypeLabelKey: Record<SelectableMonitorType, string> = {
http: "optionHttp",
ping: "optionPing",
docker: "optionDocker",
port: "optionPort",
game: "optionGame",
grpc: "optionGrpc",
websocket: "optionWebSocket",
dns: "optionDns",
};
export const DnsRecordTypes = ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] as const;
export type DnsRecordType = (typeof DnsRecordTypes)[number];
+1 -1
View File
@@ -583,7 +583,7 @@
},
"port": {
"label": "Port to monitor",
"placeholder": 80
"placeholder": "80"
},
"grpcServiceName": {
"label": "Service name",