Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add wds multiselect component #39300

Merged
merged 21 commits into from
Feb 18, 2025
Merged

Conversation

jsartisan
Copy link
Contributor

@jsartisan jsartisan commented Feb 15, 2025

WDS Multi-Select component

CleanShot.2025-02-17.at.13.10.38.mp4

Note: This is a kind of hacky multi-select component. React aria does not provide us with any multi select component. So we are making it with existing components like popover, listbox, and autocomplete.

/ok-to-test tags="@tag.Anvil"

Summary by CodeRabbit

  • New Features

    • Introduced a new MultiSelect component that offers a dynamic multi-select interface with responsive display, clear error messaging, and loading states.
    • Added a new MultiSelectValue component to enhance the display of selected items in the MultiSelect interface.
  • Enhancements

    • Improved styling flexibility across key components—including ListBox, ListBoxItem, TextField, and Select—to support custom appearances.
    • Refined Calendar and DatePicker interactions for a smoother, more reliable user experience.
    • Added new export statements for selectStyles and fieldErrorStyles to improve modularity and accessibility.
    • Updated the version of the react-aria-components dependency for enhanced performance and features.

Tip

🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/13386938922
Commit: 9d486cb
Cypress dashboard.
Tags: @tag.Anvil
Spec:


Tue, 18 Feb 2025 09:33:19 UTC

@jsartisan jsartisan requested a review from KelvinOm as a code owner February 15, 2025 10:31
Copy link
Contributor

coderabbitai bot commented Feb 15, 2025

Walkthrough

The changes update package metadata and introduce minor style and export adjustments across multiple components. A dependency version is updated in a package file. Enhancements include adding className props with conditional class name composition using clsx in ListBox, ListBoxItem, and TextField components. A new MultiSelect component is introduced along with its supporting components, types, styles, and Storybook stories. Additionally, the Menu, Calendar, and Datepicker components are modified to improve styling references and safeguard event handling against null values.

Changes

File(s) Change Summary
app/client/package.json
app/client/packages/design-system/widgets/package.json
Added a newline in one file; updated dependency "react-aria-components" version from ^1.2.1 to ^1.6.0.
app/client/packages/design-system/widgets/src/components/FieldError/src/index.ts
app/client/packages/design-system/widgets/src/components/ListBox/src/index.ts
app/client/packages/design-system/widgets/src/components/Select/src/index.ts
app/client/packages/design-system/widgets/src/index.ts
Export updates: added exports (fieldErrorStyles, selectStyles, re-export of MultiSelect) and renamed export from listStyles to listBoxStyles.
app/client/packages/design-system/widgets/src/components/ListBox/src/ListBox.tsx
app/client/packages/design-system/widgets/src/components/ListBoxItem/src/ListBoxItem.tsx
app/client/packages/design-system/widgets/src/components/TextField/src/TextField.tsx
Enhanced components: introduced className prop and integrated clsx for conditional class name composition.
app/client/packages/design-system/widgets/src/components/MultiSelect/* Introduced MultiSelect component: added new components (MultiSelect, MultiSelectValue), associated types, styles, and Storybook stories.
app/client/packages/design-system/widgets/src/components/Menu/src/Menu.tsx Updated Menu: renamed styling reference from listStyles to listBoxStyles and removed MenuNestingContext logic.
app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts Minor adjustment: reordered import statement for ListBoxItemProps.
app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx
app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx
app/client/src/modules/ui-builder/ui/wds/WDSDatePickerWidget/widget/index.tsx
Modified date-related components: added null checks for state and updated onChange handlers for non-null value enforcement.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MultiSelect
    participant ListBox
    User->>MultiSelect: Press ArrowDown/ArrowUp
    MultiSelect->>MultiSelect: Open popover
    MultiSelect->>ListBox: Render options
    User->>ListBox: Select option
    ListBox->>MultiSelect: Update selection
Loading

Suggested labels

Enhancement, IDE Product, Task, IDE Pod

Suggested reviewers

  • albinAppsmith
  • ankitakinger
  • KelvinOm

Poem

In lines of code we weave our art,
New widgets come to life and start.
MultiSelect shines with stories bold,
Class names and styles subtly told.
Code dances in a realm so bright—
Cheers to changes that feel just right!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the skip-changelog Adding this label to a PR prevents it from being listed in the changelog label Feb 15, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (11)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (2)

77-81: Consider Handling Additional Keyboard Navigation

While opening the popover on arrow key presses is helpful, you might consider handling navigation focus (e.g., focusing the first or last item in the list) for a more complete multi-select keyboard experience.


169-173: Hide Error Text When Not Applicable

Currently, the error message container is rendered regardless of the content. For a cleaner UI, consider conditionally rendering this block only if errorMessage has content.

 <div className={fieldErrorStyles.errorText}>
-  <Text color="negative" size="caption">
-    {errorMessage}
-  </Text>
+  {errorMessage && (
+    <Text color="negative" size="caption">
+      {errorMessage}
+    </Text>
+  )}
 </div>
app/client/packages/design-system/widgets/src/components/MultiSelect/src/types.ts (1)

18-18: Consider using explicit size values.

Instead of using Exclude with SIZES, consider defining the exact allowed values for better type clarity.

-  size?: Exclude<keyof typeof SIZES, "xSmall" | "large">;
+  size?: "small" | "medium";
app/client/packages/design-system/widgets/src/components/TextField/src/TextField.tsx (1)

46-46: Document the purpose of data-input attribute.

Add a comment explaining the purpose and usage of this data attribute for future maintainers.

app/client/packages/design-system/widgets/src/components/Select/src/Select.tsx (1)

48-48: Simplify boolean logic.

The Boolean() wrapper is redundant as the OR operator already coerces values to boolean.

-            isDisabled={Boolean(isDisabled) || Boolean(isLoading)}
+            isDisabled={isDisabled || isLoading}
app/client/packages/design-system/widgets/src/components/MultiSelect/stories/MultiSelect.stories.tsx (2)

8-11: Consider sharing test data with Select stories.

The items array could be moved to a shared test data file, similar to selectData.ts used in Select stories.


38-41: Consider extracting size filtering logic.

This filtering logic is duplicated from Select stories. Consider extracting it to a shared utility function.

// utils/storybook.ts
export const getSupportedSizes = () => 
  Object.keys(SIZES).filter(
    (size): size is NonNullable<SelectProps["size"]> =>
      !["xSmall", "large"].includes(size)
  );
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (3)

34-38: Consider memoizing the Array.from conversion.

The items array is converted in every render. Consider memoizing the array conversion for better performance.

  const selectedItems = useMemo(() => {
+   const itemsArray = items ? Array.from(items) : [];
    return [...selectedKeys].map((key) =>
-     items ? Array.from(items).find((item) => item.value === key) : undefined,
+     itemsArray.find((item) => item.value === key),
    );
  }, [items, selectedKeys]);

123-123: Remove redundant Boolean calls.

The Boolean calls are unnecessary as the values will be coerced to boolean.

-  data-invalid={Boolean(isInvalid) ? "" : undefined}
+  data-invalid={isInvalid ? "" : undefined}

-  {Boolean(isLoading) ? (
+  {isLoading ? (

Also applies to: 149-149

🧰 Tools
🪛 Biome (1.9.4)

[error] 123-123: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


144-146: Consider accessibility for overflow indicator.

The overflow indicator should have an ARIA label for screen readers.

-  <Text style={{ width: "7ch" }}>
+  <Text aria-label={`and ${totalItems - visibleItems} more items`} style={{ width: "7ch" }}>
     {visibleItems < totalItems && <>...+{totalItems - visibleItems}</>}
   </Text>
app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css (1)

31-37: Consider adding a focus state for the checkbox.

The checkbox should have a visible focus state for keyboard navigation.

.listBoxItem[data-selected]::before {
  background-color: var(--color-bg-accent);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22white%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M5%2012l5%205l10%20-10%22%20%2F%3E%20%3C%2Fsvg%3E%20");
  background-size: var(--sizing-5);
  background-position: center;
  background-repeat: no-repeat;
  --checkbox-border-color: var(--color-bd-accent);
+  outline: var(--border-width-1) solid var(--color-bd-accent);
+  outline-offset: var(--border-width-1);
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 007f598 and b6735c4.

⛔ Files ignored due to path filters (1)
  • app/client/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (23)
  • app/client/package.json (1 hunks)
  • app/client/packages/design-system/widgets/package.json (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Button/src/Button.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/FieldError/src/index.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Input/src/Input.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/ListBox/src/ListBox.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/ListBox/src/index.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/ListBox/src/styles.module.css (1 hunks)
  • app/client/packages/design-system/widgets/src/components/ListBoxItem/src/ListBoxItem.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Menu/src/Menu.tsx (2 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/index.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/index.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/types.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/stories/MultiSelect.stories.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Select/src/Select.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Select/src/index.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Select/stories/Select.stories.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/TextField/src/TextField.tsx (3 hunks)
  • app/client/packages/design-system/widgets/src/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (9)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/index.ts
  • app/client/packages/design-system/widgets/src/components/ListBox/src/index.ts
  • app/client/packages/design-system/widgets/src/components/ListBox/src/styles.module.css
  • app/client/packages/design-system/widgets/src/components/Input/src/Input.tsx
  • app/client/package.json
  • app/client/packages/design-system/widgets/src/components/Select/src/index.ts
  • app/client/packages/design-system/widgets/src/components/Button/src/Button.tsx
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/index.ts
  • app/client/packages/design-system/widgets/src/components/Menu/src/Menu.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx

[error] 123-123: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 149-149: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-build / client-build
  • GitHub Check: client-prettier / prettier-check
  • GitHub Check: chromatic-deployment
  • GitHub Check: storybook-tests
  • GitHub Check: chromatic-deployment
🔇 Additional comments (11)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (3)

1-21: Imports and Dependencies Look Good

The set of imports and dependencies is well managed, bringing in essential react-aria elements, custom styles, and utility libraries. No conflicts or unused imports are evident.


32-38: EmptyState Component Implementation

This component is straightforward and effective for displaying a default message when no options are found. Its minimal design and concise structure are fine as-is.


92-112: Label Click Focus Handling

Good job replicating the label-to-control focus behavior manually. This ensures strong accessibility compliance in lieu of a specialized multi-select hook.

app/client/packages/design-system/widgets/src/components/FieldError/src/index.ts (1)

3-3: New Styles Export Approved

Exporting fieldErrorStyles allows for consistency across form error displays. No issues found.

app/client/packages/design-system/widgets/src/components/ListBox/src/ListBox.tsx (1)

6-16: Enhanced Styling Flexibility

Introducing clsx and exposing className enables consumers to merge custom styles with the default listBox styles. The approach is succinct and correct.

app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts (1)

1-12: LGTM! Well-structured type definition with clear documentation.

The prefix prop is appropriately typed as ReactNode and well-documented with its intended use case for MultiSelect.

app/client/packages/design-system/widgets/src/components/ListBoxItem/src/ListBoxItem.tsx (1)

7-21: LGTM! Clean implementation of className handling.

The use of clsx for combining classNames follows best practices, maintaining existing styles while allowing for customization.

app/client/packages/design-system/widgets/src/index.ts (1)

38-38: LGTM!

The MultiSelect export follows the established pattern and is appropriately placed with other form components.

app/client/packages/design-system/widgets/src/components/Select/stories/Select.stories.tsx (1)

90-96: LGTM!

The reformatting improves code readability while maintaining the same functionality.

app/client/packages/design-system/widgets/package.json (1)

29-29:

❓ Verification inconclusive

Verify react-aria-components upgrade impact.

Please verify the changelog for breaking changes between 1.2.1 and 1.6.0.


🌐 Web query:

What are the breaking changes in react-aria-components between versions 1.2.1 and 1.6.0?

💡 Result:

Here are the key breaking changes in react-aria-components between v1.2.1 and v1.6.0 based on available release notes:

Major API Changes

  1. Deprecated validationState
    Replaced with simpler isInvalid boolean prop across form components[4][7]

  2. Client-side Routing Changes
    Introduced new RouterProvider component for router integration[4]. Existing routing implementations may need migration.

  3. Collection Component Props
    Moved isDisabled/onAction props from root components to individual collection items[7].
    Example migration:

    - <Menu onAction={handler} isDisabled>
    + <MenuItem isDisabled onAction={handler}>

Component Behavior Changes

  1. Strict Mode Compatibility
    Now requires React 18+ and uses native React.useId instead of polyfilled ID generation[4]

  2. Selection Behavior
    GridList selection handling updated - custom checkboxes now require explicit slot="selection" assignment[1][5]

  3. Accessibility Requirements
    Added mandatory aria-label/textValue props to several components including GridList/Tree[1][5]

CSS/Theme Breaking Changes

  1. Link Styling Reset
    Default link styling removed - must be explicitly defined in themes[2]

  2. GridList Layout
    Requires additional CSS for proper rendering[2]:

    .react-aria-GridList {
      display: grid;
      gap: 8px;
    }

Removed Features

  1. Legacy Color Components
    Old color picker APIs removed in favor of new ColorPicker/ColorArea components[7]

  2. Deprecated Drag & Drop API
    Original implementation replaced with new hooks-based DnD system[4]

Always check upgrade guides and test visual regressions when updating between major versions of react-aria-components[3][8]. The library maintains detailed changelogs with migration instructions for each release[4][7].

Citations:


React-Aria-Components Upgrade Review

We've confirmed that upgrading from version 1.2.1 to 1.6.0 introduces several breaking changes, notably:

  • Deprecated Prop: The old validationState prop has been removed in favor of a simpler isInvalid boolean.
  • Routing Update: A new RouterProvider component has been introduced, which may require migration of existing routing implementations.
  • Component Prop Adjustments: Props like isDisabled and onAction for collection components have shifted from the root to individual items.
  • React Compatibility: The upgrade now mandates React 18+ due to its switch from polyfilled ID generation to native React.useId.
  • Behavior & Accessibility: Updates include modified selection behavior (e.g., explicit assignment for custom checkboxes) and stricter accessibility requirements, such as mandatory aria-label/textValue props.
  • CSS/Theme Changes: There are changes like the removal of default link styling and layout adjustments for components like GridList.

Action Items:

  • Verify that all usages in our codebase accommodate these updated APIs.
  • Update any references to deprecated properties or components accordingly.
  • Run integration tests to ensure that routing, collection components, and UI rendering behave as expected.
app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css (1)

67-70: LGTM: Good use of flex properties.

The flex properties ensure proper text truncation and prevent layout issues.

@jsartisan jsartisan marked this pull request as draft February 15, 2025 10:50
@@ -26,7 +26,7 @@
"@tabler/icons-react": "^3.10.0",
"clsx": "^2.0.0",
"lodash": "*",
"react-aria-components": "^1.2.1",
"react-aria-components": "^1.6.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need 1.6.0 version as it has the AutoComplete component that we need in multi select dropdown for filtering list

@jsartisan jsartisan marked this pull request as ready for review February 17, 2025 07:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx (1)

18-18: 💡 Verification agent

🧩 Analysis chain

Verify CalendarStateContext usage.

Let's confirm if the context is guaranteed to provide a non-null state value.


🌐 Web query:

Does react-aria-components CalendarStateContext guarantee a non-null state value? What's the recommended way to handle the state from CalendarStateContext?

💡 Result:

The React Aria Components CalendarStateContext does not inherently guarantee a non-null state value by default, but its usage patterns suggest specific handling recommendations:

  1. Non-null assertion in child components
    When accessing CalendarStateContext within calendar children (e.g., custom components inside <Calendar>), developers typically use TypeScript's non-null assertion operator (!) since these components are only valid when rendered within a parent calendar component:

    let state = React.useContext(CalendarStateContext)!;  // Common pattern [1][2]
  2. Null safety considerations
    While the context itself isn't explicitly typed as non-nullable:

    • Parent <Calendar> components initialize and manage state
    • Child components are expected to only render within valid calendar contexts
    • Attempting to set null directly via state methods will cause errors [5]
  3. Recommended handling

    function CalendarChild() {
      const state = React.useContext(CalendarStateContext);
      
      // Optional chaining for safety
      const dateValue = state?.value?.toDate(getLocalTimeZone());
      
      // Fallback handling
      if (!state) return null;
      
      // Main logic using state...
    }
  4. TypeScript best practices

    // Safe access pattern with type guards
    function CalendarValue() {
      const state = React.useContext(CalendarStateContext);
      if (!state) throw new Error('Must be used within <Calendar>');
      
      // Now safely use state properties
      const date = state.value?.toDate(getLocalTimeZone());
    }

The documentation examples demonstrate direct access via non-null assertions (!) due to their guaranteed placement within calendar hierarchies [1][2]. For reusable components, explicit type checking and error boundaries are recommended for robustness [5][9].

Citations:


Ensure safe usage of CalendarStateContext in CalendarHeading.tsx

Our investigation shows that the React Aria Components’ CalendarStateContext does not inherently guarantee a non-null state. While many patterns use the non-null assertion operator—assuming that child components are always rendered within a valid <Calendar>—this can be risky without explicit checks.

  • In app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx at line 18, the current usage is:
    const state = useContext(CalendarStateContext);
  • Recommendation:
    • If you're confident that <CalendarHeading> is always rendered within a valid <Calendar> component, consider using:
      const state = useContext(CalendarStateContext)!;
    • Alternatively, add an explicit null check and a meaningful fallback or error:
      const state = useContext(CalendarStateContext);
      if (!state) {
        throw new Error('CalendarStateContext is not available. Ensure <CalendarHeading> is used within a <Calendar> component.');
      }

This guard ensures robustness against potential null values, aligning with common best practices when working with context provided by react-aria-components.

🧹 Nitpick comments (4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (3)

16-26: Add JSDoc documentation for the interface.

Consider adding JSDoc documentation to describe the purpose and usage of each prop in the MultiSelectValueProps interface.

+/**
+ * Props for the MultiSelectValue component
+ */
 interface MultiSelectValueProps {
+  /** Whether to exclude the component from tab order */
   excludeFromTabOrder?: boolean;
+  /** Whether the component is disabled */
   isDisabled?: boolean;
+  /** Whether the component is in loading state */
   isLoading?: boolean;
+  /** Set of selected item keys */
   selectedKeys?: Selection;
+  /** Collection of selectable items */
   items?: Iterable<{ label: string; value: string }>;
+  /** Placeholder text when no items are selected */
   placeholder?: string;
+  /** Size variant of the component */
   size?: MultiSelectProps<object>["size"];
+  /** Reference to the trigger button element */
   triggerRef: React.RefObject<HTMLButtonElement>;
+  /** Whether the component is in an invalid state */
   isInvalid?: boolean;
 }

