Skip to content

Commit 5774e76

Browse files
authored
create an About menu within react native (#51)
1 parent df2bf73 commit 5774e76

File tree

4 files changed

+170
-5
lines changed

4 files changed

+170
-5
lines changed

app/app.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { DevSettings, NativeModules, StatusBar, View, type ViewStyle } from "react-native"
88
import { connectToServer } from "./state/connectToServer"
99
import { useTheme, themed } from "./theme/theme"
10-
import { useEffect, useMemo } from "react"
10+
import { useEffect, useMemo, useState } from "react"
1111
import { TimelineScreen } from "./screens/TimelineScreen"
1212
import { useMenuItem } from "./utils/useMenuItem"
1313
import { Titlebar } from "./components/Titlebar/Titlebar"
@@ -19,6 +19,7 @@ import { HelpScreen } from "./screens/HelpScreen"
1919
import { TimelineItem } from "./types"
2020
import { PortalHost } from "./components/Portal"
2121
import { StateScreen } from "./screens/StateScreen"
22+
import { AboutModal } from "./components/AboutModal"
2223

2324
if (__DEV__) {
2425
// This is for debugging Reactotron with ... Reactotron!
@@ -31,11 +32,19 @@ function App(): React.JSX.Element {
3132
const { toggleSidebar } = useSidebar()
3233
const [activeItem, setActiveItem] = useGlobal<MenuItemId>("sidebar-active-item", "logs")
3334
const [, setTimelineItems] = withGlobal<TimelineItem[]>("timelineItems", [])
35+
const [aboutVisible, setAboutVisible] = useState(false)
3436

3537
const menuConfig = useMemo(
3638
() => ({
37-
remove: ["File", "Edit", "Format"],
39+
remove: ["File", "Edit", "Format", "Reactotron > About Reactotron"],
3840
items: {
41+
Reactotron: [
42+
{
43+
label: "About Reactotron",
44+
position: 0,
45+
action: () => setAboutVisible(true),
46+
},
47+
],
3948
View: [
4049
{
4150
label: "Toggle Sidebar",
@@ -93,7 +102,7 @@ function App(): React.JSX.Element {
93102
],
94103
},
95104
}),
96-
[toggleSidebar],
105+
[toggleSidebar, setActiveItem],
97106
)
98107

99108
useMenuItem(menuConfig)
@@ -131,6 +140,7 @@ function App(): React.JSX.Element {
131140
<View style={$contentContainer}>{renderActiveItem()}</View>
132141
</View>
133142
<PortalHost />
143+
<AboutModal visible={aboutVisible} onClose={() => setAboutVisible(false)} />
134144
</View>
135145
)
136146
}

app/components/AboutModal.tsx

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import {
2+
Image,
3+
NativeModules,
4+
Pressable,
5+
Text,
6+
View,
7+
type ImageStyle,
8+
type TextStyle,
9+
type ViewStyle,
10+
} from "react-native"
11+
import { themed } from "../theme/theme"
12+
import { Portal } from "./Portal"
13+
import { getReactotronAppId } from "../state/connectToServer"
14+
import Clipboard from "../native/IRClipboard/NativeIRClipboard"
15+
import { useState } from "react"
16+
17+
interface AboutModalProps {
18+
visible: boolean
19+
onClose: () => void
20+
}
21+
22+
export function AboutModal({ visible, onClose }: AboutModalProps) {
23+
const [copied, setCopied] = useState(false)
24+
if (!visible) return null
25+
26+
const copyToClipboard = () => {
27+
Clipboard.setString(reactotronAppId)
28+
setCopied(true)
29+
setTimeout(() => setCopied(false), 3000)
30+
}
31+
32+
const reactotronAppId = getReactotronAppId()
33+
const appVersion: string = require("../../package.json").version
34+
const appBuild: string =
35+
(NativeModules as any)?.PlatformConstants?.appBuildVersion ??
36+
(NativeModules as any)?.PlatformConstants?.CFBundleVersion ??
37+
""
38+
39+
return (
40+
<Portal name="about-modal">
41+
<View style={$backdrop()}>
42+
<Pressable style={$backdropTouchable} onPress={onClose} />
43+
44+
<View style={$card()}>
45+
<View style={$header()}>
46+
<Image
47+
source={require("../../assets/images/reactotronLogo.png")}
48+
style={$logo()}
49+
resizeMode="contain"
50+
/>
51+
<Text style={$title()}>Reactotron</Text>
52+
<Text style={$bodyText()}>Copyright © 2025 Infinite Red, Inc.</Text>
53+
<Text style={$bodyText()}>{`Version ${appVersion}${
54+
appBuild ? ` (${appBuild})` : ""
55+
}`}</Text>
56+
<Pressable
57+
style={[$uuidButton(), $button(), $linkContainer()]}
58+
onPress={copyToClipboard}
59+
>
60+
<Text style={$bodyText()}>{copied ? "Copied!" : `UUID: ${reactotronAppId}`}</Text>
61+
</Pressable>
62+
</View>
63+
64+
<View style={$footer()}>
65+
<Pressable style={[$button(), $linkContainer()]} onPress={onClose}>
66+
<Text style={$buttonText()}>Close</Text>
67+
</Pressable>
68+
</View>
69+
</View>
70+
</View>
71+
</Portal>
72+
)
73+
}
74+
75+
const $backdrop = themed<ViewStyle>(({ colors }) => ({
76+
position: "absolute",
77+
top: 0,
78+
left: 0,
79+
right: 0,
80+
bottom: 0,
81+
backgroundColor: colors.background,
82+
alignItems: "center",
83+
justifyContent: "center",
84+
}))
85+
86+
const $backdropTouchable: ViewStyle = {
87+
position: "absolute",
88+
top: 0,
89+
left: 0,
90+
right: 0,
91+
bottom: 0,
92+
cursor: "default",
93+
}
94+
95+
const $card = themed<ViewStyle>(({ colors, spacing }) => ({
96+
width: 520,
97+
maxWidth: "90%",
98+
backgroundColor: colors.cardBackground,
99+
borderColor: colors.border,
100+
borderWidth: 1,
101+
borderRadius: spacing.sm,
102+
overflow: "hidden",
103+
}))
104+
105+
const $header = themed<ViewStyle>(({ spacing }) => ({
106+
alignItems: "center",
107+
padding: spacing.lg,
108+
gap: spacing.sm,
109+
}))
110+
111+
const $logo = themed<ImageStyle>(() => ({
112+
width: 64,
113+
height: 64,
114+
}))
115+
116+
const $title = themed<TextStyle>(({ typography, colors }) => ({
117+
fontSize: typography.heading,
118+
color: colors.mainText,
119+
}))
120+
121+
const $bodyText = themed<TextStyle>(({ colors }) => ({
122+
color: colors.mainText,
123+
}))
124+
125+
const $footer = themed<ViewStyle>(({ spacing, colors }) => ({
126+
borderTopColor: colors.border,
127+
borderTopWidth: 1,
128+
padding: spacing.md,
129+
alignItems: "flex-end",
130+
}))
131+
132+
const $button = themed<ViewStyle>(({ colors, spacing }) => ({
133+
backgroundColor: colors.background,
134+
borderColor: colors.border,
135+
borderWidth: 1,
136+
paddingHorizontal: spacing.md,
137+
paddingVertical: spacing.xs,
138+
borderRadius: spacing.xs,
139+
}))
140+
141+
const $buttonText = themed<TextStyle>(({ colors }) => ({
142+
color: colors.mainText,
143+
}))
144+
145+
const $linkContainer = themed<ViewStyle>(() => ({
146+
cursor: "pointer",
147+
}))
148+
149+
const $uuidButton = themed<ViewStyle>(({ spacing }) => ({
150+
marginTop: spacing.lg,
151+
alignItems: "center",
152+
justifyContent: "center",
153+
width: "90%",
154+
}))

app/components/Portal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ export function PortalHost() {
4646
// Listen for portal changes and force re-render when they occur
4747
const listener = () => forceUpdate({})
4848
listeners.add(listener)
49-
return () => listeners.delete(listener)
49+
return () => {
50+
listeners.delete(listener)
51+
}
5052
}, [])
5153

5254
return (

app/components/Sidebar/Sidebar.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const Sidebar = () => {
2020
inputRange: [0, 1],
2121
outputRange: [COLLAPSED_WIDTH, EXPANDED_WIDTH],
2222
})
23-
console.log("progress", progress)
2423

2524
return (
2625
<Animated.View style={[{ width: animatedWidth }, $overflowHidden]}>

0 commit comments

Comments
 (0)