Skip to content

component(ContextMenuViewport): move to ui-next with theme support#6008

Open
dan-rukas wants to merge 4 commits into
OHIF:masterfrom
dan-rukas:feat/context-menu-update
Open

component(ContextMenuViewport): move to ui-next with theme support#6008
dan-rukas wants to merge 4 commits into
OHIF:masterfrom
dan-rukas:feat/context-menu-update

Conversation

@dan-rukas
Copy link
Copy Markdown
Member

@dan-rukas dan-rukas commented May 8, 2026

Context

Moves the viewport context menu from @ohif/ui to @ohif/ui-next as ContextMenuViewport. The legacy component used hardcoded colors that don't respond to theming. The new component uses ui-next token-based styling and includes minor UX improvements.

Changes & Results

  • Added ContextMenuViewport to @ohif/ui-next — same props API as the legacy component, now with theme-aware styling and UX polish
  • Updated contextMenuUICustomization.ts to import from @ohif/ui-next
  • No changes to the controller, items builder, or any mode/extension customizations

Testing

  1. Load a study and create a measurement annotation
  2. Right-click the annotation — context menu should appear with themed colors
  3. Verify "Delete measurement" and "Add Label" work as expected
  4. Dismiss via Escape or clicking outside

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Suggested PR title: feat(ui-next): add ContextMenuViewport component with theme support

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • OS: macOS Sequoia 15.7.4 (24G517)
  • Node version: 20.19.1
  • Browser: Chrome 147.0.7727.138 (Official Build) (arm64)

Greptile Summary

Migrates the viewport context menu from @ohif/ui (ContextMenu) to @ohif/ui-next (ContextMenuViewport), replacing hardcoded color classes with Tailwind token-based theming and adding a subtle open animation. The props contract and item-action wiring are preserved, so existing controller and menu-item builder code works without changes.

  • ContextMenuViewport is functionally equivalent to the old component; item.action(item, props) still has access to onClose, onShowSubMenu, and onDefault via the ...props spread, so menu close-on-click and submenu navigation continue to work.
  • cursor-default select-none replaces cursor-pointer on interactive items — this matches shadcn/ui conventions but removes the pointer-cursor affordance users typically expect from clickable menu rows.
  • defaultPosition is declared in the TypeScript interface but is never read by the component (positioning is managed externally by the dialog service).

Confidence Score: 4/5

Safe to merge; the migration is behaviorally equivalent and all observations are minor style and polish items.

The component faithfully mirrors the old ContextMenu's prop wiring and close-on-click behavior. The only tangible change worth a second look is the switch from cursor-pointer to cursor-default on menu items, which removes a standard interactive affordance without a clear design reason.

ContextMenuViewport.tsx — the cursor class and unused interface prop are both worth a quick look before merge.

Important Files Changed

Filename Overview
platform/ui-next/src/components/ContextMenuViewport/ContextMenuViewport.tsx New component replacing the legacy ContextMenu; functionally equivalent with token-based theming, but uses cursor-default instead of cursor-pointer on interactive items and declares an unused defaultPosition prop in the interface.
extensions/default/src/customizations/contextMenuUICustomization.ts Single-line import swap from @ohif/ui ContextMenu to @ohif/ui-next ContextMenuViewport; straightforward and correct.
platform/ui-next/src/components/ContextMenuViewport/index.ts Standard barrel export for the new component; no issues.
platform/ui-next/src/components/index.ts Adds ContextMenuViewport import and export to the ui-next barrel; correctly placed alongside peer components.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
platform/ui-next/src/components/ContextMenuViewport/ContextMenuViewport.tsx:31
**Cursor no longer signals clickability on menu items**

The old component used `cursor-pointer`, but the new one uses `cursor-default`. Context menu items are interactive (they fire `item.action` on click), so users expect the hand cursor as a hover affordance. With `cursor-default`, hovering over any item looks the same as hovering over non-interactive content, which is a subtle but real UX regression — especially for power users who rely on the pointer change before clicking.

### Issue 2 of 3
platform/ui-next/src/components/ContextMenuViewport/ContextMenuViewport.tsx:11-12
`defaultPosition` is declared in the interface but never read in the component body. Since the dialog service handles positioning externally via `uiDialogService.show({ defaultPosition })`, this prop will never be forwarded to the component as a prop, making the declaration dead code.

```suggestion
  [key: string]: unknown;
```

### Issue 3 of 3
platform/ui-next/src/components/ContextMenuViewport/ContextMenuViewport.tsx:16-18
**Empty array bypasses the early-return guard**

The guard `if (!items)` returns `null` only when `items` is `undefined` or `null`. If `ContextMenuItemsBuilder.getMenuItems` returns `[]`, the controller's own `if (!items) return` also passes through, and the component renders an empty container div — a small invisible-but-present box at the calculated position. Adding `|| items.length === 0` to the guard would prevent this; note the old component has the same behavior so this is not a regression introduced by this PR.

Reviews (1): Last reviewed commit: "Refine styling" | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 8, 2026

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit 0173f26
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/69fe0028241b1d0008fc13dd
😎 Deploy Preview https://deploy-preview-6008--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment on lines +11 to +12
defaultPosition?: { x: number; y: number };
[key: string]: unknown;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 defaultPosition is declared in the interface but never read in the component body. Since the dialog service handles positioning externally via uiDialogService.show({ defaultPosition }), this prop will never be forwarded to the component as a prop, making the declaration dead code.

Suggested change
defaultPosition?: { x: number; y: number };
[key: string]: unknown;
[key: string]: unknown;
Prompt To Fix With AI
This is a comment left during a code review.
Path: platform/ui-next/src/components/ContextMenuViewport/ContextMenuViewport.tsx
Line: 11-12

Comment:
`defaultPosition` is declared in the interface but never read in the component body. Since the dialog service handles positioning externally via `uiDialogService.show({ defaultPosition })`, this prop will never be forwarded to the component as a prop, making the declaration dead code.

```suggestion
  [key: string]: unknown;
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think @greptileai is correct here. Please update.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed — here's the fix. Just drop the defaultPosition line; the index signature already handles any extra props passed through:

Suggested change
defaultPosition?: { x: number; y: number };
[key: string]: unknown;
[key: string]: unknown;

That removes the dead declaration while keeping the interface flexible for anything else the dialog service might forward.

Comment on lines +16 to +18
if (!items) {
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Empty array bypasses the early-return guard

The guard if (!items) returns null only when items is undefined or null. If ContextMenuItemsBuilder.getMenuItems returns [], the controller's own if (!items) return also passes through, and the component renders an empty container div — a small invisible-but-present box at the calculated position. Adding || items.length === 0 to the guard would prevent this; note the old component has the same behavior so this is not a regression introduced by this PR.

Prompt To Fix With AI
This is a comment left during a code review.
Path: platform/ui-next/src/components/ContextMenuViewport/ContextMenuViewport.tsx
Line: 16-18

Comment:
**Empty array bypasses the early-return guard**

The guard `if (!items)` returns `null` only when `items` is `undefined` or `null`. If `ContextMenuItemsBuilder.getMenuItems` returns `[]`, the controller's own `if (!items) return` also passes through, and the component renders an empty container div — a small invisible-but-present box at the calculated position. Adding `|| items.length === 0` to the guard would prevent this; note the old component has the same behavior so this is not a regression introduced by this PR.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since it is not a regression let's leave it.

@jbocce
Copy link
Copy Markdown
Collaborator

jbocce commented May 11, 2026

@claude review

Copy link
Copy Markdown
Collaborator

@jbocce jbocce left a comment

Choose a reason for hiding this comment

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

Thanks for this. One small comment.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants