Skip to content

Conversation

@shohag121
Copy link
Member

@shohag121 shohag121 commented Feb 12, 2025

Important

This PR is for the Dokan Plugin Compatibility.
Please do not close or remove the Branch.

In this PR we are resolving a styling conflict for Tailwind BASE and other layers that take effect on customer websites' styling.

Summary by CodeRabbit

  • New Features

    • Modal backdrops can be customized and now close when clicked.
    • Select components (searchable & async) support extensive styling and component overrides; FormSelect accepts a custom portal target.
    • React-Select API re-exported for easier access.
  • Style

    • Tailwind styles scoped to the app layout for improved style isolation.
  • Chores

    • Dependencies reorganized/updated (Headless UI, React/react-dom versioning); added scoped preflight package.
    • Build output directory is now tracked (removed from .gitignore).

@shohag121 shohag121 added the Status: Needs Dev Review It requires review from a developer and approval label Feb 12, 2025
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2025

Walkthrough

Updates package dependencies, adds scoped Tailwind preflight, exposes react-select namespace, extends Select components with components/styles/className passthroughs and portal options, makes Modal backdrop customizable and clickable to close, and removes dist from .gitignore.

Changes

Cohort / File(s) Change Summary
Package manifest
package.json
Reordered dependencies; moved @headlessui/react from devDependencies to dependencies and upgraded to ^2.2.0; relaxed react/react-dom to >=18; added @radix-ui/react-tooltip; added tailwindcss-scoped-preflight to devDependencies.
Tailwind config
tailwind.config.ts
Added imports for scopedPreflightStyles and isolateInsideOfContainer; introduced rootClass = '.dokan-layout'; set important: rootClass; registered scopedPreflightStyles plugin with isolateInsideOfContainer(rootClass) for scoped preflight.
Modal component
src/components/Modal.tsx
Added backdropClassName?: string to ModalProps; replaced Dialog.Overlay with Headless UI DialogBackdrop; merged backdropClassName into backdrop classes and added onClick on backdrop to call onClose.
Async searchable select
src/components/AsyncSearchableSelect.tsx
Added components?: typeof components; allow user components to merge with built-ins; introduced dynamic styles merging pipeline; improved className merging (twMerge) and forwarded menuPortalTarget and menuPosition; passed through remaining props to AsyncSelect.
Searchable select
src/components/SearchableSelect.tsx
Added components?: typeof components prop; removed ref?: any from public props; implemented dynamic styles and className merging with twMerge; merged user components with internal indicators.
Form select
src/components/FormSelect.tsx
Added portalTarget?: HTMLElement to FormSelectProps and used it as container for Select.Portal (defaults to document.body).
Public exports
src/index.tsx
Added export * as ReactSelect from 'react-select';.
Ignored files
.gitignore
Removed the line ignoring dist (so dist is now tracked).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Modal as ModalComponent
  participant Backdrop as DialogBackdrop

  User->>Modal: Open modal (isOpen = true)
  Modal->>Backdrop: render with default classes + `backdropClassName`
  Note right of Backdrop: backdrop includes transitions\nand onClick that calls onClose
  User->>Backdrop: click
  Backdrop->>Modal: call onClose()
  Modal->>User: modal closes (isOpen = false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Status: Needs Dev Review

Suggested reviewers

  • ahsanshaheen199
  • arafatkn
  • mostafizurhimself

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title directly and accurately describes the PR's primary change—scoping Tailwind with an "important" selector using .dokan-layout to resolve Dokan plugin style conflicts—matching the PR objectives and the tailwind.config.ts changes. It is specific, actionable, and readable for a teammate scanning history. The "chore:" prefix appropriately signals a maintenance change and the parenthetical clarifies intent.

Poem

🐰
I hopped through code with gentle paws,
Scoped Tailwind stitched within my claws.
Backdrop taps and selects that bend,
Exports shared for every friend.
Dist now tracked — a carrot-send!

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dokan-plugin

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: 0

🧹 Nitpick comments (1)
src/components/Modal.tsx (1)

29-34: Consider adding type safety for backdrop classes.

While the implementation is correct, consider using a union type for common backdrop class combinations to prevent invalid class combinations.

+type BackdropClass = 'backdrop-blur-lg' | 'backdrop-blur-sm' | 'backdrop-blur-none';
+
 export type ModalProps = {
   children: React.ReactNode;
   className?: string;
-  backdropClassName?: string;
+  backdropClassName?: BackdropClass | string;
   isOpen: boolean;
   showXButton?: boolean;
   onClose: () => void;
 } & HTMLAttributes<HTMLDivElement>;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cc52d43 and d922335.

📒 Files selected for processing (3)
  • package.json (3 hunks)
  • src/components/Modal.tsx (2 hunks)
  • tailwind.config.ts (2 hunks)
🔇 Additional comments (8)
tailwind.config.ts (3)

3-8: LGTM! Good choice of scoping strategy.

The addition of tailwindcss-scoped-preflight with its isolation utilities is a good approach for preventing style leaks.


12-12: Verify the impact of using important with rootClass.

While using important with rootClass can help maintain style precedence within the scope, it might make it harder to override styles in specific cases.

Consider testing edge cases where styles need to be overridden within the .dokan-layout scope.


55-60: LGTM! Well-structured plugin configuration.

The plugin configuration correctly sets up style isolation using the rootClass scope.

src/components/Modal.tsx (2)

2-2: LGTM! Good upgrade to use DialogBackdrop.

The switch to DialogBackdrop from Dialog.Overlay aligns with the latest Headless UI practices.


8-8: LGTM! Good addition of customization prop.

The backdropClassName prop enhances the component's flexibility by allowing custom backdrop styling.

package.json (3)

17-17: LGTM! Good addition of prepare script.

The prepare script ensures the package is built before being used as a dependency.


76-76: LGTM! Good addition of scoped preflight dependency.

The addition of tailwindcss-scoped-preflight aligns with the style scoping strategy implemented in the Tailwind config.


31-33: Verify compatibility with Headless UI v2.

The upgrade from v1 to v2 of @headlessui/react might introduce breaking changes.

❌ Verification inconclusive

🌐 Web query:

What are the breaking changes in @headlessui/react v2?

💡 Result:

The transition to @headlessui/react v2 introduces several breaking changes from previous versions, primarily focused on improved accessibility, new component APIs, and React 18 compatibility:

React 18 Requirement

  • Minimum React version bumped to 18.x[3][7][8], dropping support for older React versions

Component Changes

  • RadioGroup.Option deprecated in favor of new Radio component[3][7]
  • PopoverOverlay renamed to PopoverBackdrop[3]
  • Dialog focus behavior now focuses dialog itself by default instead of first focusable element[3][7]
  • New HTML structure for:
    • ListboxOptions/ListboxOption
    • ComboboxOptions/ComboboxOption
    • TabGroup[3]

Prop Changes

  • active prop deprecated in favor of focus prop[3][7]
  • Form components now require explicit name props instead of auto-generated IDs[1][3]
  • Transition components changed from class-based to data-attribute styling:
// Old (v1)
<Transition
  enter="transition-opacity duration-75"
  enterFrom="opacity-0"
  enterTo="opacity-100"
>

// New (v2)
<Transition className="transition-opacity duration-75 data-[closed]:opacity-0">

Behavior Changes

  • Disabled state handling now uses data-disabled attributes instead of native disabled attribute[1][3]
  • Combobox virtualization requires using new virtual prop instead of automatic detection[1][3]
  • Menu positioning now requires explicit anchor prop configuration[1][3]

TypeScript Changes

  • Component types updated with stricter type constraints
  • New Ref types required for form components[3][7]

For migration help, refer to the official upgrade guide mentioned in [1] and check the detailed changelog entries in [3][7]. Projects using older patterns should audit their:

  1. Radio group implementations
  2. Dialog focus management
  3. Transition class names
  4. Form control markup
  5. Combobox virtualization logic
# Recommended upgrade path
npm install @headlessui/react@latest --force

Citations:


Dependency Update Verified – Double Check Component Usages

The dependency update in your package.json (using "@headlessui/react": "^2.2.0") correctly requires React 18 and aligns with the v2 upgrade. According to the web query, Headless UI v2 introduces several breaking changes (such as API updates for Radio Group components, renaming of PopoverOverlay to PopoverBackdrop, altered dialog focus behavior, and transition class changes).

You should verify that any parts of your codebase using Headless UI components (e.g., RadioGroup, Dialog, Transition, etc.) have been updated according to the v2 guidelines. For instance:

  • Replace deprecated or renamed components (e.g., use Radio instead of RadioGroup.Option if applicable).
  • Update component props like switching from active to focus.
  • Adjust any custom transition class names or HTML structuring as per the new API.

No issues were found in the dependency declaration, but please manually review your component implementations to ensure full compatibility with Headless UI v2.

# Conflicts:
#	package.json
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: 0

🧹 Nitpick comments (2)
package.json (2)

82-82: Tailwind Scoped Preflight Integration
The inclusion of "tailwindcss-scoped-preflight": "^3.4.9" is key to supporting improved style encapsulation, in line with the Tailwind configuration changes. However, if this package is only required during development or build-time, consider moving it to devDependencies to optimize the production bundle.


25-86: Duplicate Dependency Declarations
It appears that some packages—specifically react-hot-toast, react-select, and @radix-ui/react-tooltip—are declared in both dependencies and devDependencies. This duplication might lead to confusion or potential version conflicts. Please review these entries and consolidate them if they are not intentionally separated for runtime versus development needs.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2963cb0 and ff02c63.

📒 Files selected for processing (1)
  • package.json (3 hunks)
🔇 Additional comments (2)
package.json (2)

13-13: New Prepare Script Added
The addition of the "prepare": "vite build" script is appropriate for streamlining the build process. Please ensure that this script integrates well with your existing build pipeline and does not conflict with other build steps.


26-31: Updated Dependencies for UI Components and Runtime Libraries
The updated dependency declarations for @radix-ui/react-tooltip, @headlessui/react, react, react-dom, react-hot-toast, and react-select now use a more flexible version specification. This change can improve compatibility across different environments. Please verify that these updates meet the requirements of all consumers and that no unintended side-effects occur.

* fix: `SimpleInput` left addon and right addon text overlapping

* added build files

* fix: simple radio static indigo color

* fix: modal backdrop close

* fix: build files
# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/components/SimpleInput.tsx
#	src/stories/SimpleInput.stories.tsx
@shohag121 shohag121 changed the title chore: Tailwind Important with .dokan-layout added chore: Tailwind Important with .dokan-layout added (Resolves Dokan Plugin Style Conflict) Aug 29, 2025
@mrabbani mrabbani added DO NOT MERGE Requires help from the design team and removed Status: Needs Dev Review It requires review from a developer and approval labels Sep 2, 2025
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: 2

🧹 Nitpick comments (2)
src/components/AsyncSearchableSelect.tsx (2)

3-3: Consider using a more specific type for the components prop.

The current typing typeof components from react-select is very broad and may not provide adequate type safety. Consider defining a more specific type that only includes the components that can be overridden in AsyncSelect.

-import { GroupBase, MenuPosition, components } from 'react-select';
+import { GroupBase, MenuPosition, ComponentsConfig } from 'react-select';
-  components?: typeof components;
+  components?: ComponentsConfig<Option, IsMulti, Group>;

This would provide better type safety and IntelliSense support for users of the component.

Also applies to: 23-23


112-253: Consider extracting style builder logic to reduce repetition.

The style building pattern is repeated 12+ times with identical structure. This could be refactored into a helper function to improve maintainability and reduce the chance of bugs.

Add a helper function at the top of the component:

const buildStyle = <T extends Record<string, any>>(
  base: T,
  userStyle: ((base: T, state: any) => T) | T | undefined,
  state: any,
  customStyles: T
): T => {
  const userControlStyles = typeof userStyle === 'function' 
    ? userStyle(base, state) 
    : userStyle || {};
  return {
    ...base,
    ...customStyles,
    ...userControlStyles,
  };
};

Then simplify each style handler:

-          control: (base, state) => {
-            const userControl = props.styles?.control;
-            const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : {};
-            return {
-              ...base,
-              minHeight: '40px',
-              boxShadow: 'none',
-              borderRadius: '5px',
-              fontSize: '14px',
-              // Merge user-provided control styles (object or function result)
-              ...userControlStyles,
-            };
-          },
+          control: (base, state) => buildStyle(
+            base,
+            props.styles?.control,
+            state,
+            {
+              minHeight: '40px',
+              boxShadow: 'none',
+              borderRadius: '5px',
+              fontSize: '14px',
+            }
+          ),

This pattern could be applied to all the style handlers to reduce duplication and improve maintainability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0a6ada5 and e6f1ee2.

⛔ Files ignored due to path filters (7)
  • dist/components/AsyncSearchableSelect.d.ts is excluded by !**/dist/**
  • dist/components/GooglePlaceAutocomplete.d.ts is excluded by !**/dist/**
  • dist/components/SearchableSelect.d.ts is excluded by !**/dist/**
  • dist/dokan-ui.css is excluded by !**/dist/**
  • dist/index.d.ts is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • src/components/AsyncSearchableSelect.tsx (3 hunks)
  • src/index.tsx (1 hunks)
🧰 Additional context used
🪛 ESLint
src/components/AsyncSearchableSelect.tsx

[error] 92-92: Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free.

(@typescript-eslint/ban-ts-comment)

🔇 Additional comments (1)
src/index.tsx (1)

47-47: LGTM! Export addition is well-placed and follows conventions.

The re-export of react-select components under the ReactSelect namespace is correctly positioned after the Select component export, maintaining the file's alphabetical organization pattern for component exports.

Comment on lines 89 to 107
...( props?.classNames ? props.classNames : {} ),
control: (currentProps) => {
const userControl = props.classNames?.control;
// @ts-ignore
const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError: hasError }) : '';
if (hasError && currentProps.isFocused) {
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}

if (isFocused) {
return '!border-2 !border-primary-500';
if (currentProps.isFocused) {
return twMerge( '!border !ring-primary-500 !ring-1', userControlStyles );
}

if (hasError) {
return '!border !border-danger-500';
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}

return '!border-gray-200';
return twMerge( '!border-gray-200', userControlStyles );
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace @ts-ignore with @ts-expect-error for better type safety.

The TypeScript compiler directive should be more specific about the expected error.

-            // @ts-ignore
+            // @ts-expect-error - hasError is not part of the original type but added for custom behavior
             const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError: hasError }) : '';
📝 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
...( props?.classNames ? props.classNames : {} ),
control: (currentProps) => {
const userControl = props.classNames?.control;
// @ts-ignore
const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError: hasError }) : '';
if (hasError && currentProps.isFocused) {
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}
if (isFocused) {
return '!border-2 !border-primary-500';
if (currentProps.isFocused) {
return twMerge( '!border !ring-primary-500 !ring-1', userControlStyles );
}
if (hasError) {
return '!border !border-danger-500';
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}
return '!border-gray-200';
return twMerge( '!border-gray-200', userControlStyles );
},
...(props?.classNames ? props.classNames : {}),
control: (currentProps) => {
const userControl = props.classNames?.control;
// @ts-expect-error - hasError is not part of the original type but added for custom behavior
const userControlStyles = typeof userControl === 'function'
? userControl({ ...currentProps, hasError: hasError })
: '';
if (hasError && currentProps.isFocused) {
return twMerge('!border !ring-danger-500 !ring-1', userControlStyles);
}
if (currentProps.isFocused) {
return twMerge('!border !ring-primary-500 !ring-1', userControlStyles);
}
if (hasError) {
return twMerge('!border !ring-danger-500 !ring-1', userControlStyles);
}
return twMerge('!border-gray-200', userControlStyles);
},
🧰 Tools
🪛 ESLint

[error] 92-92: Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free.

(@typescript-eslint/ban-ts-comment)

🤖 Prompt for AI Agents
In src/components/AsyncSearchableSelect.tsx around lines 89 to 107, replace the
existing // @ts-ignore comment immediately above the userControlStyles
assignment with // @ts-expect-error to be explicit that the type error is
expected; ensure the comment sits directly above the line where
userControlStyles is computed and, if possible, narrow the type of userControl
(or cast it to unknown/any locally) so the expect-error only suppresses the
specific known issue rather than hiding other type problems.

@getdokan getdokan deleted a comment from coderabbitai bot Sep 3, 2025
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: 7

♻️ Duplicate comments (1)
src/components/AsyncSearchableSelect.tsx (1)

90-109: Replace ts-ignore; keep extended state consciously.

Switch to @ts-expect-error with a reason so eslint passes and intent is clear when adding hasError to the user control callback input.

-            // @ts-ignore
+            // @ts-expect-error - passing hasError extends ClassNamesState for user styling
             const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError: hasError }) : '';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e6f1ee2 and 3f0e4b5.

⛔ Files ignored due to path filters (2)
  • dist/components/SearchableSelect.d.ts is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (2)
  • src/components/AsyncSearchableSelect.tsx (4 hunks)
  • src/components/SearchableSelect.tsx (3 hunks)
🧰 Additional context used
🪛 ESLint
src/components/SearchableSelect.tsx

[error] 100-100: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 120-120: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 122-122: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 134-134: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 143-143: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 153-153: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 162-162: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 174-174: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

src/components/AsyncSearchableSelect.tsx

[error] 93-93: Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free.

(@typescript-eslint/ban-ts-comment)


[error] 113-113: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🔇 Additional comments (3)
src/components/AsyncSearchableSelect.tsx (1)

110-112: LGTM: menu portal passthrough.

Passing menuPortalTarget and menuPosition through is correct and matches react-select’s API.

src/components/SearchableSelect.tsx (2)

5-5: LGTM: twMerge usage.

Importing twMerge for class merging is appropriate here.


95-96: LGTM: user components override defaults.

Spreading user components after local defaults gives consumers override control.

import { CgSpinner } from 'react-icons/cg';
import { FiChevronDown } from 'react-icons/fi';
import { GroupBase, MenuPosition } from 'react-select';
import { GroupBase, MenuPosition, components } from 'react-select';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use proper react-select types; drop unused value import.

Prefer Components, ClassNamesConfig, and StylesConfig types over typeof components. Also remove the unused value import of components.

Apply this diff:

-import { GroupBase, MenuPosition, components } from 'react-select';
+import { GroupBase, MenuPosition, Components, ClassNamesConfig, StylesConfig } from 'react-select';
📝 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
import { GroupBase, MenuPosition, components } from 'react-select';
import { GroupBase, MenuPosition, Components, ClassNamesConfig, StylesConfig } from 'react-select';
🤖 Prompt for AI Agents
In src/components/AsyncSearchableSelect.tsx around line 3, the current import
pulls in the runtime 'components' value and uses typeof components for typing;
instead remove the unused runtime import and import the proper react-select
types: Components, ClassNamesConfig, and StylesConfig (and keep GroupBase,
MenuPosition). Update any type references that used typeof components to use the
Components generic type (e.g., Components<OptionType, IsMulti, GroupType>), and
replace any classNames/styles type usages with ClassNamesConfig and StylesConfig
respectively so the file uses proper react-select types rather than typeof
components.

Comment on lines +23 to 24
components?: typeof components;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Type the components prop as Partial<Components<...>>.

This aligns with react-select’s Components typing and enables proper generics.

-  components?: typeof components;
+  components?: Partial<Components<Option, IsMulti, Group>>;
📝 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
components?: typeof components;
};
components?: Partial<Components<Option, IsMulti, Group>>;
};
🤖 Prompt for AI Agents
In src/components/AsyncSearchableSelect.tsx around lines 23 to 24, replace the
loose typeof components typing with react-select's Components generic: change
components?: typeof components; to components?: Partial<Components<OptionType,
IsMulti, GroupType>> (ensure you import Components from 'react-select' and use
the same OptionType/IsMulti/GroupType generics used by the component), so the
prop matches react-select's Components typing and preserves proper generics.

Comment on lines +113 to +116
...(props?.styles ? (props.styles as any) : {}),
control: (base, state) => {
const userControl = props.styles?.control;
const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : {};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove any from styles and type control slot correctly.

Use StylesConfig generics and handle function/object forms safely.

-        styles={{
-          ...(props?.styles ? (props.styles as any) : {}),
+        styles={{
+          ...(props?.styles ? (props.styles as StylesConfig<Option, IsMulti, Group>) : {}),
           control: (base, state) => {
-            const userControl = props.styles?.control;
-            const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : {};
+            const userControl = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.control;
+            const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : (userControl ?? {});
📝 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
...(props?.styles ? (props.styles as any) : {}),
control: (base, state) => {
const userControl = props.styles?.control;
const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : {};
...(props?.styles ? (props.styles as StylesConfig<Option, IsMulti, Group>) : {}),
control: (base, state) => {
const userControl = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.control;
const userControlStyles = typeof userControl === 'function'
? userControl(base, state)
: (userControl ?? {});
🧰 Tools
🪛 ESLint

[error] 113-113: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
In src/components/AsyncSearchableSelect.tsx around lines 113–116, the code casts
props.styles to any and treats the control slot loosely; replace the any by
typing props.styles with react-select's StylesConfig generics (e.g.
StylesConfig<Option, IsMulti, Group>) on the component props, and type the
control slot as either a StylesConfig control function or style object. When
composing styles, detect if props.styles.control is a function then call it with
(base, state) and merge its return with your overrides, otherwise merge the
object directly; remove the (props.styles as any) cast and ensure the control
parameter types match react-select's ControlProps/state types so the
function/object forms are handled safely.

@@ -1,7 +1,8 @@
import Select, { GroupBase, MenuPosition, Props } from 'react-select';
import Select, { GroupBase, MenuPosition, Props, components } from 'react-select';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Import react-select types instead of the value components.

Use Components, ClassNamesConfig, and StylesConfig; the value import components isn’t needed for typing.

-import Select, { GroupBase, MenuPosition, Props, components } from 'react-select';
+import Select, { GroupBase, MenuPosition, Props, Components, ClassNamesConfig, StylesConfig } from 'react-select';
📝 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
import Select, { GroupBase, MenuPosition, Props, components } from 'react-select';
import Select, { GroupBase, MenuPosition, Props, Components, ClassNamesConfig, StylesConfig } from 'react-select';
🤖 Prompt for AI Agents
In src/components/SearchableSelect.tsx at line 1, the module currently imports
the runtime value `components` from 'react-select' but only needs type
definitions; replace that import with type imports for Components,
ClassNamesConfig, and StylesConfig (and any other needed react-select types) and
remove the runtime `components` import to avoid bundling the value; use `import
type { Components, ClassNamesConfig, StylesConfig, GroupBase, MenuPosition,
Props } from 'react-select'` (or equivalent) and update any type annotations to
reference these types.

Comment on lines +21 to 22
components?: typeof components;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Type the components prop as Partial<Components<...>>.

Aligns with react-select’s public API and fixes downstream type inference.

-  components?: typeof components;
+  components?: Partial<Components<Option, IsMulti, Group>>;
🤖 Prompt for AI Agents
In src/components/SearchableSelect.tsx around lines 21-22, the components prop
is currently typed incorrectly as typeof components; change it to be typed as
Partial of react-select's Components type using the same generics as the select
(i.e., the option type, isMulti flag and group type) so it matches
react-select's public API; also import Components from 'react-select' if not
already imported and ensure the prop type uses those generics to preserve
downstream type inference.

Comment on lines +98 to 116
...(props?.classNames ? props.classNames : {}),
control: (currentProps) => {
const userControl = props.classNames?.control as any;
const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError }) : '';

if (hasError && currentProps.isFocused) {
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}

if (isFocused) {
return '!border-2 !border-primary-500';
if (currentProps.isFocused) {
return twMerge( '!border !ring-primary-500 !ring-1', userControlStyles );
}

if (hasError) {
return '!border !border-danger-500';
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}

return '!border-gray-200';
return twMerge( '!border-gray-200', userControlStyles );
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove any in classNames control; use ts-expect-error for extended state.

Type userControl with ClassNamesConfig and use @ts-expect-error when passing hasError.

         classNames={{
           ...(props?.classNames ? props.classNames : {}),
           control: (currentProps) => {
-            const userControl = props.classNames?.control as any;
-            const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError }) : '';
+            const userControl = props.classNames?.control as ClassNamesConfig<Option, IsMulti, Group>['control'] | undefined;
+            // @ts-expect-error - we intentionally pass hasError to extend ClassNamesState for user styling
+            const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError }) : '';
 
             if (hasError && currentProps.isFocused) {
               return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
             }
 
             if (currentProps.isFocused) {
               return twMerge( '!border !ring-primary-500 !ring-1', userControlStyles );
             }
 
             if (hasError) {
               return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
             }
 
             return twMerge( '!border-gray-200', userControlStyles );
           },
         }}
📝 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
...(props?.classNames ? props.classNames : {}),
control: (currentProps) => {
const userControl = props.classNames?.control as any;
const userControlStyles = typeof userControl === 'function' ? userControl({ ...currentProps, hasError }) : '';
if (hasError && currentProps.isFocused) {
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}
if (isFocused) {
return '!border-2 !border-primary-500';
if (currentProps.isFocused) {
return twMerge( '!border !ring-primary-500 !ring-1', userControlStyles );
}
if (hasError) {
return '!border !border-danger-500';
return twMerge( '!border !ring-danger-500 !ring-1', userControlStyles );
}
return '!border-gray-200';
return twMerge( '!border-gray-200', userControlStyles );
},
...(props?.classNames ? props.classNames : {}),
control: (currentProps) => {
- const userControl = props.classNames?.control as any;
const userControl = props.classNames?.control as ClassNamesConfig<Option, IsMulti, Group>['control'] | undefined;
// @ts-expect-error - we intentionally pass hasError to extend ClassNamesState for user styling
const userControlStyles = typeof userControl === 'function'
? userControl({ ...currentProps, hasError })
: '';
if (hasError && currentProps.isFocused) {
return twMerge('!border !ring-danger-500 !ring-1', userControlStyles);
}
if (currentProps.isFocused) {
return twMerge('!border !ring-primary-500 !ring-1', userControlStyles);
}
if (hasError) {
return twMerge('!border !ring-danger-500 !ring-1', userControlStyles);
}
return twMerge('!border-gray-200', userControlStyles);
},
🧰 Tools
🪛 ESLint

[error] 100-100: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
In src/components/SearchableSelect.tsx around lines 98 to 116, the current code
types userControl as any and uses unknown "any" return handling; instead type
userControl using the proper ClassNamesConfig control type and avoid using any
in classNames control, and when calling the userControl function with the
extended state that includes hasError add a single-line @ts-expect-error above
that call to silence the type mismatch; keep using typeof userControl ===
'function' to decide invocation, pass { ...currentProps, hasError } to it, and
retain twMerge usage for combining returned styles.

Comment on lines +119 to +181
// Pass through any container-level custom styles if provided
...(props?.styles ? (props.styles as any) : {}),
control: (base, state) => {
const userControl = props.styles?.control as any;
const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : {};
return {
...base,
minHeight: '40px',
boxShadow: 'none',
borderRadius: '5px',
fontSize: '14px',
...userControlStyles,
};
},
option: (base, state) => {
const userOption = props.styles?.option as any;
const userOptionStyles = typeof userOption === 'function' ? userOption(base, state) : {};
return {
...base,
fontSize: '0.875rem',
...userOptionStyles,
};
},
multiValue: (base, state) => {
const userMultiValue = props.styles?.multiValue as any;
const userMultiValueStyles = typeof userMultiValue === 'function' ? userMultiValue(base, state) : {};
return {
...base,
background: 'var(--colors-primary-50)',
borderRadius: '0.25rem',
...userMultiValueStyles,
};
},
multiValueLabel: (base, state) => {
const userMultiValueLabel = props.styles?.multiValueLabel as any;
const userMultiValueLabelStyles = typeof userMultiValueLabel === 'function' ? userMultiValueLabel(base, state) : {};
return {
...base,
color: 'var(--colors-primary-600)',
...userMultiValueLabelStyles,
};
},
multiValueRemove: (base, state) => {
const userMultiValueRemove = props.styles?.multiValueRemove as any;
const userMultiValueRemoveStyles = typeof userMultiValueRemove === 'function' ? userMultiValueRemove(base, state) : {};
return {
...base,
color: 'var(--colors-primary-600)',
':hover': {
background: 'var(--colors-primary-100)',
},
...userMultiValueRemoveStyles,
};
},
option: (base) => ({
...base,
fontSize: '0.875rem',
}),
multiValue: (base) => ({
...base,
background: 'var(--colors-primary-50)',
borderRadius: '0.25rem',
}),
multiValueLabel: (base) => ({
...base,
color: 'var(--colors-primary-600)',
}),
multiValueRemove: (base) => ({
...base,
color: 'var(--colors-primary-600)',
':hover': {
background: 'var(--colors-primary-100)',
},
}),
menuPortal: (base) => ({
...base,
zIndex: 9999,
}),
menuPortal: (base, state) => {
const userMenuPortal = props.styles?.menuPortal as any;
const userMenuPortalStyles = typeof userMenuPortal === 'function' ? userMenuPortal(base, state) : {};
return {
...base,
zIndex: 9999,
...userMenuPortalStyles,
};
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Eliminate explicit any in styles; use StylesConfig across slots.

Type-safe merge handles both function and object user styles.

         styles={{
-          // Pass through any container-level custom styles if provided
-          ...(props?.styles ? (props.styles as any) : {}),
+          // Pass through any container-level custom styles if provided
+          ...(props?.styles ? (props.styles as StylesConfig<Option, IsMulti, Group>) : {}),
           control: (base, state) => {
-            const userControl = props.styles?.control as any;
-            const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : {};
+            const userControl = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.control;
+            const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : (userControl ?? {});
             return {
               ...base,
               minHeight: '40px',
               boxShadow: 'none',
               borderRadius: '5px',
               fontSize: '14px',
               ...userControlStyles,
             };
           },
           option: (base, state) => {
-            const userOption = props.styles?.option as any;
-            const userOptionStyles = typeof userOption === 'function' ? userOption(base, state) : {};
+            const userOption = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.option;
+            const userOptionStyles = typeof userOption === 'function' ? userOption(base, state) : (userOption ?? {});
             return {
               ...base,
               fontSize: '0.875rem',
               ...userOptionStyles,
             };
           },
           multiValue: (base, state) => {
-            const userMultiValue = props.styles?.multiValue as any;
-            const userMultiValueStyles = typeof userMultiValue === 'function' ? userMultiValue(base, state) : {};
+            const userMultiValue = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.multiValue;
+            const userMultiValueStyles = typeof userMultiValue === 'function' ? userMultiValue(base, state) : (userMultiValue ?? {});
             return {
               ...base,
               background: 'var(--colors-primary-50)',
               borderRadius: '0.25rem',
               ...userMultiValueStyles,
             };
           },
           multiValueLabel: (base, state) => {
-            const userMultiValueLabel = props.styles?.multiValueLabel as any;
-            const userMultiValueLabelStyles = typeof userMultiValueLabel === 'function' ? userMultiValueLabel(base, state) : {};
+            const userMultiValueLabel = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.multiValueLabel;
+            const userMultiValueLabelStyles = typeof userMultiValueLabel === 'function' ? userMultiValueLabel(base, state) : (userMultiValueLabel ?? {});
             return {
               ...base,
               color: 'var(--colors-primary-600)',
               ...userMultiValueLabelStyles,
             };
           },
           multiValueRemove: (base, state) => {
-            const userMultiValueRemove = props.styles?.multiValueRemove as any;
-            const userMultiValueRemoveStyles = typeof userMultiValueRemove === 'function' ? userMultiValueRemove(base, state) : {};
+            const userMultiValueRemove = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.multiValueRemove;
+            const userMultiValueRemoveStyles = typeof userMultiValueRemove === 'function' ? userMultiValueRemove(base, state) : (userMultiValueRemove ?? {});
             return {
               ...base,
               color: 'var(--colors-primary-600)',
               ':hover': {
                 background: 'var(--colors-primary-100)',
               },
               ...userMultiValueRemoveStyles,
             };
           },
           menuPortal: (base, state) => {
-            const userMenuPortal = props.styles?.menuPortal as any;
-            const userMenuPortalStyles = typeof userMenuPortal === 'function' ? userMenuPortal(base, state) : {};
+            const userMenuPortal = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.menuPortal;
+            const userMenuPortalStyles = typeof userMenuPortal === 'function' ? userMenuPortal(base, state) : (userMenuPortal ?? {});
             return {
               ...base,
               zIndex: 9999,
               ...userMenuPortalStyles,
             };
           }
         }}
📝 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
// Pass through any container-level custom styles if provided
...(props?.styles ? (props.styles as any) : {}),
control: (base, state) => {
const userControl = props.styles?.control as any;
const userControlStyles = typeof userControl === 'function' ? userControl(base, state) : {};
return {
...base,
minHeight: '40px',
boxShadow: 'none',
borderRadius: '5px',
fontSize: '14px',
...userControlStyles,
};
},
option: (base, state) => {
const userOption = props.styles?.option as any;
const userOptionStyles = typeof userOption === 'function' ? userOption(base, state) : {};
return {
...base,
fontSize: '0.875rem',
...userOptionStyles,
};
},
multiValue: (base, state) => {
const userMultiValue = props.styles?.multiValue as any;
const userMultiValueStyles = typeof userMultiValue === 'function' ? userMultiValue(base, state) : {};
return {
...base,
background: 'var(--colors-primary-50)',
borderRadius: '0.25rem',
...userMultiValueStyles,
};
},
multiValueLabel: (base, state) => {
const userMultiValueLabel = props.styles?.multiValueLabel as any;
const userMultiValueLabelStyles = typeof userMultiValueLabel === 'function' ? userMultiValueLabel(base, state) : {};
return {
...base,
color: 'var(--colors-primary-600)',
...userMultiValueLabelStyles,
};
},
multiValueRemove: (base, state) => {
const userMultiValueRemove = props.styles?.multiValueRemove as any;
const userMultiValueRemoveStyles = typeof userMultiValueRemove === 'function' ? userMultiValueRemove(base, state) : {};
return {
...base,
color: 'var(--colors-primary-600)',
':hover': {
background: 'var(--colors-primary-100)',
},
...userMultiValueRemoveStyles,
};
},
option: (base) => ({
...base,
fontSize: '0.875rem',
}),
multiValue: (base) => ({
...base,
background: 'var(--colors-primary-50)',
borderRadius: '0.25rem',
}),
multiValueLabel: (base) => ({
...base,
color: 'var(--colors-primary-600)',
}),
multiValueRemove: (base) => ({
...base,
color: 'var(--colors-primary-600)',
':hover': {
background: 'var(--colors-primary-100)',
},
}),
menuPortal: (base) => ({
...base,
zIndex: 9999,
}),
menuPortal: (base, state) => {
const userMenuPortal = props.styles?.menuPortal as any;
const userMenuPortalStyles = typeof userMenuPortal === 'function' ? userMenuPortal(base, state) : {};
return {
...base,
zIndex: 9999,
...userMenuPortalStyles,
};
}
styles={{
// Pass through any container-level custom styles if provided
...(props?.styles ? (props.styles as StylesConfig<Option, IsMulti, Group>) : {}),
control: (base, state) => {
const userControl = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.control;
const userControlStyles = typeof userControl === 'function'
? userControl(base, state)
: (userControl ?? {});
return {
...base,
minHeight: '40px',
boxShadow: 'none',
borderRadius: '5px',
fontSize: '14px',
...userControlStyles,
};
},
option: (base, state) => {
const userOption = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.option;
const userOptionStyles = typeof userOption === 'function'
? userOption(base, state)
: (userOption ?? {});
return {
...base,
fontSize: '0.875rem',
...userOptionStyles,
};
},
multiValue: (base, state) => {
const userMultiValue = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.multiValue;
const userMultiValueStyles = typeof userMultiValue === 'function'
? userMultiValue(base, state)
: (userMultiValue ?? {});
return {
...base,
background: 'var(--colors-primary-50)',
borderRadius: '0.25rem',
...userMultiValueStyles,
};
},
multiValueLabel: (base, state) => {
const userMultiValueLabel = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.multiValueLabel;
const userMultiValueLabelStyles = typeof userMultiValueLabel === 'function'
? userMultiValueLabel(base, state)
: (userMultiValueLabel ?? {});
return {
...base,
color: 'var(--colors-primary-600)',
...userMultiValueLabelStyles,
};
},
multiValueRemove: (base, state) => {
const userMultiValueRemove = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.multiValueRemove;
const userMultiValueRemoveStyles = typeof userMultiValueRemove === 'function'
? userMultiValueRemove(base, state)
: (userMultiValueRemove ?? {});
return {
...base,
color: 'var(--colors-primary-600)',
':hover': {
background: 'var(--colors-primary-100)',
},
...userMultiValueRemoveStyles,
};
},
menuPortal: (base, state) => {
const userMenuPortal = (props.styles as StylesConfig<Option, IsMulti, Group> | undefined)?.menuPortal;
const userMenuPortalStyles = typeof userMenuPortal === 'function'
? userMenuPortal(base, state)
: (userMenuPortal ?? {});
return {
...base,
zIndex: 9999,
...userMenuPortalStyles,
};
}
}}
🧰 Tools
🪛 ESLint

[error] 120-120: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 122-122: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 134-134: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 143-143: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 153-153: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 162-162: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 174-174: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
In src/components/SearchableSelect.tsx around lines 119 to 181, the code uses
explicit any for user style slots; replace those anys with react-select's typed
StylesConfig and CSSObject types and type the props.styles accordingly (e.g.
import { StylesConfig, CSSObject } from 'react-select' and treat props.styles as
StylesConfig<OptionType, IsMulti>), then for each slot cast the slot from
props.styles to the correct union type (function or object) and handle both
cases in a type-safe way (if typeof slot === 'function' call it with (base,
state) to get a CSSObject, otherwise use the object directly), and update the
slot function signatures to return CSSObject so the merged style objects are
fully type-checked instead of using any.

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: 1

🧹 Nitpick comments (4)
src/components/FormSelect.tsx (4)

16-17: Good addition: portalTarget prop enables scoped portals

Enables styling within a scoped root. Consider aligning the prop name with similar components (e.g., menuPortalTarget used in Async/Searchable Select) to keep API consistent across your Select variants.


61-63: Use a stable key

Avoid index keys to prevent reconciliation issues when options change order. Use the option value instead.

-                  key={index}
+                  key={String(option.value)}

20-27: Preserve original value type in onChange

Radix Select emits strings. Your onChange API advertises string | number but currently forwards only strings. Map back to the original option to keep numbers as numbers.

-      onValueChange={(value) => {
-        onChange?.(value);
-      }}
+      onValueChange={(next) => {
+        const match = options.find((o) => String(o.value) === next);
+        onChange?.(match ? match.value : next);
+      }}

19-19: Prefer named export to match repo conventions

Guidelines specify named exports for components. If feasible, migrate to a named export to keep APIs consistent.

-export default function FormSelect(props: FormSelectProps) {
+export function FormSelect(props: FormSelectProps) {

Note: re-export/update imports where necessary.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f0e4b5 and 8f6576c.

⛔ Files ignored due to path filters (2)
  • dist/components/FormSelect.d.ts is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (1)
  • src/components/FormSelect.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/{components,utils}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/{components,utils}/**/*.{ts,tsx}: Use TypeScript for all components and utilities
Use named exports for components and utilities

Files:

  • src/components/FormSelect.tsx
src/components/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/components/**/*.tsx: Use functional React components and hooks for all components
Use Tailwind CSS utility classes for component styling
Define and export TypeScript prop types/interfaces for each component

Files:

  • src/components/FormSelect.tsx
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{ts,tsx}: Use @ as an alias for src/ in imports
Use debounceSearch for debounced async search interactions

Files:

  • src/components/FormSelect.tsx
src/components/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place new components at src/components/ComponentName.tsx

Files:

  • src/components/FormSelect.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: getdokan/dokan-ui#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-04T09:20:19.197Z
Learning: Use React 18+ features (update for React 19 as needed)

</Select.Trigger>

<Select.Portal>
<Select.Portal container={props?.portalTarget || document.body}>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid document.body default: SSR-unsafe and breaks scoped Tailwind (.dokan-layout)

  • Directly referencing document.body during render can throw on SSR.
  • With Tailwind important: '.dokan-layout', portaling to body causes utilities to miss the .dokan-layout scope, producing unstyled menus unless consumers remember to pass portalTarget.

Refactor to compute a safe default that prefers the nearest .dokan-layout when available, otherwise let Radix default to body.

Apply within this range:

-      <Select.Portal container={props?.portalTarget || document.body}>
+      <Select.Portal container={portalContainer}>

Add inside the component before the return (outside this hunk):

// Prefer consumer-provided target; otherwise, default to the scoped root if present.
// Falls back to undefined on SSR so Radix defaults safely.
let portalContainer: HTMLElement | undefined = props.portalTarget;
if (!portalContainer && typeof window !== 'undefined') {
  const root = document.querySelector('.dokan-layout');
  portalContainer = root instanceof HTMLElement ? root : document.body;
}
🤖 Prompt for AI Agents
In src/components/FormSelect.tsx around line 45, avoid referencing document.body
directly in the JSX; instead, before the component return (outside the shown
hunk) add logic to compute a safe portalContainer that prefers
props.portalTarget, otherwise uses the nearest .dokan-layout when running in the
browser and falls back to document.body only on client (leaving undefined during
SSR). Then update the Select.Portal container prop to use this portalContainer
(so on SSR it passes undefined and Radix defaults safely, and on client it uses
the scoped root when present).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE Requires help from the design team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants