Skip to content

Conversation

@DimitriGilbert
Copy link
Owner

@DimitriGilbert DimitriGilbert commented Nov 20, 2025

Summary by CodeRabbit

  • Chores
    • Added dependencies for enhanced component styling and UI management (class-variance-authority, clsx, lucide-react, tailwind-merge).
    • Refactored field components to improve internal state management and consistency across form inputs.
    • Introduced shared utilities for standardized input styling and option handling.

✏️ Tip: You can customize this high-level summary in your review settings.

…onents

Phase 1 refactoring to eliminate code duplication by using existing utilities:

- Updated 22 field components to use useFieldState hook instead of manual
  field API value extraction and event handler boilerplate
- Updated 3 dropdown fields (multi-select, color-picker, phone) to use
  useDropdown hook instead of manual click-outside detection
- Updated 7 option-based fields to use normalizeOptions utility instead
  of inline normalization logic

Benefits:
- Eliminated ~215 lines of duplicate code
- Single source of truth for common field patterns
- Improved maintainability and consistency
- All existing functionality preserved exactly

Files updated:
- text-field, number-field, textarea-field, select-field, radio-field
- checkbox-field, switch-field, slider-field, rating-field, date-field
- combobox-field, multi-select-field, multicombobox-field
- color-picker-field, phone-field, file-upload-field
- array-field, autocomplete-field, masked-input-field, duration-picker-field

No breaking changes - all fields maintain identical behavior.
Added missing dependencies that are imported by field components:
- lucide-react ^0.487.0 (for icons)
- clsx ^2.1.1 (for className utility)
- tailwind-merge ^3.3.1 (for className merging)
- class-variance-authority ^0.7.1 (for component variants)

Also fixed TypeScript type issue in slider-field.tsx where raw value
was being rendered without casting (line 129).

Versions match those used in web app to ensure compatibility.

Build now succeeds:
- ESM build: ✓
- CJS build: ✓
- DTS build: ✓
Created new utilities to further reduce code duplication:

1. Created useFormValues hook (~40 lines saved)
   - Extracts form subscription pattern used in date-field and object-field
   - Provides reactive access to all form values for dynamic field behavior
   - Location: src/hooks/use-form-values.ts

2. Created getFieldInputClassName utility (~30 lines saved)
   - Standardizes error styling pattern across 10+ field components
   - Provides consistent border-destructive styling for validation errors
   - Added to src/lib/utils.ts

3. Updated field components to use new utilities:
   - date-field.tsx: Now uses useFormValues for dynamic date restrictions
   - object-field.tsx: Now uses useFormValues for dynamic text resolution
   - text-field, number-field, textarea-field, select-field: Use getFieldInputClassName
   - date-field, combobox-field, phone-field, color-picker-field: Use getFieldInputClassName
   - multicombobox-field, radio-field: Use getFieldInputClassName

4. Type consolidation analysis completed:
   - Discovered that *FieldProps and *FieldSpecificProps serve different purposes
   - *FieldProps used in config/schema definitions
   - *FieldSpecificProps used in actual components
   - Both type systems are necessary and actively used - no consolidation needed

Total reduction: ~70 lines of duplicate code eliminated
Build status: ✓ All builds passing (ESM, CJS, DTS)
@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

The PR refactors field component state management by introducing new hooks and utilities to standardize how form fields interact with their underlying field API. It replaces direct fieldApi manipulation across 20+ components with a consistent abstraction layer, adds four new UI library dependencies, and centralizes class name composition for error styling.

Changes

Cohort / File(s) Summary
Dependencies
packages/formedible/package.json
Added four new dependencies: class-variance-authority, clsx, lucide-react, and tailwind-merge.
State Management Hooks
packages/formedible/src/hooks/use-form-values.ts
New hook that subscribes to form store changes and exposes form values, replacing manual state subscriptions.
Utilities
packages/formedible/src/lib/utils.ts
Added getFieldInputClassName utility to centralize error-state class composition; existing utilities cn and normalizeOptions unchanged.
Field Components Refactored
packages/formedible/src/components/formedible/fields/*-field.tsx (array, autocomplete, checkbox, color-picker, combobox, date, duration-picker, file-upload, masked-input, multi-select, multicombobox, number, object, phone, radio, rating, select, slider, switch, text, textarea)
Systematic refactoring across 21 field components to use useFieldState hook instead of direct fieldApi access; replaced fieldApi.handleChange/handleBlur with onChange/onBlur from hook; integrated getFieldInputClassName for error styling and normalizeOptions for option processing where applicable.

Sequence Diagram

sequenceDiagram
    participant FC as Field Component
    participant Hook as useFieldState Hook
    participant FieldAPI as FieldAPI (form core)
    
    rect rgb(220, 240, 255)
    Note over FC,FieldAPI: Old Pattern
    FC->>FieldAPI: Direct access fieldApi.state.value
    FC->>FieldAPI: Call fieldApi.handleChange(newValue)
    FieldAPI->>FC: (no abstraction)
    end
    
    rect rgb(240, 255, 220)
    Note over FC,FieldAPI: New Pattern
    FC->>Hook: useFieldState(fieldApi)
    Hook->>FieldAPI: Extract state & subscribe
    Hook->>FC: Return { value, onChange, onBlur, ... }
    FC->>Hook: Call onChange(newValue)
    Hook->>FieldAPI: Delegates to fieldApi.handleChange
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25–30 minutes

  • Hook implementation verification: Ensure useFieldState and useFormValues correctly extract and synchronize state; check subscription and cleanup logic in useFormValues.
  • Syntax anomaly in utils.ts: Review the reported extra closing brace in packages/formedible/src/lib/utils.ts to confirm no compilation errors.
  • Pattern consistency: Spot-check 5–6 field components across the refactored set to confirm all follow the same hook-based pattern correctly.
  • Option normalization: Verify that normalizeOptions is applied consistently where options are processed (autocomplete, combobox, multi-select, multicombobox, radio, select).
  • Dependency alignment: Confirm that new dependencies (class-variance-authority, clsx, lucide-react, tailwind-merge) are used appropriately in the refactored components or are foundational for future updates.

Poem

🐰 State management hops were made so clean,
No more direct fieldApi in between,
Hooks now abstract the messy flow,
Consistency blooms as components grow,
A refactor's grace in every sight! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Review and refactor formedible package code' is vague and generic. It uses non-descriptive terms that don't convey the specific changes being made. Replace with a more specific title that highlights the main change, such as 'Refactor form fields to use custom hooks for state management' or 'Introduce useFieldState hook and utility consolidation'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/refactor-formedible-package-01UhvSbFweAsZRdZY1TN7gSy

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

@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

Caution

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

⚠️ Outside diff range comments (2)
packages/formedible/src/components/formedible/fields/rating-field.tsx (1)

126-126: Use the hook-provided onBlur for consistency.

Line 126 directly calls fieldApi.handleBlur(), while line 47 uses the onBlur callback from useFieldState. For consistency with the rest of this component and the broader refactor pattern, use the hook-provided onBlur here as well.

Apply this diff:

-                onBlur={() => fieldApi.handleBlur()}
+                onBlur={onBlur}
packages/formedible/src/components/formedible/fields/slider-field.tsx (1)

80-80: Use useMemo or useId for stable slider ID generation.

The Math.random() call executes on every render, generating a new ID each time. This causes:

  • Accumulation of unused <style> tags in the DOM (lines 155-160)
  • Unnecessary re-renders due to className changes
  • Performance degradation

Apply this diff to generate a stable ID:

+import React, { useId } from "react";
...
-  const sliderId = `slider-${name}-${Math.random().toString(36).substring(2, 9)}`;
+  const sliderId = `slider-${name}-${useId().replace(/:/g, '-')}`;

Alternatively, use useMemo:

+import React, { useMemo } from "react";
...
-  const sliderId = `slider-${name}-${Math.random().toString(36).substring(2, 9)}`;
+  const sliderId = useMemo(
+    () => `slider-${name}-${Math.random().toString(36).substring(2, 9)}`,
+    [name]
+  );
🧹 Nitpick comments (4)
packages/formedible/src/hooks/use-form-values.ts (1)

20-22: Replace any type assertion with proper typing.

The type assertion to any on line 21 bypasses TypeScript's type safety. Since you're already accessing fieldApi.form.state.values on line 14, the store subscription should use the same type structure.

Apply this diff to use proper typing:

     const unsubscribe = fieldApi.form.store.subscribe((state) => {
-      setFormValues((state as any).values);
+      setFormValues(state.values || {});
     });

Alternatively, if the store state type is available from @tanstack/react-form, import and use it explicitly:

import type { FormState } from '@tanstack/react-form';

const unsubscribe = fieldApi.form.store.subscribe((state: FormState<any>) => {
  setFormValues(state.values || {});
});
packages/formedible/src/components/formedible/fields/combobox-field.tsx (1)

39-41: Consider adding a fallback for the value cast.

The value is cast to string without a fallback. If the value is null or undefined, this could cause comparison issues.

Consider adding a fallback:

   const selectedOption = normalizedOptions.find(
-    (option) => option.value === (value as string)
+    (option) => option.value === ((value as string) || "")
   );
packages/formedible/src/components/formedible/fields/array-field.tsx (1)

246-264: Consider using onBlur from the hook for consistency.

Line 259 uses fieldApi.handleBlur directly in the mock field API creation. While this works for propagating blur events to the parent field, it would be more consistent with the refactoring pattern to use onBlur from the useFieldState hook.

Apply this diff for consistency:

   const createItemFieldApi = useCallback(
     (index: number) => {
       return {
         name: `${name}[${index}]`,
         state: {
           value: value[index],
           meta: {
             errors: [],
             isTouched: false,
             isValidating: false,
           },
         },
         handleChange: (newValue: unknown) => updateItem(index, newValue),
-        handleBlur: () => fieldApi.handleBlur(),
+        handleBlur: onBlur,
         form: fieldApi.form,
       };
     },
-    [name, value, updateItem, fieldApi]
+    [name, value, updateItem, onBlur, fieldApi]
   );
packages/formedible/src/components/formedible/fields/select-field.tsx (1)

9-9: Select hook + normalization look solid; minor type/key cleanups possible

The refactor to useFieldState, normalizeOptions, and getFieldInputClassName is consistent and keeps the component nicely declarative.

Two small, optional cleanups:

  • You’re manually re‑declaring the option shape in SelectFieldSpecificProps. Since normalizeOptions already works with NormalizedOption, consider importing that type and using options: (string | NormalizedOption)[] here to avoid divergence if the option shape evolves.
  • key={option.value + index} works, but if option.value is already unique within the options array, key={option.value} is simpler and avoids index usage in keys.

Also applies to: 12-12, 28-32, 35-35, 47-47, 59-63

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5eb063 and 170741e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • packages/formedible/package.json (1 hunks)
  • packages/formedible/src/components/formedible/fields/array-field.tsx (4 hunks)
  • packages/formedible/src/components/formedible/fields/autocomplete-field.tsx (1 hunks)
  • packages/formedible/src/components/formedible/fields/checkbox-field.tsx (2 hunks)
  • packages/formedible/src/components/formedible/fields/color-picker-field.tsx (6 hunks)
  • packages/formedible/src/components/formedible/fields/combobox-field.tsx (2 hunks)
  • packages/formedible/src/components/formedible/fields/date-field.tsx (4 hunks)
  • packages/formedible/src/components/formedible/fields/duration-picker-field.tsx (3 hunks)
  • packages/formedible/src/components/formedible/fields/file-upload-field.tsx (2 hunks)
  • packages/formedible/src/components/formedible/fields/masked-input-field.tsx (3 hunks)
  • packages/formedible/src/components/formedible/fields/multi-select-field.tsx (4 hunks)
  • packages/formedible/src/components/formedible/fields/multicombobox-field.tsx (5 hunks)
  • packages/formedible/src/components/formedible/fields/number-field.tsx (3 hunks)
  • packages/formedible/src/components/formedible/fields/object-field.tsx (2 hunks)
  • packages/formedible/src/components/formedible/fields/phone-field.tsx (6 hunks)
  • packages/formedible/src/components/formedible/fields/radio-field.tsx (4 hunks)
  • packages/formedible/src/components/formedible/fields/rating-field.tsx (2 hunks)
  • packages/formedible/src/components/formedible/fields/select-field.tsx (4 hunks)
  • packages/formedible/src/components/formedible/fields/slider-field.tsx (6 hunks)
  • packages/formedible/src/components/formedible/fields/switch-field.tsx (2 hunks)
  • packages/formedible/src/components/formedible/fields/text-field.tsx (3 hunks)
  • packages/formedible/src/components/formedible/fields/textarea-field.tsx (3 hunks)
  • packages/formedible/src/hooks/use-form-values.ts (1 hunks)
  • packages/formedible/src/lib/utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
packages/formedible/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Use TypeScript for the core Formedible library code

Files:

  • packages/formedible/src/components/formedible/fields/object-field.tsx
  • packages/formedible/src/components/formedible/fields/autocomplete-field.tsx
  • packages/formedible/src/components/formedible/fields/masked-input-field.tsx
  • packages/formedible/src/components/formedible/fields/date-field.tsx
  • packages/formedible/src/components/formedible/fields/combobox-field.tsx
  • packages/formedible/src/components/formedible/fields/duration-picker-field.tsx
  • packages/formedible/src/components/formedible/fields/file-upload-field.tsx
  • packages/formedible/src/components/formedible/fields/phone-field.tsx
  • packages/formedible/src/components/formedible/fields/rating-field.tsx
  • packages/formedible/src/components/formedible/fields/multicombobox-field.tsx
  • packages/formedible/src/components/formedible/fields/switch-field.tsx
  • packages/formedible/src/components/formedible/fields/select-field.tsx
  • packages/formedible/src/components/formedible/fields/color-picker-field.tsx
  • packages/formedible/src/components/formedible/fields/textarea-field.tsx
  • packages/formedible/src/components/formedible/fields/text-field.tsx
  • packages/formedible/src/components/formedible/fields/array-field.tsx
  • packages/formedible/src/components/formedible/fields/multi-select-field.tsx
  • packages/formedible/src/components/formedible/fields/radio-field.tsx
  • packages/formedible/src/components/formedible/fields/checkbox-field.tsx
  • packages/formedible/src/hooks/use-form-values.ts
  • packages/formedible/src/components/formedible/fields/number-field.tsx
  • packages/formedible/src/components/formedible/fields/slider-field.tsx
  • packages/formedible/src/lib/utils.ts
packages/formedible/**

📄 CodeRabbit inference engine (GEMINI.md)

Place and maintain all core library code under packages/formedible

Files:

  • packages/formedible/src/components/formedible/fields/object-field.tsx
  • packages/formedible/src/components/formedible/fields/autocomplete-field.tsx
  • packages/formedible/src/components/formedible/fields/masked-input-field.tsx
  • packages/formedible/src/components/formedible/fields/date-field.tsx
  • packages/formedible/src/components/formedible/fields/combobox-field.tsx
  • packages/formedible/src/components/formedible/fields/duration-picker-field.tsx
  • packages/formedible/src/components/formedible/fields/file-upload-field.tsx
  • packages/formedible/src/components/formedible/fields/phone-field.tsx
  • packages/formedible/src/components/formedible/fields/rating-field.tsx
  • packages/formedible/src/components/formedible/fields/multicombobox-field.tsx
  • packages/formedible/src/components/formedible/fields/switch-field.tsx
  • packages/formedible/src/components/formedible/fields/select-field.tsx
  • packages/formedible/src/components/formedible/fields/color-picker-field.tsx
  • packages/formedible/src/components/formedible/fields/textarea-field.tsx
  • packages/formedible/src/components/formedible/fields/text-field.tsx
  • packages/formedible/src/components/formedible/fields/array-field.tsx
  • packages/formedible/src/components/formedible/fields/multi-select-field.tsx
  • packages/formedible/src/components/formedible/fields/radio-field.tsx
  • packages/formedible/src/components/formedible/fields/checkbox-field.tsx
  • packages/formedible/src/hooks/use-form-values.ts
  • packages/formedible/src/components/formedible/fields/number-field.tsx
  • packages/formedible/src/components/formedible/fields/slider-field.tsx
  • packages/formedible/src/lib/utils.ts
  • packages/formedible/package.json
🧠 Learnings (1)
📚 Learning: 2025-08-22T14:55:03.957Z
Learnt from: CR
Repo: DimitriGilbert/Formedible PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-08-22T14:55:03.957Z
Learning: Applies to packages/formedible/**/*.{ts,tsx} : Use TypeScript for the core Formedible library code

Applied to files:

  • packages/formedible/src/components/formedible/fields/object-field.tsx
  • packages/formedible/src/components/formedible/fields/autocomplete-field.tsx
  • packages/formedible/src/components/formedible/fields/date-field.tsx
  • packages/formedible/src/components/formedible/fields/combobox-field.tsx
  • packages/formedible/src/components/formedible/fields/duration-picker-field.tsx
  • packages/formedible/src/components/formedible/fields/file-upload-field.tsx
  • packages/formedible/src/components/formedible/fields/phone-field.tsx
  • packages/formedible/src/components/formedible/fields/rating-field.tsx
  • packages/formedible/src/components/formedible/fields/multicombobox-field.tsx
  • packages/formedible/src/components/formedible/fields/select-field.tsx
  • packages/formedible/src/components/formedible/fields/textarea-field.tsx
  • packages/formedible/src/components/formedible/fields/text-field.tsx
  • packages/formedible/src/components/formedible/fields/array-field.tsx
  • packages/formedible/src/components/formedible/fields/radio-field.tsx
  • packages/formedible/src/components/formedible/fields/checkbox-field.tsx
  • packages/formedible/src/hooks/use-form-values.ts
  • packages/formedible/src/components/formedible/fields/number-field.tsx
  • packages/formedible/package.json
🧬 Code graph analysis (19)
packages/formedible/src/components/formedible/fields/object-field.tsx (1)
packages/formedible/src/hooks/use-form-values.ts (1)
  • useFormValues (12-28)
packages/formedible/src/components/formedible/fields/masked-input-field.tsx (1)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/components/formedible/fields/date-field.tsx (3)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/hooks/use-form-values.ts (1)
  • useFormValues (12-28)
packages/formedible/src/lib/utils.ts (2)
  • cn (4-6)
  • getFieldInputClassName (28-38)
packages/formedible/src/components/formedible/fields/combobox-field.tsx (1)
packages/formedible/src/lib/utils.ts (3)
  • normalizeOptions (13-19)
  • cn (4-6)
  • getFieldInputClassName (28-38)
packages/formedible/src/components/formedible/fields/duration-picker-field.tsx (1)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/components/formedible/fields/file-upload-field.tsx (1)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/components/formedible/fields/phone-field.tsx (2)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/lib/utils.ts (2)
  • getFieldInputClassName (28-38)
  • cn (4-6)
packages/formedible/src/components/formedible/fields/rating-field.tsx (1)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/components/formedible/fields/multicombobox-field.tsx (1)
packages/formedible/src/lib/utils.ts (2)
  • normalizeOptions (13-19)
  • getFieldInputClassName (28-38)
