Skip to content

Conversation

@jpan-box
Copy link
Collaborator

@jpan-box jpan-box commented Aug 14, 2025

Assumptions:

  • Ellipses icon should show when one or more rows have been selected
    • Therefore, the ellipses "bulk action" icon only shows if Select All is also enabled in the tableProps
  • No icons or subactions
  • Selecting an action triggers an onClick handler (provided by the consumer) which receives all selected Item Ids.
Screenshot 2025-08-15 at 10 10 47 AM

Summary by CodeRabbit

  • New Features

    • Bulk actions dropdown added to the Content Explorer sub-header and shown when items are selected; supports configurable actions.
  • Accessibility

    • ARIA label added to the Bulk actions control for improved screen-reader support.
  • Style

    • Added spacing to the Bulk actions trigger for improved layout.
  • Tests

    • Storybook visuals and unit tests updated to cover bulk-action visibility, menu interactions, and callback invocation.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 14, 2025

Walkthrough

Adds a reusable BulkItemActionMenu component and forwards an optional bulkItemActions prop from ContentExplorer → SubHeader → SubHeaderRight. When metadata-v2 is enabled and items are selected, SubHeaderRight renders a localized bulk-actions dropdown; styles, i18n, stories, and tests updated accordingly. The i18n key was added twice in i18n/en-US.properties.

Changes

Cohort / File(s) Summary of Changes
i18n updates
i18n/en-US.properties
Added boxui.subHeader.bulkItemActionMenuAriaLabel = Bulk actions (inserted twice).
SubHeader plumbing
src/elements/common/sub-header/SubHeader.tsx
Added import BulkItemAction type, new optional prop bulkItemActions?: BulkItemAction[], and forwarded bulkItemActions and selectedItemIds to SubHeaderRight.
SubHeaderRight integration
src/elements/common/sub-header/SubHeaderRight.tsx, .../SubHeaderRight.scss, .../messages.ts
Added bulkItemActions?: BulkItemAction[] prop; conditionally render BulkItemActionMenu when feature flag + selection + actions exist; added .be-BulkItemActionMenu-trigger { margin-right: var(--space-2); }; added bulkItemActionMenuAriaLabel message.
New bulk action component
src/elements/common/sub-header/BulkItemActionMenu.tsx
New BulkItemAction type and BulkItemActionMenu component: ellipsis-trigger DropdownMenu, localized ARIA label, maps actions to menu items and calls onClick(selectedItemIds) on select.
ContentExplorer wiring
src/elements/content-explorer/ContentExplorer.tsx
Added optional bulkItemActions?: BulkItemAction[] to ContentExplorerProps and forwarded to SubHeader.
Stories & tests
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx, src/elements/content-explorer/__tests__/ContentExplorer.test.tsx, src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx
Added visual story and unit tests for bulk-action menu visibility and invocation; introduced metadataViewV2WithBulkItemActions; tests updated to be feature-flag aware and assert onClick behavior and UI interactions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ContentExplorer
  participant SubHeader
  participant SubHeaderRight
  participant BulkItemActionMenu
  participant BulkActionHandler as BulkItemAction.onClick

  User->>ContentExplorer: select item(s)
  ContentExplorer->>SubHeader: render({ bulkItemActions, selectedItemIds })
  SubHeader->>SubHeaderRight: render({ bulkItemActions, selectedItemIds })
  alt feature-enabled & selection & actions exist
    SubHeaderRight->>BulkItemActionMenu: render(actions, selectedItemIds)
    User->>BulkItemActionMenu: open & choose action
    BulkItemActionMenu->>BulkActionHandler: onClick(selectedItemIds)
  else menu not shown
    SubHeaderRight->>SubHeaderRight: render metadata Button only
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • greg-in-a-box
  • tjuanitas
  • tjiang-box

Poem

A rabbit taps the header, neat and new,
Ellipses bloom — bulk actions hop into view.
Selected IDs gathered, we nibble and pick,
A menu opens fast and slyly quick.
“Bulk actions!” hums the bunny — click by click. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 49e0580 and e50b5cf.

📒 Files selected for processing (4)
  • src/elements/common/sub-header/SubHeaderRight.tsx (5 hunks)
  • src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (3 hunks)
  • src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/elements/common/sub-header/tests/SubHeaderRight.test.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
  • src/elements/content-explorer/tests/ContentExplorer.test.tsx
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Rule: Automatic merge queue (queue)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
  • GitHub Check: Queue: Embarked in merge queue
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@jpan-box jpan-box force-pushed the mdv-bca branch 4 times, most recently from 0f0765d to 0ce592f Compare August 15, 2025 14:04
@jpan-box
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 15, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🔭 Outside diff range comments (1)
src/elements/content-explorer/MetadataViewContainer.tsx (1)

74-82: Leaking unknown prop to MetadataView (potential TS error) — exclude from spread

bulkItemActionMenuProps is declared on MetadataViewContainerProps but is not consumed here and is included in the {...rest} spread into . If MetadataViewProps doesn’t declare this prop (it likely doesn’t), TypeScript will error and, at runtime, React will pass an unknown prop to a composite component.

Extract and omit the prop from the spread to avoid leaking unknown props.

Apply this diff:

