mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-26 13:22:50 -05:00
Refactor DateTimeFormField to use RAC based input Closes #2359
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import type { CalendarDateTime } from "@internationalized/date";
|
||||
import {
|
||||
Controller,
|
||||
type FieldPath,
|
||||
type FieldValues,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { dayMonthYearToDateValue } from "../../utils/dates";
|
||||
import { dateToDateValue, dayMonthYearToDateValue } from "../../utils/dates";
|
||||
import type { DayMonthYear } from "../../utils/zod";
|
||||
import { SendouDatePicker } from "../elements/DatePicker";
|
||||
import type { FormFieldSize } from "./form-utils";
|
||||
@@ -15,12 +16,14 @@ export function DateFormField<T extends FieldValues>({
|
||||
bottomText,
|
||||
required,
|
||||
size,
|
||||
granularity = "day",
|
||||
}: {
|
||||
label: string;
|
||||
name: FieldPath<T>;
|
||||
bottomText?: string;
|
||||
required?: boolean;
|
||||
size?: FormFieldSize;
|
||||
granularity?: "day" | "minute";
|
||||
}) {
|
||||
const methods = useFormContext();
|
||||
|
||||
@@ -33,17 +36,21 @@ export function DateFormField<T extends FieldValues>({
|
||||
fieldState: { invalid, error },
|
||||
}) => {
|
||||
const getValue = () => {
|
||||
const originalValue = value as DayMonthYear | null;
|
||||
const originalValue = value as DayMonthYear | Date | null;
|
||||
|
||||
if (!originalValue) return null;
|
||||
|
||||
return dayMonthYearToDateValue(originalValue);
|
||||
if (originalValue instanceof Date) {
|
||||
return dateToDateValue(originalValue);
|
||||
}
|
||||
|
||||
return dayMonthYearToDateValue(originalValue as DayMonthYear);
|
||||
};
|
||||
|
||||
return (
|
||||
<SendouDatePicker
|
||||
label={label}
|
||||
granularity="day"
|
||||
granularity={granularity}
|
||||
isRequired={required}
|
||||
errorText={error?.message as string | undefined}
|
||||
value={getValue()}
|
||||
@@ -53,11 +60,23 @@ export function DateFormField<T extends FieldValues>({
|
||||
onBlur={onBlur}
|
||||
onChange={(value) => {
|
||||
if (value) {
|
||||
onChange({
|
||||
day: value.day,
|
||||
month: value.month - 1,
|
||||
year: value.year,
|
||||
});
|
||||
if (granularity === "minute") {
|
||||
onChange(
|
||||
new Date(
|
||||
value.year,
|
||||
value.month - 1,
|
||||
value.day,
|
||||
(value as CalendarDateTime).hour,
|
||||
(value as CalendarDateTime).minute,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
onChange({
|
||||
day: value.day,
|
||||
month: value.month - 1,
|
||||
year: value.year,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
type FieldPath,
|
||||
type FieldValues,
|
||||
get,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { FormMessage } from "~/components/FormMessage";
|
||||
import { Label } from "~/components/Label";
|
||||
|
||||
export function DateTimeFormField<T extends FieldValues>({
|
||||
label,
|
||||
name,
|
||||
bottomText,
|
||||
}: { label: string; name: FieldPath<T>; bottomText?: string }) {
|
||||
const methods = useFormContext();
|
||||
const id = React.useId();
|
||||
|
||||
const error = get(methods.formState.errors, name);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label htmlFor={id}>{label}</Label>
|
||||
<input id={id} {...methods.register(name)} type="datetime-local" />
|
||||
{error && (
|
||||
<FormMessage type="error">{error.message as string}</FormMessage>
|
||||
)}
|
||||
{bottomText && !error ? (
|
||||
<FormMessage type="info">{bottomText}</FormMessage>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { z } from "zod";
|
||||
import { Label } from "~/components/Label";
|
||||
import { DateTimeFormField } from "~/components/form/DateTimeFormField";
|
||||
import { DateFormField } from "~/components/form/DateFormField";
|
||||
import { SendouForm } from "~/components/form/SendouForm";
|
||||
import { TextAreaFormField } from "~/components/form/TextAreaFormField";
|
||||
import { nullFilledArray } from "~/utils/arrays";
|
||||
@@ -62,10 +62,11 @@ export default function NewScrimPage() {
|
||||
>
|
||||
<WithFormField usersTeams={data.teams} />
|
||||
|
||||
<DateTimeFormField<FormFields>
|
||||
<DateFormField<FormFields>
|
||||
label={t("scrims:forms.when.title")}
|
||||
name="at"
|
||||
bottomText={t("scrims:forms.when.explanation")}
|
||||
granularity="minute"
|
||||
/>
|
||||
|
||||
<BaseVisibilityFormField associations={data.associations} />
|
||||
@@ -143,9 +144,10 @@ function NotFoundVisibilityFormField({
|
||||
return (
|
||||
<div>
|
||||
<div className="stack horizontal sm">
|
||||
<DateTimeFormField<FormFields>
|
||||
<DateFormField<FormFields>
|
||||
label={t("scrims:forms.notFoundVisibility.title")}
|
||||
name="notFoundVisibility.at"
|
||||
granularity="minute"
|
||||
/>
|
||||
{date ? (
|
||||
<div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseDate } from "@internationalized/date";
|
||||
import { CalendarDateTime, parseDate } from "@internationalized/date";
|
||||
import { getWeek } from "date-fns";
|
||||
import type { MonthYear } from "~/features/plus-voting/core";
|
||||
import type { DayMonthYear } from "./zod";
|
||||
@@ -26,6 +26,20 @@ export function dayMonthYearToDate({ day, month, year }: DayMonthYear) {
|
||||
return new Date(Date.UTC(year, month, day, 12));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JavaScript Date object into a CalendarDateTime object (used by react-aria-components).
|
||||
*/
|
||||
export function dateToDateValue(date: Date) {
|
||||
return new CalendarDateTime(
|
||||
date.getFullYear(),
|
||||
date.getMonth() + 1,
|
||||
date.getDate(),
|
||||
date.getHours(),
|
||||
date.getMinutes(),
|
||||
date.getSeconds(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a date represented by day, month, and year into a DateValue object (used by react-aria-components), noon UTC.
|
||||
*/
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"forms.with.user": "Nutzer {{nth}}",
|
||||
"forms.with.pick-up": "Pick-up",
|
||||
"forms.when.title": "Wann",
|
||||
"forms.when.explanation": "Leer lassen, wenn du jetzt nach einem Scrim suchen willst.",
|
||||
"forms.when.explanation": "",
|
||||
"forms.text.title": "Text",
|
||||
"forms.visibility.title": "Sichtbarkeit",
|
||||
"forms.visibility.public": "Öffentlich",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"forms.with.user": "User {{nth}}",
|
||||
"forms.with.pick-up": "Pick-up",
|
||||
"forms.when.title": "When",
|
||||
"forms.when.explanation": "Leave blank if you want to look for a scrim now",
|
||||
"forms.when.explanation": "Leave to default if you want to look for a scrim now",
|
||||
"forms.text.title": "Text",
|
||||
"forms.visibility.title": "Visibility",
|
||||
"forms.visibility.public": "Public",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"forms.with.user": "Utilisateur {{nth}}",
|
||||
"forms.with.pick-up": "Pick-up",
|
||||
"forms.when.title": "Quand",
|
||||
"forms.when.explanation": "Laissez vide si vous voulez chercher un scrim dès maintenant",
|
||||
"forms.when.explanation": "",
|
||||
"forms.text.title": "Texte",
|
||||
"forms.visibility.title": "Visibilité",
|
||||
"forms.visibility.public": "Public",
|
||||
|
||||
Reference in New Issue
Block a user