Repository: itizawa/thread-note
Pull request #201: Input コンポーネントを改修して error 表示できるようにした
Author: login: itizawa
Created: 2025-04-11T01:27:59Z | Merged: 2025-04-11T01:37:00Z

## 対象の Issue

Fix: #199

## 変更点(UI の変更があればスクリーンショットも)

-


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced a labeled grouping element to improve form field organization and error display.
- **Refactor**
	- Streamlined the thread detail form by unifying label management and simplifying form handling.
- **Style**
	- Increased the default height of the description field.
	- Enhanced visual feedback for error states on input and textarea components.
- **Bug Fixes**
	- Improved error handling and message display for input and textarea components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/features/threadDetail/PublicStatusSheet/index.tsx b/src/features/threadDetail/PublicStatusSheet/index.tsx
index 27eb7a0b..34576c38 100644
--- a/src/features/threadDetail/PublicStatusSheet/index.tsx
+++ b/src/features/threadDetail/PublicStatusSheet/index.tsx
@@ -1,7 +1,6 @@
 "use client";
 
 import { updateThreadPublicStatus } from "@/app/actions/threadActions";
-import { useAutoResizeTextarea } from "@/entities/post/PostForm/hooks/useAutoResizeTextarea";
 import { useServerAction } from "@/shared/lib/useServerAction";
 import { Button } from "@/shared/ui/button";
 import { Input } from "@/shared/ui/input";
@@ -15,11 +14,12 @@ import {
 } from "@/shared/ui/sheet";
 import { Textarea } from "@/shared/ui/textarea";
 import { Tooltip } from "@/shared/ui/Tooltip";
+import { WithLabel } from "@/shared/ui/WithLabel";
 import { trpc } from "@/trpc/client";
-import { useForm, useStore } from "@tanstack/react-form";
+import { useForm } from "@tanstack/react-form";
 import { Eye, EyeOff, Globe, Lock, Save } from "lucide-react";
 import Image from "next/image";
-import { useRef, useState } from "react";
+import { useState } from "react";
 import { z } from "zod";
 import { ShareInformation } from "./parts/ShareInformation";
 
@@ -41,7 +41,6 @@ export function PublicStatusSheet({
   const [open, setOpen] = useState(false);
   const { isPending, enqueueServerAction } = useServerAction();
   const utils = trpc.useUtils();
-  const textareaRef = useRef<HTMLTextAreaElement>(null);
 
   const { mutate: updateOgpInfo, isPending: isOgpUpdatePending } =
     trpc.thread.updateThreadOgpInfo.useMutation({
@@ -51,7 +50,7 @@ export function PublicStatusSheet({
       },
     });
 
-  const { Field, store, Subscribe, handleSubmit } = useForm({
+  const { Field, Subscribe, handleSubmit } = useForm({
     defaultValues: {
       ogpTitle: ogpTitle || "",
       ogpDescription: ogpDescription || "",
@@ -71,14 +70,6 @@ export function PublicStatusSheet({
     },
   });
 
-  const value = useStore(store, (state) => state.values.ogpDescription);
-  // テキストエリアの高さを自動調整するフックを使用
-  const { handleResize } = useAutoResizeTextarea({
-    value,
-    textareaRef,
-    minHeight: 78,
-  });
-
   const handleTogglePublicStatus = async () => {
     enqueueServerAction({
       action: () =>
@@ -173,61 +164,41 @@ export function PublicStatusSheet({
               className="flex flex-col space-y-4 mt-6 pt-6 border-t"
             >
               <h4 className="font-medium">OGP設定</h4>
-              <div className="flex flex-col space-y-2">
-                <label htmlFor="ogpTitle" className="text-sm font-medium">
-                  OGPタイトル
-                </label>
-                <Field name="ogpTitle">
-                  {({ state, handleBlur, handleChange }) => (
-                    <div className="flex flex-col space-y-2">
-                      <Input
-                        id="ogpTitle"
-                        placeholder="未入力の場合はスレッドタイトルが使用されます"
-                        value={state.value}
-                        onBlur={handleBlur}
-                        onChange={(e) => handleChange(e.target.value)}
-                      />
-                      {state.meta.errors[0]?.message && (
-                        <p className="text-red-500 text-xs">
-                          {state.meta.errors[0]?.message}
-                        </p>
-                      )}
-                    </div>
-                  )}
-                </Field>
-              </div>
-              <div className="flex flex-col space-y-2">
-                <label htmlFor="ogpDescription" className="text-sm font-medium">
-                  OGP説明文
-                </label>
-                <Field name="ogpDescription">
-                  {({ state, handleBlur, handleChange }) => (
-                    <div className="flex flex-col space-y-2">
-                      <Textarea
-                        id="ogpDescription"
-                        placeholder="OGP説明文を入力（検索結果に表示される説明文）"
-                        value={state.value}
-                        onBlur={handleBlur}
-                        rows={3}
-                        onChange={(e) => {
-                          handleChange(e.target.value);
-                          handleResize();
-                        }}
-                        ref={textareaRef}
-                      />
-                      {state.meta.errors[0]?.message && (
-                        <p className="text-red-500 text-xs">
-                          {state.meta.errors[0]?.message}
-                        </p>
-                      )}
-                    </div>
-                  )}
-                </Field>
-              </div>
-              <div className="flex flex-col space-y-2">
-                <label htmlFor="ogpImage" className="text-sm font-medium">
-                  OGPイメージ(保存すると反映されます)
-                </label>
+              <Field name="ogpTitle">
+                {({ state, handleBlur, handleChange }) => (
+                  <WithLabel label="OGPタイトル">
+                    <Input
+                      id="ogpTitle"
+                      placeholder="未入力の場合はスレッドタイトルが使用されます"
+                      value={state.value}
+                      onBlur={handleBlur}
+                      error={!!state.meta.errors[0]?.message}
+                      errorMessage={state.meta.errors[0]?.message}
+                      onChange={(e) => handleChange(e.target.value)}
+                    />
+                  </WithLabel>
+                )}
+              </Field>
+              <Field name="ogpDescription">
+                {({ state, handleBlur, handleChange }) => (
+                  <WithLabel label="OGP説明文">
+                    <Textarea
+                      id="ogpDescription"
+                      placeholder="OGP説明文を入力（検索結果に表示される説明文）"
+                      value={state.value}
+                      onBlur={handleBlur}
+                      rows={5}
+                      onChange={(e) => handleChange(e.target.value)}
+                    />
+                    {state.meta.errors[0]?.message && (
+                      <p className="text-red-500 text-xs">
+                        {state.meta.errors[0]?.message}
+                      </p>
+                    )}
+                  </WithLabel>
+                )}
+              </Field>
+              <WithLabel label="OGPイメージ(保存すると反映されます)">
                 <Image
                   src={`/api/og?title=${encodeURIComponent(
                     ogpTitle || threadTitle || ""
@@ -237,7 +208,7 @@ export function PublicStatusSheet({
                   height={630}
                   className="rounded-md"
                 />
-              </div>
+              </WithLabel>
             </form>
           )}
         </div>
diff --git a/src/shared/ui/WithLabel/index.tsx b/src/shared/ui/WithLabel/index.tsx
new file mode 100644
index 00000000..fd177ec7
--- /dev/null
+++ b/src/shared/ui/WithLabel/index.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import { ReactNode } from "react";
+
+type LinkToBackProps = {
+  children: ReactNode;
+  label: ReactNode;
+};
+
+export function WithLabel({ children, label }: LinkToBackProps) {
+  return (
+    <div className="flex flex-col space-y-2">
+      <label className="text-xs">{label}</label>
+      {children}
+    </div>
+  );
+}
diff --git a/src/shared/ui/input.tsx b/src/shared/ui/input.tsx
index ae2ba6c5..c10981b7 100644
--- a/src/shared/ui/input.tsx
+++ b/src/shared/ui/input.tsx
@@ -4,8 +4,12 @@ import { cn } from "@/shared/lib/utils";
 
 const Input = React.forwardRef<
   HTMLInputElement,
-  React.ComponentProps<"input"> & { forceFocus?: boolean }
->(({ className, type, forceFocus, ...props }, ref) => {
+  React.ComponentProps<"input"> & {
+    forceFocus?: boolean;
+    error?: boolean;
+    errorMessage?: string;
+  }
+>(({ className, type, forceFocus, error, errorMessage, ...props }, ref) => {
   const inputRef = React.useRef<HTMLInputElement>(null);
 
   React.useEffect(() => {
@@ -18,15 +22,21 @@ const Input = React.forwardRef<
   }, [forceFocus]);
 
   return (
-    <input
-      type={type}
-      className={cn(
-        "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
-        className
-      )}
-      ref={ref || inputRef}
-      {...props}
-    />
+    <div className="flex flex-col space-y-2 w-full">
+      <input
+        type={type}
+        className={cn(
+          "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+          error
+            ? "border-red-500 focus-visible:ring-red-500"
+            : "border-input focus-visible:ring-ring",
+          className
+        )}
+        ref={ref || inputRef}
+        {...props}
+      />
+      {errorMessage && <p className="text-red-500 text-xs">{errorMessage}</p>}
+    </div>
   );
 });
 Input.displayName = "Input";
diff --git a/src/shared/ui/textarea.tsx b/src/shared/ui/textarea.tsx
index 153acb8a..510a6a28 100644
--- a/src/shared/ui/textarea.tsx
+++ b/src/shared/ui/textarea.tsx
@@ -4,8 +4,12 @@ import { cn } from "@/shared/lib/utils";
 
 const Textarea = React.forwardRef<
   HTMLTextAreaElement,
-  React.ComponentProps<"textarea"> & { forceFocus?: boolean }
->(({ className, forceFocus, ...props }, ref) => {
+  React.ComponentProps<"textarea"> & {
+    forceFocus?: boolean;
+    error?: boolean;
+    errorMessage?: string;
+  }
+>(({ className, forceFocus, error, errorMessage, ...props }, ref) => {
   const inputRef = React.useRef<HTMLTextAreaElement>(null);
   React.useImperativeHandle(ref, () => inputRef.current!, [inputRef]);
 
@@ -19,14 +23,20 @@ const Textarea = React.forwardRef<
   }, [forceFocus]);
 
   return (
-    <textarea
-      className={cn(
-        "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
-        className
-      )}
-      ref={inputRef}
-      {...props}
-    />
+    <div className="flex flex-col space-y-2 w-full">
+      <textarea
+        className={cn(
+          "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base placeholder:text-muted-foreground focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+          error
+            ? "border-red-500 focus-visible:ring-red-500"
+            : "border-input focus-visible:ring-ring",
+          className
+        )}
+        ref={inputRef}
+        {...props}
+      />
+      {errorMessage && <p className="text-red-500 text-xs">{errorMessage}</p>}
+    </div>
   );
 });
 Textarea.displayName = "Textarea";


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>src/shared/ui/WithLabel/index.tsx (1)</summary><blockquote>

`10-17`: **Update function signature to use the correct type**

The function is using the misnamed type. Also, consider adding additional props like error handling capabilities to make this component more versatile.


```diff
-export function WithLabel({ children, label }: LinkToBackProps) {
+export function WithLabel({ children, label }: WithLabelProps) {
  return (
    <div className="flex flex-col space-y-2">
      <label className="text-xs">{label}</label>
      {children}
    </div>
  );
}
```

</blockquote></details>
<details>
<summary>src/shared/ui/textarea.tsx (1)</summary><blockquote>

`27-34`: **Apply consistent styling across components**

The styles for the Textarea component are similar to the Input component but with slight differences. Consider extracting common styles to ensure consistency.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e345f42a8f3bb817e6d7f250d0046d2262297cb0 and 9c924a4a1f69eb9f15bc30ee47ee946593fd3b08.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `src/features/threadDetail/PublicStatusSheet/index.tsx` (4 hunks)
* `src/shared/ui/WithLabel/index.tsx` (1 hunks)
* `src/shared/ui/input.tsx` (2 hunks)
* `src/shared/ui/textarea.tsx` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>src/shared/ui/input.tsx (1)</summary><blockquote>

<details>
<summary>src/shared/lib/utils.ts (1)</summary>

* `cn` (4-6)

</details>

</blockquote></details>
<details>
<summary>src/shared/ui/textarea.tsx (1)</summary><blockquote>

<details>
<summary>src/shared/lib/utils.ts (1)</summary>

* `cn` (4-6)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>src/shared/ui/textarea.tsx (1)</summary>

`7-11`: **Error handling props added correctly**

The addition of error handling props improves the component's flexibility and provides visual feedback for validation errors.

</details>
<details>
<summary>src/shared/ui/input.tsx (2)</summary>

`7-11`: **Error handling props added correctly**

The addition of error handling props improves the component's flexibility and provides visual feedback for validation errors.

---

`25-39`: **Consistent UI implementation for form components**

The Input component now has a similar structure to the Textarea component, which provides a consistent user experience. Both display error states with red borders and error messages below the input.

</details>
<details>
<summary>src/features/threadDetail/PublicStatusSheet/index.tsx (4)</summary>

`17-22`: **Import changes improve code organization**

The component now uses `WithLabel` for form structure and removes unnecessary imports. The change from `useStore` to `useForm` seems to be a refactoring decision that simplifies the code.

---

`167-181`: **Improved form field structure with WithLabel**

The OGP title field now uses the WithLabel component for better organization and consistent error handling.

---

`190-190`: **Increased rows for better user experience**

Increasing the textarea rows from 3 to 5 provides more space for users to enter OGP descriptions, improving usability.

---

`201-211`: **Consistent use of WithLabel for OGPイメージ**

The OGP image is now consistently wrapped with the WithLabel component, maintaining UI consistency.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-11T01:35:39Z

Review comments:
diff_hunk: @@ -0,0 +1,17 @@
+"use client";
+
+import { ReactNode } from "react";
+
+type LinkToBackProps = {
+  children: ReactNode;
+  label: ReactNode;
+};
path: src/shared/ui/WithLabel/index.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Rename type to match component purpose**

The type name `LinkToBackProps` doesn't match the component's purpose. It suggests navigation functionality, but this component is for form field labeling.


```diff
-type LinkToBackProps = {
+type WithLabelProps = {
  children: ReactNode;
  label: ReactNode;
};
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
type WithLabelProps = {
  children: ReactNode;
  label: ReactNode;
};
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-11T01:35:38Z
author_association: NONE
diff_hunk: @@ -19,14 +23,20 @@ const Textarea = React.forwardRef<
   }, [forceFocus]);
 
   return (
-    <textarea
-      className={cn(
-        "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
-        className
-      )}
-      ref={inputRef}
-      {...props}
-    />
+    <div className="flex flex-col space-y-2 w-full">
+      <textarea
+        className={cn(
+          "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+          error
+            ? "border-red-500 focus-visible:ring-red-500"
+            : "border-input focus-visible:ring-ring",
+          className
+        )}
+        ref={inputRef}
+        {...props}
+      />
+      {errorMessage && <p className="text-red-500 text-xs">{errorMessage}</p>}
+    </div>
path: src/shared/ui/textarea.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider handling error message rendering more consistently**

The Textarea component is rendering error messages outside the textarea element directly, but the error styling seems correct. However, in the PublicStatusSheet component, the error message for the description field is still manually added (lines 193-197) despite this component having error message capability.


Improve the error handling by using the errorMessage prop consistently:

```diff
<Textarea
  id="ogpDescription"
  placeholder="OGP説明文を入力（検索結果に表示される説明文）"
  value={state.value}
  onBlur={handleBlur}
  rows={5}
+  error={!!state.meta.errors[0]?.message}
+  errorMessage={state.meta.errors[0]?.message}
  onChange={(e) => handleChange(e.target.value)}
/>
-{state.meta.errors[0]?.message && (
-  <p className="text-red-500 text-xs">
-    {state.meta.errors[0]?.message}
-  </p>
-)}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
<Textarea
  id="ogpDescription"
  placeholder="OGP説明文を入力（検索結果に表示される説明文）"
  value={state.value}
  onBlur={handleBlur}
  rows={5}
  error={!!state.meta.errors[0]?.message}
  errorMessage={state.meta.errors[0]?.message}
  onChange={(e) => handleChange(e.target.value)}
/>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-11T01:35:38Z
author_association: NONE
diff_hunk: @@ -173,61 +164,41 @@ export function PublicStatusSheet({
               className="flex flex-col space-y-4 mt-6 pt-6 border-t"
             >
               <h4 className="font-medium">OGP設定</h4>
-              <div className="flex flex-col space-y-2">
-                <label htmlFor="ogpTitle" className="text-sm font-medium">
-                  OGPタイトル
-                </label>
-                <Field name="ogpTitle">
-                  {({ state, handleBlur, handleChange }) => (
-                    <div className="flex flex-col space-y-2">
-                      <Input
-                        id="ogpTitle"
-                        placeholder="未入力の場合はスレッドタイトルが使用されます"
-                        value={state.value}
-                        onBlur={handleBlur}
-                        onChange={(e) => handleChange(e.target.value)}
-                      />
-                      {state.meta.errors[0]?.message && (
-                        <p className="text-red-500 text-xs">
-                          {state.meta.errors[0]?.message}
-                        </p>
-                      )}
-                    </div>
-                  )}
-                </Field>
-              </div>
-              <div className="flex flex-col space-y-2">
-                <label htmlFor="ogpDescription" className="text-sm font-medium">
-                  OGP説明文
-                </label>
-                <Field name="ogpDescription">
-                  {({ state, handleBlur, handleChange }) => (
-                    <div className="flex flex-col space-y-2">
-                      <Textarea
-                        id="ogpDescription"
-                        placeholder="OGP説明文を入力（検索結果に表示される説明文）"
-                        value={state.value}
-                        onBlur={handleBlur}
-                        rows={3}
-                        onChange={(e) => {
-                          handleChange(e.target.value);
-                          handleResize();
-                        }}
-                        ref={textareaRef}
-                      />
-                      {state.meta.errors[0]?.message && (
-                        <p className="text-red-500 text-xs">
-                          {state.meta.errors[0]?.message}
-                        </p>
-                      )}
-                    </div>
-                  )}
-                </Field>
-              </div>
-              <div className="flex flex-col space-y-2">
-                <label htmlFor="ogpImage" className="text-sm font-medium">
-                  OGPイメージ(保存すると反映されます)
-                </label>
+              <Field name="ogpTitle">
+                {({ state, handleBlur, handleChange }) => (
+                  <WithLabel label="OGPタイトル">
+                    <Input
+                      id="ogpTitle"
+                      placeholder="未入力の場合はスレッドタイトルが使用されます"
+                      value={state.value}
+                      onBlur={handleBlur}
+                      error={!!state.meta.errors[0]?.message}
+                      errorMessage={state.meta.errors[0]?.message}
+                      onChange={(e) => handleChange(e.target.value)}
+                    />
+                  </WithLabel>
+                )}
+              </Field>
+              <Field name="ogpDescription">
+                {({ state, handleBlur, handleChange }) => (
+                  <WithLabel label="OGP説明文">
+                    <Textarea
+                      id="ogpDescription"
+                      placeholder="OGP説明文を入力（検索結果に表示される説明文）"
+                      value={state.value}
+                      onBlur={handleBlur}
+                      rows={5}
+                      onChange={(e) => handleChange(e.target.value)}
+                    />
+                    {state.meta.errors[0]?.message && (
+                      <p className="text-red-500 text-xs">
+                        {state.meta.errors[0]?.message}
+                      </p>
+                    )}
+                  </WithLabel>
+                )}
+              </Field>
path: src/features/threadDetail/PublicStatusSheet/index.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Error handling inconsistency in Textarea usage**

While the WithLabel component is correctly used here, the error message is still manually added instead of using the `errorMessage` prop of the Textarea component.


```diff
<Textarea
  id="ogpDescription"
  placeholder="OGP説明文を入力（検索結果に表示される説明文）"
  value={state.value}
  onBlur={handleBlur}
  rows={5}
+  error={!!state.meta.errors[0]?.message}
+  errorMessage={state.meta.errors[0]?.message}
  onChange={(e) => handleChange(e.target.value)}
/>
-{state.meta.errors[0]?.message && (
-  <p className="text-red-500 text-xs">
-    {state.meta.errors[0]?.message}
-  </p>
-)}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
              <Field name="ogpDescription">
                {({ state, handleBlur, handleChange }) => (
                  <WithLabel label="OGP説明文">
                    <Textarea
                      id="ogpDescription"
                      placeholder="OGP説明文を入力（検索結果に表示される説明文）"
                      value={state.value}
                      onBlur={handleBlur}
                      rows={5}
                      error={!!state.meta.errors[0]?.message}
                      errorMessage={state.meta.errors[0]?.message}
                      onChange={(e) => handleChange(e.target.value)}
                    />
                  </WithLabel>
                )}
              </Field>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-11T01:35:38Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-04-11T01:28:03Z
body: [vc]: #qO+0ir49J4wWfBxIjxZ97a8wIpSoGhz/I6AkQhIjCT4=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0aHJlYWQtbm90ZSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJ0aHJlYWQtbm90ZS1naXQtZmVhdHVyZS1jcmVhdGUtaW5wdXQtYy1hYjY5ODMtaXRpemF3YXMtcHJvamVjdHMudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vaXRpemF3YXMtcHJvamVjdHMvdGhyZWFkLW5vdGUvRVRNaG9nVUREZnRFTEZ4b0tSMktlNnVCWGtEVyIsInByZXZpZXdVcmwiOiJ0aHJlYWQtbm90ZS1naXQtZmVhdHVyZS1jcmVhdGUtaW5wdXQtYy1hYjY5ODMtaXRpemF3YXMtcHJvamVjdHMudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **thread-note** | ✅ Ready ([Inspect](https://vercel.com/itizawas-projects/thread-note/ETMhogUDDftELFxoKR2Ke6uBXkDW)) | [Visit Preview](https://thread-note-git-feature-create-input-c-ab6983-itizawas-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/thread-note-git-feature-create-input-c-ab6983-itizawas-projects.vercel.app?via=pr-comment-feedback-link) | Apr 11, 2025 1:34am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-11T01:28:05Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The pull request refactors the form handling in the PublicStatusSheet component by removing the auto-resize and store hooks. Form fields are updated to use the new WithLabel component for displaying labels and error messages. Additionally, both the Input and Textarea components are enhanced to support error handling via new props, with the Textarea’s default rows increased from 3 to 5.

## Changes

| File(s)                                                       | Change Summary                                                                                                                                                        |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/features/.../PublicStatusSheet/index.tsx`                | Removed `useAutoResizeTextarea` and `useStore` hooks; refactored form fields to use `WithLabel`; increased `textarea` rows from 3 to 5.                             |
| `src/shared/ui/WithLabel/index.tsx`                           | Added new `WithLabel` component to wrap elements with a label and error display in a vertically organized layout.                                                     |
| `src/shared/ui/{input,textarea}.tsx`                          | Extended both components to accept `error` and `errorMessage` props; updated rendering to conditionally display error styles and messages.                           |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant PS as PublicStatusSheet
    participant WL as WithLabel
    participant Field as Input/Textarea

    User ->> PS: Interact with form
    PS ->> WL: Render form fields with labels
    WL ->> Field: Pass props (including error states)
    Field -->> WL: Return field state and error indication
    WL -->> PS: Render combined field with error feedback
```

## Assessment against linked issues

| Objective                                                              | Addressed | Explanation         |
| ---------------------------------------------------------------------- | --------- | ------------------- |
| #199: Implement error props for input fields to allow error messaging. | ✅        |                     |

## Poem

> I'm a rabbit hopping through lines of code,  
> Removing hooks and lightening every load.  
> With a WithLabel charm, fields now have their say,  
> Error messages dance in a neat display.  
> I nibble on carrots while bugs scurry away—  
> CodeRabbit celebrates a brighter day! 🐇  
> Hoppity, hoppity, on our code we play!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9c924a4a1f69eb9f15bc30ee47ee946593fd3b08 and 4ca356220299ee0b183d95025226cdb0cdc717bc.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/shared/ui/textarea.tsx` (2 hunks)

</details>

</details>
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=itizawa/thread-note&utm_content=201):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE