mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Improve mobile experience on the calculator
This commit is contained in:
@@ -8,6 +8,7 @@ import { IoAdd, IoCloseOutline } from "react-icons/io5";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { RadioGroupField } from "@/components/form/RadioGroupField";
|
||||||
import { SelectField } from "@/components/form/SelectField";
|
import { SelectField } from "@/components/form/SelectField";
|
||||||
import { TextField } from "@/components/form/TextField";
|
import { TextField } from "@/components/form/TextField";
|
||||||
import { getNewRating } from "@/utils/eloCalculator";
|
import { getNewRating } from "@/utils/eloCalculator";
|
||||||
@@ -103,7 +104,7 @@ export const ManualEloForm = () => {
|
|||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
<div className="grid w-full grid-cols-1 gap-2 sm:grid-cols-2">
|
<div className="grid w-full grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<TextField
|
<TextField
|
||||||
name="currentElo"
|
name="currentElo"
|
||||||
label={t("currentEloLabel")}
|
label={t("currentEloLabel")}
|
||||||
@@ -135,7 +136,7 @@ export const ManualEloForm = () => {
|
|||||||
return (
|
return (
|
||||||
<div key={i} className="flex w-full flex-col">
|
<div key={i} className="flex w-full flex-col">
|
||||||
<div key={i} className="flex w-full items-center space-x-2">
|
<div key={i} className="flex w-full items-center space-x-2">
|
||||||
<div className="grid w-full grid-cols-1 gap-2 sm:grid-cols-2">
|
<div className="grid w-full grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<TextField
|
<TextField
|
||||||
name={`games.${i}.opponentElo`}
|
name={`games.${i}.opponentElo`}
|
||||||
placeholder={t("opponentEloPlaceholder")}
|
placeholder={t("opponentEloPlaceholder")}
|
||||||
@@ -144,14 +145,12 @@ export const ManualEloForm = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<SelectField
|
<RadioGroupField
|
||||||
name={`games.${i}.result`}
|
name={`games.${i}.result`}
|
||||||
placeholder={t("gameResultPlaceholder")}
|
|
||||||
options={["win", "draw", "loss"].map((result) => ({
|
options={["win", "draw", "loss"].map((result) => ({
|
||||||
value: result,
|
value: result,
|
||||||
label: t("gameResult", { result }),
|
label: t("gameResult", { result }),
|
||||||
}))}
|
}))}
|
||||||
isClearable={false}
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{gameFields.length > 1 && (
|
{gameFields.length > 1 && (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
import { NextIntlClientProvider, useLocale } from "next-intl";
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
import { getTranslator } from "next-intl/server";
|
import { getTranslator } from "next-intl/server";
|
||||||
import { Inter, Julius_Sans_One } from "next/font/google";
|
import { Inter, Julius_Sans_One } from "next/font/google";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
@@ -51,6 +51,12 @@ export default async function RootLayout({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale} className={`${inter.variable} ${title.variable}`}>
|
<html lang={locale} className={`${inter.variable} ${title.variable}`}>
|
||||||
|
<head>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Providers>
|
<Providers>
|
||||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { RadioGroup } from "@headlessui/react";
|
||||||
|
import { get, isNil } from "lodash";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { Field, FieldProps } from "./Field";
|
||||||
|
|
||||||
|
export type BaseOption<T = string, D = unknown> = {
|
||||||
|
value: T;
|
||||||
|
label: string | JSX.Element;
|
||||||
|
disabled?: boolean;
|
||||||
|
data?: D;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RadioGroupFieldProps<T = string, D = unknown> = FieldProps & {
|
||||||
|
required?: boolean;
|
||||||
|
options: BaseOption<T, D>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RadioGroupField = <T extends React.Key = string, D = unknown>({
|
||||||
|
name,
|
||||||
|
className,
|
||||||
|
labelClassName,
|
||||||
|
childrenWrapperClassName,
|
||||||
|
label,
|
||||||
|
hideErrorMessage,
|
||||||
|
required,
|
||||||
|
|
||||||
|
options,
|
||||||
|
}: RadioGroupFieldProps<T, D>) => {
|
||||||
|
const t = useTranslations("App");
|
||||||
|
const form = useFormContext();
|
||||||
|
|
||||||
|
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: "" };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field
|
||||||
|
{...{
|
||||||
|
name,
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
labelClassName,
|
||||||
|
childrenWrapperClassName,
|
||||||
|
hideErrorMessage,
|
||||||
|
required,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name={name}
|
||||||
|
control={form.control}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
const optionValue = isNil(value) ? null : valueToOption(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadioGroup
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
className="flex w-full justify-stretch gap-2"
|
||||||
|
>
|
||||||
|
{options.map((option) => (
|
||||||
|
<RadioGroup.Option
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
className={({ checked }) =>
|
||||||
|
twMerge(
|
||||||
|
"flex flex-1 cursor-pointer items-center justify-center rounded-lg border px-5 py-3 text-center text-sm font-medium focus:outline-none",
|
||||||
|
"border-gray-300 bg-gray-50 text-gray-900",
|
||||||
|
"dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||||
|
checked &&
|
||||||
|
"border-primary text-primary dark:border-primary dark:text-primary",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
disabled={option.disabled}
|
||||||
|
>
|
||||||
|
<RadioGroup.Label as="span">{option.label}</RadioGroup.Label>
|
||||||
|
</RadioGroup.Option>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -87,7 +87,7 @@ export const TextField = (
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex w-full content-center rounded-lg border p-3 text-sm",
|
"flex w-full content-center rounded-lg border p-3 text-sm",
|
||||||
"border-gray-300 bg-gray-50 text-gray-900 shadow-sm focus:border-primary-500 focus:ring-primary-500",
|
"border-gray-300 bg-gray-50 text-gray-900 shadow-sm focus:border-primary-500 focus:ring-primary-500",
|
||||||
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500",
|
"dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500",
|
||||||
hasError && "!border-orange-700 focus:!border-orange-700",
|
hasError && "!border-orange-700 focus:!border-orange-700",
|
||||||
disabled && "dark:bg-gray-50",
|
disabled && "dark:bg-gray-50",
|
||||||
|
|||||||
Reference in New Issue
Block a user