Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8c660da
Simplify for example
SeanBarker182 Sep 12, 2025
ae2c078
fix: include modules that annotate with REACT_TURBO_MODULE
SeanBarker182 Sep 17, 2025
f0e9e97
Merge branch 'main' into feature/windows-menu
SeanBarker182 Sep 17, 2025
12c9590
use turbo module
SeanBarker182 Sep 22, 2025
9a2c3c8
Fix fabric imports
SeanBarker182 Sep 23, 2025
35f6eac
Add passthrough module
SeanBarker182 Sep 23, 2025
c687230
Group titlebar components
SeanBarker182 Sep 23, 2025
fb794cd
Code style fixes
SeanBarker182 Sep 23, 2025
a66f7c2
lint fix
SeanBarker182 Sep 23, 2025
d678ee7
Simplify module
SeanBarker182 Sep 24, 2025
d6a01be
Fix component registration
SeanBarker182 Sep 24, 2025
540d1d8
Fix memory leak issue that's caused by mounting and unmounting passt…
SeanBarker182 Sep 24, 2025
bdc9036
Merge branch 'feature/windows-menu' of https://github.com/infinitered…
SeanBarker182 Sep 24, 2025
d63a9e0
generate uuid
SeanBarker182 Sep 25, 2025
8e735a7
Add basic menu
SeanBarker182 Sep 25, 2025
0f06046
Add titlebar menu
SeanBarker182 Sep 25, 2025
3a1ae66
More stable useGlobal for windows
SeanBarker182 Sep 25, 2025
fa870f6
WIP: Add windows support for useMenuItem hook
SeanBarker182 Sep 25, 2025
0478fd9
Merge branch 'main' into feature/windows-menu
SeanBarker182 Sep 25, 2025
0745644
Rename IRMenuItemManager to IRSystemMenuManager for clarity
SeanBarker182 Sep 26, 2025
e3cc8ad
Fix native types
SeanBarker182 Sep 26, 2025
210bc9a
Break out platform implementations
SeanBarker182 Sep 26, 2025
10e6a51
improve and document the useGlobal windows hook
SeanBarker182 Sep 26, 2025
a5e2ac3
Remove RNW useKeyboard return
SeanBarker182 Sep 26, 2025
c9f3ea5
fix imports
SeanBarker182 Sep 26, 2025
fbd9f0f
rename
SeanBarker182 Sep 26, 2025
31456f5
Merge branch 'main' into feature/windows-menu
SeanBarker182 Nov 4, 2025
5f7f3bb
Use a content island to listen to keyboard
SeanBarker182 Nov 4, 2025
ff2c72d
Make shortcuts platform specific
SeanBarker182 Nov 4, 2025
29df030
accept an initialValue parameter
SeanBarker182 Nov 4, 2025
3127ce3
fix shortcut registration
SeanBarker182 Nov 4, 2025
b08e6a1
Merge branch 'feature/windows-menu' of https://github.com/infinitered…
SeanBarker182 Nov 4, 2025
9532238
update menu config
SeanBarker182 Nov 4, 2025
ad64da6
Update SystemMenu shortcuts to be platform-specific for macOS and Win…
SeanBarker182 Nov 4, 2025
69d737f
Fix linting issues
SeanBarker182 Nov 4, 2025
00d4f2c
Remove redundant shortcut formatting replacement in MenuDropdownItem …
SeanBarker182 Nov 4, 2025
e326df8
Add Windows setup script and update readme
jamonholmgren Nov 5, 2025
22561ae
Lock react-native versions and only run postinstall on macos
jamonholmgren Nov 5, 2025
f0dd6fe
Autolinking stuff
jamonholmgren Nov 5, 2025
064e5d7
Manually include ucrt (for Jamon's machine, oof)
jamonholmgren Nov 5, 2025
59f7bdd
Update lock file
SeanBarker182 Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ msbuild.binlog

.claude/*

# Windows reserved device names
nul
# Windows
nul
ReactNativeWindows.dsc.yaml
WindowsDevTools.dsc.yaml
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ npm run macos-release

#### System Requirements

First, install the system requirements for React Native Windows: https://microsoft.github.io/react-native-windows/docs/rnw-dependencies
First, install the system requirements for React Native Windows by running the following in an elevated Powershell session:

```powershell
.\bin\windows-setup.ps1
```

This should install everything for you.

**Alternative**: Follow the official installation steps (results vary): https://microsoft.github.io/react-native-windows/docs/rnw-dependencies

**Alternative**: If you experience issues with the official `rnw-dependencies.ps1` script, consider using Josh Yoes' improved setup process: https://github.com/joshuayoes/ReactNativeWindowsSandbox

Expand All @@ -60,6 +68,26 @@ npm run windows
npm run windows-release
```

**Troubleshooting**: If you run into a build error with ctype.h or ROSLYNCODETASKFACTORYCSHARPCOMPILER, try making a symlink like this (it's weird, I know):

```powershell
mklink /D "C:\Program Files\Windows Kits\10\lib" "C:\Program Files (x86)\Windows Kits\10\lib"
```

Somehow, `C:\Program Files\` instead of `C:\Program Files (x86)\` is being used, which breaks things. This makes a symlink to fix that.

You may also need to add this path to the `reactotron.vcxproj`:

```
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>
$(ProjectDir)..\..\app;%(AdditionalIncludeDirectories);C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\ucrt
</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
```

### Cross-Platform Native Development

Both platforms use unified commands for native module development:
Expand Down
132 changes: 19 additions & 113 deletions app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import { DevSettings, NativeModules, StatusBar, View, type ViewStyle } from "react-native"
import { StatusBar, View, type ViewStyle } from "react-native"
import { connectToServer } from "./state/connectToServer"
import { useTheme, themed } from "./theme/theme"
import { useEffect, useMemo, useState } from "react"
import { useEffect } from "react"
import { TimelineScreen } from "./screens/TimelineScreen"
import { useMenuItem } from "./utils/useMenuItem"
import { Titlebar } from "./components/Titlebar/Titlebar"
import { Sidebar } from "./components/Sidebar/Sidebar"
import { useSidebar } from "./state/useSidebar"
import { useGlobal, withGlobal } from "./state/useGlobal"
import { useGlobal } from "./state/useGlobal"
import { MenuItemId } from "./components/Sidebar/SidebarMenu"
import { HelpScreen } from "./screens/HelpScreen"
import { TimelineItem } from "./types"
import { PortalHost } from "./components/Portal"
import { StateScreen } from "./screens/StateScreen"
import { AboutModal } from "./components/AboutModal"
import { ShortcutsProvider } from "./contexts/ShortcutsContext"
import { SystemMenu } from "./components/SystemMenu"
import { CustomCommandsScreen } from "./screens/CustomCommandsScreen"

if (__DEV__) {
Expand All @@ -30,96 +22,7 @@ if (__DEV__) {

function App(): React.JSX.Element {
const { colors } = useTheme()
const { toggleSidebar } = useSidebar()
const [activeItem, setActiveItem] = useGlobal<MenuItemId>("sidebar-active-item", "logs")
const [, setTimelineItems] = withGlobal<TimelineItem[]>("timelineItems", [])
const [aboutVisible, setAboutVisible] = useState(false)

const menuConfig = useMemo(
() => ({
remove: ["File", "Edit", "Format", "Reactotron > About Reactotron"],
items: {
Reactotron: [
{
label: "About Reactotron",
position: 0,
action: () => setAboutVisible(true),
},
],
View: [
{
label: "Toggle Sidebar",
shortcut: "cmd+b",
action: toggleSidebar,
},
{
label: "Logs Tab",
shortcut: "cmd+1",
action: () => setActiveItem("logs"),
},
{
label: "Network Tab",
shortcut: "cmd+2",
action: () => setActiveItem("network"),
},
{
label: "Performance Tab",
shortcut: "cmd+3",
action: () => setActiveItem("performance"),
},
{
label: "Plugins Tab",
shortcut: "cmd+4",
action: () => setActiveItem("plugins"),
},
{
label: "Custom Commands Tab",
shortcut: "cmd+5",
action: () => setActiveItem("customCommands"),
},
{
label: "Help Tab",
shortcut: "cmd+6",
action: () => setActiveItem("help"),
},
...(__DEV__
? [
{
label: "Toggle Dev Menu",
shortcut: "cmd+shift+d",
action: () => NativeModules.DevMenu.show(),
},
]
: []),
],
Window: [
{
label: "Reload",
shortcut: "cmd+shift+r",
action: () => DevSettings.reload(),
},
],
Tools: [
{
label: "Clear Timeline Items",
shortcut: "cmd+k",
action: () => setTimelineItems([]),
},
],
},
}),
[toggleSidebar, setActiveItem],
)

useMenuItem(menuConfig)

setTimeout(() => {
fetch("https://www.google.com")
.then((res) => res.text())
.then((text) => {
console.tron.log("text", text)
})
}, 1000)
const [activeItem] = useGlobal<MenuItemId>("sidebar-active-item", "logs")

// Connect to the server when the app mounts.
// This will update global state with the server's state
Expand All @@ -140,16 +43,19 @@ function App(): React.JSX.Element {
}

return (
<View style={$container()}>
<Titlebar />
<StatusBar barStyle={"dark-content"} backgroundColor={colors.background} />
<View style={$mainContent}>
<Sidebar />
<View style={$contentContainer}>{renderActiveItem()}</View>
</View>
<PortalHost />
<AboutModal visible={aboutVisible} onClose={() => setAboutVisible(false)} />
</View>
<ShortcutsProvider>
<SystemMenu>
<View style={$container()}>
<Titlebar />
<StatusBar barStyle={"dark-content"} backgroundColor={colors.background} />
<View style={$mainContent}>
<Sidebar />
<View style={$contentContainer}>{renderActiveItem()}</View>
</View>
<PortalHost />
</View>
</SystemMenu>
</ShortcutsProvider>
)
}

Expand Down
102 changes: 102 additions & 0 deletions app/components/Menu/MenuDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { View, type ViewStyle } from "react-native"
import { useRef, useMemo, memo } from "react"
import { themed } from "../../theme/theme"
import { Portal } from "../Portal"
import { MenuDropdownItem } from "./MenuDropdownItem"
import { useSubmenuState } from "./useSubmenuState"
import { menuSettings } from "./menuSettings"
import { type Position, type DropdownMenuItem, type MenuItem, MENU_SEPARATOR } from "./types"
import { getUUID } from "../../utils/random/getUUID"
import { Separator } from "../Separator"

interface MenuDropdownProps {
items: (DropdownMenuItem | typeof MENU_SEPARATOR)[]
position: Position
onItemPress: (item: MenuItem) => void
isSubmenu?: boolean
}

const MenuDropdownComponent = ({ items, position, onItemPress, isSubmenu }: MenuDropdownProps) => {
const portalName = useRef(`${isSubmenu ? "submenu" : "dropdown"}-${getUUID()}`).current
const { openSubmenu, submenuPosition, handleItemHover } = useSubmenuState(position)

const isSeparator = (item: MenuItem | typeof MENU_SEPARATOR): item is typeof MENU_SEPARATOR => {
return item === MENU_SEPARATOR
}
Comment on lines +23 to +25
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.


// Find the submenu item if one is open
const submenuItem = openSubmenu
? (items.find((item) => !isSeparator(item) && item.label === openSubmenu) as
| DropdownMenuItem
| undefined)
: undefined

const dropdownContent = useMemo(
() => (
<View
style={[isSubmenu ? $submenuDropdown() : $dropdown(), $menuPosition(position, isSubmenu)]}
accessibilityRole="menu"
>
{items.map((item, index) => {
if (isSeparator(item)) return <Separator key={`separator-${index}`} />

return (
<MenuDropdownItem
key={item.label}
item={item as MenuItem}
index={index}
onItemPress={onItemPress}
onItemHover={handleItemHover}
/>
)
})}
</View>
),
[items, isSubmenu, position.x, position.y, onItemPress, handleItemHover],
)

return (
<>
<Portal name={portalName}>{dropdownContent}</Portal>
{/* Render submenu */}
{submenuItem?.submenu && (
<MenuDropdown
items={submenuItem.submenu}
position={submenuPosition}
onItemPress={onItemPress}
isSubmenu={true}
/>
)}
</>
)
}

export const MenuDropdown = memo(MenuDropdownComponent)

const $dropdown = themed<ViewStyle>(({ colors, spacing }) => ({
position: "absolute",
backgroundColor: colors.cardBackground,
borderColor: colors.keyline,
borderWidth: 1,
borderRadius: 4,
minWidth: menuSettings.dropdownMinWidth,
paddingVertical: spacing.xs,
zIndex: menuSettings.zIndex.dropdown,
}))

const $submenuDropdown = themed<ViewStyle>(({ colors, spacing }) => ({
position: "absolute",
backgroundColor: colors.cardBackground,
borderColor: colors.keyline,
borderWidth: 1,
borderRadius: 4,
minWidth: menuSettings.submenuMinWidth,
paddingVertical: spacing.xs,
zIndex: menuSettings.zIndex.submenu,
}))

const $menuPosition = (position: Position, isSubmenu: boolean | undefined) => ({
left: position.x,
top: position.y,
zIndex: isSubmenu ? 10001 : 10000,
})
Loading