Form fix
All checks were successful
Lint / lint (push) Successful in 12s

This commit is contained in:
Julien Valverdé
2025-10-02 16:57:44 +02:00
parent fd6de8e621
commit 5951a0bf16
4 changed files with 38 additions and 66 deletions

View File

@@ -1,16 +1,34 @@
import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes" import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes"
import { Array, Option } from "effect" import { Array, Option } from "effect"
import { Component, Form, Hooks } from "effect-fc" import { Component, Form, Hooks } from "effect-fc"
export interface TextFieldFormInputProps interface Props
extends TextField.RootProps, Form.useInput.Options { extends TextField.RootProps, Form.useInput.Options {
readonly optional?: false
readonly field: Form.FormField<any, string> readonly field: Form.FormField<any, string>
} }
interface OptionalProps
extends Omit<TextField.RootProps, "defaultValue">, Form.useOptionalInput.Options<string> {
readonly optional: true
readonly field: Form.FormField<any, Option.Option<string>>
}
export type TextFieldFormInputProps = Props | OptionalProps
export class TextFieldFormInput extends Component.makeUntraced("TextFieldFormInput")( export class TextFieldFormInput extends Component.makeUntraced("TextFieldFormInput")(
function*(props: TextFieldFormInputProps) { function*(props: TextFieldFormInputProps) {
const { value, setValue } = yield* Form.useInput(props.field, props) const input: (
| { readonly optional: true } & Form.useOptionalInput.Result<string>
| { readonly optional: false } & Form.useInput.Result<string>
) = props.optional
// biome-ignore lint/correctness/useHookAtTopLevel: "optional" reactivity not supported
? { optional: true, ...yield* Form.useOptionalInput(props.field, props) }
// biome-ignore lint/correctness/useHookAtTopLevel: "optional" reactivity not supported
: { optional: false, ...yield* Form.useInput(props.field, props) }
const [issues, isValidating, isSubmitting] = yield* Hooks.useSubscribables( const [issues, isValidating, isSubmitting] = yield* Hooks.useSubscribables(
props.field.issuesSubscribable, props.field.issuesSubscribable,
props.field.isValidatingSubscribable, props.field.isValidatingSubscribable,
@@ -20,11 +38,21 @@ export class TextFieldFormInput extends Component.makeUntraced("TextFieldFormInp
return ( return (
<Flex direction="column" gap="1"> <Flex direction="column" gap="1">
<TextField.Root <TextField.Root
value={value} value={input.value}
onChange={e => setValue(e.target.value)} onChange={e => input.setValue(e.target.value)}
disabled={isSubmitting} disabled={(input.optional && !input.enabled) || isSubmitting}
{...props} {...props}
> >
{input.optional &&
<TextField.Slot side="left">
<Switch
size="1"
checked={input.enabled}
onCheckedChange={input.setEnabled}
/>
</TextField.Slot>
}
{isValidating && {isValidating &&
<TextField.Slot side="right"> <TextField.Slot side="right">
<Spinner /> <Spinner />

View File

@@ -1,57 +0,0 @@
import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes"
import { Array, Option } from "effect"
import { Component, Form, Hooks } from "effect-fc"
export interface TextFieldFormOptionalInputProps
extends Omit<TextField.RootProps, "defaultValue">, Form.useOptionalInput.Options<string> {
readonly field: Form.FormField<any, Option.Option<string>>
}
export class TextFieldFormOptionalInput extends Component.makeUntraced("TextFieldFormOptionalInput")(
function*(props: TextFieldFormOptionalInputProps) {
const { value, setValue, enabled, setEnabled } = yield* Form.useOptionalInput(props.field, props)
const [issues, isValidating, isSubmitting] = yield* Hooks.useSubscribables(
props.field.issuesSubscribable,
props.field.isValidatingSubscribable,
props.field.isSubmittingSubscribable,
)
return (
<Flex direction="column" gap="1">
<TextField.Root
value={value}
onChange={e => setValue(e.target.value)}
disabled={!enabled || isSubmitting}
{...props}
>
<TextField.Slot side="left">
<Switch
size="1"
checked={enabled}
onCheckedChange={setEnabled}
/>
</TextField.Slot>
{isValidating &&
<TextField.Slot side="right">
<Spinner />
</TextField.Slot>
}
{props.children}
</TextField.Root>
{Option.match(Array.head(issues), {
onSome: issue => (
<Callout.Root>
<Callout.Text>{issue.message}</Callout.Text>
</Callout.Root>
),
onNone: () => <></>,
})}
</Flex>
)
}
) {}

View File

@@ -14,6 +14,7 @@ declare module "@tanstack/react-router" {
} }
} }
// biome-ignore lint/style/noNonNullAssertion: React entrypoint
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
<ReactRuntime.Provider runtime={runtime}> <ReactRuntime.Provider runtime={runtime}>

View File

@@ -3,7 +3,6 @@ import { createFileRoute } from "@tanstack/react-router"
import { Console, Effect, Option, ParseResult, Schema } from "effect" import { Console, Effect, Option, ParseResult, Schema } from "effect"
import { Component, Form, Hooks } from "effect-fc" import { Component, Form, Hooks } from "effect-fc"
import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput" import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput"
import { TextFieldFormOptionalInput } from "@/lib/form/TextFieldFormOptionalInput"
import { DateTimeUtcFromZonedInput } from "@/lib/schema" import { DateTimeUtcFromZonedInput } from "@/lib/schema"
import { runtime } from "@/runtime" import { runtime } from "@/runtime"
@@ -53,7 +52,7 @@ class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() {
const [canSubmit] = yield* Hooks.useSubscribables(form.canSubmitSubscribable) const [canSubmit] = yield* Hooks.useSubscribables(form.canSubmitSubscribable)
const TextFieldFormInputFC = yield* TextFieldFormInput const TextFieldFormInputFC = yield* TextFieldFormInput
const TextFieldFormOptionalInputFC = yield* TextFieldFormOptionalInput
return ( return (
<Container width="300"> <Container width="300">
@@ -72,7 +71,8 @@ class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() {
debounce="200 millis" debounce="200 millis"
/> />
<TextFieldFormOptionalInputFC <TextFieldFormInputFC
optional
type="datetime-local" type="datetime-local"
field={Form.useField(form, ["birth"])} field={Form.useField(form, ["birth"])}
defaultValue="" defaultValue=""