mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-22 20:16:57 +00:00
Strongly type form components
Strongly type form components
This commit is contained in:
@@ -74,17 +74,24 @@ const TournamentForm = () => {
|
||||
<div className="col-span-4">
|
||||
<TextField
|
||||
name="tournament.tournament"
|
||||
control={form.control}
|
||||
label={t("tournamentNameLabel")}
|
||||
placeholder={t("tournamentNamePlaceholder")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<DateField name="tournament.date" label={t("dateLabel")} required />
|
||||
<DateField
|
||||
name="tournament.date"
|
||||
control={form.control}
|
||||
label={t("dateLabel")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<TextField
|
||||
name="tournament.url"
|
||||
control={form.control}
|
||||
label={t("urlLabel")}
|
||||
placeholder={t("urlPlaceholder")}
|
||||
required
|
||||
@@ -93,6 +100,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<SelectField
|
||||
name="tournament.time_control"
|
||||
control={form.control}
|
||||
label={t("tcLabel")}
|
||||
options={[
|
||||
TimeControl.Classic,
|
||||
@@ -108,12 +116,14 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<SwitchField
|
||||
name="tournament.norm_tournament"
|
||||
control={form.control}
|
||||
label={t("normLabel")}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
<TextField
|
||||
name="tournament.address"
|
||||
control={form.control}
|
||||
label={t("addressLabel")}
|
||||
placeholder={t("addressPlaceholder")}
|
||||
required
|
||||
@@ -122,6 +132,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-4 sm:col-span-2">
|
||||
<TextField
|
||||
name="tournament.town"
|
||||
control={form.control}
|
||||
label={t("townLabel")}
|
||||
placeholder={t("townPlaceholder")}
|
||||
required
|
||||
@@ -130,6 +141,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<TextField
|
||||
name="tournament.department"
|
||||
control={form.control}
|
||||
label={t("departmentLabel")}
|
||||
placeholder={t("departmentPlaceholder")}
|
||||
required
|
||||
@@ -138,6 +150,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<TextField
|
||||
name="tournament.country"
|
||||
control={form.control}
|
||||
label={t("countryLabel")}
|
||||
placeholder={t("countryPlaceholder")}
|
||||
required
|
||||
@@ -146,6 +159,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<TextField
|
||||
name="name"
|
||||
control={form.control}
|
||||
label={t("yourNameLabel")}
|
||||
placeholder={t("yourNamePlaceholder")}
|
||||
required
|
||||
@@ -154,6 +168,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<TextField
|
||||
name="email"
|
||||
control={form.control}
|
||||
label={t("yourEmailLabel")}
|
||||
placeholder={t("yourEmailPlaceholder")}
|
||||
required
|
||||
@@ -162,6 +177,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-4 row-span-2 sm:col-span-2">
|
||||
<TextAreaField
|
||||
name="message"
|
||||
control={form.control}
|
||||
label={t("messageLabel")}
|
||||
rows={6}
|
||||
placeholder={t("messagePlaceholder")}
|
||||
@@ -171,6 +187,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<TextField
|
||||
name="tournament.coordinates.0"
|
||||
control={form.control}
|
||||
label={t("latLabel")}
|
||||
type="number"
|
||||
required
|
||||
@@ -179,6 +196,7 @@ const TournamentForm = () => {
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<TextField
|
||||
name="tournament.coordinates.1"
|
||||
control={form.control}
|
||||
label={t("lngLabel")}
|
||||
type="number"
|
||||
required
|
||||
|
||||
@@ -55,6 +55,7 @@ const ContactForm = () => {
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<TextField
|
||||
name="email"
|
||||
control={form.control}
|
||||
label={t("emailLabel")}
|
||||
placeholder={t("emailPlaceholder")}
|
||||
required
|
||||
@@ -62,6 +63,7 @@ const ContactForm = () => {
|
||||
|
||||
<TextField
|
||||
name="subject"
|
||||
control={form.control}
|
||||
label={t("subjectLabel")}
|
||||
placeholder={t("subjectPlaceholder")}
|
||||
required
|
||||
@@ -69,6 +71,7 @@ const ContactForm = () => {
|
||||
|
||||
<TextAreaField
|
||||
name="message"
|
||||
control={form.control}
|
||||
rows={6}
|
||||
label={t("messageLabel")}
|
||||
placeholder={t("messagePlaceholder")}
|
||||
|
||||
@@ -107,6 +107,7 @@ export const ManualEloForm = () => {
|
||||
<div className="grid w-full grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<TextField
|
||||
name="currentElo"
|
||||
control={form.control}
|
||||
label={t("currentEloLabel")}
|
||||
placeholder={t("currentEloPlaceholder")}
|
||||
type="number"
|
||||
@@ -115,6 +116,7 @@ export const ManualEloForm = () => {
|
||||
|
||||
<SelectField
|
||||
name="kFactor"
|
||||
control={form.control}
|
||||
label={t("yourKFactorLabel")}
|
||||
options={["40", "20", "10"].map((k) => ({
|
||||
value: k,
|
||||
@@ -139,6 +141,7 @@ export const ManualEloForm = () => {
|
||||
<div className="grid w-full grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<TextField
|
||||
name={`games.${i}.opponentElo`}
|
||||
control={form.control}
|
||||
placeholder={t("opponentEloPlaceholder")}
|
||||
type="number"
|
||||
required
|
||||
@@ -147,6 +150,7 @@ export const ManualEloForm = () => {
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupField
|
||||
name={`games.${i}.result`}
|
||||
control={form.control}
|
||||
options={["win", "draw", "loss"].map((result) => ({
|
||||
value: result,
|
||||
label: t("gameResult", { result }),
|
||||
|
||||
@@ -185,6 +185,7 @@ export default function Elo() {
|
||||
<div className="flex items-end gap-4">
|
||||
<TextField
|
||||
name="url"
|
||||
control={form.control}
|
||||
label={t("resultsUrlLabel")}
|
||||
placeholder={t("resultsUrlLabel")}
|
||||
/>
|
||||
@@ -222,6 +223,7 @@ export default function Elo() {
|
||||
<div className="grid w-full grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<SelectField
|
||||
name="player"
|
||||
control={form.control}
|
||||
required
|
||||
label={t("choosePlayerLabel")}
|
||||
placeholder={t("choosePlayerPlaceholder")}
|
||||
@@ -231,6 +233,7 @@ export default function Elo() {
|
||||
|
||||
<SelectField
|
||||
name="kFactor"
|
||||
control={form.control}
|
||||
label={t("kFactorLabel")}
|
||||
options={["40", "20", "10"].map((k) => ({
|
||||
value: k,
|
||||
|
||||
@@ -6,26 +6,41 @@ import { get, range } from "lodash";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { registerLocale } from "react-datepicker";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Field, FieldProps } from "../Field";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
import { Field, GenericFieldProps } from "../Field";
|
||||
|
||||
import { InputDatePicker } from "./components";
|
||||
import { DatePickerCustomHeader } from "./components/DatePickerCustomHeader";
|
||||
|
||||
registerLocale("fr", fr);
|
||||
|
||||
type DateFieldProps = FieldProps & {
|
||||
maxDate?: Date;
|
||||
minDate?: Date;
|
||||
dateFormat?: string;
|
||||
className?: string;
|
||||
datePickerPopperClass?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
type DateFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> & {
|
||||
maxDate?: Date;
|
||||
minDate?: Date;
|
||||
dateFormat?: string;
|
||||
className?: string;
|
||||
datePickerPopperClass?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export const DateField = ({
|
||||
export const DateField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
minDate,
|
||||
maxDate,
|
||||
dateFormat = "dd/MM/yyyy",
|
||||
@@ -33,15 +48,17 @@ export const DateField = ({
|
||||
datePickerPopperClass,
|
||||
|
||||
name,
|
||||
control,
|
||||
required,
|
||||
...otherFieldProps
|
||||
}: DateFieldProps) => {
|
||||
}: DateFieldProps<TFieldValues, TFieldName>) => {
|
||||
const locale = useLocale();
|
||||
const at = useTranslations("App");
|
||||
const min = minDate ? minDate.getFullYear() : 1900;
|
||||
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const form = useFormContext();
|
||||
const form = useFormContext<TFieldValues>();
|
||||
const {
|
||||
formState: { errors },
|
||||
} = form;
|
||||
@@ -49,10 +66,10 @@ export const DateField = ({
|
||||
const hasError = name && !!get(errors, name)?.message;
|
||||
|
||||
return (
|
||||
<Field name={name} {...otherFieldProps}>
|
||||
<Field name={name} control={control} {...otherFieldProps}>
|
||||
<div className={twMerge("relative flex w-full flex-col", className)}>
|
||||
<Controller
|
||||
control={form.control}
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<DatePicker
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { get } from "lodash";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Control,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
import { ErrorMessage } from "./ErrorMessage";
|
||||
import { Label } from "./Label";
|
||||
|
||||
export type FieldProps = {
|
||||
name: string;
|
||||
export type GenericFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TFieldName;
|
||||
control: Control<TFieldValues>;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
childrenWrapperClassName?: string;
|
||||
@@ -16,12 +27,21 @@ export type FieldProps = {
|
||||
hideErrorMessage?: boolean;
|
||||
};
|
||||
|
||||
export const Field = (
|
||||
props: Omit<FieldProps, "name"> & {
|
||||
type FieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> & {
|
||||
required?: boolean;
|
||||
name?: string;
|
||||
children: ReactNode;
|
||||
},
|
||||
}
|
||||
>;
|
||||
|
||||
export const Field = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>(
|
||||
props: FieldProps<TFieldValues, TFieldName>,
|
||||
) => {
|
||||
const {
|
||||
name,
|
||||
@@ -32,9 +52,12 @@ export const Field = (
|
||||
children,
|
||||
required,
|
||||
hideErrorMessage = false,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used to restrict the `name` prop type
|
||||
control,
|
||||
} = props;
|
||||
|
||||
const form = useFormContext();
|
||||
const form = useFormContext<TFieldValues>();
|
||||
|
||||
const {
|
||||
formState: { errors },
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type LabelProps = React.DetailedHTMLProps<
|
||||
React.LabelHTMLAttributes<HTMLLabelElement>,
|
||||
HTMLLabelElement
|
||||
> & {
|
||||
required?: boolean;
|
||||
className?: string;
|
||||
tooltip?: React.ReactNode;
|
||||
};
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
type LabelProps = Prettify<
|
||||
React.DetailedHTMLProps<
|
||||
React.LabelHTMLAttributes<HTMLLabelElement>,
|
||||
HTMLLabelElement
|
||||
> & {
|
||||
required?: boolean;
|
||||
className?: string;
|
||||
tooltip?: React.ReactNode;
|
||||
}
|
||||
>;
|
||||
|
||||
export const Label = ({
|
||||
required,
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
import { RadioGroup } from "@headlessui/react";
|
||||
import { get, isNil } from "lodash";
|
||||
import { isNil } from "lodash";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Field, FieldProps } from "./Field";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
|
||||
export type BaseOption<T = string, D = unknown> = {
|
||||
value: T;
|
||||
@@ -15,13 +22,26 @@ export type BaseOption<T = string, D = unknown> = {
|
||||
data?: D;
|
||||
};
|
||||
|
||||
export type RadioGroupFieldProps<T = string, D = unknown> = FieldProps & {
|
||||
required?: boolean;
|
||||
options: BaseOption<T, D>[];
|
||||
};
|
||||
export type RadioGroupFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
T = string,
|
||||
D = unknown,
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> & {
|
||||
required?: boolean;
|
||||
options: BaseOption<T, D>[];
|
||||
}
|
||||
>;
|
||||
|
||||
export const RadioGroupField = <T extends React.Key = string, D = unknown>({
|
||||
export const RadioGroupField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
T extends React.Key = string,
|
||||
D = unknown,
|
||||
>({
|
||||
name,
|
||||
control,
|
||||
className,
|
||||
labelClassName,
|
||||
childrenWrapperClassName,
|
||||
@@ -30,16 +50,14 @@ export const RadioGroupField = <T extends React.Key = string, D = unknown>({
|
||||
required,
|
||||
|
||||
options,
|
||||
}: RadioGroupFieldProps<T, D>) => {
|
||||
}: RadioGroupFieldProps<TFieldValues, TFieldName, T, D>) => {
|
||||
const t = useTranslations("App");
|
||||
const form = useFormContext();
|
||||
const form = useFormContext<TFieldValues>();
|
||||
|
||||
const {
|
||||
formState: { errors },
|
||||
} = form;
|
||||
|
||||
const hasError = !!get(errors, name)?.message;
|
||||
|
||||
const valueToOption = (value: T): BaseOption<T, D> =>
|
||||
options.find((o) => o.value === value) ?? { value, label: "" };
|
||||
|
||||
@@ -47,6 +65,7 @@ export const RadioGroupField = <T extends React.Key = string, D = unknown>({
|
||||
<Field
|
||||
{...{
|
||||
name,
|
||||
control,
|
||||
className,
|
||||
label,
|
||||
labelClassName,
|
||||
@@ -57,7 +76,7 @@ export const RadioGroupField = <T extends React.Key = string, D = unknown>({
|
||||
>
|
||||
<Controller
|
||||
name={name}
|
||||
control={form.control}
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const optionValue = isNil(value) ? null : valueToOption(value);
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@ import React from "react";
|
||||
|
||||
import { flatMap, get, isArray, isNil, isObject } from "lodash";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import Select, {
|
||||
ActionMeta,
|
||||
ClassNamesConfig,
|
||||
GroupBase,
|
||||
OnChangeValue,
|
||||
@@ -12,7 +16,9 @@ import Select, {
|
||||
} from "react-select";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Field, FieldProps } from "./Field";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
|
||||
export type BaseOption<T = string, D = unknown> = {
|
||||
value: T;
|
||||
@@ -54,7 +60,7 @@ export const classNames = <Option, IsMulti extends boolean = false>(
|
||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
),
|
||||
groupHeading: () => "ml-3 mt-2 mb-1 text-textGray text-sm uppercase",
|
||||
option: ({ isFocused, isSelected, isDisabled }) =>
|
||||
option: ({ isFocused, isDisabled }) =>
|
||||
twMerge(
|
||||
"px-3 py-2 hover:cursor-pointer",
|
||||
isDisabled && "opacity-50",
|
||||
@@ -65,23 +71,30 @@ export const classNames = <Option, IsMulti extends boolean = false>(
|
||||
});
|
||||
|
||||
export type SelectFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
IsMulti extends boolean = false,
|
||||
T = string,
|
||||
D = unknown,
|
||||
> = FieldProps &
|
||||
Omit<
|
||||
Props<BaseOption<T, D>, IsMulti, GroupBase<BaseOption<T, D>>>,
|
||||
"onChange" | "value" | "classNames"
|
||||
> & {
|
||||
required?: boolean;
|
||||
};
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> &
|
||||
Omit<
|
||||
Props<BaseOption<T, D>, IsMulti, GroupBase<BaseOption<T, D>>>,
|
||||
"onChange" | "value" | "classNames" | "name"
|
||||
> & {
|
||||
required?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export const SelectField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
IsMulti extends boolean = false,
|
||||
T = string,
|
||||
D = unknown,
|
||||
>({
|
||||
name,
|
||||
control,
|
||||
className,
|
||||
labelClassName,
|
||||
childrenWrapperClassName,
|
||||
@@ -92,9 +105,9 @@ export const SelectField = <
|
||||
placeholder,
|
||||
options,
|
||||
...selectProps
|
||||
}: SelectFieldProps<IsMulti, T, D>) => {
|
||||
}: SelectFieldProps<TFieldValues, TFieldName, IsMulti, T, D>) => {
|
||||
const t = useTranslations("App");
|
||||
const form = useFormContext();
|
||||
const form = useFormContext<TFieldValues>();
|
||||
|
||||
const {
|
||||
formState: { errors },
|
||||
@@ -118,6 +131,7 @@ export const SelectField = <
|
||||
<Field
|
||||
{...{
|
||||
name,
|
||||
control,
|
||||
className,
|
||||
label,
|
||||
labelClassName,
|
||||
@@ -128,11 +142,10 @@ export const SelectField = <
|
||||
>
|
||||
<Controller
|
||||
name={name}
|
||||
control={form.control}
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const onSelectChange = (
|
||||
newValue: OnChangeValue<BaseOption<T, D>, IsMulti>,
|
||||
actionMeta: ActionMeta<BaseOption<T, D>>,
|
||||
) => {
|
||||
if (isNil(newValue)) onChange(null);
|
||||
else if (isArray(newValue)) {
|
||||
@@ -143,12 +156,12 @@ export const SelectField = <
|
||||
const optionValue = isNil(value)
|
||||
? null
|
||||
: isArray(value)
|
||||
? value.map(valueToOption)
|
||||
? // @ts-ignore - this is too complex for TS to understand
|
||||
value.map(valueToOption)
|
||||
: valueToOption(value);
|
||||
|
||||
return (
|
||||
<Select
|
||||
isClearable
|
||||
value={optionValue}
|
||||
onChange={onSelectChange}
|
||||
options={options}
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
import React from "react";
|
||||
|
||||
import { Switch, SwitchProps } from "@headlessui/react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Field, FieldProps } from "./Field";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
export const SwitchField = (props: FieldProps & SwitchProps<"button">) => {
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
|
||||
type SwitchFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> &
|
||||
Omit<SwitchProps<"button">, "name">
|
||||
>;
|
||||
|
||||
export const SwitchField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>(
|
||||
props: SwitchFieldProps<TFieldValues, TFieldName>,
|
||||
) => {
|
||||
const {
|
||||
name,
|
||||
control,
|
||||
label,
|
||||
disabled,
|
||||
className,
|
||||
@@ -19,12 +40,13 @@ export const SwitchField = (props: FieldProps & SwitchProps<"button">) => {
|
||||
|
||||
...rest
|
||||
} = props;
|
||||
const form = useFormContext();
|
||||
const form = useFormContext<TFieldValues>();
|
||||
|
||||
return (
|
||||
<Field
|
||||
{...{
|
||||
name,
|
||||
control,
|
||||
type: "checkbox",
|
||||
label,
|
||||
disabled,
|
||||
@@ -34,7 +56,7 @@ export const SwitchField = (props: FieldProps & SwitchProps<"button">) => {
|
||||
>
|
||||
<div className="flex w-full flex-col">
|
||||
<Controller
|
||||
control={form.control}
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<div className="flex h-[40px] items-center">
|
||||
@@ -49,7 +71,7 @@ export const SwitchField = (props: FieldProps & SwitchProps<"button">) => {
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<span className="ui-checked:translate-x-[19px] ui-not-checked:translate-x-[3px] inline-block h-[14px] w-[14px] transform rounded-full bg-white transition-transform" />
|
||||
<span className="inline-block h-[14px] w-[14px] transform rounded-full bg-white transition-transform ui-checked:translate-x-[19px] ui-not-checked:translate-x-[3px]" />
|
||||
</Switch>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
import React from "react";
|
||||
|
||||
import { get } from "lodash";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Field, FieldProps } from "./Field";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
export const TextAreaField = (
|
||||
props: FieldProps &
|
||||
React.HTMLProps<HTMLTextAreaElement> & {
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
|
||||
type TextAreaFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> &
|
||||
Omit<React.HTMLProps<HTMLTextAreaElement>, "ref" | "name"> & {
|
||||
handleChanged?: (props: { name: string }) => void;
|
||||
startIcon?: React.ReactNode;
|
||||
},
|
||||
required?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export const TextAreaField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>(
|
||||
props: TextAreaFieldProps<TFieldValues, TFieldName>,
|
||||
) => {
|
||||
const {
|
||||
name,
|
||||
control,
|
||||
label,
|
||||
required,
|
||||
disabled,
|
||||
className,
|
||||
labelClassName,
|
||||
childrenWrapperClassName,
|
||||
@@ -25,10 +42,12 @@ export const TextAreaField = (
|
||||
|
||||
startIcon,
|
||||
handleChanged,
|
||||
required,
|
||||
|
||||
disabled,
|
||||
...rest
|
||||
} = props;
|
||||
const form = useFormContext();
|
||||
const form = useFormContext<TFieldValues>();
|
||||
|
||||
const {
|
||||
formState: { errors },
|
||||
@@ -40,8 +59,8 @@ export const TextAreaField = (
|
||||
<Field
|
||||
{...{
|
||||
name,
|
||||
control,
|
||||
label,
|
||||
required,
|
||||
className,
|
||||
labelClassName,
|
||||
childrenWrapperClassName,
|
||||
@@ -50,7 +69,7 @@ export const TextAreaField = (
|
||||
>
|
||||
<div className="flex w-full flex-col">
|
||||
<Controller
|
||||
control={form.control}
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<div className="relative">
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
import React from "react";
|
||||
|
||||
import { get } from "lodash";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Field, FieldProps } from "./Field";
|
||||
import { Prettify } from "@/types";
|
||||
|
||||
export const TextField = (
|
||||
props: FieldProps &
|
||||
React.HTMLProps<HTMLInputElement> & {
|
||||
import { Field, GenericFieldProps } from "./Field";
|
||||
|
||||
type TextFieldProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = Prettify<
|
||||
GenericFieldProps<TFieldValues, TFieldName> &
|
||||
Omit<React.HTMLProps<HTMLInputElement>, "ref" | "name"> & {
|
||||
handleChanged?: (props: { name: string }) => void;
|
||||
startIcon?: React.ReactNode;
|
||||
},
|
||||
}
|
||||
>;
|
||||
|
||||
export const TextField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>(
|
||||
props: TextFieldProps<TFieldValues, TFieldName>,
|
||||
) => {
|
||||
const {
|
||||
name,
|
||||
control,
|
||||
type = "text",
|
||||
label,
|
||||
required,
|
||||
@@ -29,7 +47,7 @@ export const TextField = (
|
||||
|
||||
...rest
|
||||
} = props;
|
||||
const form = useFormContext();
|
||||
const form = useFormContext<TFieldValues>();
|
||||
|
||||
const {
|
||||
formState: { errors },
|
||||
@@ -57,6 +75,7 @@ export const TextField = (
|
||||
<Field
|
||||
{...{
|
||||
name,
|
||||
control,
|
||||
label,
|
||||
required,
|
||||
className,
|
||||
@@ -67,7 +86,7 @@ export const TextField = (
|
||||
>
|
||||
<div className="flex w-full flex-col">
|
||||
<Controller
|
||||
control={form.control}
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<div className="relative">
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@
|
||||
"resultsTitle": "Résultats",
|
||||
"opponentEloPlaceholder": "Classement de l'adversaire",
|
||||
"gameResultPlaceholder": "Résultat...",
|
||||
"gameResult": "{result, select, win {Victoire} draw {Match nul} loss {Défaite} other {{result}}}",
|
||||
"gameResult": "{result, select, win {Victoire} draw {Nul} loss {Défaite} other {{result}}}",
|
||||
"addGameButton": "Ajouter un autre résultat de partie",
|
||||
|
||||
"resultsUrlLabel": "Collez un lien vers la page de résultats de la FFE",
|
||||
|
||||
@@ -67,3 +67,11 @@ export type ResponseMessage = {
|
||||
};
|
||||
|
||||
export type ScrollableElement = Window | HTMLElement;
|
||||
|
||||
// Prettify takes a type as its argument and returns a new type that has the same properties as the original type,
|
||||
// but the properties are not intersected. This means that the new type is easier to read and understand.
|
||||
// https://gist.github.com/palashmon/db68706d4f26d2dbf187e76409905399
|
||||
|
||||
export type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
} & {};
|
||||
|
||||
Reference in New Issue
Block a user