This commit is contained in:
@@ -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 />
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {}
|
|
||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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=""
|
||||||
|
|||||||
Reference in New Issue
Block a user