packages/formedible/src/components/formedible/fields/switch-field.tsx (1)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/components/formedible/fields/select-field.tsx (1)
packages/formedible/src/lib/utils.ts (2)
  • normalizeOptions (13-19)
  • getFieldInputClassName (28-38)
packages/formedible/src/components/formedible/fields/color-picker-field.tsx (2)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/lib/utils.ts (2)
  • getFieldInputClassName (28-38)
  • cn (4-6)
packages/formedible/src/components/formedible/fields/textarea-field.tsx (1)
packages/formedible/src/lib/utils.ts (1)
  • getFieldInputClassName (28-38)
packages/formedible/src/components/formedible/fields/text-field.tsx (1)
packages/formedible/src/lib/utils.ts (1)
  • getFieldInputClassName (28-38)
packages/formedible/src/components/formedible/fields/array-field.tsx (1)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/components/formedible/fields/multi-select-field.tsx (2)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/lib/utils.ts (1)
  • normalizeOptions (13-19)
packages/formedible/src/components/formedible/fields/radio-field.tsx (2)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/lib/utils.ts (2)
  • normalizeOptions (13-19)
  • getFieldInputClassName (28-38)
packages/formedible/src/components/formedible/fields/checkbox-field.tsx (1)
packages/formedible/src/hooks/use-field-state.ts (1)
  • useFieldState (13-38)
packages/formedible/src/components/formedible/fields/number-field.tsx (1)
packages/formedible/src/lib/utils.ts (1)
  • getFieldInputClassName (28-38)
🔇 Additional comments (20)
packages/formedible/src/components/formedible/fields/masked-input-field.tsx (1)

7-7: LGTM - Consistent with hook-based refactor pattern.

The introduction of useFieldState and the replacement of direct fieldApi.handleChange with the hook-provided onChange aligns with the broader refactor described in the PR.

Also applies to: 45-45, 176-176

packages/formedible/src/components/formedible/fields/autocomplete-field.tsx (1)

7-7: LGTM - Good consolidation of shared utility.

Replacing the local normalizeOptions helper with the imported utility from @/lib/utils eliminates code duplication and centralizes the normalization logic.

packages/formedible/src/components/formedible/fields/duration-picker-field.tsx (1)

14-14: LGTM - Consistent with the state management refactor.

The migration to useFieldState for name and onChange follows the established pattern across other field components in this PR.

Also applies to: 73-73, 87-87

packages/formedible/src/components/formedible/fields/checkbox-field.tsx (1)

7-7: LGTM - Complete and clean hook migration.

The checkbox field demonstrates a thorough migration to useFieldState, extracting all necessary values (name, value, isDisabled, onChange, onBlur) and eliminating direct fieldApi handler calls.

Also applies to: 17-21, 39-39

packages/formedible/src/components/formedible/fields/object-field.tsx (1)

8-8: LGTM - Effective consolidation of form value subscriptions.

Replacing manual state management and useEffect subscriptions with the useFormValues hook centralizes form value tracking and reduces boilerplate.

Also applies to: 22-22

packages/formedible/src/components/formedible/fields/file-upload-field.tsx (1)

8-8: LGTM - Complete hook-based state management.

The file upload field comprehensively adopts useFieldState, replacing all direct fieldApi handler calls with hook-provided callbacks for consistent state management.

Also applies to: 25-26, 30-31, 35-35, 40-40

packages/formedible/src/components/formedible/fields/rating-field.tsx (1)

7-7: LGTM - Hook migration aligns with refactor pattern.

The adoption of useFieldState to derive value, isDisabled, onChange, and onBlur is consistent with the broader state management refactor across field components.

Also applies to: 38-39, 46-47, 51-51

packages/formedible/package.json (1)

58-64: All dependencies are valid, up-to-date within their pinned ranges, and free from security vulnerabilities.

Verification confirms:

  • class-variance-authority (^0.7.1), clsx (^2.1.1), lucide-react (^0.487.0), and tailwind-merge (^3.3.1) all exist on npm and match or are safely within their latest minor/patch versions.
  • Zero security advisories across all four packages.
  • Caret ranges allow safe updates: lucide-react's range safely excludes the FingerprintPattern breaking change in 0.554.0, and tailwind-merge 3.4.0 introduces no breaking changes.
packages/formedible/src/lib/utils.ts (1)

21-38: LGTM! Clean error styling abstraction.

The utility function properly centralizes error styling logic and integrates well with the existing cn utility. The type signature correctly allows undefined values in the rest parameters, which will be safely filtered by clsx.

packages/formedible/src/components/formedible/fields/switch-field.tsx (1)

7-7: LGTM! Consistent hook-based state management.

The migration to useFieldState is correctly implemented. The hook-provided onChange and onBlur handlers properly replace direct fieldApi manipulation, maintaining the component's behavior while aligning with the new state management pattern.

Also applies to: 17-21, 39-39

packages/formedible/src/components/formedible/fields/combobox-field.tsx (1)

17-17: LGTM! Well-integrated hook and utility usage.

The refactoring successfully migrates to useFieldState and shared utilities (normalizeOptions, getFieldInputClassName). All handlers are properly wired and the error styling is correctly centralized.

Also applies to: 20-20, 33-33, 45-45, 51-51, 80-80

packages/formedible/src/components/formedible/fields/textarea-field.tsx (1)

3-3: LGTM! Proper hook integration with good practices.

The refactoring correctly adopts useFieldState and getFieldInputClassName. The value handling includes a proper fallback ((value as string) || ''), and aliasing onChange to onFieldChange avoids naming conflicts with the native event handler.

Also applies to: 6-6, 19-25, 39-39

packages/formedible/src/components/formedible/fields/radio-field.tsx (1)

5-5: LGTM! Consistent refactoring with proper error styling.

The migration to useFieldState, normalizeOptions, and getFieldInputClassName is correctly implemented. The value handling includes a proper fallback, and the error styling is applied correctly to individual radio items.

Also applies to: 8-8, 20-25, 37-37, 56-56

packages/formedible/src/components/formedible/fields/array-field.tsx (1)

8-8: LGTM! Thorough migration to hook-based state management.

The refactoring successfully migrates all array operations to use useFieldState. All add, remove, update, and reorder operations correctly use onChange, and onBlur is appropriately called in removeItem. The callback dependencies are properly updated to reflect the new handlers.

Also applies to: 118-118, 174-174, 182-183, 192-192, 237-237

packages/formedible/src/components/formedible/fields/number-field.tsx (1)

4-7: Hook-based number field wiring looks correct

Using useFieldState plus a local onChange that normalizes the value to number | string | undefined and then dispatches via onFieldChange preserves expected behavior, and getFieldInputClassName centralizes error styling cleanly. No issues from my side here.

Also applies to: 22-23, 35-35, 45-45

packages/formedible/src/components/formedible/fields/phone-field.tsx (1)

5-5: Phone field refactor to useFieldState looks good

The move to useFieldState for value/disabled/errors plus pushing updates via onChange/onBlur keeps the phone field consistent with the rest of the library. Formatting logic and country handling remain intact, and error styling via getFieldInputClassName is applied in the right places.

Also applies to: 9-9, 82-84, 139-140, 156-157, 187-190, 236-236, 240-241

packages/formedible/src/components/formedible/fields/date-field.tsx (1)

4-4: Date field hook integration is solid; confirm Calendar.disabled semantics

The refactor to useFieldState / useFormValues and the disableDate(date, formValues) wrapper looks coherent and keeps the behavior extensible while preserving the original (date) => boolean usage. The disabled-date memoization and error styling via getFieldInputClassName are also in good shape.

One thing to double-check: when isDisabled is true, disabledMatchers becomes true and is passed into <Calendar disabled={disabledMatchers} />. That’s fine as long as your Calendar component (and underlying date-picker lib) explicitly supports disabled={true} as “disable all dates” in addition to arrays/matchers.

Please confirm against the Calendar component’s disabled prop typing/behavior that true is an accepted value for “disable everything”.

Also applies to: 15-16, 28-29, 40-60, 61-64, 67-71

packages/formedible/src/components/formedible/fields/multicombobox-field.tsx (1)

18-18: LGTM! Clean refactor to centralized field state management.

The migration to useFieldState and utility functions is implemented correctly:

  • Safe array defaulting on line 40 prevents runtime errors
  • normalizeOptions utility replaces manual option processing
  • Event handlers properly wired through the hook
  • Error styling delegated to getFieldInputClassName

The refactor maintains all existing functionality while improving consistency.

Also applies to: 22-22, 39-74, 101-101

packages/formedible/src/components/formedible/fields/slider-field.tsx (1)

26-26: LGTM! State management refactor is correctly implemented.

The migration to useFieldState hook is done correctly:

  • Proper destructuring of field state and handlers
  • Safe fallback to min when value is not a number
  • Event handlers correctly use onChange from the hook
  • All state interactions centralized through the hook

The refactor maintains existing functionality while improving consistency across field components.

Also applies to: 54-54, 70-70, 140-140

packages/formedible/src/components/formedible/fields/text-field.tsx (1)

4-4: LGTM! Refactor correctly implements centralized state management.

The migration to useFieldState and utility functions is well-executed:

  • Smart aliasing to onFieldChange (line 21) avoids naming conflict with the local onChange wrapper
  • getFieldInputClassName properly handles error styling with loading state
  • Existing datalist functionality preserved
  • All event handlers correctly wired through the hook

The refactor improves consistency while maintaining all functionality.

Also applies to: 7-7, 21-21, 90-98

Comment on lines +114 to 118
const { value: fieldValue, isDisabled, hasErrors, onChange, onBlur } = useFieldState(fieldApi);
const value = (fieldValue as string) || "#000000";

const [isOpen, setIsOpen] = useState(false);
const { isOpen, setIsOpen, containerRef } = useDropdown();
const [customInput, setCustomInput] = useState(value);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix stored color format and restore inputClassName on the main input

Two coupled issues here:

  1. Stored value vs. display format
  • value is treated as a hex color (normalizedValue prefixes # and is used as backgroundColor), and utilities like hexToRgb/hexToHsl assume hex.
  • In the current handlers you sometimes call onChange(formattedColor) (rgb/hsl) or arbitrary inputValue. When format !== "hex", this can store non-hex values, making normalizedValue become things like #rgb(255,0,0), which is not a valid CSS color and breaks the preview as well as the “Ensure value is always a valid hex color” contract.

You can keep the field’s stored value as canonical hex and use formatColor purely for what you render:

@@
-  const { value: fieldValue, isDisabled, hasErrors, onChange, onBlur } = useFieldState(fieldApi);
-  const value = (fieldValue as string) || "#000000";
+  const { value: fieldValue, isDisabled, hasErrors, onChange, onBlur } = useFieldState(fieldApi);
+  const value = (fieldValue as string) || "#000000";
@@
   const handleColorSelect = (color: string) => {
-    const formattedColor = formatColor(color, format);
-    onChange(formattedColor);
+    // Store hex, format only for display
+    onChange(color);
     setCustomInput(color);
     setIsOpen(false);
     onBlur();
   };
@@
   const handleCustomInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
     const inputValue = e.target.value;
     setCustomInput(inputValue);
 
-    // Validate and update if it's a valid color
-    if (inputValue.match(/^#[0-9A-Fa-f]{6}$/)) {
-      const formattedColor = formatColor(inputValue, format);
-      onChange(formattedColor);
-    }
+    // Validate and update only when it's a valid hex color
+    if (isValidColor(inputValue)) {
+      onChange(inputValue);
+    }
   };
@@
   const handleNativeColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
     const color = e.target.value;
-    const formattedColor = formatColor(color, format);
-    onChange(formattedColor);
+    // Native color input always yields hex
+    onChange(color);
     setCustomInput(color);
   };
