This commit is contained in:
@@ -1,33 +1,20 @@
|
|||||||
import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes"
|
import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes"
|
||||||
import { Array, Option, Struct } from "effect"
|
import { Array, Option } from "effect"
|
||||||
import { Component, Form, Subscribable } from "effect-fc"
|
import { Component, Form, Subscribable } from "effect-fc"
|
||||||
|
|
||||||
|
|
||||||
interface Props
|
export declare namespace TextFieldFormInputView {
|
||||||
|
export 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, "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([
|
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
|
||||||
props.field.issues,
|
props.field.issues,
|
||||||
props.field.isValidating,
|
props.field.isValidating,
|
||||||
@@ -39,19 +26,9 @@ export class TextFieldFormInputView extends Component.make("TextFieldFormInputVi
|
|||||||
<TextField.Root
|
<TextField.Root
|
||||||
value={input.value}
|
value={input.value}
|
||||||
onChange={e => input.setValue(e.target.value)}
|
onChange={e => input.setValue(e.target.value)}
|
||||||
disabled={(input.optional && !input.enabled) || isSubmitting}
|
disabled={isSubmitting}
|
||||||
{...Struct.omit(props, "optional", "defaultValue")}
|
{...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 />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}) {}
|
||||||
@@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router"
|
|||||||
import { Console, Effect, Match, Option, ParseResult, Schema } from "effect"
|
import { Console, Effect, Match, Option, ParseResult, Schema } from "effect"
|
||||||
import { Component, Form, Subscribable } from "effect-fc"
|
import { Component, Form, Subscribable } from "effect-fc"
|
||||||
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
|
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
|
||||||
|
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
|
||||||
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
|
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
|
||||||
import { runtime } from "@/runtime"
|
import { runtime } from "@/runtime"
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
|
|||||||
|
|
||||||
const runPromise = yield* Component.useRunPromise()
|
const runPromise = yield* Component.useRunPromise()
|
||||||
const TextFieldFormInput = yield* TextFieldFormInputView.use
|
const TextFieldFormInput = yield* TextFieldFormInputView.use
|
||||||
|
const TextFieldOptionalFormInput = yield* TextFieldOptionalFormInputView.use
|
||||||
|
|
||||||
yield* Component.useOnMount(() => Effect.gen(function*() {
|
yield* Component.useOnMount(() => Effect.gen(function*() {
|
||||||
yield* Effect.addFinalizer(() => Console.log("RegisterFormView unmounted"))
|
yield* Effect.addFinalizer(() => Console.log("RegisterFormView unmounted"))
|
||||||
@@ -93,8 +95,7 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
|
|||||||
field={yield* form.field(["password"])}
|
field={yield* form.field(["password"])}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextFieldFormInput
|
<TextFieldOptionalFormInput
|
||||||
optional
|
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
field={yield* form.field(["birth"])}
|
field={yield* form.field(["birth"])}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { FaArrowDown, FaArrowUp } from "react-icons/fa"
|
|||||||
import { FaDeleteLeft } from "react-icons/fa6"
|
import { FaDeleteLeft } from "react-icons/fa6"
|
||||||
import * as Domain from "@/domain"
|
import * as Domain from "@/domain"
|
||||||
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
|
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
|
||||||
|
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
|
||||||
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
|
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
|
||||||
import { TodosState } from "./TodosState"
|
import { TodosState } from "./TodosState"
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ export class TodoView extends Component.make("TodoView")(function*(props: TodoPr
|
|||||||
const runSync = yield* Component.useRunSync()
|
const runSync = yield* Component.useRunSync()
|
||||||
const runPromise = yield* Component.useRunPromise<DateTime.CurrentTimeZone>()
|
const runPromise = yield* Component.useRunPromise<DateTime.CurrentTimeZone>()
|
||||||
const TextFieldFormInput = yield* TextFieldFormInputView.use
|
const TextFieldFormInput = yield* TextFieldFormInputView.use
|
||||||
|
const TextFieldOptionalFormInput = yield* TextFieldOptionalFormInputView.use
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,8 +95,7 @@ export class TodoView extends Component.make("TodoView")(function*(props: TodoPr
|
|||||||
<TextFieldFormInput field={contentField} />
|
<TextFieldFormInput field={contentField} />
|
||||||
|
|
||||||
<Flex direction="row" justify="center" align="center" gap="2">
|
<Flex direction="row" justify="center" align="center" gap="2">
|
||||||
<TextFieldFormInput
|
<TextFieldOptionalFormInput
|
||||||
optional
|
|
||||||
field={completedAtField}
|
field={completedAtField}
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
|
|||||||
Reference in New Issue
Block a user