52-104: Optimize updateOverflow dependencies.

The updateOverflow callback's dependency array includes selectedKeys, but it's only using totalItems which is already calculated outside. Consider moving totalItems calculation inside the callback to maintain consistency.

-const totalItems = Array.from(selectedKeys).length;
 const updateOverflow = useCallback(() => {
+  const totalItems = Array.from(selectedKeys).length;
   // ... rest of the implementation
-}, [domRef, selectedKeys, setVisibleItems]);
+}, [domRef, setVisibleItems]);

106-120: Add error boundary for resize observer.

Consider wrapping the resize observer logic in an error boundary to gracefully handle potential DOM-related errors.

+const handleResize = () => {
+  try {
+    updateOverflow();
+  } catch (error) {
+    console.error('Failed to update overflow:', error);
+  }
+};

 useResizeObserver({
   ref: parentRef,
-  onResize: updateOverflow,
+  onResize: handleResize,
 });
app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx (1)

22-23: Consider safer alternatives to non-null assertions.

The non-null assertions (!), while making the code compile, might mask potential runtime issues if state is actually null. Consider using optional chaining or adding a null check.

-      <CalendarMonthDropdown state={state!} />
-      <CalendarYearDropdown state={state!} />
+      {state && (
+        <>
+          <CalendarMonthDropdown state={state} />
+          <CalendarYearDropdown state={state} />
+        </>
+      )}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6735c4 and 326a0e2.

📒 Files selected for processing (6)
  • app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx

[error] 129-129: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 155-155: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: chromatic-deployment
  • GitHub Check: storybook-tests
🔇 Additional comments (1)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1)

28-38: LGTM!

Clean component implementation with well-organized props and appropriate default values.

selectStyles.selectTriggerButton,
styles.multiSelectValue,
)}
data-invalid={Boolean(isInvalid) ? "" : undefined}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove redundant Boolean calls.

The static analysis tool correctly identified redundant Boolean calls. These can be simplified.

-data-invalid={Boolean(isInvalid) ? "" : undefined}
+data-invalid={isInvalid ? "" : undefined}

-{Boolean(isLoading) ? (
+{isLoading ? (

Also applies to: 155-155

🧰 Tools
🪛 Biome (1.9.4)

[error] 129-129: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

@@ -94,7 +94,7 @@ export const DatePicker = <T extends DateValue>(props: DatePickerProps<T>) => {
label="Time"
maxValue={timeMaxValue}
minValue={timeMinValue}
onChange={state.setTimeValue}
onChange={(value) => state.setTimeValue(value!)}
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid non-null assertion for TimeField value.

The non-null assertion (!) bypasses TypeScript's type checking and could lead to runtime errors if the value is null/undefined.

Consider a safer approach:

-onChange={(value) => state.setTimeValue(value!)}
+onChange={(value) => {
+  if (value) {
+    state.setTimeValue(value);
+  }
+}}
📝 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
onChange={(value) => state.setTimeValue(value!)}
onChange={(value) => {
if (value) {
state.setTimeValue(value);
}
}}

Copy link
Collaborator

Choose a reason for hiding this comment

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

It would also be good to fix this as well, as there may be issues related to this. Do you understand why value can be null?

Anyway, we could at least add a check for null

if (value) {
 state.setTimeValue(value);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@KelvinOm Yes, the value in TimeField's onChange can be null in several scenarios:

  1. When the user clears the time input
  2. During initial render without a value
  3. When an invalid time is entered

Your suggestion to add a null check is the right approach to handle these cases safely. I'll add this as a learning for future reviews.


✏️ Learnings added
Learnt from: KelvinOm
PR: appsmithorg/appsmith#39300
File: app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx:97-97
Timestamp: 2025-02-17T12:42:04.141Z
Learning: The TimeField component from react-aria-components can receive null values in its onChange handler, particularly when the user clears the input, during initial render without a value, or when an invalid time is entered. Always add null checks before using the value.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 326a0e2 and 87eb7bf.

📒 Files selected for processing (1)
  • app/client/src/modules/ui-builder/ui/wds/WDSDatePickerWidget/widget/index.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
  • GitHub Check: client-build / client-build
  • GitHub Check: client-prettier / prettier-check
  • GitHub Check: chromatic-deployment
  • GitHub Check: chromatic-deployment
  • GitHub Check: storybook-tests

@@ -138,7 +138,7 @@ class WDSDatePickerWidget extends BaseWidget<
label={label}
maxValue={this.parseDate(maxDate)}
minValue={this.parseDate(minDate)}
onChange={this.handleDateChange}
onChange={(value) => this.handleDateChange(value!)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider handling null values safely instead of using non-null assertion.

The non-null assertion (!) assumes the value will never be null, which could lead to runtime errors if the DatePicker allows clearing dates. Consider handling null values explicitly:

-        onChange={(value) => this.handleDateChange(value!)}
+        onChange={(value) => value && this.handleDateChange(value)}
📝 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
onChange={(value) => this.handleDateChange(value!)}
onChange={(value) => value && this.handleDateChange(value)}

Copy link

🔴🔴🔴 Cyclic Dependency Check:

This PR has increased the number of cyclic dependencies by 3, when compared with the release branch.

Refer this document to identify the cyclic dependencies introduced by this PR.

You can view the dependency diff in the run log. Look for the check-cyclic-dependencies job in the run.

@@ -19,8 +19,8 @@ function CalendarHeading(

return (
<div className={styles.monthYearDropdown}>
<CalendarMonthDropdown state={state} />
<CalendarYearDropdown state={state} />
<CalendarMonthDropdown state={state!} />
Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: had to buypass this typescript check with !.

there seems to be an issue on spectrum side in terms of types.

Copy link
Collaborator

Choose a reason for hiding this comment

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

export const CalendarStateContext: React.Context<CalendarState | null>;

Judging by the state types, it can indeed be null. I think it would be better if we handled this case from our side. Otherwise, FE can simply fall in case of state null. May I ask you to use correct types here and here instead of !?

@jsartisan jsartisan added the ok-to-test Required label for CI label Feb 17, 2025
Copy link

🔴🔴🔴 Cyclic Dependency Check:

This PR has increased the number of cyclic dependencies by 3, when compared with the release branch.

Refer this document to identify the cyclic dependencies introduced by this PR.

You can view the dependency diff in the run log. Look for the check-cyclic-dependencies job in the run.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (9)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (5)

51-103: Consider memoizing computeVisibleItems function

The computeVisibleItems function is recreated on every render within updateOverflow. Consider extracting and memoizing it to optimize performance.

+const computeVisibleItems = useCallback((visibleItems: number) => {
-const computeVisibleItems = (visibleItems: number) => {
   if (domRef.current) {
     // ... existing implementation
   }
   return visibleItems;
-};
+}, []);

128-128: Remove redundant Boolean calls

The Boolean calls are unnecessary as the values will be automatically coerced to boolean.

-data-invalid={Boolean(isInvalid) ? "" : undefined}
+data-invalid={isInvalid ? "" : undefined}

-{Boolean(isLoading) ? (
+{isLoading ? (

Also applies to: 154-154

🧰 Tools
🪛 Biome (1.9.4)

[error] 128-128: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


51-103: Consider optimizing the overflow calculation logic.

The current implementation might cause multiple rerenders due to the generator function yielding multiple state updates. Consider batching the calculations and updating the state once with the final value.

-    setVisibleItems(function* () {
-      yield {
-        visibleItems: totalItems,
-      };
-
-      const newVisibleItems = computeVisibleItems(totalItems);
-      const isMeasuring = newVisibleItems < totalItems && newVisibleItems > 0;
-
-      yield {
-        visibleItems: newVisibleItems,
-      };
-
-      if (isMeasuring) {
-        yield {
-          visibleItems: computeVisibleItems(newVisibleItems),
-        };
-      }
-    });
+    const newVisibleItems = computeVisibleItems(totalItems);
+    const isMeasuring = newVisibleItems < totalItems && newVisibleItems > 0;
+    const finalVisibleItems = isMeasuring ? computeVisibleItems(newVisibleItems) : newVisibleItems;
+    setVisibleItems({ visibleItems: finalVisibleItems });

39-43: Consider memoizing the items array conversion.

The Array.from(items) conversion is performed on every render within the useMemo hook. Consider moving the conversion outside the mapping function.

-return [...selectedKeys].map((key) =>
-  items ? Array.from(items).find((item) => item.value === key) : undefined,
-);
+const itemsArray = items ? Array.from(items) : [];
+return [...selectedKeys].map((key) =>
+  itemsArray.find((item) => item.value === key)
+);

128-128: Remove redundant Boolean calls.

The Boolean calls are unnecessary as the values will be automatically coerced to boolean.

-data-invalid={Boolean(isInvalid) ? "" : undefined}
+data-invalid={isInvalid ? "" : undefined}
-{Boolean(isLoading) ? (
+{isLoading ? (

Also applies to: 154-154

🧰 Tools
🪛 Biome (1.9.4)

[error] 128-128: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (4)

79-83: Enhance keyboard interaction support

Consider handling additional keyboard events like Enter and Space for better accessibility.

 const onKeyDown = (e: React.KeyboardEvent) => {
-  if (e.key === "ArrowDown" || e.key === "ArrowUp") {
+  if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
     setOpen(true);
   }
 };

167-174: Consider creating a custom error context for consistency.

Since we can't use the FieldErrorContext, consider creating a custom error context to maintain consistency with other form components.

+const MultiSelectErrorContext = React.createContext<{ errorMessage?: string }>({});
+
+const useMultiSelectError = () => React.useContext(MultiSelectErrorContext);
+
 <div className={fieldErrorStyles.errorText}>
+  <MultiSelectErrorContext.Provider value={{ errorMessage }}>
     <Text color="negative" size="caption">
       {errorMessage}
     </Text>
+  </MultiSelectErrorContext.Provider>
 </div>

69-71: Simplify state management using controlled props pattern.

The current implementation mixes controlled and uncontrolled patterns. Consider using a custom hook to handle this logic.

-const [_selectedKeys, _setSelectedKeys] = useState<Selection>();
-const selectedKeys = selectedKeysProp ?? _selectedKeys ?? defaultSelectedKeys;
-const setSelectedKeys = onSelectionChangeProp ?? _setSelectedKeys;
+const useControlledState = (value: Selection | undefined, defaultValue: Selection) => {
+  const [state, setState] = useState(value ?? defaultValue);
+  return [value ?? state, value ? onSelectionChangeProp : setState] as const;
+};
+const [selectedKeys, setSelectedKeys] = useControlledState(selectedKeysProp, defaultSelectedKeys);

170-174: Consider extracting error handling to a reusable component.

The error message implementation could be extracted into a reusable component to maintain consistency across the design system.

+const FieldErrorMessage = ({ message }: { message: string }) => (
+  <div className={fieldErrorStyles.errorText}>
+    <Text color="negative" size="caption">
+      {message}
+    </Text>
+  </div>
+);

-<div className={fieldErrorStyles.errorText}>
-  <Text color="negative" size="caption">
-    {errorMessage}
-  </Text>
-</div>
+<FieldErrorMessage message={errorMessage} />
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87eb7bf and 4b43438.

📒 Files selected for processing (3)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css
👮 Files not reviewed due to content moderation or server errors (2)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx

[error] 128-128: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 154-154: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: parse-tags
  • GitHub Check: build
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-build / client-build
  • GitHub Check: client-prettier / prettier-check
  • GitHub Check: chromatic-deployment
  • GitHub Check: chromatic-deployment
  • GitHub Check: storybook-tests
🔇 Additional comments (11)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (6)

1-25: Well-structured interface with appropriate types!

The props interface is well-defined with clear optional flags and proper TypeScript types.


121-162: Clean and accessible rendering implementation!

The component handles various states effectively with proper accessibility attributes and clear conditional rendering.

🧰 Tools
🪛 Biome (1.9.4)

[error] 128-128: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 154-154: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


1-25: Well-structured interface with proper type definitions!

The props interface is comprehensive and follows TypeScript best practices.


128-128: Remove redundant Boolean calls.

The Boolean calls are unnecessary as the values will be coerced to boolean automatically.

Also applies to: 154-154

🧰 Tools
🪛 Biome (1.9.4)

[error] 128-128: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


121-162: Clean and well-structured rendering logic!

The component follows React best practices with proper conditional rendering and className composition.

🧰 Tools
🪛 Biome (1.9.4)

[error] 128-128: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 154-154: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


1-25: LGTM! Well-structured imports and type definitions.

The props interface is comprehensive and type-safe, with all necessary dependencies properly imported.

app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (5)

88-178: Well-structured component composition!

The implementation effectively combines React Aria components with custom behavior while maintaining accessibility.


40-47:

✅ Verification successful

Consider tracking React Aria's MultiSelect implementation

The current implementation is noted as temporary. Consider creating an issue to track React Aria's progress on a native MultiSelect component for future migration.


🌐 Web query:

Does React Aria have plans for a native MultiSelect component?

💡 Result:

React Aria's current Select component (as of February 2025) doesn't include native multi-select capabilities according to available documentation[1][9]. However, there are several indications of related functionality and potential workarounds:

Current state of multi-selection:

  • The core Select component only supports single selection through selectedKey/onSelectionChange props[1][5]
  • Multiple selection is explicitly mentioned as unavailable in GitHub issues from late 2023 through 2024[4][9]

Alternative approaches:

  1. ToggleButtonGroup (available since Nov 2024 release):

    <ToggleButtonGroup 
      selectionMode="multiple" 
      onSelectionChange={setSelected}
    >
      <ToggleButton id="option1">Option 1</ToggleButton>
      <ToggleButton id="option2">Option 2</ToggleButton>
    </ToggleButtonGroup>

    Supports arrow navigation and multiple selection modes[3][7]

  2. Menu component (since Nov 2024):

    <Menu selectionMode="multiple">
      <Item>Option 1</Item>
      <Item>Option 2</Item>
    </Menu>

    Allows mixing single/multiple selection within sections[3][7]

Future considerations:

  • Recent additions like section-level selection in Menu components (Nov 2024) suggest ongoing work on selection mechanics[3][7]
  • Community discussions indicate active interest in multi-select combobox functionality[4]
  • The ARIA infrastructure supports multi-select through aria-multiselectable[8], suggesting potential foundation for future implementation

While no official roadmap mentions a native MultiSelect component specifically, React Aria's pattern of gradually exposing lower-level hooks (like useSelect) alongside higher-level components suggests this could emerge as an extension point for custom implementations[2][5]. Developers needing multi-select today should consider combining existing primitives or following GitHub discussions for updates[4][9].

Citations:


Action: Track Updates on React Aria's MultiSelect Support

The current implementation is a temporary workaround given that React Aria does not yet offer a native MultiSelect component. Please consider creating a tracking issue to monitor any changes or new releases from React Aria that might introduce official multi-select support—this will help ensure we can migrate to a more robust solution when available.


69-72: Well-implemented state management!

The component properly handles both controlled and uncontrolled modes with clear default values.


26-26:

❓ Verification inconclusive

Consider the implications of using UNSTABLE_Autocomplete.

The component relies on an unstable API which might change in future updates. Consider adding a tracking issue to migrate to the stable API when available.

Also applies to: 141-141


🌐 Web query:

Is there a stable Autocomplete component in react-aria-components?

💡 Result:

The React Aria Components library does not currently have a stable Autocomplete component as of February 2025. Here's the key information:

  1. Alpha Status
    The UNSTABLE_Autocomplete component remains in alpha according to:

    • Official documentation showing an "Under construction" warning[1]
    • Installation requiring UNSTABLE_ prefix in imports[1]
    • Recent January 2025 release notes listing Autocomplete as "alpha"[10]
  2. Current Capabilities
    While unstable, it provides core autocomplete functionality:

    import {UNSTABLE_Autocomplete} from 'react-aria-components'
    // Basic usage
    <Autocomplete filter={contains}>
      <SearchField />
      <Menu>
        <MenuItem>Option 1</MenuItem>
      </Menu>
    </Autocomplete>

    Features include filtering hooks (useFilter)[1], keyboard navigation[8], and accessibility support[6].

  3. Production Considerations
    The React Aria team states:

    "When we say unstable [...] we mean we haven't settled the API yet. Behaviors are pretty stable"[3]

    Key implications:

    • ✅ Safe for production if you can handle future API changes
    • ❌ Not recommended if you require long-term API stability

For teams needing a stable solution, alternatives like Material UI Autocomplete[7] or RSuite AutoComplete[4] might be preferable while React Aria's implementation matures. The React Aria team has been actively developing components throughout 2024[9][10], suggesting stable Autocomplete support could arrive in future releases.

Citations:


Attention: Unstable API Warning for UNSTABLE_Autocomplete

The component currently uses UNSTABLE_Autocomplete from react-aria-components, which is in alpha and subject to change. While it delivers core autocomplete functionality (filtering hooks, keyboard navigation, etc.), its unstable status requires extra caution for long-term maintenance. Please consider the following:

  • Add a tracking issue to revisit this component once a stable API becomes available.
  • Review usage at:
    • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx at line 26
    • and at line 141.
  • Evaluate alternative autocomplete solutions if API stability is critical for your use case.

141-163:

❓ Verification inconclusive

Verify stability of UNSTABLE_Autocomplete API.

The component uses an unstable API which might change in future versions. Consider:

  1. Documenting this dependency
  2. Adding version pinning
  3. Planning for future migration

🌐 Web query:

What is the current status of UNSTABLE_Autocomplete in react-aria-components?

💡 Result:

As of February 2025, the UNSTABLE_Autocomplete component in react-aria-components remains in alpha status, though it has seen active development since its initial release in January 2025[1][3]. Here's the current state:

Key Details

  • Latest version: Part of React Aria Components v1.6.0[1]
  • Installation: Requires explicit import as an unstable component:
    import {UNSTABLE_Autocomplete} from 'react-aria-components'
  • Core functionality: Combines text input with a filterable menu/ListBox[1][3]
  • Key features:
    • Supports both TextField and SearchField inputs
    • Works with Menu or ListBox collections
    • Customizable filtering via filter prop
    • Arrow key navigation while keeping input focus[3]

Recent Developments

The component was first introduced in React Aria's January 15, 2025 release as an alpha feature targeting:

  • Command palettes
  • Searchable menus
  • Flexible layout configurations where input and menu are siblings[3]

Limitations

  • Documentation is still marked as "coming soon"[1]
  • API stability warnings remain (hence the UNSTABLE_ prefix)
  • Missing advanced features like type-ahead search or complex item rendering[3]

The React Aria team encourages developers to test this component and provide feedback while they work toward a stable release[3]. For production use cases requiring more maturity, their ComboBox component remains the recommended alternative[3][4].

Citations:


Warning: Usage of UNSTABLE_Autocomplete (Alpha Status)

The implementation relies on the UNSTABLE_Autocomplete component from react-aria-components, which remains in alpha as of February 2025 (React Aria Components v1.6.0). Since this API is unstable and subject to changes, please consider:

  • Documenting this dependency: Clearly note that this component is experimental.
  • Version pinning: Lock the dependency version for consistency.
  • Planning for a future migration: Evaluate a switch to a stable alternative (e.g., ComboBox) if used in production.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1)

37-102: Consider extracting helper functions for better readability.

The overflow calculation logic is complex but well-implemented. Consider extracting the following calculations into helper functions:

  • Item width calculations
  • Container size checks

Example refactor:

+const calculateItemWidth = (item: HTMLLIElement) => 
+  item.getBoundingClientRect().width;
+
+const isItemVisible = (
+  calculatedSize: number, 
+  containerSize: number, 
+  ellipsisWidth: number
+) => 
+  calculatedSize <= containerSize && 
+  calculatedSize + ellipsisWidth < containerSize;
+
 const updateOverflow = useCallback(() => {
   const computeVisibleItems = (visibleItems: number) => {
     if (domRef.current) {
       const containerSize = domRef.current.getBoundingClientRect().width;
       const listItems = Array.from(
         domRef.current.children,
       ) as HTMLLIElement[];
       const ellipisisItem = listItems.pop();
       const ellipisisItemWidth =
         ellipisisItem?.getBoundingClientRect().width ?? 0;
       let calculatedSize = 0;
       let newVisibleItems = 0;

       for (const item of listItems) {
-        const itemWidth = item.getBoundingClientRect().width;
+        const itemWidth = calculateItemWidth(item);
         calculatedSize += itemWidth;

-        if (
-          calculatedSize <= containerSize &&
-          calculatedSize + ellipisisItemWidth < containerSize
-        ) {
+        if (isItemVisible(calculatedSize, containerSize, ellipisisItemWidth)) {
           newVisibleItems++;
         } else {
           break;
         }
       }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b43438 and 7ee36e0.

📒 Files selected for processing (3)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/types.ts
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx

[error] 127-127: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 153-153: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-build / client-build
  • GitHub Check: client-prettier / prettier-check
  • GitHub Check: chromatic-deployment
  • GitHub Check: chromatic-deployment
  • GitHub Check: storybook-tests
🔇 Additional comments (4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (4)

1-24: LGTM! Well-structured imports and interface definition.

The props interface is well-defined with appropriate types, and imports are organized logically.


26-36: LGTM! Clean component implementation.

Props destructuring is well-organized with appropriate default values.


104-118: LGTM! Efficient resize handling implementation.

Good use of useResizeObserver and memoization for parent ref.


127-127: Remove redundant Boolean calls.

The static analysis tool correctly identified redundant Boolean calls that can be simplified.

Apply this diff:

-data-invalid={Boolean(isInvalid) ? "" : undefined}
+data-invalid={isInvalid ? "" : undefined}

-{Boolean(isLoading) ? (
+{isLoading ? (

Also applies to: 153-153

🧰 Tools
🪛 Biome (1.9.4)

[error] 127-127: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

@@ -19,8 +19,8 @@ function CalendarHeading(

return (
<div className={styles.monthYearDropdown}>
<CalendarMonthDropdown state={state} />
<CalendarYearDropdown state={state} />
<CalendarMonthDropdown state={state!} />
Copy link
Collaborator

Choose a reason for hiding this comment

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

export const CalendarStateContext: React.Context<CalendarState | null>;

Judging by the state types, it can indeed be null. I think it would be better if we handled this case from our side. Otherwise, FE can simply fall in case of state null. May I ask you to use correct types here and here instead of !?

@@ -94,7 +94,7 @@ export const DatePicker = <T extends DateValue>(props: DatePickerProps<T>) => {
label="Time"
maxValue={timeMaxValue}
minValue={timeMinValue}
onChange={state.setTimeValue}
onChange={(value) => state.setTimeValue(value!)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would also be good to fix this as well, as there may be issues related to this. Do you understand why value can be null?

Anyway, we could at least add a check for null

if (value) {
 state.setTimeValue(value);
}

@@ -26,7 +26,10 @@ export const Menu = (props: MenuProps) => {
UNSTABLE_portalContainer={isRootMenu ? root : undefined}
maxHeight={POPOVER_LIST_BOX_MAX_HEIGHT}
>
<HeadlessMenu className={clsx(listStyles.listBox, className)} {...rest}>
<HeadlessMenu
className={clsx(listBoxStyles.listBox, className)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

The sub-menu is broken. Could you take a look?

Снимок экрана 2025-02-17 в 15 09 59

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

return (
<div className={styles.emptyState}>
<Text color="neutral-subtle">No options found</Text>
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

We could just pass className to Text component.

    <Text className={styles.emptyState} color="neutral-subtle">
      No options found
    </Text>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh yeah..totally slipped my mind. ( AI and its side effects )

);
};

export default MultiSelectValue;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you please use a named export?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


/* this is a hack to show checkbox in the listbox in the dropdown. We are forced to do this because listitems are not getting re-rendered when the selected keys change.
* So when we use checkbox component, it does not get checked or unchecked. */
.listBoxItem[data-selected]::before {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we consider passing IsSelected prop to the ListBoxItem and then render the Checkbox there? We do something similar with icon.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, i tried that only. It didn't work. Their listboxitem just don't re-render.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The original ListBoxItem component can take the function as a children. So we can modify our components as follows

 const renderedChildren = () => {
    if (typeof children === "function") {
      return (
        <HeadlessListBoxItem
          {...rest}
          className={clsx(styles.listBoxItem, className)}
        >
          {(renderProps) => children(renderProps)}
        </HeadlessListBoxItem>
      );
    }

    return (
      <HeadlessListBoxItem
        {...rest}
        className={clsx(styles.listBoxItem, className)}
      >
        {icon && <Icon name={icon} />}
        <Text lineClamp={1}>{children}</Text>
      </HeadlessListBoxItem>
    );
  };

  return renderedChildren();
  

After that, we can use it like this

 <ListBoxItem
      className={styles.listBoxItem}
      id={item.value}
      key={item.value}
      textValue={item.label}
    >
      {({ isSelected }) => (
        <>
          <Checkbox isSelected={isSelected} />
          {item.label}
        </>
      )}
    </ListBoxItem>
  )}
</ListBox>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This really worked. Thanks a lot.

@@ -45,7 +45,7 @@ export const Select = (props: SelectProps) => {
{label}
</FieldLabel>
<SelectTrigger
isDisabled={isDisabled}
isDisabled={Boolean(isDisabled) || Boolean(isLoading)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is not entirely correct using isLoading for disabled state., since for disabled state we use opacity, which should not happen for a loading state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted. I just did because i was able to open the dropdown when is loading was true.

export const Menu = (props: MenuProps) => {
const { children, className, ...rest } = props;
const root = useRootContainer();

const nestingLevel = useContext(MenuNestingContext);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@KelvinOm seems all this is not required anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice! Thank you for fixing this!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (2)
app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarYearDropdown.tsx (2)

15-20: ⚠️ Potential issue

Add null check for state before accessing setFocusedDate

The code attempts to access state.setFocusedDate without checking if state is null, which could cause runtime errors.

Add null check:

  const onChange = (value: Key | null) => {
    const index = Number(value);
    const date = years[index].value;

-   state.setFocusedDate(date);
+   state?.setFocusedDate?.(date);
  };

27-27: ⚠️ Potential issue

Remove hardcoded selectedKey value

The selectedKey={20} appears to be hardcoded, which might not reflect the actual selected year.

The selected key should be derived from the current state:

-     selectedKey={20}
+     selectedKey={years.findIndex(year => 
+       year.value.getFullYear() === state?.focusedDate?.getFullYear()
+     )}
🧹 Nitpick comments (2)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (2)

21-21: Consider using a more specific type for the size prop.

Instead of using the generic MultiSelectProps<object>["size"], define an explicit union type of allowed sizes for better type safety and documentation.

-  size?: MultiSelectProps<object>["size"];
+  size?: "small" | "medium" | "large";

50-102: Consider adding error boundary protection for overflow calculations.

The overflow calculation logic involves DOM measurements that could fail in certain edge cases. Consider wrapping this logic in a try-catch block or implementing an error boundary.

 const updateOverflow = useCallback(() => {
+  try {
     const computeVisibleItems = (visibleItems: number) => {
       // ... existing code ...
     };
     
     setVisibleItems(function* () {
       // ... existing code ...
     });
+  } catch (error) {
+    // Fallback to showing all items
+    setVisibleItems({ visibleItems: totalItems });
+  }
 }, [domRef, selectedKeys, setVisibleItems]);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ee36e0 and e222ecd.

📒 Files selected for processing (6)
  • app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarYearDropdown.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/Menu/src/Menu.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/index.ts
  • app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx
  • app/client/packages/design-system/widgets/src/components/Menu/src/Menu.tsx
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx

[error] 127-127: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 153-153: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: perform-test / server-build / server-unit-tests
  • GitHub Check: perform-test / client-build / client-build
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: client-prettier / prettier-check
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
  • GitHub Check: client-build / client-build
  • GitHub Check: chromatic-deployment
  • GitHub Check: chromatic-deployment
  • GitHub Check: storybook-tests
🔇 Additional comments (4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (2)

127-127: Remove redundant Boolean calls.

The Boolean calls are unnecessary as the values will be automatically coerced.

-data-invalid={Boolean(isInvalid) ? "" : undefined}
+data-invalid={isInvalid ? "" : undefined}

-{Boolean(isLoading) ? (
+{isLoading ? (

Also applies to: 153-153

🧰 Tools
🪛 Biome (1.9.4)

[error] 127-127: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


163-163: LGTM! Named export implemented as requested.

The component is correctly exported using a named export.

app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarYearDropdown.tsx (2)

8-12: Breaking Change: State parameter now accepts null

This change modifies the public API and requires careful consideration of backward compatibility.

Please verify if this breaking change is intentional and update the component's documentation accordingly.


13-13:

❓ Verification inconclusive

Verify useYearOptions handles null state

The useYearOptions hook is called with potentially null state.


🏁 Script executed:

#!/bin/bash
# Check if useYearOptions handles null state
ast-grep --pattern 'function useYearOptions($state) {
  $$$
}'

Length of output: 64


I'll need to further verify that the hook’s definition actually handles a null state. The previous script produced no output, so let’s search for where and how useYearOptions is defined (whether as a traditional function or an arrow function) to inspect its null-checking logic.

Please run the following shell script to locate the definition of useYearOptions across the repository:

Once we have the definition output, we can verify if it includes a null-check for the state parameter.


🏁 Script executed:

[run_scripts]
#!/bin/bash
# Search for the definition of useYearOptions (handles both traditional and arrow function definitions)
rg -n "function useYearOptions" || rg -n "const useYearOptions" -n

Length of output: 296


Review: Verify Null State Handling in useYearOptions Hook

  • The hook is defined in
    app/client/packages/design-system/widgets/src/components/Calendar/utils/calendar.ts:4
    as:
    export function useYearOptions(state: CalendarState) {
  • Our search did not reveal an explicit null check for the state parameter inside the hook.
  • Verify that the parent component guarantees that state is non-null when calling useYearOptions or add a defensive null check (e.g., if (!state) { … }) within the hook to prevent potential runtime issues.

export const Menu = (props: MenuProps) => {
const { children, className, ...rest } = props;
const root = useRootContainer();

const nestingLevel = useContext(MenuNestingContext);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice! Thank you for fixing this!


/* this is a hack to show checkbox in the listbox in the dropdown. We are forced to do this because listitems are not getting re-rendered when the selected keys change.
* So when we use checkbox component, it does not get checked or unchecked. */
.listBoxItem[data-selected]::before {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The original ListBoxItem component can take the function as a children. So we can modify our components as follows

 const renderedChildren = () => {
    if (typeof children === "function") {
      return (
        <HeadlessListBoxItem
          {...rest}
          className={clsx(styles.listBoxItem, className)}
        >
          {(renderProps) => children(renderProps)}
        </HeadlessListBoxItem>
      );
    }

    return (
      <HeadlessListBoxItem
        {...rest}
        className={clsx(styles.listBoxItem, className)}
      >
        {icon && <Icon name={icon} />}
        <Text lineClamp={1}>{children}</Text>
      </HeadlessListBoxItem>
    );
  };

  return renderedChildren();
  

After that, we can use it like this

 <ListBoxItem
      className={styles.listBoxItem}
      id={item.value}
      key={item.value}
      textValue={item.label}
    >
      {({ isSelected }) => (
        <>
          <Checkbox isSelected={isSelected} />
          {item.label}
        </>
      )}
    </ListBoxItem>
  )}
</ListBox>

@jsartisan jsartisan requested a review from KelvinOm February 18, 2025 04:41
import React, { useRef, useState } from "react";

import { useField } from "react-aria";
import { type Selection, ListBoxItem } from "react-aria-components";
Copy link
Collaborator

Choose a reason for hiding this comment

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

The idea was to modify WDS component, which is already have needed styles, instead of using spectrum component directly. This way we can reuse it more easily and styles will be used only in it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea right. Had to update our ListBoxItem to accept renderProps too.

Updated with our ListBoxItem.

border-radius: 0;
}

.listBoxItem::before {
Copy link
Collaborator

Choose a reason for hiding this comment

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

These styles are no longer needed, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed. Sorry for being sloppy.

{({ isSelected }) => (
<>
<span className={styles.listBoxItemCheckbox}>
<Checkbox isSelected={isSelected} />
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why can't we pass the styles directly to the checkbox?

Suggested change
<Checkbox isSelected={isSelected} />
<Checkbox className={styles.listBoxItemCheckbox} isSelected={isSelected} />

Copy link
Contributor Author

@jsartisan jsartisan Feb 18, 2025

Choose a reason for hiding this comment

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

Yea. Had to update our checkbox API which was not accepting the className prop before. Fixed it.

}

.ellipsisText {
width: 7ch;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this be the max-width and increase as the number increases?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. It seems to be working fine with max-width too.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (2)

38-42: Optimize selectedItems calculation.

The current implementation uses multiple Array conversions which can be simplified.

-const selectedItems = useMemo(() => {
-  return [...selectedKeys].map((key) =>
-    items ? Array.from(items).find((item) => item.value === key) : undefined,
-  );
-}, [items, selectedKeys]);
+const selectedItems = useMemo(() => {
+  if (!items) return Array.from(selectedKeys).map(() => undefined);
+  const itemsArray = Array.from(items);
+  return Array.from(selectedKeys).map((key) =>
+    itemsArray.find((item) => item.value === key)
+  );
+}, [items, selectedKeys]);

50-102: Add error handling for DOM measurements.

The overflow calculations rely heavily on DOM measurements which could fail. Consider adding error boundaries and cleanup.

 const updateOverflow = useCallback(() => {
   const computeVisibleItems = (visibleItems: number) => {
     if (domRef.current) {
+      try {
         const containerSize = domRef.current.getBoundingClientRect().width;
         const listItems = Array.from(
           domRef.current.children,
         ) as HTMLLIElement[];
         const ellipisisItem = listItems.pop();
         const ellipisisItemWidth =
           ellipisisItem?.getBoundingClientRect().width ?? 0;
         let calculatedSize = 0;
         let newVisibleItems = 0;

         for (const item of listItems) {
           const itemWidth = item.getBoundingClientRect().width;
           calculatedSize += itemWidth;
           if (
             calculatedSize <= containerSize &&
             calculatedSize + ellipisisItemWidth < containerSize
           ) {
             newVisibleItems++;
           } else {
             break;
           }
         }

         return newVisibleItems;
+      } catch (error) {
+        console.error('Error calculating visible items:', error);
+        return visibleItems;
+      }
     }

     return visibleItems;
   };
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6059092 and b9a4589.

📒 Files selected for processing (5)
  • app/client/packages/design-system/widgets/src/components/Checkbox/src/Checkbox.tsx (3 hunks)
  • app/client/packages/design-system/widgets/src/components/ListBoxItem/src/ListBoxItem.tsx (2 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx

[error] 127-127: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 152-152: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: perform-test / client-build / client-build
  • GitHub Check: perform-test / rts-build / build
  • GitHub Check: perform-test / server-build / server-unit-tests
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-build / client-build
  • GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
  • GitHub Check: client-prettier / prettier-check
  • GitHub Check: storybook-tests
  • GitHub Check: chromatic-deployment
  • GitHub Check: chromatic-deployment
🔇 Additional comments (9)
app/client/packages/design-system/widgets/src/components/Checkbox/src/Checkbox.tsx (3)

1-1: LGTM! Good choice using clsx.

The addition of clsx for conditional class name management aligns with React best practices.


14-21: LGTM! Props destructuring is clean and well-organized.

The addition of the className prop follows React conventions and maintains good code organization.


27-27: LGTM! Proper composition of class names.

The use of clsx to combine base styles with custom className maintains component styling flexibility while preserving default styles.

app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (3)

14-24: LGTM! Well-structured props interface.

Props follow React Aria conventions with appropriate TypeScript types.


127-127: Remove redundant Boolean calls.

The static analysis tool correctly identified redundant Boolean calls. These can be simplified.

Also applies to: 152-152

🧰 Tools
🪛 Biome (1.9.4)

[error] 127-127: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


121-158: LGTM! Clean styling implementation.

Good use of clsx for class composition and consistent styling approach.

🧰 Tools
🪛 Biome (1.9.4)

[error] 127-127: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 152-152: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

app/client/packages/design-system/widgets/src/components/ListBoxItem/src/ListBoxItem.tsx (3)

1-1: LGTM! Good choice using clsx.

The addition of clsx for class name composition is a standard approach.


13-24: Clean implementation of flexible rendering patterns.

Good improvements:

  1. Proper className composition with clsx
  2. Support for both render prop and standard children patterns
  3. Maintained backward compatibility

9-10:

❓ Verification inconclusive

Verify ListBoxItemProps type definition.

The className prop is destructured but we need to ensure the type definition includes it.


🏁 Script executed:

#!/bin/bash
# Search for ListBoxItemProps type definition
ast-grep --pattern 'type ListBoxItemProps = {
  $$$
}'

Length of output: 56


Action Required: Verify ListBoxItemProps Type Definition Includes "className"

The ListBoxItem component now destructures a className prop, but the verification command we initially ran provided no output. Please manually verify the type definition in
app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts to ensure it has a className property (e.g., className?: string). If it's missing, update the type accordingly.


🏁 Script executed:

[run_scripts]
#!/bin/bash
# Re-run the search for the ListBoxItemProps type definition and check for "className" in the file
rg "export type ListBoxItemProps" -n app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts
rg "className" -n app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts

Length of output: 342


Attention: Verify ListBoxItemProps Type Declaration

The ListBoxItem component now destructures a className prop. However, our earlier grep did not produce any output from the types.ts file, so we couldn’t conclusively verify whether ListBoxItemProps includes the className property. Please manually verify that the type definition (located at app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts) includes something like:

export type ListBoxItemProps = {
  className?: string;
  // ...other props
}

If the property is missing, update the type accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (2)

27-39: Add documentation for the MAX_ELLIPSIS_COUNT constant.

Consider adding a JSDoc comment explaining the purpose and significance of this constant.

+/** Maximum number of additional items to show in the ellipsis count */
 const MAX_ELLIPSIS_COUNT = 999;

105-105: Remove unused dependency from useCallback.

The selectedKeys dependency isn't used within the updateOverflow function.

-}, [domRef, selectedKeys, setVisibleItems]);
+}, [domRef, setVisibleItems]);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9a4589 and 9d486cb.

📒 Files selected for processing (2)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (1 hunks)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/client/packages/design-system/widgets/src/components/MultiSelect/src/styles.module.css
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx

[error] 130-130: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)


[error] 157-157: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: perform-test / client-build / client-build
  • GitHub Check: perform-test / server-build / server-unit-tests
  • GitHub Check: perform-test / rts-build / build
  • GitHub Check: client-lint / client-lint
  • GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
  • GitHub Check: client-unit-tests / client-unit-tests
  • GitHub Check: client-prettier / prettier-check
  • GitHub Check: client-build / client-build
  • GitHub Check: chromatic-deployment
  • GitHub Check: storybook-tests
  • GitHub Check: chromatic-deployment
🔇 Additional comments (2)
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelectValue.tsx (2)

1-25: LGTM! Well-structured imports and type definitions.

The imports are properly organized and the interface is well-defined with clear types.


130-130: Remove redundant Boolean calls.

The Boolean calls are unnecessary as the values will be automatically coerced to booleans.

-data-invalid={Boolean(isInvalid) ? "" : undefined}
+data-invalid={isInvalid ? "" : undefined}

-{Boolean(isLoading) ? (
+{isLoading ? (

Also applies to: 157-157

🧰 Tools
🪛 Biome (1.9.4)

[error] 130-130: Avoid redundant Boolean call

It is not necessary to use Boolean call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant Boolean call

(lint/complexity/noExtraBooleanCast)

@jsartisan jsartisan requested a review from KelvinOm February 18, 2025 09:31
@jsartisan jsartisan merged commit dc923c9 into release Feb 18, 2025
50 of 53 checks passed
@jsartisan jsartisan deleted the chore/wds-multi-select-component branch February 18, 2025 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ok-to-test Required label for CI skip-changelog Adding this label to a PR prevents it from being listed in the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants