Skip to content

Conversation

@SeanBarker182
Copy link
Contributor

@SeanBarker182 SeanBarker182 commented Sep 25, 2025

Adds a menu to the Windows version of Reactotron.

Screen.Recording.2025-11-04.at.1.38.14.PM.mov

(Click to view screen recording.)

We went with a React Native menu system rather than native for flexibility; however, we may want to replace with a system menu in the future.

Here's a system menu as an example:

Windows system menu

For the future, we can possibly use GetSystemMenu and:

AppendMenuW(hMenu, MF_STRING, ID_MENU_ITEM, L"Menu Item");
InsertMenuW(hMenu, position, MF_BYPOSITION | MF_STRING, ID_MENU_ITEM, L"Item");
ModifyMenuW(hMenu, ID_MENU_ITEM, MF_BYCOMMAND | MF_STRING, ID_MENU_ITEM, L"New Text");
DeleteMenu(hMenu, ID_MENU_ITEM, MF_BYCOMMAND);

@SeanBarker182 SeanBarker182 marked this pull request as draft September 25, 2025 21:04
@SeanBarker182 SeanBarker182 changed the title WIP: Windows Menu Support Windows Menu Support Nov 4, 2025
@SeanBarker182 SeanBarker182 marked this pull request as ready for review November 4, 2025 19:09
Copy link
Member

@jamonholmgren jamonholmgren left a comment

Choose a reason for hiding this comment

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

I forgot to send these earlier this week ....

Comment on lines +23 to +25
const isSeparator = (item: MenuItem | typeof MENU_SEPARATOR): item is typeof MENU_SEPARATOR => {
return item === MENU_SEPARATOR
}
Copy link
Member

Choose a reason for hiding this comment

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

I'd move this function out of the component, since it doesn't seem to rely on anything in scope here in the component. Could either move it below the component as a hoisted function isSeparator or into a util somewhere.

@@ -0,0 +1,21 @@
import { PlatformShortcut } from "app/utils/useSystemMenu/types"
Copy link
Member

Choose a reason for hiding this comment

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

Change to relative path (e.g. ../../...); while this works okay for types, if you try to import something from app/* at runtime, Metro won't find it.

export type DropdownMenuItem = MenuItem

// Menu separator constant
export const MENU_SEPARATOR = "menu-item-separator" as const
Copy link
Member

Choose a reason for hiding this comment

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

I think this const could live in useSystemMenu instead ... that way this file could be entirely types and would be removed at runtime. I get why it kinda makes sense here too, just a suggestion.

}: MenuDropdownItemProps) => {
const [hoveredItem, setHoveredItem] = useState<string | null>(null)
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const enabled = item.enabled !== false
Copy link
Member

Choose a reason for hiding this comment

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

Does it make more sense to go with disabled rather than enabled, so you don't have to check for false explicitly, and it aligns better with HTML's disabled prop?

const handlePress = useCallback(() => {
if (!item.action || !enabled) return
item.action()
onItemPress(item)
Copy link
Member

Choose a reason for hiding this comment

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

This line (calling onItemPress) gets skipped even if the item is enabled and has an onItemPress callback, but is missing the item.action. Is that intentional?

onHoverIn={handleHoverIn}
onHoverOut={handleHoverOut}
onPress={handlePress}
disabled={!enabled}
Copy link
Member

Choose a reason for hiding this comment

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

This gives more weight to the idea that we might want to use disabled instead of enabled.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants