-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Inputs #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom Inputs #36
Conversation
…ve form structure - Added FullWidthLabel component for clickable label areas in checkbox list stories. - Updated Checkbox component to accept custom components for flexibility. - Refactored checkbox stories to utilize new features and improve layout. - Adjusted form structure for better spacing and usability in checkbox examples. - Added noNamespaceImport rule to biome configuration for better compatibility.
|
|
WalkthroughThis pull request introduces a comprehensive framework for custom form components, detailing a component injection pattern with new interfaces and updated component implementations. Multiple Storybook stories for various form elements (e.g., checkbox, radio group, switch, text field, and textarea) have been added or updated to demonstrate and validate these customizations. Configuration files and package dependencies have also been modified to support the new patterns and ensure consistency across the project. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant FormField
participant MergeLogic
participant BaseComponent
User->>FormField: Pass custom components via props
FormField->>MergeLogic: Extract & merge custom components with defaults
MergeLogic-->>FormField: Return merged components
FormField->>BaseComponent: Render using merged components
BaseComponent-->>FormField: Return UI output
Possibly related PRs
Suggested reviewers
Poem
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
- Removed outdated dependency "@date-fns/tz" from yarn.lock. - Upgraded "@radix-ui/react-slot" to version 1.1.2 in package.json and yarn.lock. - Downgraded "react-day-picker" to version 8.10.1 in package.json and yarn.lock. - Updated components alias in components.json for improved path resolution. - Refactored button component styles for better layout and spacing. - Cleaned up imports in date-picker-field component.
…ntations and examples - Added comprehensive Storybook examples for custom checkbox and radio group components. - Implemented custom components for checkbox, including PurpleCheckbox, PurpleIndicator, CustomLabel, and CustomErrorMessage. - Updated RadioGroup to support custom components and improved structure for better flexibility. - Enhanced RadioGroupField to accept custom components and added className support for styling. - Included interactive tests to verify custom component functionality and styling. - Documented key takeaways for component overriding to ensure type safety and accessibility.
…examples - Added `radioGroupClassName` prop to `RadioGroupFieldProps` for custom container styling. - Expanded documentation with implementation examples for various customization approaches in radio groups. - Updated Storybook titles for better readability and consistency. - Removed deprecated custom checkbox and radio group stories to streamline examples.
…ved structure - Updated Switch component to accept custom components for enhanced flexibility. - Introduced default styled Switch and Switch Thumb components for consistent styling. - Refactored SwitchField to utilize new component structure, improving maintainability. - Ensured proper integration with Remix form context for better form handling.
…ructure - Updated Textarea and TextField components to support custom component props for greater flexibility. - Introduced TextareaFieldComponents and TextFieldComponents interfaces for improved type safety. - Refactored components to utilize custom components if provided, enhancing reusability. - Cleaned up imports and ensured consistent structure across form components.
… testing - Updated CustomTextFieldExample to utilize React.forwardRef for better ref handling in custom input components. - Enhanced Storybook tests by replacing label text with placeholder text for improved accessibility. - Added waitFor functionality in tests to ensure proper focus and blur behavior for password input. - Updated documentation to clarify the use of forwardRef with inline components.
…ccessibility and structure - Simplified IconInput implementation by directly integrating the icon within the component. - Enhanced CustomTextFieldExample to utilize the updated IconInput for better clarity and maintainability. - Streamlined Storybook tests by removing unnecessary waitFor calls, ensuring efficient input handling. - Updated documentation to reflect changes in IconInput structure and usage.
There was a problem hiding this 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
🧹 Nitpick comments (18)
biome.json (1)
33-34: New Linter Rule Added: "noNamespaceImport"The addition of
"noNamespaceImport": "off"follows the existing configuration style. Please confirm that disabling namespace import checks is intentional and aligns with our coding standards.apps/docs/src/remix-hook-form/radio-group.stories.tsx (1)
124-124: Trailing Annotation CheckA "~" marker appears at the end of the file. If this was not intended as part of the code, please remove it to keep the file clean.
packages/components/src/remix-hook-form/textarea.tsx (1)
14-20: Consider aligning implementation pattern with checkbox.tsxWhile this implementation works correctly, it differs slightly from the pattern used in checkbox.tsx. For consistency, consider:
- Destructuring
componentsfrom props to avoid passing it down- Adding comments explaining the pattern (like in checkbox.tsx)
- const components: Partial<TextareaFieldComponents> = { + // Destructure components from props to avoid it being overridden + const { components: customComponents, ...restProps } = props; + + // Create a new components object that merges the default components with any custom components + const mergedComponents: Partial<TextareaFieldComponents> = { FormControl, FormLabel, FormDescription, FormMessage, - ...props.components, + ...customComponents, // Merge user components }; - return <BaseTextareaField control={control} components={components} {...props} />; + // Pass the merged components to the BaseTextareaField + return <BaseTextareaField control={control} components={mergedComponents} {...restProps} />;packages/components/src/ui/checkbox-field.tsx (1)
74-76: Consider alternative to forced style overrideThe use of
!text-inheritapplies an important flag to override styles. Consider handling this through the component design system rather than forcing style overrides.- <FormLabel Component={components?.FormLabel} className="!text-inherit"> + <FormLabel Component={components?.FormLabel} className="text-inherit">Alternatively, create a custom variant in your design system that doesn't require the important flag.
apps/docs/src/remix-hook-form/text-field-custom.stories.tsx (3)
14-18: Confirm the accuracy of the schema's validation rules.The Zod schema checks typical constraints for username, email, and password. Ensure the error messages align with UX requirements and that the constraints (min length, correct email format, etc.) are sufficient for the intended use case.
46-69: Check for icon accessibility updates and ARIA labeling.The IconInput includes an inline SVG locked behind a
<title>Lock</title>element. Ensure that screen readers handle this as desired. In some scenarios, additional ARIA attributes might be necessary for describing the icon if it conveys critical meaning.
70-123: Encourage thorough unit tests for form submission logic.The
CustomTextFieldExampleusesuseFetcheranduseRemixFormto manage the form. Summaries match well with the code. It would be beneficial to add end-to-end or integration tests that verify actual form submissions and ensure errors or success messages are handled gracefully for each field.apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx (3)
53-67: CustomLabel effectively extends a base label.Appending a "★" is a nice way to differentiate required or special fields. Ensure that screen readers handle it in an accessible manner if it has semantic meaning.
68-80: Ensure the error message component is user-friendly on multiple color backgrounds.It might be beneficial to confirm that the text and background remain legible under various themes or modes (e.g., dark mode) if your codebase supports that.
122-159: CustomLabelExample is consistent with the prior example.This example highlights how to override just label and error message. Encourage param-based or theming-based approach if your codebase aims for multiple color themes.
ai/CustomInputsProject.md (2)
474-474: Correct heading level increment.Static analysis indicates a mismatch in heading levels around line 474. Change “####” to “###” (or ensure heading hierarchy is coherent) to comply with standard Markdown heading rules.
Apply this diff to fix the heading:
-#### Example: Custom Checkbox Story Implementation +### Example: Custom Checkbox Story Implementation🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
474-474: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4(MD001, heading-increment)
475-936: Ensure each component injection example references up-to-date usage patterns.The examples for radio groups, text fields, and checkboxes appear consistent. If future updates to
RadioGroupFieldComponentsorTextFieldComponentsarise, keep these docs in sync.apps/docs/src/remix-hook-form/textarea-custom.stories.tsx (3)
14-18: Validation rules forfeedback,bio, andnotesare well defined.Minimum character constraints make sense for typical input. Keep an eye on performance if extremely large text fields are allowed.
50-75: CounterTextarea is a neat enhancement.This example of handling local state for character counts is nicely encapsulated. Consider whether to unify logic across multiple fields (like computed fields in a larger data structure).
95-169: Implementation of the main form is well structured.This example showcases default, purple-styled, and character-counter textareas. Consider adding a small label or helper text for the character counter to explain its significance to end users.
apps/docs/src/remix-hook-form/switch-custom.stories.tsx (3)
23-57: Custom purple switch and thumb.Creation of
PurpleSwitchandPurpleSwitchThumbis clean, with Radix UI's states well-handled. If you plan to support non-English locales, consider allowing the ON/OFF labels to be passed in as props for localization.
58-71: Custom purple label and message.Styling is straightforward and consistent. Abstracting color variables or class names could make theming easier, but this is optional depending on your design goals.
170-179:handleFormSubmissionfunction.Returns a message on success or errors if validation fails. Consider logging or displaying errors if they exist to give users or developers more visibility beyond the success message.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (28)
ai/CustomInputsProject.md(1 hunks)apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx(1 hunks)apps/docs/src/remix-hook-form/checkbox-list.stories.tsx(6 hunks)apps/docs/src/remix-hook-form/checkbox.stories.tsx(3 hunks)apps/docs/src/remix-hook-form/date-picker.stories.tsx(2 hunks)apps/docs/src/remix-hook-form/radio-group-custom.stories.tsx(1 hunks)apps/docs/src/remix-hook-form/radio-group.stories.tsx(2 hunks)apps/docs/src/remix-hook-form/switch-custom.stories.tsx(1 hunks)apps/docs/src/remix-hook-form/text-field-custom.stories.tsx(1 hunks)apps/docs/src/remix-hook-form/textarea-custom.stories.tsx(1 hunks)biome.json(1 hunks)packages/components/components.json(1 hunks)packages/components/package.json(2 hunks)packages/components/src/remix-hook-form/checkbox.tsx(1 hunks)packages/components/src/remix-hook-form/radio-group.tsx(1 hunks)packages/components/src/remix-hook-form/switch.tsx(3 hunks)packages/components/src/remix-hook-form/textarea.tsx(1 hunks)packages/components/src/ui/button.tsx(1 hunks)packages/components/src/ui/checkbox-field.tsx(2 hunks)packages/components/src/ui/date-picker-field.tsx(0 hunks)packages/components/src/ui/form.tsx(0 hunks)packages/components/src/ui/radio-group-field.tsx(4 hunks)packages/components/src/ui/radio-group.tsx(2 hunks)packages/components/src/ui/switch-field.tsx(1 hunks)packages/components/src/ui/text-field.tsx(3 hunks)packages/components/src/ui/text-input.tsx(0 hunks)packages/components/src/ui/textarea-field.tsx(3 hunks)packages/components/src/ui/textarea.tsx(1 hunks)
💤 Files with no reviewable changes (3)
- packages/components/src/ui/text-input.tsx
- packages/components/src/ui/date-picker-field.tsx
- packages/components/src/ui/form.tsx
🧰 Additional context used
🧠 Learnings (2)
packages/components/src/ui/textarea.tsx (1)
Learnt from: jaruesink
PR: lambda-curry/forms#14
File: packages/components/src/ui/textarea.tsx:32-34
Timestamp: 2025-03-15T03:53:08.880Z
Learning: In the `Textarea` component (`packages/components/src/ui/textarea.tsx`), it's acceptable to assign `field.ref` directly to the `ref` of the textarea element without merging it with external refs.
packages/components/src/ui/textarea-field.tsx (2)
Learnt from: jaruesink
PR: lambda-curry/forms#14
File: packages/components/src/ui/textarea.tsx:17-24
Timestamp: 2025-03-15T03:53:08.880Z
Learning: In `packages/components/src/ui/textarea.tsx`, the `Textarea` component forwards its ref to `FormItem`, and the textarea element receives `field.ref`, so the ref forwarding works correctly.
Learnt from: jaruesink
PR: lambda-curry/forms#14
File: packages/components/src/ui/textarea.tsx:32-34
Timestamp: 2025-03-15T03:53:08.880Z
Learning: In the `Textarea` component (`packages/components/src/ui/textarea.tsx`), it's acceptable to assign `field.ref` directly to the `ref` of the textarea element without merging it with external refs.
🪛 markdownlint-cli2 (0.17.2)
ai/CustomInputsProject.md
474-474: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4
(MD001, heading-increment)
🔇 Additional comments (81)
packages/components/src/ui/button.tsx (1)
7-7: Enhanced button styling for icon support.The changes to
buttonVariantsimprove the button's appearance and behavior when containing SVG icons by:
- Adding
gap-2ensures consistent spacing between text and icons[&_svg]:pointer-events-noneprevents SVG elements from capturing click events[&_svg]:size-4standardizes icon size to maintain visual consistency[&_svg]:shrink-0prevents icons from shrinking in constrained spacesThese changes align well with the PR objective of enhancing custom form components and create a more robust button component for the design system.
apps/docs/src/remix-hook-form/date-picker.stories.tsx (1)
61-61: Title Update for Improved ReadabilityChanging the title from
"RemixHookForm/DatePicker"to"RemixHookForm/Date Picker"improves the clarity of component categorization in Storybook.packages/components/components.json (1)
13-14: Updated Alias for ComponentsThe alias for
"components"has been updated from"@/src"to"src". Ensure that all import paths across the project have been updated accordingly to reflect this change.packages/components/package.json (2)
53-53: Dependency Update: @radix-ui/react-slot Version BumpThe version of
@radix-ui/react-slothas been updated to"^1.1.2". This minor update should include bug fixes or improvements—please verify that it remains compatible with our implementations.
65-65: Dependency Downgrade: Verify react-day-picker VersionThe version of
react-day-pickerhas been downgraded to"8.10.1". Please ensure that this downgrade does not negatively impact any functionality or features dependent on a later version.apps/docs/src/remix-hook-form/radio-group.stories.tsx (1)
81-81: Title Update for ConsistencyThe title has been updated to
"RemixHookForm/Radio Group", adding a space for better readability and consistency with other Storybook stories.packages/components/src/remix-hook-form/switch.tsx (3)
3-3: Good addition of the SwitchFieldComponents type importThis type import enables the component injection pattern that is being implemented across form components.
12-21: Good implementation of component customizationThe updated function signature now properly destructures the
componentsprop and merges it with default components. This pattern allows consumers to override specific form components while maintaining defaults for the rest.The
mergedComponentsobject with the explicitPartial<SwitchFieldComponents>type annotation is a good practice for type safety.
29-30: Proper props forwardingCorrectly passing the merged components and explicitly passing the className to the base component ensures that both custom components and styling are properly applied.
packages/components/src/remix-hook-form/checkbox.tsx (3)
2-7: Well-structured importsGood organization of imports with proper type inclusion for the component injection pattern.
14-24: Well-implemented component customization with helpful commentsThe code nicely destructures
componentsascustomComponentsto avoid naming conflicts, and the comments explain the purpose of each step, which improves code maintainability.The merge pattern ensures default components are used when custom ones aren't provided.
27-27: Clean props forwardingProperly passing the merged components to BaseCheckbox while also spreading the rest of the props ensures all properties are correctly passed down.
packages/components/src/remix-hook-form/textarea.tsx (1)
2-6: Good imports structure for component customizationThe import organization properly includes the necessary types for implementing the component injection pattern.
packages/components/src/ui/textarea.tsx (2)
4-8: Good extension of TextareaProps for customizationAdding the
CustomTextareaprop with proper typing as aForwardRefExoticComponentensures that custom implementations properly handle ref forwarding, which is essential for accessibility and form control.
10-24: Well-implemented conditional rendering with ref forwardingThe implementation cleanly handles both custom and default textarea rendering scenarios:
- If
CustomTextareais provided, it renders that component with the same props and ref- Otherwise, it falls back to the default textarea implementation
This pattern maintains all functionality while enabling customization.
I see from the retrieved learnings that directly assigning
field.refto the textarea element is acceptable, which aligns with the ref forwarding implementation here.apps/docs/src/remix-hook-form/checkbox.stories.tsx (3)
39-40: Improved spacing enhances visual hierarchyThe increased spacing between form elements (from gap-4 to gap-8) provides better visual separation, making the form more readable and user-friendly.
Also applies to: 46-47
48-49: Consistent spacing pattern applied to submit buttonThe increased margin above the button (from mt-4 to mt-8) maintains visual consistency with the gap changes applied to the checkbox group.
113-113: Better naming convention for the story exportRenaming from
TeststoDefaultfollows Storybook best practices by providing a clear, standard primary example.packages/components/src/remix-hook-form/radio-group.tsx (3)
2-7: Well-structured imports for component customizationThe additional imports of form components provide the foundation for the component injection pattern being implemented.
14-21: Effective implementation of component injection patternThe
componentsobject merges default form components with any custom components provided through props, enabling flexible UI customization while maintaining the component's core functionality.
22-22: Clean API maintains consistencyThe updated return statement passes both the control context and the components object to the base component, preserving a consistent API while adding customization capabilities.
packages/components/src/ui/text-field.tsx (4)
14-16: Well-designed component extension interfaceThe new
TextFieldComponentsinterface cleanly extends the baseFieldComponentsinterface, providing type safety for the custom Input component option.
26-26: Type safety improvement for components propUpdating the
componentsprop type to usePartial<TextFieldComponents>ensures proper type checking when custom components are used.
31-32: Flexible component substitution mechanismThe implementation allows for using a custom Input component while falling back to the default TextInput when none is provided, maintaining backward compatibility.
41-41: Seamless component replacement with prop forwardingThe component properly forwards all relevant props to either the custom or default Input component, ensuring consistent behavior regardless of which is used.
packages/components/src/ui/checkbox-field.tsx (3)
17-20: Comprehensive component customization interfaceThe
CheckboxFieldComponentsinterface extends the base interface to allow customization of both the checkbox and its indicator, providing granular control over the component's appearance.
38-45: Robust component extraction with fallbacksThe implementation properly extracts custom components with appropriate fallbacks, ensuring the component works correctly whether custom components are provided or not.
47-52: Intelligent conditional styling approachThe code intelligently manages default styling based on whether custom components are provided, preserving the component's visual identity while allowing for complete customization.
packages/components/src/ui/textarea-field.tsx (4)
14-18: Clean interface design for TextareaFieldComponentsThe new
TextareaFieldComponentsinterface properly extends the baseFieldComponentsand provides a well-typed optionalTextAreacomponent. This follows the design pattern used throughout the PR for customizable form components.
28-28: Correctly updated prop typeGood update to use the new
TextareaFieldComponentsinterface for the components prop, maintaining type safety while enabling greater component customization.
33-34: Clean component extraction patternExcellent approach for extracting the custom component with a fallback to the default component. This pattern is consistent with other component implementations in this PR.
43-43: Proper ref forwarding maintainedThe ref forwarding implementation correctly passes
field.refto the textarea component, consistent with the retrieved learnings about ref handling in the Textarea component.packages/components/src/ui/radio-group.tsx (4)
6-13: Well-defined RadioGroupItemComponents interfaceThe interface clearly specifies the component types that can be customized, with appropriate React component type definitions that maintain the original props requirements.
27-27: Properly extended component propsGood addition of the optional
componentsproperty to theRadioGroupItemprops, maintaining backward compatibility while enabling customization.
30-36: Clean component extraction and indicator handlingThe extraction of custom components with fallbacks is well-implemented, and the indicator content determination is elegantly simplified.
38-48: Updated rendering with custom componentsThe rendering logic now correctly uses the extracted components while maintaining all the original functionality and styling.
packages/components/src/ui/radio-group-field.tsx (5)
1-1: Added necessary importsGood addition of the type import for
RadioGroupPrimitiveand thecnutility function for class name manipulation.Also applies to: 14-14
16-18: Well-defined RadioGroupFieldComponents interfaceThe interface correctly extends
FieldComponentsand defines the optionalRadioGroupcomponent with appropriate type constraints.
28-29: Enhanced props for better customizationGood updates to the props interface, both updating the
componentsproperty type and adding the newradioGroupClassNameproperty for additional styling flexibility.
33-36: Proper props destructuring and component extractionThe destructuring now includes the new properties, and the component extraction pattern with fallback is consistently implemented.
45-54: Well-structured custom component renderingThe implementation correctly uses the custom or default component, passes all required props, and includes the children prop within the component. The use of the
cnutility for class name composition is a good practice.apps/docs/src/remix-hook-form/checkbox-list.stories.tsx (4)
32-47: Great implementation of the FullWidthLabel componentThe custom
FullWidthLabelcomponent is well-implemented as a forward ref component with proper TypeScript typing. It demonstrates a practical use case for the component customization pattern by making the entire label area clickable.
86-95: Good demonstration of custom component usageThis example shows how to effectively use the new component customization feature by passing the
FullWidthLabelcomponent to theCheckboxcomponent via thecomponentsprop.
128-128: Improved title formattingThe title change from "RemixHookForm/CheckboxList" to "RemixHookForm/Checkbox List" improves readability.
180-210: Comprehensive documentationThe updated documentation description and source code example provide clear guidance on how to implement and use the custom label component, which is very helpful for other developers.
apps/docs/src/remix-hook-form/text-field-custom.stories.tsx (4)
1-13: Validate consistent naming across imports and usage.The imports for
TextField,Button,FormLabel, and other form components look coherent. It's good practice to verify that these imports are actually used in the code (e.g.,FormMessageorFormLabelfrom@lambdacurry/forms/ui/form). Everything appears consistent here.
22-31: Commendation on usingforwardReffor PurpleInput.This custom input is properly forwarding the ref and spreading props. Using a descriptive
displayNameis a great practice for debugging. Good job implementing all best practices.
150-151: High-level metadata definitions are consistent.All Storybook configuration parameters look fine. The layout is set to
'centered', and'autodocs'is in the tags. This aligns with typical Storybook usage.
125-133:✅ Verification successful
Verify that all usage of
handleFormSubmissionproperly handles errors.The structure returns
{ errors }or{ message }. Confirm that any code calling this respects the possibility of partial or complete errors.Are there references in the codebase that expect a different shape for the returned error? Please run:
and look at how the returned object is handled.
--- 🏁 Script executed: ```shell #!/bin/bash rg "handleFormSubmission" -A 5Length of output: 14643
Error Handling in
handleFormSubmissionis ConsistentAfter searching the codebase, all consumers of
handleFormSubmission—including those in thetext-field-custom.stories.tsxfile—follow the pattern of returning{ errors }on validation failures and handling that accordingly in their respective action functions. There’s no evidence that any caller expects a different error shape.apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx (7)
1-14: Validation schema logic looks appropriate for checkboxes.The schema for
terms,marketing, andrequiredfollows typical patterns. Usingz.boolean()with.refinehandles mandatory checks well. Keep this consistent across other custom form components.
23-36: PurpleCheckbox is well structured withforwardRef.Implementation is suitably minimal. The usage of
data-[state=checked]for styling is a nice approach with Radix UI.
38-51: Maintain a consistent approach to custom indicators.PurpleIndicator is similarly well-implemented, referencing
CheckboxPrimitive.Indicator. This approach of separate component definitions is consistent with the overall custom injection pattern.
82-120: Commendation on using local state management withuseRemixForm.The PurpleCheckboxExample is a good demonstration of how to pass custom components into the configuration. This design pattern fosters reusability across many UI variants.
161-214: AllCustomComponentsExample aggregates multiple overrides effectively.Merging with spread operators is a straightforward approach for combining sets of custom components. This is a well-structured example.
217-225: Double-check error-handling logic inhandleFormSubmission.Confirmed consistency with other form submission handling. No changes needed unless there's a scenario requiring partial validation results.
227-246: Story metadata is properly specified.The story is registered under
'RemixHookForm/Checkbox Customized', which is consistent with other custom components categories.ai/CustomInputsProject.md (1)
1-473: Overall approach aligns with a robust custom form framework.Everything preceding line 474 is well-organized, explaining the injection pattern thoroughly. This fosters maintainable and flexible code.
apps/docs/src/remix-hook-form/textarea-custom.stories.tsx (5)
1-13: Imports are coherent with the rest of the codebase.
zodResolver,Textarea, and other form UI imports match your usage of the injection-based pattern. Good job.
22-33: PurpleTextarea adheres to consistent naming and styling.The usage of
React.forwardRefis properly done. The additional classes for a “purple” theme align nicely with prior custom components.
77-94: AccessibleFormItem and AccessibleFormControl appear to be placeholders for accessibility improvements.No immediate issues here. Confirm that they do set or propagate
idandhtmlForproperly in the real environment.
171-179:handleFormSubmissionis consistent with the approach in other stories.Returning
{ errors }or a success message is straightforward. All set here.
181-194: Story configuration is complete.Title, component, layout, and decorators are all in line with the custom approach. Good job adding
autodocs.apps/docs/src/remix-hook-form/switch-custom.stories.tsx (6)
1-14: Imports look good.All necessary libraries, hooks, and types are being imported appropriately. No concerns here.
15-22: Schema and type definitions.Defines form data validation via Zod and constructs the
FormDatatype. This is well-structured and ensures robust validation.
72-86: Green switch and thumb.Consistently follows the same pattern as the purple components. Great approach for theming and reusability of color-specific styles.
111-125: Fetcher and form context initialization.Using
useRemixFormwithfetcheris an effective approach for handling form data. No issues found.
126-169: CustomSwitchExample structure.Form composition is clear and demonstrates multiple switch variants. This effectively showcases theming and customization.
181-361: Story setup.The metadata and story definition comprehensively illustrate usage. The
playfunction provides test coverage, ensuring correct toggling and submission.packages/components/src/ui/switch-field.tsx (5)
1-5: Imports and type references.Importing
@radix-ui/react-switchand referencingSwitchfrom a nearby file. Looks consistent with the rest of the codebase.
8-40: Default switch and thumb primitives.These provide well-structured fallback components. Good usage of
React.forwardRefandRadixdata states. The classes exhibit typical transitions and states.
42-45:SwitchFieldComponentsinterface.Nicely extends
FieldComponentsto include optional custom switch and thumb. This approach makes the code highly extensible.
50-56:SwitchPropsextendsOmit<...>correctly.Preventing direct external overrides of
checked,defaultChecked, andonCheckedChangehelps centralize control. Suitable choice for ensuring controlled input usage.
60-87:SwitchFieldmain implementation.Solid composition of form control with custom or default switch and thumb. The fallback logic is straightforward and fosters reusability.
apps/docs/src/remix-hook-form/radio-group-custom.stories.tsx (8)
1-16: Imports and schema.Imports for radio group, form utilities, and validation. Zod schema aptly enforces a valid plan selection.
17-24: Plan fields in the schema.Both
planandrequiredPlanare enumerated. This is a good approach for ensuring the user picks a valid option.
26-41:PurpleRadioGroup.Provides a decorative container with custom coloring. Straightforward usage of
React.forwardRefand Radix UI.
43-73:PurpleRadioGroupItemandPurpleRadioGroupIndicator.Properly captures state transitions and styling for selected items. Good encapsulation for reusability.
75-102:IconRadioGroupIndicator.Using a custom SVG is a solid approach to differentiate indicator visuals. No issues found.
103-115:PurpleLabelandPurpleErrorMessage.Well-designed custom label and error message components. Good use of forwardRef for potential parent references.
117-164:CardRadioGroupItem.Implements a card-style approach. This pattern is neat for highlighting selections. The usage of
cnmerges classes effectively.
166-371: Form usage and story.Comprehensive demonstration of various radio styles: standard, custom with icons, card-style, and required fields. Thoroughly tested in the
playfunction.
| export const CustomComponents: Story = { | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: ` | ||
| ### TextField Component Customization | ||
|
|
||
| This example demonstrates three different approaches to customizing the TextField component with complete control over styling and behavior. | ||
|
|
||
| #### 1. Default Styling | ||
|
|
||
| The first text field uses the default styling with no customization needed: | ||
|
|
||
| \`\`\`tsx | ||
| <TextField | ||
| name="username" | ||
| label="Username" | ||
| placeholder="Enter your username" | ||
| /> | ||
| \`\`\` | ||
|
|
||
| #### 2. Custom Styling with Purple Theme | ||
|
|
||
| The second text field customizes the Input, FormLabel, and FormMessage components with purple styling: | ||
|
|
||
| \`\`\`tsx | ||
| <TextField | ||
| name="email" | ||
| label="Email" | ||
| placeholder="Enter your email" | ||
| components={{ | ||
| Input: PurpleInput, | ||
| FormLabel: PurpleLabel, | ||
| FormMessage: PurpleMessage, | ||
| }} | ||
| /> | ||
| \`\`\` | ||
|
|
||
| Where the custom components are defined as: | ||
|
|
||
| \`\`\`tsx | ||
| // Custom Input component | ||
| const PurpleInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>((props, ref) => ( | ||
| <input | ||
| ref={ref} | ||
| {...props} | ||
| className="w-full rounded-lg border-2 border-purple-300 bg-purple-50 px-4 py-2 text-purple-900 placeholder:text-purple-400 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50" | ||
| /> | ||
| )); | ||
|
|
||
| // Custom Form Label component | ||
| const PurpleLabel = React.forwardRef<HTMLLabelElement, React.ComponentPropsWithoutRef<typeof FormLabel>>( | ||
| ({ className, ...props }, ref) => <FormLabel ref={ref} className="text-lg font-bold text-purple-700" {...props} />, | ||
| ); | ||
|
|
||
| // Custom Form Message component | ||
| const PurpleMessage = React.forwardRef<HTMLParagraphElement, React.ComponentPropsWithoutRef<typeof FormMessage>>( | ||
| ({ className, ...props }, ref) => ( | ||
| <FormMessage ref={ref} className="text-purple-500 bg-purple-50 p-2 rounded-md mt-1" {...props} /> | ||
| ), | ||
| ); | ||
| \`\`\` | ||
|
|
||
| #### 3. Icon Input | ||
|
|
||
| The third text field demonstrates how to create a custom input with an icon: | ||
|
|
||
| \`\`\`tsx | ||
| <TextField | ||
| name="password" | ||
| label="Password" | ||
| type="password" | ||
| placeholder="Enter your password" | ||
| components={{ | ||
| Input: IconInput, | ||
| }} | ||
| /> | ||
| \`\`\` | ||
|
|
||
| With the IconInput component defined as: | ||
|
|
||
| \`\`\`tsx | ||
| const IconInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>( | ||
| ({ ...props }, ref) => ( | ||
| <div className="relative"> | ||
| <div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> | ||
| <title>Lock</title> | ||
| <path | ||
| fillRule="evenodd" | ||
| d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" | ||
| clipRule="evenodd" | ||
| /> | ||
| </svg> | ||
| </div> | ||
| <input | ||
| ref={ref} | ||
| {...props} | ||
| className="w-full rounded-md border border-gray-300 py-2 pl-10 pr-3 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" | ||
| /> | ||
| </div> | ||
| ), | ||
| ); | ||
| \`\`\` | ||
|
|
||
| ### Key Points | ||
|
|
||
| - Always use React.forwardRef when creating custom components | ||
| - Make sure to spread the props to pass all necessary attributes | ||
| - Include the ref to maintain form functionality | ||
| - Add a displayName to your component for better debugging | ||
| - The components prop accepts replacements for Input, FormLabel, FormMessage, and FormDescription | ||
| `, | ||
| }, | ||
| }, | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
|
|
||
| // Fill in the form fields | ||
| const usernameInput = canvas.getByPlaceholderText('Enter your username'); | ||
| const emailInput = canvas.getByPlaceholderText('Enter your email'); | ||
| const passwordInput = canvas.getByPlaceholderText('Enter your password'); | ||
|
|
||
| // Type values | ||
| await userEvent.type(usernameInput, 'johndoe'); | ||
| await userEvent.type(emailInput, 'john@example.com'); | ||
| await userEvent.type(passwordInput, 'password123'); | ||
|
|
||
| // Submit the form | ||
| const submitButton = canvas.getByRole('button', { name: 'Submit' }); | ||
| await userEvent.click(submitButton); | ||
|
|
||
| // Verify successful submission | ||
| await expect(await canvas.findByText('Form submitted successfully')).toBeInTheDocument(); | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Story shows strong coverage of typical usage scenarios.
The play function demonstrates how user interaction is tested. Consider adding negative test scenarios, e.g., invalid email, short password, etc., to ensure error states are thoroughly tested.
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: 'Examples of custom checkbox components with different styling options.', | ||
| }, | ||
| source: { | ||
| code: ` | ||
| // Custom checkbox component | ||
| const PurpleCheckbox = React.forwardRef< | ||
| HTMLButtonElement, | ||
| React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> | ||
| >((props, ref) => ( | ||
| <CheckboxPrimitive.Root | ||
| ref={ref} | ||
| {...props} | ||
| className="h-8 w-8 rounded-full border-4 border-purple-500 bg-white data-[state=checked]:bg-purple-500" | ||
| > | ||
| {props.children} | ||
| </CheckboxPrimitive.Root> | ||
| )); | ||
|
|
||
| // Custom indicator | ||
| const PurpleIndicator = React.forwardRef< | ||
| HTMLDivElement, | ||
| React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Indicator> | ||
| >((props, ref) => ( | ||
| <CheckboxPrimitive.Indicator | ||
| ref={ref} | ||
| {...props} | ||
| className="flex h-full w-full items-center justify-center text-white" | ||
| > | ||
| ✓ | ||
| </CheckboxPrimitive.Indicator> | ||
| )); | ||
|
|
||
| // Custom form label component | ||
| const CustomLabel = React.forwardRef< | ||
| HTMLLabelElement, | ||
| React.ComponentPropsWithoutRef<typeof FormLabel> | ||
| >(({ className, htmlFor, ...props }, ref) => ( | ||
| <label | ||
| ref={ref} | ||
| htmlFor={htmlFor} | ||
| className={\`custom-label text-purple-600 font-bold text-lg \${className}\`} | ||
| {...props} | ||
| > | ||
| {props.children} ★ | ||
| </label> | ||
| )); | ||
|
|
||
| // Custom error message component | ||
| const CustomErrorMessage = React.forwardRef< | ||
| HTMLParagraphElement, | ||
| React.ComponentPropsWithoutRef<typeof FormMessage> | ||
| >(({ className, ...props }, ref) => ( | ||
| <p | ||
| ref={ref} | ||
| className={\`custom-error flex items-center text-red-500 bg-red-100 p-2 rounded-md \${className}\`} | ||
| {...props} | ||
| > | ||
| <span className="mr-1 text-lg">⚠️</span> {props.children} | ||
| </p> | ||
| )); | ||
|
|
||
| // Usage in form | ||
| <Checkbox | ||
| name="terms" | ||
| label="Accept terms and conditions" | ||
| components={{ | ||
| ...customCheckboxComponents, | ||
| ...customLabelComponents, | ||
| }} | ||
| /> | ||
|
|
||
| <Checkbox | ||
| name="required" | ||
| label="This is a required checkbox" | ||
| components={customLabelComponents} | ||
| />`, | ||
| }, | ||
| }, | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
|
|
||
| // Find all checkboxes | ||
| const checkboxElements = canvas.getAllByRole('checkbox', { hidden: true }); | ||
|
|
||
| // Get all button checkboxes | ||
| const checkboxButtons = Array.from(checkboxElements) | ||
| .map((checkbox) => checkbox.closest('button')) | ||
| .filter((button) => button !== null) as HTMLButtonElement[]; | ||
|
|
||
| // We should have at least one custom checkbox button | ||
| expect(checkboxButtons.length).toBeGreaterThan(0); | ||
|
|
||
| // Find the custom purple checkbox (the one with rounded-full class) | ||
| const purpleCheckbox = checkboxButtons.find( | ||
| (button) => button.classList.contains('rounded-full') && button.classList.contains('border-purple-500'), | ||
| ); | ||
|
|
||
| if (purpleCheckbox) { | ||
| // Verify custom checkbox styling | ||
| expect(purpleCheckbox).toHaveClass('rounded-full'); | ||
| expect(purpleCheckbox).toHaveClass('border-purple-500'); | ||
|
|
||
| // Check the terms checkbox | ||
| await userEvent.click(purpleCheckbox); | ||
| expect(purpleCheckbox).toHaveAttribute('data-state', 'checked'); | ||
|
|
||
| // Find the required checkbox (we'll just check all remaining checkboxes) | ||
| for (const button of checkboxButtons) { | ||
| if (button !== purpleCheckbox) { | ||
| await userEvent.click(button); | ||
| } | ||
| } | ||
|
|
||
| // Submit the form | ||
| const submitButton = canvas.getByRole('button', { name: 'Submit' }); | ||
| await userEvent.click(submitButton); | ||
|
|
||
| // Verify successful submission | ||
| const successMessage = await canvas.findByText('Form submitted successfully'); | ||
| expect(successMessage).toBeInTheDocument(); | ||
| } | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Inclusion of interactive tests is excellent.
This ensures that custom styling and modifications are validated. Consider adding negative test cases for un-checked required boxes, verifying that error messages display.
| export const CustomComponents: Story = { | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: ` | ||
| ### Textarea Component Customization | ||
|
|
||
| This example demonstrates three different approaches to customizing the Textarea component: | ||
|
|
||
| 1. **Default Styling**: The first textarea uses the default styling with no customization needed. | ||
|
|
||
| 2. **Custom Styling**: The second textarea customizes the TextArea with purple styling. | ||
| \`\`\`tsx | ||
| <div className="space-y-2"> | ||
| <label htmlFor="bio-id" className="text-lg font-bold text-purple-700">Bio</label> | ||
| <PurpleTextarea | ||
| id="bio-id" | ||
| {...methods.register('bio')} | ||
| placeholder="Tell us about yourself" | ||
| /> | ||
| {methods.formState.errors.bio && ( | ||
| <p className="text-purple-500 bg-purple-50 p-2 rounded-md mt-1"> | ||
| {methods.formState.errors.bio.message} | ||
| </p> | ||
| )} | ||
| </div> | ||
| \`\`\` | ||
|
|
||
| 3. **Character Counter**: The third textarea demonstrates how to create a custom textarea with a character counter. | ||
| \`\`\`tsx | ||
| <div className="space-y-2"> | ||
| <label htmlFor="notes-id" className="text-sm font-medium">Additional Notes</label> | ||
| <CounterTextarea | ||
| id="notes-id" | ||
| {...methods.register('notes')} | ||
| placeholder="Any additional notes (optional)" | ||
| /> | ||
| </div> | ||
| \`\`\` | ||
|
|
||
| This example uses direct DOM elements with explicit label-for associations to ensure proper accessibility. | ||
| `, | ||
| }, | ||
| }, | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
|
|
||
| // Fill in the form fields | ||
| const feedbackTextarea = canvas.getByLabelText('Feedback'); | ||
| const bioTextarea = canvas.getByLabelText('Bio'); | ||
|
|
||
| await userEvent.type(feedbackTextarea, 'This is my feedback about the product. It works great!'); | ||
| await userEvent.type(bioTextarea, 'I am a software developer with 5 years of experience in React and TypeScript.'); | ||
|
|
||
| // Submit the form | ||
| const submitButton = canvas.getByRole('button', { name: 'Submit' }); | ||
| await userEvent.click(submitButton); | ||
|
|
||
| // Verify successful submission | ||
| await expect(await canvas.findByText('Form submitted successfully')).toBeInTheDocument(); | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Play function provides solid interactive testing.
It checks the success flow thoroughly. Consider adding tests for incorrect or short text input to exercise the validation errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx (1)
88-90: Type assertion may be misleadingUsing
false as truein default values is a workaround for TypeScript type checking but can be confusing. Consider using a more explicit approach to handle the required boolean fields.- terms: false as true, - marketing: false, - required: false as true, + terms: false, // Will be validated by zod refine + marketing: false, + required: false, // Will be validated by zod refine
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx(1 hunks)
🔇 Additional comments (8)
apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx (8)
53-66: Excellent component implementation with proper ref forwardingThe
CustomLabelcomponent is well-implemented with proper ref forwarding, TypeScript typing, and prop handling. The star symbol addition is a nice visual indicator.
68-80: Great error message styling with attention to accessibilityThe custom error message implementation includes both a visual icon and styled background, making errors more noticeable to users.
465-508: Consider enhancing interactive tests to verify error stateWhile the current tests verify successful form submission, they don't test error cases like submitting with unchecked required checkboxes.
Consider adding a test that:
- Attempts to submit the form without checking required boxes
- Verifies error messages are displayed correctly
- Confirms form cannot be submitted until requirements are met
#!/bin/bash # Look for error validation tests in other form component stories rg -A 5 "expect\(\s*.*error" --glob "*.stories.tsx" apps/docs/src/remix-hook-form/
179-186: Good pattern for component reusabilityExtracting component configurations into variables improves code maintainability and reusability. This pattern makes it easier to apply consistent styling across multiple form elements.
23-36: Well-implemented custom checkbox componentThe
PurpleCheckboximplementation correctly uses forwardRef, preserves props, and applies custom styling while maintaining the component's functionality.
15-19: Robust form validation with clear error messagesThe Zod schema effectively validates required checkboxes with clear, user-friendly error messages using the refine method.
192-207: Excellent demonstration of component compositionThis example effectively demonstrates different approaches to component customization:
- Full custom styling (terms checkbox)
- No customization (marketing checkbox)
- Custom label only (required checkbox)
This provides a comprehensive guide for developers implementing custom form elements.
217-225: Well-implemented form submission handlerThe server-side validation provides an additional layer of security beyond client-side validation, ensuring data integrity.
…ld component - Introduced a new measurement field in the ControlledTextFieldExample with validation. - Updated TextField component to support prefix and suffix props with improved structure. - Refactored FieldPrefix and FieldSuffix components for better styling and accessibility. - Consolidated story examples into a single export for clarity and maintainability.
…refix-suffix Add prefix and suffix props to TextField component
…ormControl and input elements - Moved FormControl inside the main input wrapper for better organization. - Enhanced readability by streamlining the JSX structure of the TextField component. - Maintained existing functionality while improving code clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/components/src/ui/text-field.tsx (1)
65-102: Effective implementation of the TextField with prefix/suffix support.The revised TextField implementation:
- Properly handles the new props in the component signature
- Uses a wrapper div with conditional classes based on prefix/suffix presence
- Conditionally renders the FieldPrefix and FieldSuffix components
- Applies appropriate border adjustments to create a seamless appearance
- Correctly moves focus styling to the wrapper level for a cohesive focus state
This implementation follows React best practices and maintains good visual design principles.
Consider adding unit tests that specifically verify the rendering behavior with various combinations of prefix and suffix props to ensure the conditional rendering and styling logic works as expected in all scenarios.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/components/src/ui/text-field.tsx(1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
packages/components/src/ui/text-field.tsx (1)
packages/components/src/ui/form.tsx (7) (7)
FieldComponents(8-13)FormField(180-196)FormItem(37-56)FormLabel(64-88)FormControl(99-113)FormDescription(121-136)FormMessage(145-170)
🔇 Additional comments (3)
packages/components/src/ui/text-field.tsx (3)
15-32: Well-structured FieldPrefix component with good styling approach.The new
FieldPrefixcomponent is well implemented with a clean API that accepts children and an optional className. The styling creates a cohesive visual connection with the input field through appropriate border styling and transitions. The use ofwhitespace-nowrapensures the prefix content maintains its integrity.
34-51: FieldSuffix implementation maintains symmetry with FieldPrefix.The
FieldSuffixcomponent maintains consistent styling patterns and API with theFieldPrefixcomponent, which is excellent for code maintainability. The border styling adjustments (border-r instead of border-l) and padding changes (pr-3 pl-0 vs pl-3 pr-0) properly mirror the prefix component to maintain visual balance.
53-63: Good type definition with explicit props.The
TextInputPropstype definition is well constructed:
- It extends the props of
TextInputusing ComponentPropsWithRef- Adds React Hook Form integration props
- Explicitly includes new
prefixandsuffixprops- Explicitly includes
classNamefor better type safetyThis approach ensures type safety while maintaining compatibility with the underlying TextInput component.
- Updated package.json files across apps and components to use React and TypeScript version 18. - Adjusted @types/react and @types/react-dom to align with the downgraded React version. - Incremented version of @lambdacurry/forms package to 0.14.0 for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/docs/package.json (1)
27-28: Ensure Consistent Type Definitions for React 18The update to
"@types/react": "^18.0.0"and the introduction of"@types/react-dom": "^18.0.0"ensure your type definitions are aligned with React 18. Please double-check that this change is consistent with the rest of the repo's configuration and that your TypeScript settings are correctly adjusted for React 18.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (2)
apps/docs/package.json(1 hunks)packages/components/package.json(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/components/package.json
🔇 Additional comments (1)
apps/docs/package.json (1)
33-34: Validate React and React-DOM Version AlignmentThe dependencies
"react": "^18.0.0"and"react-dom": "^18.0.0"have been downgraded to match the type definitions and the project’s overall React version. This alignment is critical to prevent any compatibility issues with your custom form components. Additionally, verify that having these libraries in thedevDependenciessection is intentional (typically, these are runtime dependencies, so if they are needed in production, consider moving them todependencies).
|
📝 Storybook Preview: View Storybook This preview will be updated automatically when you push new changes to this PR.
|
Summary by CodeRabbit
New Features
Chores