This commit is contained in:
45
packages/example/src/lib/TextAreaInput.tsx
Normal file
45
packages/example/src/lib/TextAreaInput.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Callout, Flex, TextArea, TextAreaProps } from "@radix-ui/themes"
|
||||||
|
import { Option, ParseResult, Schema, Struct } from "effect"
|
||||||
|
import { Component } from "effect-fc"
|
||||||
|
import { useInput } from "effect-fc/hooks"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
|
||||||
|
export interface TextAreaInputProps<A, R>
|
||||||
|
extends
|
||||||
|
Omit<useInput.Options<A, R>, "schema">,
|
||||||
|
Omit<TextAreaProps, "ref">
|
||||||
|
{}
|
||||||
|
|
||||||
|
export const TextAreaInput = <A, R>(
|
||||||
|
schema: Schema.Schema<A, string, R>
|
||||||
|
): Component.Component<
|
||||||
|
TextAreaInputProps<A, R>,
|
||||||
|
React.JSX.Element,
|
||||||
|
ParseResult.ParseError,
|
||||||
|
R
|
||||||
|
> => Component.makeUntraced(function* TextFieldInput(props) {
|
||||||
|
const input = yield* useInput({ schema, ...props })
|
||||||
|
const issues = React.useMemo(() => Option.map(
|
||||||
|
input.error,
|
||||||
|
ParseResult.ArrayFormatter.formatErrorSync,
|
||||||
|
), [input.error])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction="column" gap="1">
|
||||||
|
{Option.isSome(issues) &&
|
||||||
|
<Callout.Root color="red" role="alert">
|
||||||
|
<Callout.Text>
|
||||||
|
<ul>{issues.value.map((issue, i) => <li key={i}>{issue.message}</li>)}</ul>
|
||||||
|
</Callout.Text>
|
||||||
|
</Callout.Root>
|
||||||
|
}
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
value={input.value}
|
||||||
|
onChange={input.onChange}
|
||||||
|
{...Struct.omit(props, "ref")}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Callout, Flex, TextField } from "@radix-ui/themes"
|
import { Callout, Flex, TextField } from "@radix-ui/themes"
|
||||||
import { Option, ParseResult, Schema } from "effect"
|
import { Option, ParseResult, Schema, Struct } from "effect"
|
||||||
import { Component } from "effect-fc"
|
import { Component } from "effect-fc"
|
||||||
import { useInput } from "effect-fc/hooks"
|
import { useInput } from "effect-fc/hooks"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
|
|
||||||
export interface TextFieldInputProps<A, R> extends Omit<useInput.Options<A, R>, "schema"> {
|
export interface TextFieldInputProps<A, R>
|
||||||
readonly textFieldRootProps: Omit<TextField.RootProps, "children">
|
extends
|
||||||
readonly children?: React.ReactNode
|
Omit<useInput.Options<A, R>, "schema">,
|
||||||
}
|
Omit<TextField.RootProps, "ref">
|
||||||
|
{}
|
||||||
|
|
||||||
export const TextFieldInput = <A, R>(
|
export const TextFieldInput = <A, R>(
|
||||||
schema: Schema.Schema<A, string, R>
|
schema: Schema.Schema<A, string, R>
|
||||||
@@ -29,9 +30,7 @@ export const TextFieldInput = <A, R>(
|
|||||||
{Option.isSome(issues) &&
|
{Option.isSome(issues) &&
|
||||||
<Callout.Root color="red" role="alert">
|
<Callout.Root color="red" role="alert">
|
||||||
<Callout.Text>
|
<Callout.Text>
|
||||||
<ul>
|
<ul>{issues.value.map((issue, i) => <li key={i}>{issue.message}</li>)}</ul>
|
||||||
{issues.value.map(issue => <li>{issue.message}</li>)}
|
|
||||||
</ul>
|
|
||||||
</Callout.Text>
|
</Callout.Text>
|
||||||
</Callout.Root>
|
</Callout.Root>
|
||||||
}
|
}
|
||||||
@@ -39,10 +38,8 @@ export const TextFieldInput = <A, R>(
|
|||||||
<TextField.Root
|
<TextField.Root
|
||||||
value={input.value}
|
value={input.value}
|
||||||
onChange={input.onChange}
|
onChange={input.onChange}
|
||||||
{...props.textFieldRootProps}
|
{...Struct.omit(props, "ref")}
|
||||||
>
|
/>
|
||||||
{props.children}
|
|
||||||
</TextField.Root>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import * as Domain from "@/domain"
|
import * as Domain from "@/domain"
|
||||||
import { Box, Button, Callout, Flex, IconButton, Text, TextArea } from "@radix-ui/themes"
|
import { TextAreaInput } from "@/lib/TextAreaInput"
|
||||||
|
import { Box, Button, Flex, IconButton } from "@radix-ui/themes"
|
||||||
import { GetRandomValues, makeUuid4 } from "@typed/id"
|
import { GetRandomValues, makeUuid4 } from "@typed/id"
|
||||||
import { Chunk, Effect, Match, Option, ParseResult, Ref, Runtime, Schema, SubscriptionRef } from "effect"
|
import { Chunk, Effect, Match, Option, Ref, Runtime, Schema, SubscriptionRef } from "effect"
|
||||||
import { Component, Memo } from "effect-fc"
|
import { Component, Memo } from "effect-fc"
|
||||||
import { Hooks } from "effect-fc/hooks"
|
import { Hooks } from "effect-fc/hooks"
|
||||||
import { SubscriptionSubRef } from "effect-fc/types"
|
import { SubscriptionSubRef } from "effect-fc/types"
|
||||||
@@ -10,6 +11,8 @@ import { FaDeleteLeft } from "react-icons/fa6"
|
|||||||
import { TodosState } from "./TodosState.service"
|
import { TodosState } from "./TodosState.service"
|
||||||
|
|
||||||
|
|
||||||
|
const StringTextAreaInput = TextAreaInput(Schema.String)
|
||||||
|
|
||||||
const makeTodo = makeUuid4.pipe(
|
const makeTodo = makeUuid4.pipe(
|
||||||
Effect.map(id => Domain.Todo.Todo.make({
|
Effect.map(id => Domain.Todo.Todo.make({
|
||||||
id,
|
id,
|
||||||
@@ -41,26 +44,14 @@ export class Todo extends Component.makeUntraced(function* Todo(props: TodoProps
|
|||||||
), [props._tag, props.index])
|
), [props._tag, props.index])
|
||||||
|
|
||||||
const [size] = yield* Hooks.useSubscribeRefs(state.sizeRef)
|
const [size] = yield* Hooks.useSubscribeRefs(state.sizeRef)
|
||||||
const contentInput = yield* Hooks.useInput({ ref: contentRef, schema: Schema.Any })
|
|
||||||
|
const StringTextAreaInputFC = yield* StringTextAreaInput
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" align="stretch" gap="2">
|
<Flex direction="column" align="stretch" gap="2">
|
||||||
{Option.isSome(contentInput.error) &&
|
|
||||||
<Callout.Root color="red">
|
|
||||||
<Callout.Text>
|
|
||||||
{ParseResult.ArrayFormatter.formatErrorSync(contentInput.error.value).map(e => <>
|
|
||||||
<Text>• {e.message}</Text><br />
|
|
||||||
</>)}
|
|
||||||
</Callout.Text>
|
|
||||||
</Callout.Root>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Flex direction="row" align="center" gap="2">
|
<Flex direction="row" align="center" gap="2">
|
||||||
<Box flexGrow="1">
|
<Box flexGrow="1">
|
||||||
<TextArea
|
<StringTextAreaInputFC ref={contentRef} />
|
||||||
value={contentInput.value}
|
|
||||||
onChange={contentInput.onChange}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{props._tag === "edit" &&
|
{props._tag === "edit" &&
|
||||||
|
|||||||
Reference in New Issue
Block a user