76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes"
|
|
import { Array, Option, Struct } 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>
|
|
}
|
|
|
|
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 TextFieldFormInput extends Component.makeUntraced("TextFieldFormInput")(function*(props: TextFieldFormInputProps) {
|
|
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* 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.optional && !input.enabled) || isSubmitting}
|
|
{...Struct.omit(props, "optional", "defaultValue")}
|
|
>
|
|
{input.optional &&
|
|
<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>
|
|
)
|
|
}) {}
|