-const MetadataViewContainer = ({
-    actionBarProps,
-    columns,
-    currentCollection,
-    metadataTemplate,
-    ...rest
-}: MetadataViewContainerProps) => {
+const MetadataViewContainer = ({
+    actionBarProps,
+    columns,
+    currentCollection,
+    metadataTemplate,
+    bulkItemActionMenuProps, // intentionally not used here yet
+    ...rest
+}: MetadataViewContainerProps) => {
@@
-    return <MetadataView actionBarProps={transformedActionBarProps} columns={columns} items={items} {...rest} />;
+    return <MetadataView actionBarProps={transformedActionBarProps} columns={columns} items={items} {...rest} />;

Follow-up: If this prop is intended for SubHeader/Content (as per the PR scope), wire it there instead of to MetadataView.

Also applies to: 128-129

🧹 Nitpick comments (6)
src/elements/content-explorer/MetadataViewContainer.tsx (3)

69-72: Doc comment claims a default “Download” action that isn’t implemented

The comment states a default action will always show, but this file neither provides nor injects one. Either implement the fallback or update the comment to reflect current behavior to avoid misleading API consumers.

Apply one of the following diffs:

Option A — update the comment to current behavior:

-    /* Array of custom actions to show in the bulk item action menu. Customers may optionally provide this value, but the Element will always
-    show a default action of "Download" */
+    /* Array of custom actions to show in the bulk item action menu. If omitted, no bulk action menu is rendered. */

Option B — keep the promise, inject a default if none provided (pseudo-implementation here; actual wiring likely belongs where the menu is rendered):

 export interface BulkItemActionMenuProps {
     actions: BulkItemAction[];
 }

And at the render site of the menu (SubHeaderRight or equivalent), ensure:

const actions = bulkItemActionMenuProps?.actions?.length
  ? bulkItemActionMenuProps.actions
  : [{ label: 'Download', onClick: /* default handler */ }];

29-33: Make action onClick async-friendly

Bulk actions commonly involve async work (API calls). Loosen the type to allow returning a Promise.

Apply this diff:

 export interface BulkItemAction {
     label: string;
-    onClick: (selectedItemIds: Selection) => void;
+    onClick: (selectedItemIds: Selection) => void | Promise<void>;
 }

29-37: Type location/layering: avoid “common” importing types from “content-explorer”

Per PR notes, SubHeader imports BulkItemActionMenuProps from this container. That inverts layering (common → content-explorer) and invites cycles. Prefer a shared types module (e.g., src/elements/types/bulkActions.ts) consumed by both “common” and “content-explorer”.

If you’d like, I can propose a small refactor PR to extract these interfaces into a neutral location and update imports accordingly.

src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (1)

25-37: Add coverage for the new bulk actions dropdown

Recommend adding a test that, when selectedItemIds is non-empty (or 'all') and bulkItemActionMenuProps are provided, the ellipsis trigger renders with the aria-label from messages and clicking an item calls its onClick with the expected Selection.

I can draft a test case if helpful.

src/elements/content-explorer/ContentExplorer.tsx (1)

1738-1740: Consider implementing the TODO for default download action.

The TODO comment indicates that a default download action should be added so that bulkItemActionMenuProps always exists. This would prevent potential runtime errors when the bulk action menu is accessed but no actions are configured.

Would you like me to help implement the default download action for bulk items? This would ensure consistent behavior and prevent the need for null checks downstream.

src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (1)

131-132: Mock functions should be passed inline for proper test isolation.

Using fn() inline in the story args can cause issues with test isolation when multiple stories share the same mock. Each story should have its own mock instance.

Consider moving the mock function declaration to the story level:

 export const metadataViewV2WithBulkItemActionMenuCallsOnClick: Story = {
-    args: metadataViewV2WithBulkItemActionMenuProps,
+    args: {
+        ...metadataViewV2ElementProps,
+        metadataViewProps: {
+            columns,
+            tableProps: {
+                isSelectAllEnabled: true,
+            },
+            bulkItemActionMenuProps: {
+                actions: [
+                    {
+                        label: 'Download',
+                        onClick: fn(),
+                        icon: Download,
+                    },
+                ],
+            },
+        },
+    },
     play: async ({ canvas, args }) => {
📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b68cd70 and 0ce592f.

📒 Files selected for processing (9)
  • i18n/en-US.properties (1 hunks)
  • src/elements/common/sub-header/SubHeader.tsx (5 hunks)
  • src/elements/common/sub-header/SubHeaderRight.scss (1 hunks)
  • src/elements/common/sub-header/SubHeaderRight.tsx (6 hunks)
  • src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (1 hunks)
  • src/elements/common/sub-header/messages.ts (1 hunks)
  • src/elements/content-explorer/ContentExplorer.tsx (2 hunks)
  • src/elements/content-explorer/MetadataViewContainer.tsx (3 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/elements/common/sub-header/SubHeader.tsx (1)
src/elements/content-explorer/MetadataViewContainer.tsx (1)
  • BulkItemActionMenuProps (35-37)
src/elements/common/sub-header/SubHeaderRight.tsx (1)
src/elements/content-explorer/MetadataViewContainer.tsx (1)
  • BulkItemActionMenuProps (35-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (4)
i18n/en-US.properties (1)

1827-1828: ARIA label addition for bulk actions dropdown — looks good

Key name, value, and placement are consistent with the messages id and usage. No issues from i18n/accessibility standpoint.

src/elements/common/sub-header/messages.ts (1)

4-8: New i18n message definition is correct and consistent

Id matches en-US.properties, defaultMessage is clear, and the description is precise. Good addition.

src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (1)

22-23: Selected item ids default added — OK

Setting selectedItemIds to an empty Set aligns with react-aria Selection semantics and unblocks rendering paths.

src/elements/common/sub-header/SubHeaderRight.scss (1)

23-25: Verified — class is used; no change required

The selector is applied and the style exists. The only note is a spacing consistency choice (margin-right vs margin-left).

  • src/elements/common/sub-header/SubHeaderRight.tsx:106 — DropdownMenu.Trigger className="be-sub-header-right-dropdown-trigger"
  • src/elements/common/sub-header/SubHeaderRight.scss:23–25 — .be-sub-header-right-dropdown-trigger { margin-right: 7px; }
  • Related: other controls use margin-left: 7px (lines with .be-btn-sort, .be-btn-add, .bdl-ViewModeChangeButton)

Keep margin-right if the layout needs it; change to margin-left only for consistency if it doesn't affect positioning.

@jpan-box jpan-box marked this pull request as ready for review August 15, 2025 14:43
@jpan-box jpan-box requested a review from a team as a code owner August 15, 2025 14:43
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

🔭 Outside diff range comments (1)
src/elements/content-explorer/ContentExplorer.tsx (1)

106-112: Prevent undefined being passed to SubHeader: default bulkItemActionMenuProps to an empty config

SubHeader intentionally treats bulkItemActionMenuProps as required (per product direction and retrieved learnings). Here, ContentExplorerProps makes it optional and forwards it directly, which can type-error or pass undefined at runtime.

Harden the handoff by always supplying a default object.

Apply this diff:

 interface ContentExplorerProps {
@@
-    bulkItemActionMenuProps?: BulkItemActionMenuProps;
+    bulkItemActionMenuProps?: BulkItemActionMenuProps;
@@
                             selectedItemIds={this.state.selectedItemIds}
                             title={title}
-                            bulkItemActionMenuProps={bulkItemActionMenuProps}
+                            bulkItemActionMenuProps={bulkItemActionMenuProps ?? { actions: [] }}
                         />

If you prefer strict alignment with SubHeader’s required prop, we can also make bulkItemActionMenuProps required here and set a default in defaultProps, but the above avoids a breaking change while maintaining type-safety downstream.

Also applies to: 1771-1774

🧹 Nitpick comments (2)
src/elements/common/sub-header/BulkItemActionMenu.tsx (1)

38-45: Use a stable key instead of label to avoid duplicate-key issues

Using label as the React key can produce collisions if two actions share the same label, causing reconciliation bugs. Prefer a stable identifier.

Two options:

  • Add an id field to BulkItemAction and use it as the key.
  • Fallback to index if no id is available.

Example minimal change (adds id as optional and prefers it as key):

 export interface BulkItemAction {
+    id?: string;
     label: string;
     onClick: (selectedItemIds: Selection) => void;
 }
@@
-                {actions.map(({ label, onClick }) => {
+                {actions.map(({ id, label, onClick }, index) => {
                     return (
-                        <DropdownMenu.Item key={label} onSelect={() => onClick(selectedItemIds)}>
+                        <DropdownMenu.Item key={id ?? `${label}-${index}`} onSelect={() => onClick(selectedItemIds)}>
                             {label}
                         </DropdownMenu.Item>
                     );
                 })}
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (1)

186-197: Story name says “Shows Ellipsis” but there’s no assertion — add one for intent clarity

The play function selects a row but doesn’t assert that the bulk actions trigger is present. Add an explicit check so the story exercises its stated behavior.

 export const metadataViewV2WithBulkItemActionMenuShowsEllipsis: Story = {
     args: metadataViewV2WithBulkItemActionMenuProps,
     play: async ({ canvas }) => {
         await waitFor(() => {
             expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
         });

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
         userEvent.click(checkbox);
+
+        await waitFor(() => {
+            expect(canvas.getByRole('button', { name: 'Show actions for selected items' })).toBeInTheDocument();
+        });
     },
 };
📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce592f and d9c83ed.

📒 Files selected for processing (9)
  • i18n/en-US.properties (1 hunks)
  • src/elements/common/sub-header/BulkItemActionMenu.tsx (1 hunks)
  • src/elements/common/sub-header/SubHeader.tsx (5 hunks)
  • src/elements/common/sub-header/SubHeaderRight.scss (1 hunks)
  • src/elements/common/sub-header/SubHeaderRight.tsx (6 hunks)
  • src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (1 hunks)
  • src/elements/common/sub-header/messages.ts (1 hunks)
  • src/elements/content-explorer/ContentExplorer.tsx (5 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/elements/common/sub-header/SubHeaderRight.scss
  • i18n/en-US.properties
  • src/elements/common/sub-header/messages.ts
  • src/elements/common/sub-header/tests/SubHeaderRight.test.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
  • src/elements/common/sub-header/SubHeader.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-15T14:42:09.910Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.

Applied to files:

  • src/elements/common/sub-header/BulkItemActionMenu.tsx
  • src/elements/content-explorer/ContentExplorer.tsx
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
📚 Learning: 2025-08-15T14:42:01.813Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/common/sub-header/BulkItemActionMenu.tsx
  • src/elements/content-explorer/ContentExplorer.tsx
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
🧬 Code Graph Analysis (1)
src/elements/content-explorer/ContentExplorer.tsx (1)
src/elements/common/sub-header/BulkItemActionMenu.tsx (1)
  • BulkItemActionMenuProps (16-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary

@jpan-box jpan-box force-pushed the mdv-bca branch 2 times, most recently from 11651a5 to 74005fa Compare August 18, 2025 01:24
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 (7)
src/elements/common/sub-header/SubHeaderRight.scss (3)

22-25: Potential double-spacing: use start-margin for consistency with siblings

Siblings use margin-left: 7px; adding margin-right here can create 14px of space between this trigger and the next control (7px right + 7px left). Also hurts RTL consistency.

Apply this minimal fix to align with existing pattern:

-    .be-SubHeaderRight-bulkItemActionMenuDropdownTrigger {
-        margin-right: 7px;
-    }
+    .be-SubHeaderRight-bulkItemActionMenuDropdownTrigger {
+        margin-left: 7px;
+    }

22-25: Prefer logical properties or flex gap for RTL-safe, uniform spacing

If feasible, prefer margin-inline-start (or a container gap) to avoid hardcoding physical left/right and to make RTL support trivial.

Option A — logical margin (localized change):

-    .be-SubHeaderRight-bulkItemActionMenuDropdownTrigger {
-        margin-right: 7px;
-    }
+    .be-SubHeaderRight-bulkItemActionMenuDropdownTrigger {
+        margin-inline-start: 7px;
+    }

Option B — container-level gap (requires touching siblings outside this hunk): set gap on the flex container and remove per-child margins.

In this file outside the selected lines:

  • Add to .be-sub-header-right: gap: 7px;
  • Remove margin-left from .be-btn-sort, .be-btn-add, .bdl-ViewModeChangeButton, and this new trigger.

Example:

.be-sub-header-right {
display: flex;
align-items: center;
gap: 7px;
}


22-25: Class naming consistency

Internal classes here are mostly kebab-case (e.g., .be-btn-sort), while this introduces a long mixed-case class. If this selector mirrors a component-provided class, all good; otherwise consider a kebab-case variant for consistency (non-blocking).

src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4)

99-127: Per-item inline actions: icons/submenu are fine; consider fn() instead of noop for future assertions

Current usage is valid since the “no icons” rule applies only to bulk actions. To make it easier to assert per-item action invocations later, consider using fn() instead of noop.

Apply this diff to swap noop for fn():

             actions: [
                 {
                     label: 'Download',
-                    onClick: noop,
+                    onClick: fn(),
                     icon: Download,
                 },
             ],
@@
             subMenuActions: [
                 {
                     label: 'Request Signature',
-                    onClick: noop,
+                    onClick: fn(),
                     icon: SignMeOthers,
                 },
             ],

185-196: Assert the ellipsis appears (and await clicks) to match the story’s intent

The story name promises visibility of the bulk ellipsis, but there’s no assertion. Also, awaiting userEvent.click reduces flakiness.

Apply this diff:

 export const metadataViewV2WithBulkItemActionMenuShowsEllipsis: Story = {
     args: metadataViewV2WithBulkItemActionMenuProps,
     play: async ({ canvas }) => {
         await waitFor(() => {
             expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
         });

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);
+
+        await waitFor(() => {
+            expect(canvas.getByRole('button', { name: 'Show actions for selected items' })).toBeInTheDocument();
+        });
     },
 };

198-221: Await interactions to avoid test flakiness

Awaiting userEvent.click for the checkbox and the ellipsis button yields more deterministic tests.

Apply this diff:

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);

         await waitFor(() => {
             expect(canvas.getByRole('button', { name: 'Show actions for selected items' })).toBeInTheDocument();
         });

         const ellipsisButton = canvas.getByRole('button', { name: 'Show actions for selected items' });

-        userEvent.click(ellipsisButton);
+        await userEvent.click(ellipsisButton);

223-252: Strengthen the onClick test: await interactions and verify callback receives selected IDs

  • Await each click to reduce timing issues.
  • Verify that the handler receives an array of selected IDs (length 1 in this scenario), aligning with the PR’s contract.

Apply this diff:

 export const metadataViewV2WithBulkItemActionMenuCallsOnClick: Story = {
     args: metadataViewV2WithBulkItemActionMenuProps,
     play: async ({ canvas, args }) => {
         await waitFor(() => {
             expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
         });

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);

         await waitFor(() => {
             expect(canvas.getByRole('button', { name: 'Show actions for selected items' })).toBeInTheDocument();
         });

         const ellipsisButton = canvas.getByRole('button', { name: 'Show actions for selected items' });

-        userEvent.click(ellipsisButton);
+        await userEvent.click(ellipsisButton);

         await waitFor(() => {
             expect(screen.getByRole('menuitem', { name: 'Download' })).toBeInTheDocument();
         });
         const downloadAction = screen.getByRole('menuitem', { name: 'Download' });
-        userEvent.click(downloadAction);
+        await userEvent.click(downloadAction);

         await waitFor(() => {
             expect(args.bulkItemActionMenuProps.actions[0].onClick).toHaveBeenCalled();
         });
+
+        // Verify the handler received a single-element array of selected IDs
+        await waitFor(() => {
+            const calls = (args.bulkItemActionMenuProps.actions[0].onClick as any).mock.calls;
+            expect(calls.length).toBeGreaterThan(0);
+            const [selectedIds] = calls[0];
+            expect(Array.isArray(selectedIds)).toBe(true);
+            expect(selectedIds).toHaveLength(1);
+        });
     },
 };
📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d9c83ed and 74005fa.

📒 Files selected for processing (6)
  • src/elements/common/sub-header/BulkItemActionMenu.tsx (1 hunks)
  • src/elements/common/sub-header/SubHeader.tsx (5 hunks)
  • src/elements/common/sub-header/SubHeaderRight.scss (1 hunks)
  • src/elements/common/sub-header/SubHeaderRight.tsx (6 hunks)
  • src/elements/content-explorer/ContentExplorer.tsx (5 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/elements/common/sub-header/SubHeader.tsx
  • src/elements/common/sub-header/BulkItemActionMenu.tsx
  • src/elements/content-explorer/ContentExplorer.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-15T14:42:09.910Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.

Applied to files:

  • src/elements/common/sub-header/SubHeaderRight.scss
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
📚 Learning: 2025-08-15T14:42:01.813Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (4)
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4)

5-5: Storybook test utilities import looks good

Using expect, fn, userEvent, waitFor, within, and screen from storybook/test is appropriate for play-function assertions and portal-rendered menus.


78-85: Bulk actions align with contract (no icons) and use a spy for onClick

Thanks for removing icons from bulk actions and wiring onClick with fn(); this matches the “no icons/subactions” assumption and avoids type errors noted earlier.


129-137: Correctly gating bulk menu behind Select All

Setting tableProps.isSelectAllEnabled = true for bulk-action scenarios matches the documented assumption.


279-280: Type alias move is fine

type Story = StoryObj is the standard Storybook 7/8 pattern. Looks good.

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 (5)
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5)

143-153: Await user interactions to reduce flakiness in the play function

Awaiting userEvent calls is recommended for reliability in Storybook’s test runner.

Apply this diff:

-        userEvent.click(ellipsesButton);
+        await userEvent.click(ellipsesButton);

185-201: Stabilize the test by awaiting the click; optionally assert precondition

  • Await the checkbox click to avoid race conditions.
  • Optional: assert the bulk actions button is not present before selection to strengthen the test.

Apply this diff:

         await waitFor(() => {
             expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
         });
 
-        const firstRow = canvas.getByRole('row', { name: /Child 2/i });
-        const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        const firstRow = canvas.getByRole('row', { name: /Child 2/i });
+        const checkbox = within(firstRow).getByRole('checkbox');
+        await userEvent.click(checkbox);
 
         await waitFor(() => {
             expect(canvas.getByRole('button', { name: 'Bulk actions' })).toBeInTheDocument();
         });

Optional strengthening (precondition check):

         await waitFor(() => {
             expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
         });
 
+        // Not shown until at least one selection exists
+        expect(canvas.queryByRole('button', { name: 'Bulk actions' })).not.toBeInTheDocument();

202-226: Await interactions; screen usage here is correct due to portalled menus

  • Await both checkbox and ellipsis clicks to reduce test flakiness.
  • Keep using screen for the menu item lookup since the menu is likely portalled outside the canvas.

Apply this diff:

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);
 
         await waitFor(() => {
             expect(canvas.getByRole('button', { name: 'Bulk actions' })).toBeInTheDocument();
         });
 
         const ellipsisButton = canvas.getByRole('button', { name: 'Bulk actions' });
 
-        userEvent.click(ellipsisButton);
+        await userEvent.click(ellipsisButton);
 
         await waitFor(() => {
             expect(screen.getByRole('menuitem', { name: 'Download' })).toBeInTheDocument();
         });

227-256: Await interactions and assert callback argument shape for stronger coverage

  • Await all userEvent.click calls.
  • In addition to “was called,” assert the callback received an array (selected IDs). This locks in the contract without relying on fixture-specific IDs.

Apply this diff:

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);
 
         await waitFor(() => {
             expect(canvas.getByRole('button', { name: 'Bulk actions' })).toBeInTheDocument();
         });
 
         const ellipsisButton = canvas.getByRole('button', { name: 'Bulk actions' });
 
-        userEvent.click(ellipsisButton);
+        await userEvent.click(ellipsisButton);
 
         await waitFor(() => {
             expect(screen.getByRole('menuitem', { name: 'Download' })).toBeInTheDocument();
         });
         const downloadAction = screen.getByRole('menuitem', { name: 'Download' });
-        userEvent.click(downloadAction);
+        await userEvent.click(downloadAction);
 
         await waitFor(() => {
             expect(args.bulkItemActionMenuProps.actions[0].onClick).toHaveBeenCalled();
+            expect(args.bulkItemActionMenuProps.actions[0].onClick).toHaveBeenCalledWith(expect.any(Array));
         });

129-137: Consider adding a story that verifies ellipsis shows even when actions is empty

Per the recorded learning, the dropdown should still show even if bulkItemActionMenuProps.actions is empty (preparing for a default Download). A small story will guard this behavior against regressions.

Example story (add near the other bulk stories):

export const metadataViewV2WithEmptyBulkActionsStillShowsEllipsis: Story = {
    args: {
        ...metadataViewV2WithBulkItemActionMenuProps,
        bulkItemActionMenuProps: { actions: [] },
    },
    play: async ({ canvas }) => {
        await waitFor(() => {
            expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
        });
        const firstRow = canvas.getByRole('row', { name: /Child 2/i });
        const checkbox = within(firstRow).getByRole('checkbox');
        await userEvent.click(checkbox);
        await waitFor(() => {
            expect(canvas.getByRole('button', { name: 'Bulk actions' })).toBeInTheDocument();
        });
    },
};

If you want, I can prep a patch adding this story and aligning all userEvent calls across the file.

Also applies to: 185-201

📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 74005fa and 6fa24f6.

📒 Files selected for processing (5)
  • i18n/en-US.properties (1 hunks)
  • src/elements/common/sub-header/BulkItemActionMenu.tsx (1 hunks)
  • src/elements/common/sub-header/SubHeaderRight.scss (1 hunks)
  • src/elements/common/sub-header/messages.ts (1 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/elements/common/sub-header/SubHeaderRight.scss
  • i18n/en-US.properties
  • src/elements/common/sub-header/messages.ts
  • src/elements/common/sub-header/BulkItemActionMenu.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-15T14:42:01.813Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
📚 Learning: 2025-08-15T14:42:09.910Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (5)
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5)

5-5: Importing screen is appropriate for portalled menus

Good call importing screen; it’s needed to query portalled menu content outside the canvas. No issues here.


78-85: Bulk menu props align with the “no icons” contract and use a proper mock

The bulk action menu uses fn() for onClick and omits icons, matching the stated assumptions and types. Looks good.


99-127: Inline item actions: icons and submenu usage are OK (bulk-only restriction doesn’t apply)

Using icon for inline item actions and a submenu trigger is fine and consistent with the “no icons” restriction applying only to bulk actions.


129-137: Select-all enabled to surface bulk actions

Setting tableProps.isSelectAllEnabled: true meets the condition for showing the bulk actions control. Good wiring.


283-284: Story type alias looks good

Switching to type Story = StoryObj<typeof meta> at the bottom is clear and conventional. No concerns.

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

♻️ Duplicate comments (1)
src/elements/common/sub-header/BulkItemActionMenu.tsx (1)

25-25: Align className with SUIT CSS naming used in this repo

Use the component’s PascalCase in the class for consistency with SUIT conventions previously noted in reviews.

Apply:

-            <DropdownMenu.Trigger className="be-bulkItemActionMenu-trigger">
+            <DropdownMenu.Trigger className="be-BulkItemActionMenu-trigger">

Also update the corresponding selector in SubHeaderRight.scss to match:

.be-BulkItemActionMenu-trigger {
  margin-right: var(--space-2);
}
🧹 Nitpick comments (3)
src/elements/common/sub-header/BulkItemActionMenu.tsx (3)

8-8: Use a simpler local import path for messages

Minor readability win; keep it relative to the current folder.

Apply:

-import messages from '../../common/sub-header/messages';
+import messages from './messages';

27-32: Remove redundant role="button" on a native Button

Button already has implicit button semantics. Let DropdownMenu.Trigger manage aria-haspopup/aria-expanded. Removing the redundant role reduces potential a11y noise.

Apply:

                 <Button
-                    role="button"
                     aria-label={formatMessage(messages.bulkItemActionMenuAriaLabel)}
                     icon={Ellipsis}
                     size="large"
                     variant="secondary"
                 />

10-13: Use a stable id for React keys instead of label

Using label as the key risks collisions and unstable reconciliation if labels change. Introduce an explicit id field and use it for keys.

Apply:

 export interface BulkItemAction {
+    id: string;
     label: string;
     onClick: (selectedItemIds: Selection) => void;
 }
-                {actions.map(({ label, onClick }) => {
+                {actions.map(({ id, label, onClick }) => {
                     return (
-                        <DropdownMenu.Item key={label} onSelect={() => onClick(selectedItemIds)}>
+                        <DropdownMenu.Item key={id} onSelect={() => onClick(selectedItemIds)}>
                             {label}
                         </DropdownMenu.Item>
                     );
                 })}

Also applies to: 35-39

📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa24f6 and 1758970.

📒 Files selected for processing (5)
  • i18n/en-US.properties (1 hunks)
  • src/elements/common/sub-header/BulkItemActionMenu.tsx (1 hunks)
  • src/elements/common/sub-header/SubHeaderRight.scss (1 hunks)
  • src/elements/common/sub-header/messages.ts (1 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/elements/common/sub-header/messages.ts
  • src/elements/common/sub-header/SubHeaderRight.scss
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-15T14:42:09.910Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.

Applied to files:

  • src/elements/common/sub-header/BulkItemActionMenu.tsx
  • i18n/en-US.properties
📚 Learning: 2025-08-15T14:42:01.813Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/common/sub-header/BulkItemActionMenu.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (1)
i18n/en-US.properties (1)

1827-1828: ARIA label key added correctly and matches descriptor intent

The new key and comment align with the BulkItemActionMenu trigger’s accessibility needs.

✅ Verified exactly one occurrence in i18n/en-US.properties.

⚠️ The key is currently missing in these locale files (potential l10n gaps):

  • i18n/bn-IN.properties
  • i18n/da-DK.properties
  • i18n/de-DE.properties
  • i18n/en-AU.properties
  • i18n/en-CA.properties
  • i18n/en-GB.properties
  • i18n/en-x-pseudo.properties
  • i18n/es-419.properties
  • i18n/es-ES.properties
  • i18n/fi-FI.properties
  • i18n/fr-CA.properties
  • i18n/fr-FR.properties
  • i18n/hi-IN.properties
  • i18n/it-IT.properties
  • i18n/ja-JP.properties
  • i18n/ko-KR.properties
  • i18n/nb-NO.properties
  • i18n/nl-NL.properties
  • i18n/pl-PL.properties
  • i18n/pt-BR.properties
  • i18n/ru-RU.properties
  • i18n/sv-SE.properties
  • i18n/tr-TR.properties
  • i18n/zh-CN.properties
  • i18n/zh-TW.properties

Please add this key with the appropriate translations to those files or confirm that its omission is intentional.

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

🔭 Outside diff range comments (1)
src/elements/common/sub-header/SubHeader.tsx (1)

18-42: Missing bulkItemActions forwarding to SubHeader

The bulkItemActions prop is defined on both ContentExplorerProps and SubHeaderProps, but it isn’t actually passed into the <SubHeader /> in ContentExplorer.tsx. Bulk actions won’t render unless this prop is threaded through.

• In src/elements/content-explorer/ContentExplorer.tsx (around line 1750), update the <SubHeader … /> invocation to include:

   <SubHeader
     view={view}
     viewMode={viewMode}
     rootId={rootFolderId}
     isSmall={isSmall}
     rootName={rootName}
     currentCollection={currentCollection}
     canUpload={allowUpload}
     canCreateNewFolder={allowCreate}
     gridColumnCount={gridColumnCount}
     gridMaxColumns={GRID_VIEW_MAX_COLUMNS}
     gridMinColumns={GRID_VIEW_MIN_COLUMNS}
     maxGridColumnCountForWidth={maxGridColumnCount}
     onUpload={this.upload}
     onClearSelectedItemIds={this.clearSelectedItemIds}
     onCreate={this.createFolder}
     onGridViewSliderChange={this.onGridViewSliderChange}
     onItemClick={this.fetchFolder}
     onSortChange={this.sort}
     onViewModeChange={this.changeViewMode}
     portalElement={this.rootElement}
+    bulkItemActions={bulkItemActions}
   />

• Ensure bulkItemActions is destructured (or accessed) from the ContentExplorer props and passed down.

This change will restore the intended bulk‐action menu in the SubHeader.

🧹 Nitpick comments (3)
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (2)

225-254: Also assert the selected IDs are passed to the bulk action callback

Right now the test only checks that onClick was called. Tighten the contract by asserting it’s called with a Set of selected IDs.

Apply this diff:

         await waitFor(() => {
             expect(screen.getByRole('menuitem', { name: 'Download' })).toBeInTheDocument();
         });
         const downloadAction = screen.getByRole('menuitem', { name: 'Download' });
         userEvent.click(downloadAction);

         await waitFor(() => {
-            expect(args.bulkItemActions[0].onClick).toHaveBeenCalled();
+            expect(args.bulkItemActions[0].onClick).toHaveBeenCalled();
+            // Ensure the handler receives the selected item ids
+            expect(args.bulkItemActions[0].onClick).toHaveBeenCalledWith(expect.any(Set));
         });

121-135: Minor: consider using a named mock for clarity

Using fn() inline is fine. If you find yourself expanding assertions later, a named mock (e.g., const onDownload = fn()) improves readability and reuse across stories.

src/elements/common/sub-header/SubHeader.tsx (1)

18-20: Optional: default bulkItemActions to an empty array to avoid downstream nullability friction

Since SubHeaderRight forwards these directly to BulkItemActionMenu, providing a default here prevents accidental undefined from propagating when other consumers (e.g., ContentPicker) omit the prop.

Apply this diff:

-const SubHeader = ({
-    bulkItemActions,
+const SubHeader = ({
+    bulkItemActions = [],

Also applies to: 44-69, 102-123

📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 12648ce and e080659.

📒 Files selected for processing (9)
  • i18n/en-US.properties (1 hunks)
  • src/elements/common/sub-header/BulkItemActionMenu.tsx (1 hunks)
  • src/elements/common/sub-header/SubHeader.tsx (5 hunks)
  • src/elements/common/sub-header/SubHeaderRight.scss (1 hunks)
  • src/elements/common/sub-header/SubHeaderRight.tsx (6 hunks)
  • src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (1 hunks)
  • src/elements/common/sub-header/messages.ts (1 hunks)
  • src/elements/content-explorer/ContentExplorer.tsx (5 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/elements/common/sub-header/tests/SubHeaderRight.test.tsx
  • src/elements/content-explorer/ContentExplorer.tsx
  • src/elements/common/sub-header/SubHeaderRight.scss
  • src/elements/common/sub-header/BulkItemActionMenu.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-15T14:42:09.910Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.

Applied to files:

  • src/elements/common/sub-header/messages.ts
  • i18n/en-US.properties
  • src/elements/common/sub-header/SubHeader.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
📚 Learning: 2025-08-15T14:42:01.813Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/common/sub-header/SubHeader.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
🧬 Code Graph Analysis (2)
src/elements/common/sub-header/SubHeader.tsx (1)
src/elements/common/sub-header/BulkItemActionMenu.tsx (1)
  • BulkItemAction (10-13)
src/elements/common/sub-header/SubHeaderRight.tsx (1)
src/elements/common/sub-header/BulkItemActionMenu.tsx (2)
  • BulkItemAction (10-13)
  • BulkItemActionMenu (20-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (5)
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (2)

183-198: LGTM: good coverage for “shows bulk ellipsis only when a row is selected”

The “select a row → see Bulk actions trigger” flow matches the stated assumptions and uses the ARIA label correctly.


200-223: LGTM: exercising menu open and item visibility via screen (portal-safe)

Using screen for menu content is the right call since the menu likely portals; this avoids false negatives.

i18n/en-US.properties (1)

1827-1829: LGTM: ARIA label for bulk action trigger

  • Key/value is correct and aligns with the message ID and existing tests.
  • Verified that boxui.subHeader.bulkItemActionMenuAriaLabel appears only once across all i18n/*.properties files—no duplicates found.
src/elements/common/sub-header/messages.ts (1)

4-8: LGTM: message descriptor aligns with key and tests

Key, defaultMessage, and description are consistent with en-US.properties and story queries.

src/elements/common/sub-header/SubHeaderRight.tsx (1)

23-41: Prop surface looks good and backward compatible

Making bulkItemActions and selectedItemIds optional here keeps this component usable by ContentPicker without forcing bulk actions.

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 (7)
src/elements/common/sub-header/SubHeaderRight.tsx (3)

103-108: Confirm intent: bulk menu hidden unless actions exist — this diverges from prior stated behavior

Current condition requires bulkItemActions.length > 0 to show the bulk menu. Previously (per learnings), the trigger was intentionally shown even when empty to accommodate a default Download action later.

If this change is intentional, ignore; otherwise, consider reverting to “show when there is a selection” and pass a safe empty array.

Apply this diff if you want to restore the previous behavior and keep it safe:

-                    {(selectedItemIds === 'all' || (selectedItemIds instanceof Set && selectedItemIds.size > 0)) &&
-                        bulkItemActions &&
-                        bulkItemActions.length > 0 && (
-                            <BulkItemActionMenu actions={bulkItemActions} selectedItemIds={selectedItemIds} />
-                        )}
+                    {(selectedItemIds === 'all' || (selectedItemIds instanceof Set && selectedItemIds.size > 0)) && (
+                        <BulkItemActionMenu
+                            actions={bulkItemActions ?? []}
+                            selectedItemIds={selectedItemIds as Selection}
+                        />
+                    )}

107-107: Type-safety: selectedItemIds is optional; BulkItemActionMenu expects Selection

Even with the guard, TS may not narrow selectedItemIds?: Selection to a concrete Selection for the JSX prop. Cast is sufficient here, since the condition guarantees presence.

Apply this diff:

-                            <BulkItemActionMenu actions={bulkItemActions} selectedItemIds={selectedItemIds} />
+                            <BulkItemActionMenu
+                                actions={bulkItemActions}
+                                selectedItemIds={selectedItemIds as Selection}
+                            />

24-24: API surface: optional props and feature gating

  • bulkItemActions? and selectedItemIds? are optional here. The product assumption states the trigger shows only if “Select All” is enabled. This file doesn’t know about tableProps.isSelectAllEnabled, so the gating likely happens upstream. Please confirm upstream guarantees (e.g., pass selectedItemIds only when bulk actions are enabled) to avoid accidental trigger visibility when selection is possible but Select All is disabled.

If you prefer explicitness, we can add a boolean prop like isBulkActionsEnabled to make the intent self-documenting and reduce coupling to upstream behavior.

Also applies to: 38-38

src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4)

183-198: Stabilize interactions by awaiting userEvent.click

To reduce flakiness in CI, await user interactions so event queues are flushed before assertions.

Apply this diff:

-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);

200-223: Good use of screen for portal content; consider awaiting userEvent.click and add a post-click assertion

Menu content likely renders in a portal, so screen is appropriate. Minor stability nit: await the click. Optionally assert focus or open state if available.

Apply this diff:

-        userEvent.click(ellipsisButton);
+        await userEvent.click(ellipsisButton);

225-254: Tighten assertion to validate payload shape; await clicks for stability

  • Since you select a single row, the callback should receive a Set (Selection) of 1 key. Asserting argument shape makes the test more valuable.
  • Also await the interaction steps.

Apply this diff:

-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);
@@
-        userEvent.click(ellipsisButton);
+        await userEvent.click(ellipsisButton);
@@
-        const downloadAction = screen.getByRole('menuitem', { name: 'Download' });
-        userEvent.click(downloadAction);
+        const downloadAction = screen.getByRole('menuitem', { name: 'Download' });
+        await userEvent.click(downloadAction);
@@
-        await waitFor(() => {
-            expect(args.bulkItemActions[0].onClick).toHaveBeenCalled();
-        });
+        await waitFor(() => {
+            expect(args.bulkItemActions[0].onClick).toHaveBeenCalledWith(expect.any(Set));
+        });

121-136: Optional: add a negative story to verify Select All gating

To guard the assumption “bulk icon only shows if Select All is enabled,” add a story with isSelectAllEnabled: false that verifies the ellipsis doesn’t appear even after selecting an item.

Here’s a ready-to-drop story you can add (outside this hunk):

export const metadataViewV2WithBulkItemActionMenuHiddenWhenSelectAllDisabled: Story = {
    args: {
        ...metadataViewV2ElementProps,
        bulkItemActions: [
            { label: 'Download', onClick: fn() },
        ],
        metadataViewProps: {
            columns,
            tableProps: {
                isSelectAllEnabled: false,
            },
        },
    },
    play: async ({ canvas }) => {
        await waitFor(() => {
            expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
        });
        const firstRow = canvas.getByRole('row', { name: /Child 2/i });
        const checkbox = within(firstRow).getByRole('checkbox');
        await userEvent.click(checkbox);

        // Expect: bulk actions trigger should not be present
        await waitFor(() => {
            expect(screen.queryByRole('button', { name: 'Bulk actions' })).not.toBeInTheDocument();
        });
    },
};
📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e080659 and a487726.

📒 Files selected for processing (4)
  • src/elements/common/sub-header/SubHeader.tsx (5 hunks)
  • src/elements/common/sub-header/SubHeaderRight.tsx (6 hunks)
  • src/elements/content-explorer/ContentExplorer.tsx (5 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/elements/common/sub-header/SubHeader.tsx
  • src/elements/content-explorer/ContentExplorer.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-15T14:42:01.813Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.813Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
📚 Learning: 2025-08-15T14:42:09.910Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:104-125
Timestamp: 2025-08-15T14:42:09.910Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the dropdown is intentionally shown even when bulkItemActionMenuProps has no actions, because Download will be made a default option available all the time in the future.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
🧬 Code Graph Analysis (1)
src/elements/common/sub-header/SubHeaderRight.tsx (1)
src/elements/common/sub-header/BulkItemActionMenu.tsx (2)
  • BulkItemAction (10-13)
  • BulkItemActionMenu (20-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (4)
src/elements/common/sub-header/SubHeaderRight.tsx (2)

8-8: LGTM: centralizing bulk actions into a dedicated component

Importing BulkItemAction and BulkItemActionMenu clarifies the API and keeps SubHeaderRight focused on orchestration. No issues found.


71-71: LGTM: clear, layered conditions (view type + feature flag)

Gating on both isMetadataView and isMetadataViewV2Feature keeps the legacy path untouched. Looks good.

src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (2)

121-136: Story args for bulk actions look correct

  • bulkItemActions uses fn() for onClick and no icons/sub-actions, matching the stated assumptions.
  • tableProps.isSelectAllEnabled: true aligns with the “only show when Select All is enabled” assumption for these stories.

91-119: Item-level custom actions with icons are fine; separate from bulk-action “no icons” constraint

The icons and sub-actions here apply to per-item action menus, not the bulk action menu, so they don’t violate the “no icons in bulk” assumption.

tjuanitas
tjuanitas previously approved these changes Aug 18, 2025
Copy link
Contributor

@tjuanitas tjuanitas left a comment

Choose a reason for hiding this comment

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

approving to unblock merge but i think these would be good improvements to quality

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

🧹 Nitpick comments (2)
src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1)

542-558: Scope the Download query to the opened menu to reduce flakiness

Querying the menu item through the menu container avoids accidental matches if other menus render in the DOM.

Apply this diff:

-                await waitFor(() => {
-                    expect(screen.getByRole('menuitem', { name: 'Download' })).toBeInTheDocument();
-                });
-
-                const downloadAction = screen.getByRole('menuitem', { name: 'Download' });
+                await waitFor(() => {
+                    expect(
+                        within(screen.getByRole('menu')).getByRole('menuitem', { name: 'Download' }),
+                    ).toBeInTheDocument();
+                });
+
+                const downloadAction = within(screen.getByRole('menu')).getByRole('menuitem', {
+                    name: 'Download',
+                });

Optional cleanup to simplify args extraction:

-                    const argsForFirstMockCall = mockOnClick.mock.calls[0];
-                    const firstArgToMockOnClick = argsForFirstMockCall[0];
+                    const [[firstArgToMockOnClick]] = mockOnClick.mock.calls;
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (1)

208-231: Await user interactions and scope the menu query to improve reliability

Two clicks aren’t awaited and the menu-item query isn’t scoped. Awaiting userEvent reduces timing flake; scoping the query to the menu reduces false positives.

Apply this diff:

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);

         await waitFor(() => {
             expect(canvas.getByRole('button', { name: 'Bulk actions' })).toBeInTheDocument();
         });

         const ellipsisButton = canvas.getByRole('button', { name: 'Bulk actions' });

-        userEvent.click(ellipsisButton);
+        await userEvent.click(ellipsisButton);

-        await waitFor(() => {
-            expect(screen.getByRole('menuitem', { name: 'Download' })).toBeInTheDocument();
-        });
+        await waitFor(() => {
+            expect(
+                within(screen.getByRole('menu')).getByRole('menuitem', { name: 'Download' }),
+            ).toBeInTheDocument();
+        });

Note: Keeping screen for the menu item is fine if the menu portals outside the canvas; scoping via within(screen.getByRole('menu')) reduces collisions.

📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1419ef0 and 5bfbf4d.

📒 Files selected for processing (2)
  • src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-18T20:57:20.993Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
📚 Learning: 2025-08-15T14:42:01.840Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (2)
src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1)

509-559: Solid RTL coverage for bulk bulk-item action flow

Well structured: selects a row, surfaces the Bulk actions trigger, invokes the action, and asserts the onClick payload as a Set of IDs. Matches the contract and the feature assumptions (requires isSelectAllEnabled, works with single selection).

src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (1)

121-129: LGTM: bulk actions wiring follows the contract

  • bulkItemActions provided with a single Download action and no icon/sub-actions (as per assumptions).
  • tableProps.isSelectAllEnabled set, ensuring the trigger can render when selection is present.

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 (5)
src/elements/common/sub-header/SubHeaderRight.tsx (1)

73-74: Fix type narrowing for Selection; introduce a reusable type guard and remove redundant checks

Accessing .size on selectedItemIds without narrowing can trip TypeScript since Selection is 'all' | Set<Key>. You already do a proper runtime/type-safe check in Lines 107-111. Consolidate by introducing a type guard and using it both to compute hasSelectedItems and to gate the menu rendering. This also eliminates the duplicate selection logic.

Apply this diff to use the guard:

-    const hasSelectedItems: boolean = !!(selectedItemIds && (selectedItemIds === 'all' || selectedItemIds.size > 0));
+    const hasSelectedItems = hasSelection(selectedItemIds);

And update the inner render condition:

-                    {(selectedItemIds === 'all' || (selectedItemIds instanceof Set && selectedItemIds.size > 0)) &&
-                        bulkItemActions &&
-                        bulkItemActions.length > 0 && (
-                            <BulkItemActionMenu actions={bulkItemActions} selectedItemIds={selectedItemIds} />
-                        )}
+                    {hasSelection(selectedItemIds) && bulkItemActions && bulkItemActions.length > 0 && (
+                        <BulkItemActionMenu actions={bulkItemActions} selectedItemIds={selectedItemIds} />
+                    )}

Add this type guard near the top of the file (outside the diffed lines):

const hasSelection = (sel?: Selection): sel is Selection =>
    sel === 'all' || (sel instanceof Set && sel.size > 0);
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (1)

215-226: Await user interactions to avoid race conditions in VRT

Two clicks aren’t awaited. Storybook’s userEvent APIs are async; add await for stability and to prevent flaky screenshots.

Apply this diff:

-        const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        const checkbox = within(firstRow).getByRole('checkbox');
+        await userEvent.click(checkbox);

-        const ellipsisButton = canvas.getByRole('button', { name: 'Bulk actions' });
-
-        userEvent.click(ellipsisButton);
+        const ellipsisButton = canvas.getByRole('button', { name: 'Bulk actions' });
+        await userEvent.click(ellipsisButton);
src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (2)

130-161: Great: verifies onClick payload for 'all' selection

This asserts the callback receives 'all' when that mode is active. Consider also adding a case to ensure the trigger is not shown when bulkItemActions is empty/undefined to protect against regressions in the visibility rule.

If you want, I can add a test like:

  • “does not render Bulk actions when bulkItemActions is empty”
  • “does not render Bulk actions when selectedItemIds is an empty Set”

163-179: Align test name with assertions

This test hides both the bulk actions trigger and the metadata button when the feature is disabled. Rename to reflect both expectations.

Apply this diff:

-        test('should not render metadata button when metadataViewV2 feature is disabled', async () => {
+        test('should hide Bulk actions and Metadata button when metadataViewV2 feature is disabled', async () => {
src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1)

509-559: Tighten assertion: also assert single invocation

Solid end-to-end coverage of the bulk action flow. Add toHaveBeenCalledTimes(1) to make the expectation stricter and reduce false positives from accidental double-invocations.

Apply this diff:

                 const expectedOnClickArgument = new Set(['1188890835']);
                 await waitFor(() => {
-                    expect(mockOnClick).toHaveBeenCalled();
+                    expect(mockOnClick).toHaveBeenCalled();
+                    expect(mockOnClick).toHaveBeenCalledTimes(1);
 
                     // Array conversion from sets to avoid set comparison issues in Jest
                     const argsForFirstMockCall = mockOnClick.mock.calls[0];
                     const firstArgToMockOnClick = argsForFirstMockCall[0];
 
                     expect(Array.from(firstArgToMockOnClick)).toEqual(Array.from(expectedOnClickArgument));
                 });
📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5bfbf4d and a73a97d.

📒 Files selected for processing (4)
  • src/elements/common/sub-header/SubHeaderRight.tsx (5 hunks)
  • src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (3 hunks)
  • src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-18T20:57:20.993Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
📚 Learning: 2025-08-15T14:42:01.840Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
  • src/elements/common/sub-header/SubHeaderRight.tsx
🧬 Code Graph Analysis (2)
src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (2)
src/elements/common/sub-header/SubHeaderRight.tsx (1)
  • SubHeaderRightProps (23-42)
src/elements/content-explorer/ContentExplorer.tsx (1)
  • render (1691-1964)
src/elements/common/sub-header/SubHeaderRight.tsx (1)
src/elements/common/sub-header/BulkItemActionMenu.tsx (2)
  • BulkItemAction (10-13)
  • BulkItemActionMenu (20-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (5)
src/elements/common/sub-header/SubHeaderRight.tsx (2)

105-116: Conditional render logic reads correctly and matches feature gating

The grouping isMetadataView && isMetadataViewV2Feature && hasSelectedItems and the additional guard to only show the bulk menu when actions exist align with the “show trigger only when actions are available” learning. With the type guard refactor above, this block can stay logically unchanged while getting simpler types.


8-8: Import style is correct

Using import { type BulkItemAction, BulkItemActionMenu } adheres to the preferred type-import style and matches prior feedback.

src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (2)

121-135: OK to use fn() for non-assertive story

Using fn() here is fine since this story validates visual states and doesn’t assert calls. If you ever promote this to assert behavior, switch to a captured mock to make expectations explicit.


258-259: Type alias placement is fine

Declaring type Story = StoryObj<typeof meta> at the bottom is acceptable; TS resolves types regardless of order, and meta is defined above. No change needed.

src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (1)

105-129: Positive: selection → bulk menu rendering is covered (both 'all' and Set)

Good coverage ensuring the trigger appears for both 'all' and non-empty Set selections under the feature flag.

tjuanitas
tjuanitas previously approved these changes Aug 20, 2025
Copy link
Contributor

@tjuanitas tjuanitas left a comment

Choose a reason for hiding this comment

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

i had nits but it's fine from me

tjuanitas
tjuanitas previously approved these changes Aug 20, 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

♻️ Duplicate comments (1)
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (1)

272-273: Type alias Story is declared after first use; move it above story exports

Story is referenced starting at Line 69. Type aliases aren’t hoisted; depending on TS config this can error. Move the alias near the top, independent of meta, to break the cycle.

-type Story = StoryObj<typeof meta>;
+// Moved near the top; see snippet below.

Add this near the imports (above the first export const ...: Story):

// Place after imports so it's available to all story exports
type Story = StoryObj<typeof ContentExplorer>;
🧹 Nitpick comments (5)
src/elements/common/sub-header/messages.ts (1)

4-8: Standardize “ARIA” capitalization in sub-header message description

Minor nit: update the description to capitalize “ARIA” consistently with accessibility terminology.

Location:

  • File: src/elements/common/sub-header/messages.ts (lines 4–8)

Suggested diff:

     bulkItemActionMenuAriaLabel: {
         defaultMessage: 'Bulk actions',
-        description: 'Aria-label for the dropdown menu that shows actions for selected items',
+        description: 'ARIA label for the dropdown menu that shows actions for selected items',
         id: 'boxui.subHeader.bulkItemActionMenuAriaLabel',
     },
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (4)

5-5: Prefer canvas-scoped queries; drop screen import once updated below

We’ll switch the later assertions to canvas-scoped findByRole to avoid cross-story leakage and flakiness. Then screen is unused.

-import { expect, fn, userEvent, waitFor, within, screen } from 'storybook/test';
+import { expect, fn, userEvent, waitFor, within } from 'storybook/test';

91-119: Inline custom actions configuration is clear and type-aligned

Use of icons/submenu is appropriate for inline item actions (bulk actions remain iconless). Table isSelectAllEnabled here is harmless, though only required for bulk actions per your assumptions.

If you want to tighten scope, you can omit tableProps here since it’s not needed for row-level inline menus.


121-136: Bulk actions story props meet the contract (no icons, Select All enabled)

Looks correct. One small improvement: bind the mock so it’s assertable later if you add an interaction assertion.

-    bulkItemActions: [
-        {
-            label: 'Download',
-            onClick: fn(),
-        },
-    ],
+    bulkItemActions: [
+        {
+            label: 'Download',
+            onClick: fn(), // consider assigning to a const if you plan to assert calls
+        },
+    ],

222-246: Await user events and prefer canvas-scoped findByRole to reduce flakiness

Two clicks aren’t awaited, and the last assertion mixes in screen. Awaiting events and using canvas.findByRole simplifies timing and scoping.

 export const metadataViewV2WithBulkItemActionMenuShowsItemActionMenu: Story = {
     args: metadataViewV2WithBulkItemActions,
     play: async ({ canvas }) => {
         await waitFor(() => {
             expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument();
         });

         const firstRow = canvas.getByRole('row', { name: /Child 2/i });
         const checkbox = within(firstRow).getByRole('checkbox');
-        userEvent.click(checkbox);
+        await userEvent.click(checkbox);

-        await waitFor(() => {
-            expect(canvas.getByRole('button', { name: 'Bulk actions' })).toBeInTheDocument();
-        });
-
-        const ellipsisButton = canvas.getByRole('button', { name: 'Bulk actions' });
-
-        userEvent.click(ellipsisButton);
-
-        await waitFor(() => {
-            expect(screen.getByRole('menuitem', { name: 'Download' })).toBeInTheDocument();
-        });
+        const bulkActionsButton = await canvas.findByRole('button', { name: 'Bulk actions' });
+        await userEvent.click(bulkActionsButton);
+        await canvas.findByRole('menuitem', { name: 'Download' });
     },
 };

After this change, the screen import can be removed (see comment on Line 5).

📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between af4dd88 and dff6a41.

📒 Files selected for processing (10)
  • i18n/en-US.properties (1 hunks)
  • src/elements/common/sub-header/BulkItemActionMenu.tsx (1 hunks)
  • src/elements/common/sub-header/SubHeader.tsx (4 hunks)
  • src/elements/common/sub-header/SubHeaderRight.scss (1 hunks)
  • src/elements/common/sub-header/SubHeaderRight.tsx (5 hunks)
  • src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (3 hunks)
  • src/elements/common/sub-header/messages.ts (1 hunks)
  • src/elements/content-explorer/ContentExplorer.tsx (4 hunks)
  • src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1 hunks)
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/elements/common/sub-header/tests/SubHeaderRight.test.tsx
  • src/elements/common/sub-header/SubHeaderRight.scss
  • src/elements/common/sub-header/BulkItemActionMenu.tsx
  • src/elements/content-explorer/ContentExplorer.tsx
  • src/elements/common/sub-header/SubHeader.tsx
  • src/elements/content-explorer/tests/ContentExplorer.test.tsx
  • i18n/en-US.properties
  • src/elements/common/sub-header/SubHeaderRight.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-18T20:57:20.993Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.

Applied to files:

  • src/elements/common/sub-header/messages.ts
  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
📚 Learning: 2025-08-15T14:42:01.840Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (1)
src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx (1)

156-156: Nice reuse via extracted props

Switching to metadataViewV2WithInlineCustomActionsElementProps reduces duplication in stories.

@jpan-box jpan-box force-pushed the mdv-bca branch 2 times, most recently from 5426699 to 49e0580 Compare August 21, 2025 03:03
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

♻️ Duplicate comments (1)
src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (1)

39-39: Call renderComponent() with no args when you want the defaults.

Passing defaultProps again is unnecessary since renderComponent already applies them.

Apply this diff:

-        renderComponent(defaultProps);
+        renderComponent();

Also applies to: 55-55, 68-68, 82-82

🧹 Nitpick comments (5)
src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (4)

28-32: Avoid re-spreading defaultProps inside overrides.

renderComponent already spreads defaultProps; re-spreading them in each call is redundant and can mask future changes by altering precedence. Pass only the overrides.

Apply these diffs:

@@
-        renderComponent({
-            ...defaultProps,
-            currentCollection: { items: [{ id: '1' }] },
-            gridColumnCount: 3,
-        });
+        renderComponent({
+            currentCollection: { items: [{ id: '1' }] },
+            gridColumnCount: 3,
+        });
@@
-        renderComponent({
-            ...defaultProps,
-            currentCollection: { items: [{ id: '1' }] },
-            gridColumnCount: 3,
-        });
+        renderComponent({
+            currentCollection: { items: [{ id: '1' }] },
+            gridColumnCount: 3,
+        });
@@
-        renderComponent({
-            ...defaultProps,
-            canCreateNewFolder: true,
-            currentCollection: { items: [{ id: '1' }] },
-        });
+        renderComponent({
+            canCreateNewFolder: true,
+            currentCollection: { items: [{ id: '1' }] },
+        });

Also applies to: 46-50, 73-77


99-118: Nice parameterized coverage for ‘all’ vs Set selections.

This ensures the trigger renders for both selection modes under the feature flag.

For slightly more resilient queries against i18n/title-casing, consider a case-insensitive name matcher:

-                expect(screen.getByRole('button', { name: 'Bulk actions' })).toBeInTheDocument();
+                expect(screen.getByRole('button', { name: /bulk actions/i })).toBeInTheDocument();

120-151: Verify userEvent setup style matches your test-utils wrapper.

If your testing-library wrapper re-exports @testing-library/user-event v14, the recommended usage is userEvent.setup(). If userEvent() is a convenience your wrapper provides, you’re fine; otherwise switch to .setup().

Apply this if needed:

-            const user = userEvent();
+            const user = userEvent.setup();

Also, add a complementary assertion to cover Set selections to ensure onClick receives the exact Set instance:

@@
         test('should call onClick when a bulk item action is clicked', async () => {
@@
             expect(mockOnClick).toHaveBeenCalledWith(expectedOnClickArgument);
         });
+
+        test('should pass a Set of selected IDs to onClick when selection is a Set', async () => {
+            const mockOnClick = jest.fn();
+            const user = userEvent.setup();
+            const features = { contentExplorer: { metadataViewV2: true } };
+            const selection = new Set(['1', '2']);
+
+            renderComponent(
+                {
+                    ...metadataViewV2Props,
+                    selectedItemIds: selection,
+                    bulkItemActions: [{ label: 'Download', onClick: mockOnClick }],
+                },
+                features,
+            );
+
+            await user.click(await screen.findByRole('button', { name: /bulk actions/i }));
+            await user.click(screen.getByRole('menuitem', { name: 'Download' }));
+            expect(mockOnClick).toHaveBeenCalledWith(selection);
+        });

152-164: Good negative test for feature-flag gating.

Confirms both Bulk actions and Metadata controls are withheld when the flag is off.

Consider an additional negative test where bulkItemActions is an empty array (or undefined) but the flag is on, to lock in the “show trigger only when actions exist” behavior.

src/elements/common/sub-header/SubHeaderRight.tsx (1)

73-73: Guard Selection union properly; avoid accessing .size on non-Set.

Selection is Set<Key> | 'all' | undefined. Accessing .size without a Set check can trip TypeScript and is less explicit. Use an instanceof Set guard (or a type predicate) for clarity and type-safety.

Apply this diff:

-    const hasSelectedItems: boolean = !!(selectedItemIds && (selectedItemIds === 'all' || selectedItemIds.size > 0));
+    const hasSelectedItems = selectedItemIds === 'all' || (selectedItemIds instanceof Set && selectedItemIds.size > 0);

Optional: if you want TS to narrow selectedItemIds to Selection in JSX, introduce a type guard:

const hasSelection = (s?: Selection): s is Selection =>
    s === 'all' || (s instanceof Set && s.size > 0);

Then use isMetadataView && isMetadataViewV2Feature && hasSelection(selectedItemIds) in the render condition.

📜 Review details

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

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5426699 and 49e0580.

📒 Files selected for processing (3)
  • src/elements/common/sub-header/SubHeaderRight.tsx (5 hunks)
  • src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (3 hunks)
  • src/elements/content-explorer/__tests__/ContentExplorer.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/elements/content-explorer/tests/ContentExplorer.test.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.
📚 Learning: 2025-08-18T20:57:20.993Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeaderRight.tsx:0-0
Timestamp: 2025-08-18T20:57:20.993Z
Learning: In the SubHeaderRight component's bulk action menu (src/elements/common/sub-header/SubHeaderRight.tsx), the BulkItemActionMenu trigger should only be shown when there are actual bulk actions available (bulkItemActions exists and has length > 0), not when the actions array is empty.

Applied to files:

  • src/elements/common/sub-header/SubHeaderRight.tsx
📚 Learning: 2025-08-15T14:42:01.840Z
Learnt from: jpan-box
PR: box/box-ui-elements#4227
File: src/elements/common/sub-header/SubHeader.tsx:19-19
Timestamp: 2025-08-15T14:42:01.840Z
Learning: In SubHeader.tsx, the bulkItemActionMenuProps prop is intentionally required (not optional) because there will always be a default "Download" action available, ensuring the prop is never undefined in actual usage.

Applied to files:

  • src/elements/common/sub-header/SubHeaderRight.tsx
🧬 Code Graph Analysis (2)
src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (2)
src/elements/common/sub-header/SubHeaderRight.tsx (1)
  • SubHeaderRightProps (23-42)
src/elements/content-explorer/ContentExplorer.tsx (1)
  • render (1700-1973)
src/elements/common/sub-header/SubHeaderRight.tsx (1)
src/elements/common/sub-header/BulkItemActionMenu.tsx (2)
  • BulkItemAction (10-13)
  • BulkItemActionMenu (20-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (4)
src/elements/common/sub-header/__tests__/SubHeaderRight.test.tsx (2)

24-26: LGTM: test utility now cleanly injects feature flags via wrapperProps.

The render helper taking a features arg keeps tests focused and removes the need for a dedicated provider in each spec.


86-98: Good fixture setup for metadataViewV2 path.

Predefining selectedItemIds, bulkItemActions, and view keeps the tests concise and aligned with the component’s contract.

src/elements/common/sub-header/SubHeaderRight.tsx (2)

23-25: Public API extension looks appropriate.

Making bulkItemActions?: BulkItemAction[] available at this level matches the wiring shown in ContentExplorer → SubHeader and keeps SubHeaderRight decoupled.


105-114: Correct: render BulkItemActionMenu only when actions exist and items are selected.

This aligns with the agreed behavior that the trigger should not appear with an empty actions list.

If TypeScript complains about passing an optional selectedItemIds to BulkItemActionMenu (which requires Selection), either rely on the suggested type guard in the outer condition or cast locally:

-                        <BulkItemActionMenu actions={bulkItemActions} selectedItemIds={selectedItemIds} />
+                        <BulkItemActionMenu actions={bulkItemActions} selectedItemIds={selectedItemIds as Selection} />

@mergify mergify bot merged commit 371ad5e into box:master Aug 21, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants