Skip to content

feat: form components#718

Open
rohanchkrabrty wants to merge 7 commits intomainfrom
feat-form-components
Open

feat: form components#718
rohanchkrabrty wants to merge 7 commits intomainfrom
feat-form-components

Conversation

@rohanchkrabrty
Copy link
Copy Markdown
Contributor

@rohanchkrabrty rohanchkrabrty commented Mar 23, 2026

Description

This PR adds new Form components

  • Form
  • Field
  • Fieldset

@rohanchkrabrty rohanchkrabrty requested a review from rsbh March 23, 2026 09:26
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Apr 13, 2026 2:10pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2d4ac2ec-38d6-4c7a-886a-be896c617d35

📥 Commits

Reviewing files that changed from the base of the PR and between 0fdc32e and 21cdecd.

📒 Files selected for processing (1)
  • apps/www/src/content/docs/components/textarea/demo.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/www/src/content/docs/components/textarea/demo.ts

📝 Walkthrough

Walkthrough

Adds a new Field system and related form primitives across the codebase: a composable Field component (root, Label, Control, Error, Description, context hook), Fieldset, and Form, plus documentation, demos, and tests. Input controls (InputField, TextArea, Combobox, Select, Checkbox, Radio, Switch, NumberField) were refactored to delegate label/description/error/optional UI to Field and to inherit required state from Field context. CSS modules and docs were updated and new playground examples were added.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant App as Client App
  participant FormComp as Form
  participant FieldComp as Field
  participant Control as Field.Control
  participant Input as InputField
  participant Validation as Browser/Validity

  User->>App: interacts / types / submits
  App->>FormComp: submit event
  FormComp->>FieldComp: aggregate child fields
  FieldComp->>Control: render control wrapper
  Control->>Input: render input control
  Input->>Validation: browser validation (required/type/pattern)
  Validation-->>FieldComp: validity state (valueMissing/typeMismatch/...)
  FieldComp->>FieldComp: render Field.Error matching validity
  FormComp-->>App: invoke onFormSubmit if valid
Loading

Possibly related issues

Suggested reviewers

  • rsbh
  • paanSinghCoder
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat: form components' accurately and concisely summarizes the main change—adding new Form, Field, and Fieldset components to the codebase.
Description check ✅ Passed The pull request description relates to the changeset by identifying the three new form components being added (Form, Field, Fieldset), though it lacks implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/raystack/components/text-area/text-area.tsx (3)

24-24: ⚠️ Potential issue | 🟡 Minor

style.width is currently overridden by default width.

Line 40 uses { ...style, width }, so with default width='100%' (Line 24), a consumer-provided style.width is ignored.

Suggested fix
-        style={{ ...style, width }}
+        style={{ width, ...style }}

Also applies to: 40-40

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/text-area/text-area.tsx` at line 24, The
component sets a default width prop that ends up overriding any
consumer-provided style.width because the JSX merges props using "{ ...style,
width }"; update the merge so consumer styles win by placing the default width
first and then spreading style (e.g., change the merge in the TextArea component
where style and width are combined to use width before spreading style) or
conditionally apply width only when style.width is not provided; locate this in
the TextArea function/props handling to adjust the object merge order for style
and width.

9-16: ⚠️ Potential issue | 🟠 Major

Breaking prop-removal migration incomplete.

TextArea no longer accepts label in its props interface, but it's still used at apps/www/src/app/examples/page.tsx:1641. This will cause a type error or silently drop the label UI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/text-area/text-area.tsx` around lines 9 - 16,
TextAreaProps no longer declares a label prop but callers (e.g., the example
page that passes label to TextArea) still rely on it; either restore the label
prop on the component or remove those usages. To fix, add an optional label?:
React.ReactNode (or string) to the TextAreaProps interface and update the
TextArea component to accept and render that label (e.g., render label above the
textarea when label is provided), or alternatively remove the label prop usage
where TextArea is instantiated. Update references to TextAreaProps and the
TextArea component accordingly so the types and runtime UI stay in sync.

5-16: ⚠️ Potential issue | 🟠 Major

Use TextareaHTMLAttributes to expose textarea-specific props in the type interface.

At line 9, HTMLAttributes<HTMLTextAreaElement> omits textarea-specific props (rows, cols, maxLength, wrap, defaultValue, etc.) from the public type surface. Although the component spreads ...props and accepts these attributes at runtime, the TypeScript interface doesn't reflect that capability. Use React.TextareaHTMLAttributes<HTMLTextAreaElement> (the conventional React type for textarea elements) to provide accurate typing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/text-area/text-area.tsx` around lines 5 - 16,
The TextAreaProps interface currently extends
PropsWithChildren<HTMLAttributes<HTMLTextAreaElement>> which omits
textarea-specific attributes; update it to extend
PropsWithChildren<React.TextareaHTMLAttributes<HTMLTextAreaElement>> so props
like rows, cols, maxLength, wrap, defaultValue, etc. are properly typed; modify
the TextAreaProps declaration (the interface named TextAreaProps) and any import
usage to use React.TextareaHTMLAttributes<HTMLTextAreaElement> instead of
HTMLAttributes<HTMLTextAreaElement>.
packages/raystack/components/calendar/date-picker.tsx (1)

174-189: ⚠️ Potential issue | 🟠 Major

Invalid visual state is not applied after switching to aria-invalid.

Line 178 sets aria-invalid, but InputField invalid styling is keyed off wrapper data-invalid (packages/raystack/components/input-field/input-field.module.css), and the wrapper never receives that attribute (packages/raystack/components/input-field/input-field.tsx). Result: error border/state won’t render for DatePicker.

💡 Proposed fix at root cause (`InputField`)
--- a/packages/raystack/components/input-field/input-field.tsx
+++ b/packages/raystack/components/input-field/input-field.tsx
@@
 export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(
   (
     {
@@
       containerRef,
       ...props
     },
     ref
   ) => {
+    const isInvalid =
+      props['aria-invalid'] === true || props['aria-invalid'] === 'true';
+
     return (
       <div
         className={cx(
           inputWrapper({ size, variant }),
           disabled && styles['input-disabled-wrapper'],
           chips?.length && styles['has-chips']
         )}
+        data-invalid={isInvalid ? '' : undefined}
         style={{ width: width || '100%' }}
         ref={containerRef}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/calendar/date-picker.tsx` around lines 174 -
189, The DatePicker sets aria-invalid on the InputField but InputField's wrapper
uses data-invalid for styling and never receives it, so the invalid visual state
doesn't render; fix by updating the InputField component (input-field.tsx /
InputField) to map/forward the aria-invalid prop to the wrapper as data-invalid
(e.g., compute const isInvalid = props['aria-invalid'] || props.error and add
data-invalid={isInvalid} on the wrapper element) and ensure the InputField prop
types accept aria-invalid so DatePicker's usage continues to work.
🧹 Nitpick comments (11)
apps/www/src/content/docs/components/form/props.ts (1)

19-22: Prefer unknown over any/object in public docs types.

Using Record<string, any> and object weakens type safety in the generated API docs. Consider Record<string, unknown> and Record<string, unknown> (or a named event-details type).

Suggested refinement
   onFormSubmit?: (
-    formValues: Record<string, any>,
-    eventDetails: object
+    formValues: Record<string, unknown>,
+    eventDetails: Record<string, unknown>
   ) => void;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/form/props.ts` around lines 19 - 22, The
onFormSubmit type weakly uses Record<string, any> and object; change it to use
stronger types such as Record<string, unknown> for formValues and a structured
type for eventDetails (e.g., Record<string, unknown> or a named interface) so
the public docs show precise types — update the onFormSubmit signature to accept
formValues: Record<string, unknown> and eventDetails: Record<string, unknown>
(or replace eventDetails with a named EventDetails type) where the onFormSubmit
declaration is defined.
packages/raystack/index.tsx (1)

36-40: Consider re-exporting the new props types from root as well.

Since ./components/field, ./components/fieldset, and ./components/form expose *Props, adding root type re-exports would make top-level imports consistent for consumers.

Suggested addition
 export { Field } from './components/field';
+export type { FieldProps } from './components/field';
 export { Fieldset } from './components/fieldset';
+export type { FieldsetProps } from './components/fieldset';
 export { Form } from './components/form';
+export type { FormProps } from './components/form';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/index.tsx` around lines 36 - 40, The root re-exports
currently export components Field, Fieldset, FilterChip, Flex, and Form but not
their prop types; add type re-exports for the props (e.g., FieldProps from
'./components/field', FieldsetProps from './components/fieldset', and FormProps
from './components/form') alongside the existing component exports so consumers
can import both components and their types from the package root (keep existing
symbol names: Field, Fieldset, Form and add corresponding FieldProps,
FieldsetProps, FormProps).
packages/raystack/components/text-area/text-area.module.css (1)

62-68: Consolidate disabled styling into one selector.

Line 62 duplicates the same disabled declarations already defined in .textarea:disabled (Line 38+). Keeping both increases drift risk; prefer a single source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/text-area/text-area.module.css` around lines 62
- 68, Duplicate disabled styles exist in the .disabled rule and
.textarea:disabled; remove the .disabled block and consolidate the styles into a
single source of truth by keeping the .textarea:disabled selector (or replace it
with a grouped selector ".textarea:disabled, .disabled" if you must preserve
runtime .disabled class usages), and update any markup that relied on the
.disabled class to use the disabled attribute on the textarea component so
styling remains identical.
apps/www/src/components/playground/form-examples.tsx (1)

24-36: Add name attributes to form controls in the example.

This demo currently omits name, which makes form-value mapping and server-error association less representative of real usage.

♻️ Suggested update
         <Fieldset legend='Personal Information'>
           <Field label='First Name' required>
-            <InputField placeholder='John' />
+            <InputField name='firstName' placeholder='John' />
           </Field>
           <Field label='Last Name' required>
-            <InputField placeholder='Doe' />
+            <InputField name='lastName' placeholder='Doe' />
           </Field>
         </Fieldset>
         <Field label='Email' required helperText="We'll send a confirmation">
-          <InputField type='email' placeholder='john@example.com' />
+          <InputField name='email' type='email' placeholder='john@example.com' />
         </Field>
         <Field label='Message' optional>
-          <TextArea placeholder='Tell us more...' />
+          <TextArea name='message' placeholder='Tell us more...' />
         </Field>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/components/playground/form-examples.tsx` around lines 24 - 36,
The example form controls (InputField and TextArea inside Field/Fieldset) are
missing name attributes; update each form control to include descriptive name
props so form libraries and server errors map correctly — e.g. on the InputField
in the Field labeled "First Name" add name="firstName", the "Last Name"
InputField add name="lastName", the "Email" InputField add name="email", and the
"Message" TextArea add name="message"; keep the existing placeholders, types and
other props intact so only the name prop is introduced on InputField and
TextArea components.
apps/www/src/content/docs/components/textarea/index.mdx (1)

32-34: Make the example snippet self-contained.

error={errors.bio?.message} references an undeclared symbol in this snippet; consider a literal error string or include the surrounding state snippet.

📝 Minimal self-contained snippet option
-<Field label="Bio" helperText="Tell us about yourself" error={errors.bio?.message}>
+<Field label="Bio" helperText="Tell us about yourself" error="Bio is required">
   <TextArea placeholder="Write something..." />
 </Field>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/textarea/index.mdx` around lines 32 -
34, The example uses an undeclared symbol errors.bio?.message which breaks the
snippet; either make the snippet self-contained by replacing
error={errors.bio?.message} with a literal (e.g., error="This field is
required") or include the minimal surrounding form state that declares errors
(for example showing useForm/useState and an errors object) so the Field
component's error prop has a defined source; update the snippet referencing
Field and TextArea accordingly.
packages/raystack/components/fieldset/__tests__/fieldset.test.tsx (1)

68-72: Make the “no legend” assertion implementation-agnostic.

Line 71 currently checks only [role="presentation"], which can miss regressions if legend markup changes. Assert absence of actual legend output instead of a single role selector.

Proposed test hardening
-      expect(
-        container.querySelector('[role="presentation"]')
-      ).not.toBeInTheDocument();
+      expect(container.querySelector('legend')).not.toBeInTheDocument();
+      expect(screen.queryByRole('presentation')).not.toBeInTheDocument();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/fieldset/__tests__/fieldset.test.tsx` around
lines 68 - 72, The test currently checks for a specific role selector
([role="presentation"]) which is implementation-specific; instead update the
"does not render legend when not provided" test to assert absence of an actual
<legend> element (e.g., use container.querySelector('legend') or RTL's
screen.queryByRole/queryByText targeting the legend element) so it fails only if
a legend node is rendered; change the assertion in the Fieldset test to check
for no legend element rather than the role selector.
packages/raystack/components/field/field.module.css (1)

78-81: Strengthen keyboard focus visibility.

Current focus styling relies mainly on border color. Add a :focus-visible ring to improve perceivability for keyboard users.

Suggested CSS tweak
 .input:focus {
   border-color: var(--rs-color-border-accent-emphasis);
   background-color: var(--rs-color-background-base-primary);
 }
+
+.input:focus-visible {
+  outline: 2px solid var(--rs-color-border-accent-emphasis);
+  outline-offset: 1px;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/field/field.module.css` around lines 78 - 81,
The current .input:focus rule only changes border and background which can be
hard to perceive; add a .input:focus-visible rule that applies a clear visible
ring (e.g., an outline or box-shadow using a high-contrast color variable like
--rs-color-border-accent-emphasis or a dedicated --rs-color-focus) while
preserving existing border/background changes so keyboard users get a stronger
visual cue; update the CSS by adding .input:focus-visible { outline: none;
box-shadow: 0 0 0 3px var(--rs-color-focus,
var(--rs-color-border-accent-emphasis)); } (and keep .input:focus as-is) to
target keyboard focus specifically.
apps/www/src/content/docs/components/field/index.mdx (1)

138-140: Prefer behavior-focused accessibility wording.

These lines lock docs to specific attribute internals. Consider phrasing as “automatically associates label/description/error with the control” to reduce future doc drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/field/index.mdx` around lines 138 - 140,
Update the three doc lines to use behavior-focused accessibility wording instead
of specific ARIA attribute names: change the `Field.Label` line to say it
"automatically associates the label with the control", `Field.Description` to
"automatically associates the description with the control", and `Field.Error`
to "automatically associates error messages with the control", referencing the
`Field.Label`, `Field.Description`, and `Field.Error` symbols so the docs
describe behavior rather than specific attributes like
`aria-labelledby`/`aria-describedby`.
packages/raystack/components/field/__tests__/field.test.tsx (1)

30-34: Ref forwarding test is too weak; assert the forwarded element instance.

At Line 33, toHaveBeenCalled() can pass even if only null was passed. Prefer checking ref.current with createRef<HTMLDivElement>().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/field/__tests__/field.test.tsx` around lines 30
- 34, The ref-forwarding test for Field is too weak; replace the mock ref with a
real React ref (e.g., createRef<HTMLDivElement>()) and after render assert the
forwarded element instance instead of just that the ref was called.
Specifically, in the test for Field, use createRef to create ref, render(<Field
ref={ref}>...), then assert ref.current is not null and is the expected element
(e.g., expect(ref.current).toBeInstanceOf(HTMLDivElement) or check tagName) to
verify proper DOM ref forwarding.
apps/www/src/content/docs/components/field/demo.ts (1)

6-15: Avoid forwarding mixed rest props directly to InputField in snippet generation.

At Lines 6–15, rest is blindly serialized into <InputField />. Explicitly separate Field-level props from InputField props to prevent invalid code generation as controls evolve.

✅ Suggested refactor
-export const getCode = (props: any) => {
-  const { label, helperText, error, required, optional, ...rest } = props;
+export const getCode = (props: any) => {
+  const {
+    label,
+    helperText,
+    error,
+    required,
+    optional,
+    width,
+    className,
+    ...inputProps
+  } = props;
   const fieldProps: Record<string, unknown> = {};
   if (label) fieldProps.label = label;
   if (helperText) fieldProps.helperText = helperText;
   if (error) fieldProps.error = error;
   if (required) fieldProps.required = required;
   if (optional) fieldProps.optional = optional;
+  if (width !== undefined) fieldProps.width = width;
+  if (className) fieldProps.className = className;
   return `<Field${getPropsString(fieldProps)}>
-  <InputField${getPropsString(rest)} placeholder="Enter text" />
+  <InputField${getPropsString(inputProps)} placeholder="Enter text" />
 </Field>`;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/field/demo.ts` around lines 6 - 15, The
snippet currently forwards the entire rest object into InputField which can
include Field-level props (label, helperText, error, required, optional) and
produce invalid generated code; update the demo to explicitly separate Field
props from InputField props by building fieldProps from
props.label/helperText/error/required/optional (as now) and building inputProps
by copying rest but removing those Field keys before calling getPropsString; use
getPropsString(fieldProps) for <Field> and getPropsString(inputProps) for
<InputField> so only input-relevant props are serialized.
packages/raystack/components/field/field.tsx (1)

40-40: Use nullish coalescing for width default.

Using || means width={0} or width="" will fallback to '100%'. If a consumer explicitly passes 0, they likely expect 0, not 100%. Prefer ?? for a more predictable default.

♻️ Suggested fix
-        style={{ width: width || '100%' }}
+        style={{ width: width ?? '100%' }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/field/field.tsx` at line 40, The inline style
default for width uses the logical OR operator which treats falsy values like 0
or empty string as absent; update the style expression in the Field component
(the element with style={{ width: width || '100%' }}) to use the nullish
coalescing operator so an explicit 0 or "" is preserved (i.e., change the width
fallback from "width || '100%'" to "width ?? '100%'" in the Field component).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/src/components/playground/text-area-examples.tsx`:
- Line 16: The standalone TextArea example renders a control without an
accessible name; update the example that uses the TextArea component so it has
an explicit accessible label by either wrapping it in your Field/Label
components (e.g., add a Field with a Label and put TextArea inside) or by adding
an aria-label (or aria-labelledby) prop directly to TextArea; ensure the label
text is descriptive and not just placeholder content so screen readers can
announce the control.

In `@apps/www/src/content/docs/components/field/props.ts`:
- Around line 29-33: The JSDoc for the validate prop is outdated: update the
comment above the validate function to reflect its actual signature by
mentioning that it may return a string, an array of strings, null, or a Promise
resolving to string | string[] | null; modify the comment line "Custom
validation function. Return an error string or null." to something like "Custom
validation function. Return a string, an array of strings, null, or a Promise
resolving to string | string[] | null." so it matches the validate?: (value:
unknown) => string | string[] | null | Promise<string | string[] | null>;
declaration.

In `@apps/www/src/content/docs/components/fieldset/index.mdx`:
- Around line 60-61: Update the inaccurate description for Fieldset.Legend:
change the text that currently says it "renders a <div> element with
role='presentation'" to state that Fieldset.Legend renders a native <legend>
element (the semantic, accessible element for fieldset legends). Edit the
description near the Fieldset.Legend docs so it matches the implementation and
the later correct note (line 93) that the legend provides the accessible name
for the group.

In `@packages/raystack/components/field/field.tsx`:
- Around line 12-13: The required and optional props on FieldProps/Field can
conflict (both true) causing contradictory UI; make them mutually exclusive or
enforce runtime precedence: update the prop type (FieldProps) to a discriminated
union so required and optional cannot both be provided, or add runtime logic
inside the Field component (e.g., compute const isRequired = props.required ===
true; const isOptional = !isRequired && props.optional === true) and use
isOptional/isRequired when rendering the label so required wins and the optional
marker is suppressed when required is true.

In `@packages/raystack/components/fieldset/__tests__/fieldset.test.tsx`:
- Around line 99-107: The test "sets data-disabled attribute when disabled" only
asserts the data attribute but not native disabled behavior; update this test in
packages/raystack/components/fieldset/__tests__/fieldset.test.tsx (the it block
that renders <Fieldset disabled>...) to also assert that native disabled
semantics are propagated: check that the rendered <fieldset> element has
fieldset.disabled === true and that the child input
(querySelector('[data-testid="child-input"]')) has the disabled attribute (or
disabled property true) and is not focusable/interactable (e.g.,
document.activeElement cannot be the input after a focus attempt). This ensures
both the data attribute and native disabled behavior on Fieldset and its
children are covered.

In `@packages/raystack/components/fieldset/fieldset.tsx`:
- Around line 21-27: The current JSX uses a truthy check `legend &&
<FieldsetPrimitive.Legend ...>` which hides valid ReactNode values like 0;
change this to an explicit null/undefined check so non-null values render.
Locate the render block using FieldsetPrimitive.Legend (and props
legendClassName, styles.legend) in fieldset.tsx and replace the conditional with
a check such as `legend != null` (or `legend !== null && legend !== undefined`)
so numbers and other valid ReactNode values are rendered while still excluding
null/undefined.
- Around line 34-40: The ref type for the FieldsetLegend forwardRef is
incorrect: change the generic from HTMLDivElement to HTMLLegendElement so the
ref matches the rendered <legend> element; update the forwardRef declaration for
FieldsetLegend (and any related type annotations referencing HTMLDivElement) to
HTMLLegendElement while keeping the component body using
FieldsetPrimitive.Legend and cx(styles.legend, className) unchanged.

In `@packages/raystack/components/input-field/input-field.module.css`:
- Around line 28-33: Add an "invalid" boolean prop to InputFieldProps and have
the InputField component set the data-invalid attribute on the wrapper when
invalid is true so the existing .inputWrapper[data-invalid] styles are applied;
update the component's prop destructuring/defaults to include invalid (default
false) and ensure the wrapper element (the one that currently uses className
'inputWrapper') receives data-invalid={invalid} and that the prop is forwarded
where needed and reflected in types/interfaces (InputFieldProps, InputField).

In `@packages/raystack/components/input-field/input-field.tsx`:
- Around line 68-76: The wrapper div for the InputField component (the element
using inputWrapper({ size, variant }) and ref containerRef) never sets the
data-invalid attribute, so the CSS selector .inputWrapper[data-invalid] is never
triggered; update the wrapper rendering in input-field.tsx (both places around
the containerRef usage, including the similar block at lines ~99-113) to add
data-invalid={!!invalid} (or data-invalid when invalid) so the existing
.inputWrapper[data-invalid] styles apply when the component's invalid prop/state
is true.

---

Outside diff comments:
In `@packages/raystack/components/calendar/date-picker.tsx`:
- Around line 174-189: The DatePicker sets aria-invalid on the InputField but
InputField's wrapper uses data-invalid for styling and never receives it, so the
invalid visual state doesn't render; fix by updating the InputField component
(input-field.tsx / InputField) to map/forward the aria-invalid prop to the
wrapper as data-invalid (e.g., compute const isInvalid = props['aria-invalid']
|| props.error and add data-invalid={isInvalid} on the wrapper element) and
ensure the InputField prop types accept aria-invalid so DatePicker's usage
continues to work.

In `@packages/raystack/components/text-area/text-area.tsx`:
- Line 24: The component sets a default width prop that ends up overriding any
consumer-provided style.width because the JSX merges props using "{ ...style,
width }"; update the merge so consumer styles win by placing the default width
first and then spreading style (e.g., change the merge in the TextArea component
where style and width are combined to use width before spreading style) or
conditionally apply width only when style.width is not provided; locate this in
the TextArea function/props handling to adjust the object merge order for style
and width.
- Around line 9-16: TextAreaProps no longer declares a label prop but callers
(e.g., the example page that passes label to TextArea) still rely on it; either
restore the label prop on the component or remove those usages. To fix, add an
optional label?: React.ReactNode (or string) to the TextAreaProps interface and
update the TextArea component to accept and render that label (e.g., render
label above the textarea when label is provided), or alternatively remove the
label prop usage where TextArea is instantiated. Update references to
TextAreaProps and the TextArea component accordingly so the types and runtime UI
stay in sync.
- Around line 5-16: The TextAreaProps interface currently extends
PropsWithChildren<HTMLAttributes<HTMLTextAreaElement>> which omits
textarea-specific attributes; update it to extend
PropsWithChildren<React.TextareaHTMLAttributes<HTMLTextAreaElement>> so props
like rows, cols, maxLength, wrap, defaultValue, etc. are properly typed; modify
the TextAreaProps declaration (the interface named TextAreaProps) and any import
usage to use React.TextareaHTMLAttributes<HTMLTextAreaElement> instead of
HTMLAttributes<HTMLTextAreaElement>.

---

Nitpick comments:
In `@apps/www/src/components/playground/form-examples.tsx`:
- Around line 24-36: The example form controls (InputField and TextArea inside
Field/Fieldset) are missing name attributes; update each form control to include
descriptive name props so form libraries and server errors map correctly — e.g.
on the InputField in the Field labeled "First Name" add name="firstName", the
"Last Name" InputField add name="lastName", the "Email" InputField add
name="email", and the "Message" TextArea add name="message"; keep the existing
placeholders, types and other props intact so only the name prop is introduced
on InputField and TextArea components.

In `@apps/www/src/content/docs/components/field/demo.ts`:
- Around line 6-15: The snippet currently forwards the entire rest object into
InputField which can include Field-level props (label, helperText, error,
required, optional) and produce invalid generated code; update the demo to
explicitly separate Field props from InputField props by building fieldProps
from props.label/helperText/error/required/optional (as now) and building
inputProps by copying rest but removing those Field keys before calling
getPropsString; use getPropsString(fieldProps) for <Field> and
getPropsString(inputProps) for <InputField> so only input-relevant props are
serialized.

In `@apps/www/src/content/docs/components/field/index.mdx`:
- Around line 138-140: Update the three doc lines to use behavior-focused
accessibility wording instead of specific ARIA attribute names: change the
`Field.Label` line to say it "automatically associates the label with the
control", `Field.Description` to "automatically associates the description with
the control", and `Field.Error` to "automatically associates error messages with
the control", referencing the `Field.Label`, `Field.Description`, and
`Field.Error` symbols so the docs describe behavior rather than specific
attributes like `aria-labelledby`/`aria-describedby`.

In `@apps/www/src/content/docs/components/form/props.ts`:
- Around line 19-22: The onFormSubmit type weakly uses Record<string, any> and
object; change it to use stronger types such as Record<string, unknown> for
formValues and a structured type for eventDetails (e.g., Record<string, unknown>
or a named interface) so the public docs show precise types — update the
onFormSubmit signature to accept formValues: Record<string, unknown> and
eventDetails: Record<string, unknown> (or replace eventDetails with a named
EventDetails type) where the onFormSubmit declaration is defined.

In `@apps/www/src/content/docs/components/textarea/index.mdx`:
- Around line 32-34: The example uses an undeclared symbol errors.bio?.message
which breaks the snippet; either make the snippet self-contained by replacing
error={errors.bio?.message} with a literal (e.g., error="This field is
required") or include the minimal surrounding form state that declares errors
(for example showing useForm/useState and an errors object) so the Field
component's error prop has a defined source; update the snippet referencing
Field and TextArea accordingly.

In `@packages/raystack/components/field/__tests__/field.test.tsx`:
- Around line 30-34: The ref-forwarding test for Field is too weak; replace the
mock ref with a real React ref (e.g., createRef<HTMLDivElement>()) and after
render assert the forwarded element instance instead of just that the ref was
called. Specifically, in the test for Field, use createRef to create ref,
render(<Field ref={ref}>...), then assert ref.current is not null and is the
expected element (e.g., expect(ref.current).toBeInstanceOf(HTMLDivElement) or
check tagName) to verify proper DOM ref forwarding.

In `@packages/raystack/components/field/field.module.css`:
- Around line 78-81: The current .input:focus rule only changes border and
background which can be hard to perceive; add a .input:focus-visible rule that
applies a clear visible ring (e.g., an outline or box-shadow using a
high-contrast color variable like --rs-color-border-accent-emphasis or a
dedicated --rs-color-focus) while preserving existing border/background changes
so keyboard users get a stronger visual cue; update the CSS by adding
.input:focus-visible { outline: none; box-shadow: 0 0 0 3px
var(--rs-color-focus, var(--rs-color-border-accent-emphasis)); } (and keep
.input:focus as-is) to target keyboard focus specifically.

In `@packages/raystack/components/field/field.tsx`:
- Line 40: The inline style default for width uses the logical OR operator which
treats falsy values like 0 or empty string as absent; update the style
expression in the Field component (the element with style={{ width: width ||
'100%' }}) to use the nullish coalescing operator so an explicit 0 or "" is
preserved (i.e., change the width fallback from "width || '100%'" to "width ??
'100%'" in the Field component).

In `@packages/raystack/components/fieldset/__tests__/fieldset.test.tsx`:
- Around line 68-72: The test currently checks for a specific role selector
([role="presentation"]) which is implementation-specific; instead update the
"does not render legend when not provided" test to assert absence of an actual
<legend> element (e.g., use container.querySelector('legend') or RTL's
screen.queryByRole/queryByText targeting the legend element) so it fails only if
a legend node is rendered; change the assertion in the Fieldset test to check
for no legend element rather than the role selector.

In `@packages/raystack/components/text-area/text-area.module.css`:
- Around line 62-68: Duplicate disabled styles exist in the .disabled rule and
.textarea:disabled; remove the .disabled block and consolidate the styles into a
single source of truth by keeping the .textarea:disabled selector (or replace it
with a grouped selector ".textarea:disabled, .disabled" if you must preserve
runtime .disabled class usages), and update any markup that relied on the
.disabled class to use the disabled attribute on the textarea component so
styling remains identical.

In `@packages/raystack/index.tsx`:
- Around line 36-40: The root re-exports currently export components Field,
Fieldset, FilterChip, Flex, and Form but not their prop types; add type
re-exports for the props (e.g., FieldProps from './components/field',
FieldsetProps from './components/fieldset', and FormProps from
'./components/form') alongside the existing component exports so consumers can
import both components and their types from the package root (keep existing
symbol names: Field, Fieldset, Form and add corresponding FieldProps,
FieldsetProps, FormProps).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6a4a6e4-c7b2-4371-b489-cae600196284

📥 Commits

Reviewing files that changed from the base of the PR and between 9d4cf92 and 9ef4c38.

📒 Files selected for processing (41)
  • apps/www/src/components/playground/field-examples.tsx
  • apps/www/src/components/playground/fieldset-examples.tsx
  • apps/www/src/components/playground/form-examples.tsx
  • apps/www/src/components/playground/index.ts
  • apps/www/src/components/playground/input-field-examples.tsx
  • apps/www/src/components/playground/text-area-examples.tsx
  • apps/www/src/content/docs/components/field/demo.ts
  • apps/www/src/content/docs/components/field/index.mdx
  • apps/www/src/content/docs/components/field/props.ts
  • apps/www/src/content/docs/components/fieldset/demo.ts
  • apps/www/src/content/docs/components/fieldset/index.mdx
  • apps/www/src/content/docs/components/fieldset/props.ts
  • apps/www/src/content/docs/components/form/demo.ts
  • apps/www/src/content/docs/components/form/index.mdx
  • apps/www/src/content/docs/components/form/props.ts
  • apps/www/src/content/docs/components/input-field/demo.ts
  • apps/www/src/content/docs/components/input-field/index.mdx
  • apps/www/src/content/docs/components/input-field/props.ts
  • apps/www/src/content/docs/components/textarea/demo.ts
  • apps/www/src/content/docs/components/textarea/index.mdx
  • apps/www/src/content/docs/components/textarea/props.ts
  • packages/raystack/components/calendar/date-picker.tsx
  • packages/raystack/components/field/__tests__/field.test.tsx
  • packages/raystack/components/field/field.module.css
  • packages/raystack/components/field/field.tsx
  • packages/raystack/components/field/index.tsx
  • packages/raystack/components/fieldset/__tests__/fieldset.test.tsx
  • packages/raystack/components/fieldset/fieldset.module.css
  • packages/raystack/components/fieldset/fieldset.tsx
  • packages/raystack/components/fieldset/index.tsx
  • packages/raystack/components/form/__tests__/form.test.tsx
  • packages/raystack/components/form/form.module.css
  • packages/raystack/components/form/form.tsx
  • packages/raystack/components/form/index.tsx
  • packages/raystack/components/input-field/__tests__/input-field.test.tsx
  • packages/raystack/components/input-field/input-field.module.css
  • packages/raystack/components/input-field/input-field.tsx
  • packages/raystack/components/text-area/__tests__/text-area.test.tsx
  • packages/raystack/components/text-area/text-area.module.css
  • packages/raystack/components/text-area/text-area.tsx
  • packages/raystack/index.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
packages/raystack/components/fieldset/fieldset.tsx (1)

9-22: Prevent dual-legend rendering when both APIs are used.

Fieldset currently allows legend prop and <Fieldset.Legend> children at the same time, which can render two legends in one fieldset. Consider guarding one path to keep semantics unambiguous.

Possible guard to avoid duplicate legends
+import { Children, isValidElement } from 'react';
 import { Fieldset as FieldsetPrimitive } from '@base-ui/react/fieldset';
 import { cx } from 'class-variance-authority';
 import styles from './fieldset.module.css';
@@
 function FieldsetRoot({
   legend,
   className,
   children,
   ...props
 }: FieldsetProps) {
+  const hasLegendChild = Children.toArray(children).some(
+    (child) => isValidElement(child) && child.type === FieldsetLegend
+  );
+
   return (
     <FieldsetPrimitive.Root
       className={cx(styles.fieldset, className)}
       {...props}
     >
-      {legend && <FieldsetLegend>{legend}</FieldsetLegend>}
+      {legend && !hasLegendChild ? <FieldsetLegend>{legend}</FieldsetLegend> : null}
       {children}
     </FieldsetPrimitive.Root>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/fieldset/fieldset.tsx` around lines 9 - 22, The
FieldsetRoot component can render two legends if both the legend prop and a
<Fieldset.Legend> child are provided; update FieldsetRoot to detect and prefer
one source of truth (e.g., prefer an explicit FieldsetLegend child over the
legend prop or vice versa) by inspecting children for a FieldsetLegend element
and only render either {legend && <FieldsetLegend>{legend}</FieldsetLegend>} or
the existing children-based <FieldsetLegend>, but not both; modify the rendering
logic in FieldsetRoot to skip creating a legend from the legend prop when a
FieldsetLegend child is present (using the FieldsetLegend identifier) to ensure
a single legend is rendered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/src/content/docs/components/combobox/demo.ts`:
- Line 115: The demo incorrectly passes an unsupported description prop to
Combobox.Input; instead, wrap the Combobox in the documented Field composition
(use Field, Field.Label, Field.Description, Field.Error) so
label/description/error live outside Combobox.Input, remove description from
Combobox.Input, and move the text "Choose your favorite fruit" into
Field.Description; update any examples referencing Combobox.Input to show
Field.Label for the label and Field.Description for the description while
keeping Combobox.Input only responsible for the input itself.

In `@apps/www/src/content/docs/components/combobox/props.ts`:
- Around line 64-65: The docs list a description?: string prop for
Combobox.Input but the component surface (ComboboxInput -> InputFieldProps)
doesn't accept or forward description; remove the description entry from the
Combobox.Input props documentation in props.ts (or alternatively implement
support by adding a description prop to InputFieldProps and forwarding it in
ComboboxInput), ensuring the docs and implementation stay in sync; reference
Combobox.Input, description prop, ComboboxInput, and InputFieldProps when making
the change.

In `@apps/www/src/content/docs/components/form/demo.ts`:
- Around line 5-13: The demos use a non-existent `optional` prop on the Field
component: update the examples (the code string in
apps/www/src/content/docs/components/form/demo.ts that contains <Form ...> and
the Field usages) to use the supported API by replacing `optional` with
`required={false}`, or alternatively implement true optional support by adding
`optional?: boolean` (or mapping to `required?: boolean`) to `FieldProps` and
updating `FieldRoot`/Field component to handle `optional` correctly; reference
the `Field`, `FieldProps`, and `FieldRoot` symbols when making the change so the
prop is either removed from examples or fully supported in the component API.

In `@apps/www/src/content/docs/components/input-field/demo.ts`:
- Line 29: The InputField examples in demo.ts lack an accessible name and rely
solely on placeholder text; update every standalone <InputField ... /> snippet
(including the instances shown and those at lines noted) to provide an
accessible name by adding an aria-label attribute (e.g., aria-label="...") or by
wrapping the InputField with a Field/FormLabel pair so the control has an
associated visible label; ensure each example uses a clear, unique aria-label or
label text that matches the intended placeholder/usage.
- Line 6: The generated snippet returns a standalone <InputField> without an
accessible name; update the generator (the getCode function that uses
getPropsString and returns `return `<InputField${getPropsString(props)}
placeholder="Enter text" />`;`) to always include an accessible name—either by
wrapping the component in a `<Field label="Enter text">...</Field>` or by adding
an explicit accessible prop such as `aria-label="Enter text"` (merge with
existing props via getPropsString) so copied examples are not unlabeled inputs.

In `@packages/raystack/components/field/field-root.tsx`:
- Around line 24-39: The FieldRoot component currently sets required to true by
default which makes every Field required via context; change the default in the
FieldRoot signature (the required parameter in the FieldProps destructuring)
from true to false so required fields must opt-in, and ensure the provided
context value (in FieldContext.Provider) continues to use the computed required
value; update any related tests or story examples that relied on the previous
default if needed.

In `@packages/raystack/components/field/use-field-context.tsx`:
- Around line 8-11: Add the "use client" directive at the top of the file and
remove the environment conditional so that useContext is invoked unconditionally
inside useFieldContext; specifically, in useFieldContext replace the
conditional-return pattern with a direct call to useContext(FieldContext)
(keeping the biome lint ignore if desired) and ensure the file is marked as a
Client Component by adding 'use client' at the top so hooks are called in the
same order across renders.

In `@packages/raystack/components/form/form.tsx`:
- Around line 5-9: The module does not export the FormProps type, which breaks
the re-export in index.tsx; add a named export for the component props such as
"export type FormProps = FormPrimitive.Props" (or re-export the Props from the
imported FormPrimitive) alongside the existing FormRoot/Form export so consumers
can import FormProps correctly.

In `@packages/raystack/figma/input.figma.tsx`:
- Around line 37-40: The diff maps a `description` knob in
packages/raystack/figma/input.figma.tsx onto the InputField, but InputFieldProps
does not define `description`, causing incorrect generated usage; either remove
the `description: figma.boolean(...)` mapping from input.figma.tsx or update the
InputField API to accept a description (add a `description?: string | ReactNode`
prop to InputFieldProps and forward it in the InputField component), or if the
intended prop is a helper text rename the mapping to the supported prop (e.g.,
`helperText`); update the mapping and the referenced symbol names: `description`
in input.figma.tsx, `InputFieldProps`, and `InputField` accordingly.

In `@packages/raystack/figma/textarea.tsx`:
- Around line 20-23: The PR wires a "description" knob into the component props
(description: figma.boolean...) but the target component TextArea and its prop
type TextAreaProps do not define a "description" field, causing an inaccurate
usage surface; update the mapping to either remove the "description" prop or map
it to the correct prop name that exists on TextAreaProps (e.g., helperText or
aria-description if that’s the intended prop), and ensure the code referencing
description in this file is changed to the correct prop name or omitted so the
props passed to TextArea exactly match TextAreaProps.

---

Nitpick comments:
In `@packages/raystack/components/fieldset/fieldset.tsx`:
- Around line 9-22: The FieldsetRoot component can render two legends if both
the legend prop and a <Fieldset.Legend> child are provided; update FieldsetRoot
to detect and prefer one source of truth (e.g., prefer an explicit
FieldsetLegend child over the legend prop or vice versa) by inspecting children
for a FieldsetLegend element and only render either {legend &&
<FieldsetLegend>{legend}</FieldsetLegend>} or the existing children-based
<FieldsetLegend>, but not both; modify the rendering logic in FieldsetRoot to
skip creating a legend from the legend prop when a FieldsetLegend child is
present (using the FieldsetLegend identifier) to ensure a single legend is
rendered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a23a3295-3403-4d14-898a-9ec3da531235

📥 Commits

Reviewing files that changed from the base of the PR and between 9ef4c38 and 6f5b830.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (37)
  • apps/www/package.json
  • apps/www/src/app/examples/page.tsx
  • apps/www/src/components/playground/field-examples.tsx
  • apps/www/src/components/playground/form-examples.tsx
  • apps/www/src/components/playground/input-field-examples.tsx
  • apps/www/src/components/playground/text-area-examples.tsx
  • apps/www/src/content/docs/components/combobox/demo.ts
  • apps/www/src/content/docs/components/combobox/index.mdx
  • apps/www/src/content/docs/components/combobox/props.ts
  • apps/www/src/content/docs/components/field/demo.ts
  • apps/www/src/content/docs/components/field/index.mdx
  • apps/www/src/content/docs/components/field/props.ts
  • apps/www/src/content/docs/components/form/demo.ts
  • apps/www/src/content/docs/components/form/index.mdx
  • apps/www/src/content/docs/components/input-field/demo.ts
  • apps/www/src/content/docs/components/input-field/index.mdx
  • apps/www/src/content/docs/components/textarea/demo.ts
  • apps/www/src/content/docs/components/textarea/index.mdx
  • packages/raystack/components/checkbox/checkbox.tsx
  • packages/raystack/components/combobox/combobox-root.tsx
  • packages/raystack/components/field/__tests__/field.test.tsx
  • packages/raystack/components/field/field-misc.tsx
  • packages/raystack/components/field/field-root.tsx
  • packages/raystack/components/field/field.module.css
  • packages/raystack/components/field/index.ts
  • packages/raystack/components/field/use-field-context.tsx
  • packages/raystack/components/fieldset/fieldset.tsx
  • packages/raystack/components/form/form.tsx
  • packages/raystack/components/input-field/input-field.module.css
  • packages/raystack/components/input-field/input-field.tsx
  • packages/raystack/components/number-field/number-field.tsx
  • packages/raystack/components/radio/radio.tsx
  • packages/raystack/components/select/select-root.tsx
  • packages/raystack/components/switch/switch.tsx
  • packages/raystack/components/text-area/text-area.tsx
  • packages/raystack/figma/input.figma.tsx
  • packages/raystack/figma/textarea.tsx
💤 Files with no reviewable changes (1)
  • apps/www/src/app/examples/page.tsx
✅ Files skipped from review due to trivial changes (8)
  • apps/www/src/content/docs/components/combobox/index.mdx
  • apps/www/src/components/playground/input-field-examples.tsx
  • apps/www/src/content/docs/components/field/props.ts
  • packages/raystack/components/field/field.module.css
  • apps/www/src/content/docs/components/field/index.mdx
  • packages/raystack/components/field/tests/field.test.tsx
  • apps/www/src/content/docs/components/form/index.mdx
  • apps/www/package.json
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/www/src/components/playground/text-area-examples.tsx
  • apps/www/src/components/playground/field-examples.tsx
  • apps/www/src/components/playground/form-examples.tsx
  • apps/www/src/content/docs/components/textarea/index.mdx
  • apps/www/src/content/docs/components/input-field/index.mdx
  • apps/www/src/content/docs/components/textarea/demo.ts
  • packages/raystack/components/input-field/input-field.tsx
  • apps/www/src/content/docs/components/field/demo.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
apps/www/src/content/docs/components/input-field/demo.ts (1)

6-6: ⚠️ Potential issue | 🟠 Major

Standalone InputField snippets are still unlabeled.

These examples rely on placeholder text only, so copied snippets don't give the control an accessible name. Add aria-label to the standalone variants, or wrap them in Field when you want a visible label.

Example fix pattern
 export const getCode = (props: any) => {
-  return `<InputField${getPropsString(props)} placeholder="Enter text" />`;
+  return `<InputField aria-label="Text input"${getPropsString(props)} placeholder="Enter text" />`;
 };

Apply the same pattern to basicDemo, prefixDemo, iconDemo, disabledDemo, widthDemo, sizeDemo, and sizeChipDemo.

Also applies to: 29-29, 49-54, 59-64, 69-73, 78-81, 86-95, 99-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/input-field/demo.ts` at line 6, Several
standalone InputField demo snippets (functions basicDemo, prefixDemo, iconDemo,
disabledDemo, widthDemo, sizeDemo, sizeChipDemo and other similar demos) rely
solely on placeholder text and lack an accessible name; update each demo that
returns `<InputField... placeholder="..."/>` to either add a descriptive
aria-label (e.g., aria-label="Enter text") on the InputField or wrap the control
inside a Field with a visible label so copied snippets include an accessible
name; ensure you update every matching return expression in the demo functions
mentioned so the control is accessible.
🧹 Nitpick comments (1)
apps/www/src/content/docs/components/form/demo.ts (1)

160-179: Consider adding submit actions to all validation-mode tabs for parity.

onBlur and onChange tabs currently omit a submit button, which makes side-by-side behavior comparison less consistent with the onSubmit tab.

Suggested docs snippet adjustment
       code: `<Form validationMode="onBlur">
   <Field name="email">
     <Field.Label>Email</Field.Label>
     <Field.Control type="email" required placeholder="Validated on blur" />
     <Field.Error match="valueMissing">Email is required</Field.Error>
   </Field>
+  <Button type="submit">Submit</Button>
 </Form>`
       code: `<Form validationMode="onChange">
   <Field name="email">
     <Field.Label>Email</Field.Label>
     <Field.Control type="email" required placeholder="Validated on change" />
     <Field.Error match="typeMismatch">Enter a valid email</Field.Error>
   </Field>
+  <Button type="submit">Submit</Button>
 </Form>`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/form/demo.ts` around lines 160 - 179,
The onBlur and onChange demo entries for the Form component omit submit actions,
making them inconsistent with the onSubmit tab; update the demo objects with
name 'onBlur' and name 'onChange' to include a submit control inside the <Form>
(e.g., add a submit Button or <Field.Control type="submit">) so each Form
example has a submit action and parity with the onSubmit demo; modify the code
string values for those entries in demo.ts to include the submit element within
the Form markup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/src/content/docs/components/textarea/demo.ts`:
- Around line 43-45: In the ControlledExample demo function, replace the unbound
hook call useState with React.useState so the snippet uses the React namespace
the docs runtime expects; update the ControlledExample function (and any other
local demo functions using useState) to call React.useState for state
initialization and setters to match the pattern used across other demo files.

In `@packages/raystack/figma/input.figma.tsx`:
- Around line 45-49: The example renderer function (example: ({ label,
description, optional, ...props }) => { ... }) currently forces a required state
with required={!optional}; change it so you do not collapse the neutral/default
state into required — only pass required={false} when optional is true, and if
there's an explicit Figma "required" flag map that to required={true}; otherwise
omit the required prop entirely on the <Field> component (keep Field and
InputField usage the same).

In `@packages/raystack/figma/textarea.tsx`:
- Around line 27-31: The Field currently forces required={!isOptional}, which
makes labeled textareas required by default; instead only set the required prop
when the Figma toggle explicitly marks the field optional. In the example
component (example) change the Field usage to pass required conditionally: set
required={false} when isOptional === true, and leave required unset (undefined)
when isOptional is false or undefined (e.g. required={isOptional === true ?
false : undefined}). This ensures Field is only marked optional when the toggle
explicitly models that variant.

---

Duplicate comments:
In `@apps/www/src/content/docs/components/input-field/demo.ts`:
- Line 6: Several standalone InputField demo snippets (functions basicDemo,
prefixDemo, iconDemo, disabledDemo, widthDemo, sizeDemo, sizeChipDemo and other
similar demos) rely solely on placeholder text and lack an accessible name;
update each demo that returns `<InputField... placeholder="..."/>` to either add
a descriptive aria-label (e.g., aria-label="Enter text") on the InputField or
wrap the control inside a Field with a visible label so copied snippets include
an accessible name; ensure you update every matching return expression in the
demo functions mentioned so the control is accessible.

---

Nitpick comments:
In `@apps/www/src/content/docs/components/form/demo.ts`:
- Around line 160-179: The onBlur and onChange demo entries for the Form
component omit submit actions, making them inconsistent with the onSubmit tab;
update the demo objects with name 'onBlur' and name 'onChange' to include a
submit control inside the <Form> (e.g., add a submit Button or <Field.Control
type="submit">) so each Form example has a submit action and parity with the
onSubmit demo; modify the code string values for those entries in demo.ts to
include the submit element within the Form markup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: efc2ae01-a1a0-4e3e-aa36-a32b207381e1

📥 Commits

Reviewing files that changed from the base of the PR and between 6f5b830 and 0fdc32e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (29)
  • apps/www/src/app/examples/page.tsx
  • apps/www/src/components/playground/field-examples.tsx
  • apps/www/src/components/playground/fieldset-examples.tsx
  • apps/www/src/components/playground/form-examples.tsx
  • apps/www/src/content/docs/components/combobox/demo.ts
  • apps/www/src/content/docs/components/combobox/index.mdx
  • apps/www/src/content/docs/components/combobox/props.ts
  • apps/www/src/content/docs/components/field/demo.ts
  • apps/www/src/content/docs/components/field/index.mdx
  • apps/www/src/content/docs/components/fieldset/demo.ts
  • apps/www/src/content/docs/components/fieldset/props.ts
  • apps/www/src/content/docs/components/form/demo.ts
  • apps/www/src/content/docs/components/input-field/demo.ts
  • apps/www/src/content/docs/components/textarea/demo.ts
  • docs/V1-migration.md
  • packages/raystack/components/calendar/date-picker.tsx
  • packages/raystack/components/field/field.module.css
  • packages/raystack/components/fieldset/__tests__/fieldset.test.tsx
  • packages/raystack/components/form/form.tsx
  • packages/raystack/components/input-field/input-field.module.css
  • packages/raystack/components/input-field/input-field.tsx
  • packages/raystack/components/number-field/number-field.tsx
  • packages/raystack/components/radio/radio.tsx
  • packages/raystack/components/select/select-root.tsx
  • packages/raystack/components/switch/switch.tsx
  • packages/raystack/components/text-area/text-area.module.css
  • packages/raystack/components/text-area/text-area.tsx
  • packages/raystack/figma/input.figma.tsx
  • packages/raystack/figma/textarea.tsx
💤 Files with no reviewable changes (1)
  • apps/www/src/app/examples/page.tsx
✅ Files skipped from review due to trivial changes (8)
  • packages/raystack/components/calendar/date-picker.tsx
  • apps/www/src/content/docs/components/combobox/index.mdx
  • apps/www/src/components/playground/field-examples.tsx
  • apps/www/src/content/docs/components/fieldset/props.ts
  • apps/www/src/components/playground/fieldset-examples.tsx
  • packages/raystack/components/field/field.module.css
  • apps/www/src/content/docs/components/fieldset/demo.ts
  • apps/www/src/content/docs/components/field/index.mdx
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/www/src/content/docs/components/combobox/demo.ts
  • packages/raystack/components/text-area/text-area.module.css
  • packages/raystack/components/radio/radio.tsx
  • packages/raystack/components/switch/switch.tsx
  • apps/www/src/content/docs/components/combobox/props.ts
  • packages/raystack/components/form/form.tsx
  • apps/www/src/components/playground/form-examples.tsx
  • packages/raystack/components/input-field/input-field.module.css
  • packages/raystack/components/fieldset/tests/fieldset.test.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants