0.1.4 #5

Merged
Thilawyn merged 67 commits from next into master 2025-10-02 18:18:23 +02:00
3 changed files with 53 additions and 38 deletions
Showing only changes of commit 040e671fd3 - Show all commits

View File

@@ -260,7 +260,6 @@ export namespace useInput {
export interface Result<T> { export interface Result<T> {
readonly value: T readonly value: T
readonly setValue: React.Dispatch<React.SetStateAction<T>> readonly setValue: React.Dispatch<React.SetStateAction<T>>
readonly issues: readonly ParseResult.ArrayFormatterIssue[]
} }
} }
@@ -275,7 +274,6 @@ export const useInput: {
) { ) {
const internalValueRef = yield* Hooks.useMemo(() => Effect.andThen(field.encodedValueRef, SubscriptionRef.make), [field]) const internalValueRef = yield* Hooks.useMemo(() => Effect.andThen(field.encodedValueRef, SubscriptionRef.make), [field])
const [value, setValue] = yield* Hooks.useRefState(internalValueRef) const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable)
yield* Hooks.useFork(() => Effect.all([ yield* Hooks.useFork(() => Effect.all([
Stream.runForEach( Stream.runForEach(
@@ -296,7 +294,7 @@ export const useInput: {
), ),
], { concurrency: "unbounded" }), [field, internalValueRef, options?.debounce]) ], { concurrency: "unbounded" }), [field, internalValueRef, options?.debounce])
return { value, setValue, issues } return { value, setValue }
}) })
export namespace useOptionalInput { export namespace useOptionalInput {
@@ -329,7 +327,6 @@ export const useOptionalInput: {
const [enabled, setEnabled] = yield* Hooks.useRefState(enabledRef) const [enabled, setEnabled] = yield* Hooks.useRefState(enabledRef)
const [value, setValue] = yield* Hooks.useRefState(internalValueRef) const [value, setValue] = yield* Hooks.useRefState(internalValueRef)
const [issues] = yield* Hooks.useSubscribables(field.issuesSubscribable)
yield* Hooks.useFork(() => Effect.all([ yield* Hooks.useFork(() => Effect.all([
Stream.runForEach( Stream.runForEach(
@@ -365,5 +362,5 @@ export const useOptionalInput: {
), ),
], { concurrency: "unbounded" }), [field, enabledRef, internalValueRef, options.debounce]) ], { concurrency: "unbounded" }), [field, enabledRef, internalValueRef, options.debounce])
return { enabled, setEnabled, value, setValue, issues } return { enabled, setEnabled, value, setValue }
}) })

View File

@@ -0,0 +1,40 @@
import { Callout, Flex, TextField } from "@radix-ui/themes"
import { Array, Option } from "effect"
import { Component, Form } from "effect-fc"
import { useSubscribables } from "effect-fc/hooks"
export interface TextFieldFormInputProps
extends TextField.RootProps, Form.useInput.Options {
readonly field: Form.FormField<any, string>
}
export class TextFieldFormInput extends Component.makeUntraced("TextFieldFormInput")(function*(props: TextFieldFormInputProps) {
const { value, setValue } = yield* Form.useInput(props.field, props)
const [issues, isValidating, isSubmitting] = yield* 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={isSubmitting}
{...props}
/>
{Option.match(Array.head(issues), {
onSome: issue => (
<Callout.Root>
<Callout.Text>{issue.message}</Callout.Text>
</Callout.Root>
),
onNone: () => <></>,
})}
</Flex>
)
}) {}

View File

@@ -1,7 +1,8 @@
import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput"
import { runtime } from "@/runtime" import { runtime } from "@/runtime"
import { Button, Callout, Container, Flex, TextField } from "@radix-ui/themes" import { Button, Container, Flex } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { Array, Effect, Option, Schema } from "effect" import { Effect, Schema } from "effect"
import { Component, Form } from "effect-fc" import { Component, Form } from "effect-fc"
import { useContext, useSubscribables } from "effect-fc/hooks" import { useContext, useSubscribables } from "effect-fc/hooks"
@@ -34,14 +35,11 @@ class RegisterForm extends Effect.Service<RegisterForm>()("RegisterForm", {
class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() { class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() {
const form = yield* RegisterForm const form = yield* RegisterForm
const emailField = Form.useField(form, ["email"])
const passwordField = Form.useField(form, ["password"])
const emailInput = yield* Form.useInput(emailField, { debounce: "200 millis" })
const passwordInput = yield* Form.useInput(passwordField, { debounce: "200 millis" })
const submit = yield* Form.useSubmit(form) const submit = yield* Form.useSubmit(form)
const [canSubmit] = yield* useSubscribables(form.canSubmitSubscribable) const [canSubmit] = yield* useSubscribables(form.canSubmitSubscribable)
const TextFieldFormInputFC = yield* TextFieldFormInput
return ( return (
<Container> <Container>
<form onSubmit={e => { <form onSubmit={e => {
@@ -49,36 +47,16 @@ class RegisterPage extends Component.makeUntraced("RegisterPage")(function*() {
void submit() void submit()
}}> }}>
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">
<TextField.Root <TextFieldFormInputFC
value={emailInput.value} field={Form.useField(form, ["email"])}
onChange={e => emailInput.setValue(e.target.value)} debounce="500 millis"
/> />
{Option.match(Array.head(emailInput.issues), { <TextFieldFormInputFC
onSome: issue => ( field={Form.useField(form, ["password"])}
<Callout.Root> debounce="500 millis"
<Callout.Text>{issue.message}</Callout.Text>
</Callout.Root>
),
onNone: () => <></>,
})}
<TextField.Root
value={passwordInput.value}
onChange={e => passwordInput.setValue(e.target.value)}
/> />
{Option.match(Array.head(passwordInput.issues), {
onSome: issue => (
<Callout.Root>
<Callout.Text>{issue.message}</Callout.Text>
</Callout.Root>
),
onNone: () => <></>,
})}
<Button disabled={!canSubmit}>Submit</Button> <Button disabled={!canSubmit}>Submit</Button>
</Flex> </Flex>
</form> </form>