@@
   <Input
     value={displayValue}
     onChange={(e) => {
       const inputValue = e.target.value;
-      onChange(inputValue);
-      // Try to extract hex value for internal use
-      if (inputValue.startsWith("#")) {
-        setCustomInput(inputValue);
-      }
+      // Allow typing hex directly into the main input; keep storage hex-only
+      setCustomInput(inputValue);
+      if (isValidColor(inputValue)) {
+        onChange(inputValue);
+      }
     }}

This keeps fieldValue as hex, while displayValue = formatColor(normalizedValue, format) continues to provide rgb/hsl/hex presentation.

  1. inputClassName not applied to the main input

The main text input currently ignores inputClassName and only applies error styling:

className={cn(
  "flex-1",
  getFieldInputClassName(undefined, hasErrors)
)}

Every other field component wires caller-supplied inputClassName through getFieldInputClassName. To match that and avoid a regression in styling control:

   className={cn(
     "flex-1",
-    getFieldInputClassName(undefined, hasErrors)
+    getFieldInputClassName(inputClassName, hasErrors)
   )}

This restores consumer-provided classes on the main input while still centralizing error styling.

Also applies to: 125-131, 133-141, 144-149, 164-167, 181-182, 188-193, 198-203

🤖 Prompt for AI Agents
In packages/formedible/src/components/formedible/fields/color-picker-field.tsx
around lines 114-118 (and related blocks at 125-131, 133-141, 144-149, 164-167,
181-182, 188-193, 198-203), the component sometimes stores non-hex color strings
(rgb/hsl or raw input) into fieldValue and also omits the caller-supplied
inputClassName on the main input; fix by keeping the canonical stored value
always as a hex string (normalize/convert any picked/parsed color to hex before
calling onChange) and use a separate displayValue = formatColor(normalizedHex,
format) for what is shown in the input/preview; update all handlers (picker
change, preset click, custom input confirm/blur) to convert to hex then call
onChange(hex) and only use formatColor for rendering; and restore inputClassName
on the main input by passing the incoming inputClassName into
getFieldInputClassName so error styling and consumer classes are both applied.

const selectedValues = Array.isArray(fieldApi.state?.value)
? fieldApi.state?.value
: [];
const { value, onChange, onBlur } = useFieldState(fieldApi);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Complete the migration to useFieldState.

The refactoring is incomplete. While useFieldState is used at line 29, several places still access fieldApi directly:

  • Line 109: derives isDisabled from fieldApi.form.state.isSubmitting instead of using the hook
  • Line 126: checks errors via fieldApi.state?.meta?.errors.length instead of using hasErrors from the hook
  • Line 172: calls fieldApi.handleBlur instead of using onBlur from the hook

Apply this diff to complete the migration:

-  const { value, onChange, onBlur } = useFieldState(fieldApi);
+  const { name, value, isDisabled, hasErrors, onChange, onBlur } = useFieldState(fieldApi);
   const selectedValues = Array.isArray(value) ? value : [];

Then update the usages:

-  const isDisabled = fieldApi.form.state.isSubmitting;
+  // Remove this line, use isDisabled from hook
             className={cn(
               "min-h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background",
               "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
-              fieldApi.state?.meta?.errors.length ? "border-destructive" : "",
+              hasErrors ? "border-destructive" : "",
               isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-text"
             )}
                   onFocus={() => setIsOpen(true)}
-                  onBlur={fieldApi.handleBlur}
+                  onBlur={onBlur}
                   placeholder={selectedValues.length === 0 ? placeholder : ""}

Also applies to: 109-109, 126-126, 172-172

{showRawValue && (
<div className="text-xs text-muted-foreground">
Raw: {fieldApi.state?.value}
Raw: {String(value)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use fieldValue for consistency with the rest of the component.

Line 129 displays the raw value, but line 54 computes fieldValue with a fallback to min when value is not a number. All other references in the component (lines 66, 86, 140, 146, 164) use fieldValue.

For consistency and to ensure the display matches the actual slider behavior, use fieldValue here.

Apply this diff:

-            Raw: {String(value)}
+            Raw: {String(fieldValue)}
📝 Committable suggestion

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

Suggested change
Raw: {String(value)}
Raw: {String(fieldValue)}
🤖 Prompt for AI Agents
In packages/formedible/src/components/formedible/fields/slider-field.tsx around
line 129, the component prints the raw value using String(value) while the rest
of the component (and the slider behavior) uses the computed fieldValue (which
falls back to min when value is not a number); replace the displayed raw value
with String(fieldValue) so the shown "Raw:" value is consistent with the slider
state and other references.

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.

3 participants