Fix form example
All checks were successful
Lint / lint (push) Successful in 15s

This commit is contained in:
Julien Valverdé
2026-03-13 21:05:47 +01:00
parent 32e43707b6
commit 631819f16f
4 changed files with 79 additions and 40 deletions

View File

@@ -1,33 +1,20 @@
import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes"
import { Array, Option, Struct } from "effect"
import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes"
import { Array, Option } from "effect"
import { Component, Form, Subscribable } from "effect-fc"
interface Props
extends TextField.RootProps, Form.useInput.Options {
readonly optional?: false
readonly field: Form.FormField<any, string>
export declare namespace TextFieldFormInputView {
export interface Props
extends TextField.RootProps, Form.useInput.Options {
readonly field: Form.FormField<any, string>
}
}
interface OptionalProps
extends Omit<TextField.RootProps, "optional" | "defaultValue">, Form.useOptionalInput.Options<string> {
readonly optional: true
readonly field: Form.FormField<any, Option.Option<string>>
}
export type TextFieldFormInputProps = Props | OptionalProps
export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*(props: TextFieldFormInputProps) {
const input: (
| { readonly optional: true } & Form.useOptionalInput.Success<string>
| { readonly optional: false } & Form.useInput.Success<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) }
export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*(
props: TextFieldFormInputView.Props
) {
const input = yield* Form.useInput(props.field, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues,
props.field.isValidating,
@@ -39,19 +26,9 @@ export class TextFieldFormInputView extends Component.make("TextFieldFormInputVi
<TextField.Root
value={input.value}
onChange={e => input.setValue(e.target.value)}
disabled={(input.optional && !input.enabled) || isSubmitting}
{...Struct.omit(props, "optional", "defaultValue")}
disabled={isSubmitting}
{...props}
>
{input.optional &&
<TextField.Slot side="left">
<Switch
size="1"
checked={input.enabled}
onCheckedChange={input.setEnabled}
/>
</TextField.Slot>
}
{isValidating &&
<TextField.Slot side="right">
<Spinner />

View File

@@ -0,0 +1,60 @@
import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes"
import { Array, Option, Struct } from "effect"
import { Component, Form, Subscribable } from "effect-fc"
export declare namespace TextFieldOptionalFormInputView {
export interface Props
extends Omit<TextField.RootProps, "defaultValue">, Form.useOptionalInput.Options<string> {
readonly field: Form.FormField<any, Option.Option<string>>
}
}
export class TextFieldOptionalFormInputView extends Component.make("TextFieldOptionalFormInputView")(function*(
props: TextFieldOptionalFormInputView.Props
) {
const input = yield* Form.useOptionalInput(props.field, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues,
props.field.isValidating,
props.field.isSubmitting,
])
return (
<Flex direction="column" gap="1">
<TextField.Root
value={input.value}
onChange={e => input.setValue(e.target.value)}
disabled={!input.enabled || isSubmitting}
{...Struct.omit(props, "defaultValue")}
>
<TextField.Slot side="left">
<Switch
size="1"
checked={input.enabled}
onCheckedChange={input.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

@@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router"
import { Console, Effect, Match, Option, ParseResult, Schema } from "effect"
import { Component, Form, Subscribable } from "effect-fc"
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
import { runtime } from "@/runtime"
@@ -71,6 +72,7 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
const runPromise = yield* Component.useRunPromise()
const TextFieldFormInput = yield* TextFieldFormInputView.use
const TextFieldOptionalFormInput = yield* TextFieldOptionalFormInputView.use
yield* Component.useOnMount(() => Effect.gen(function*() {
yield* Effect.addFinalizer(() => Console.log("RegisterFormView unmounted"))
@@ -93,8 +95,7 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
field={yield* form.field(["password"])}
/>
<TextFieldFormInput
optional
<TextFieldOptionalFormInput
type="datetime-local"
field={yield* form.field(["birth"])}
defaultValue=""

View File

@@ -6,6 +6,7 @@ import { FaArrowDown, FaArrowUp } from "react-icons/fa"
import { FaDeleteLeft } from "react-icons/fa6"
import * as Domain from "@/domain"
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
import { TodosState } from "./TodosState"
@@ -84,6 +85,7 @@ export class TodoView extends Component.make("TodoView")(function*(props: TodoPr
const runSync = yield* Component.useRunSync()
const runPromise = yield* Component.useRunPromise<DateTime.CurrentTimeZone>()
const TextFieldFormInput = yield* TextFieldFormInputView.use
const TextFieldOptionalFormInput = yield* TextFieldOptionalFormInputView.use
return (
@@ -93,8 +95,7 @@ export class TodoView extends Component.make("TodoView")(function*(props: TodoPr
<TextFieldFormInput field={contentField} />
<Flex direction="row" justify="center" align="center" gap="2">
<TextFieldFormInput
optional
<TextFieldOptionalFormInput
field={completedAtField}
type="datetime-local"
defaultValue=""