Skip to content

Commit 3e85069

Browse files
committed
Handle groups and multiple navigators
1 parent 79ff31a commit 3e85069

File tree

2 files changed

+191
-75
lines changed

2 files changed

+191
-75
lines changed

src/plugins/rehype-static-to-dynamic.mjs

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ function convertStaticToDynamic(code) {
201201
// Process all navigators
202202
if (navigatorInfos.length > 0) {
203203
const replacements = [];
204+
const navigatorConstNames = new Map(); // Track usage of navigator constant names
204205

205206
navigatorInfos.forEach((navigatorInfo) => {
206207
const {
@@ -223,7 +224,19 @@ function convertStaticToDynamic(code) {
223224
const withoutNavigator = withoutCreate.replace(/Navigator$/, ''); // "Stack"
224225
// Find the last capitalized word (e.g., "NativeStack" -> "Stack", "MaterialTopTab" -> "Tab")
225226
const match = withoutNavigator.match(/([A-Z][a-z]+)$/);
226-
const navigatorConstName = match ? match[1] : withoutNavigator;
227+
const baseNavigatorConstName = match ? match[1] : withoutNavigator;
228+
229+
// Handle multiple navigators of the same type by adding suffixes (A, B, C, etc.)
230+
let navigatorConstName = baseNavigatorConstName;
231+
const currentCount = navigatorConstNames.get(baseNavigatorConstName) || 0;
232+
233+
if (currentCount > 0) {
234+
// Add suffix: A for second occurrence, B for third, etc.
235+
const suffix = String.fromCharCode(65 + currentCount - 1); // 65 is 'A'
236+
navigatorConstName = baseNavigatorConstName + suffix;
237+
}
238+
239+
navigatorConstNames.set(baseNavigatorConstName, currentCount + 1);
227240

228241
// Parse the config object
229242
const parsedConfig = parseNavigatorConfig(config);
@@ -358,6 +371,7 @@ function convertStaticToDynamic(code) {
358371
function parseNavigatorConfig(configNode) {
359372
const result = {
360373
screens: {},
374+
groups: {}, // Store groups
361375
navigatorProps: {}, // Store all navigator-level props
362376
};
363377

@@ -372,7 +386,79 @@ function parseNavigatorConfig(configNode) {
372386

373387
const keyName = prop.key.name || prop.key.value;
374388

375-
if (keyName === 'screens' && t.isObjectExpression(prop.value)) {
389+
if (keyName === 'groups' && t.isObjectExpression(prop.value)) {
390+
// Parse groups object
391+
prop.value.properties.forEach((groupProp) => {
392+
if (!t.isObjectProperty(groupProp)) return;
393+
394+
const groupKey = groupProp.key.name || groupProp.key.value;
395+
const groupValue = groupProp.value;
396+
397+
if (t.isObjectExpression(groupValue)) {
398+
const groupConfig = {
399+
screens: {},
400+
groupProps: {},
401+
};
402+
403+
groupValue.properties.forEach((groupConfigProp) => {
404+
if (!t.isObjectProperty(groupConfigProp)) return;
405+
406+
const configKey =
407+
groupConfigProp.key.name || groupConfigProp.key.value;
408+
409+
if (
410+
configKey === 'screens' &&
411+
t.isObjectExpression(groupConfigProp.value)
412+
) {
413+
// Parse screens within the group
414+
groupConfigProp.value.properties.forEach((screenProp) => {
415+
if (!t.isObjectProperty(screenProp)) return;
416+
417+
const screenName = screenProp.key.name || screenProp.key.value;
418+
const screenValue = screenProp.value;
419+
420+
if (t.isIdentifier(screenValue)) {
421+
groupConfig.screens[screenName] = {
422+
component: screenValue.name,
423+
screenProps: {},
424+
};
425+
} else if (t.isObjectExpression(screenValue)) {
426+
let component = null;
427+
const screenProps = {};
428+
429+
screenValue.properties.forEach((screenConfigProp) => {
430+
if (!t.isObjectProperty(screenConfigProp)) return;
431+
432+
const key =
433+
screenConfigProp.key.name || screenConfigProp.key.value;
434+
435+
if (
436+
key === 'screen' &&
437+
t.isIdentifier(screenConfigProp.value)
438+
) {
439+
component = screenConfigProp.value.name;
440+
} else {
441+
screenProps[key] = screenConfigProp.value;
442+
}
443+
});
444+
445+
groupConfig.screens[screenName] = { component, screenProps };
446+
}
447+
});
448+
} else {
449+
// Store other group-level props (screenOptions, screenLayout, etc.)
450+
groupConfig.groupProps[configKey] = {
451+
key: configKey,
452+
value: groupConfigProp.value,
453+
isStringLiteral: t.isStringLiteral(groupConfigProp.value),
454+
};
455+
}
456+
});
457+
458+
result.groups[groupKey] = groupConfig;
459+
}
460+
});
461+
} else if (keyName === 'screens' && t.isObjectExpression(prop.value)) {
376462
// Parse screens object
377463
prop.value.properties.forEach((screenProp) => {
378464
if (!t.isObjectProperty(screenProp)) return;
@@ -455,6 +541,107 @@ function createNavigatorComponent(functionName, componentName, config) {
455541
// Create screen elements
456542
const screenElements = [];
457543

544+
// Handle groups if they exist
545+
if (Object.keys(config.groups).length > 0) {
546+
Object.entries(config.groups).forEach(([groupKey, groupConfig]) => {
547+
const groupProps = [
548+
t.jsxAttribute(
549+
t.jsxIdentifier('navigationKey'),
550+
t.stringLiteral(groupKey)
551+
),
552+
];
553+
554+
// Add group-level props (screenOptions, screenLayout, etc.)
555+
Object.values(groupConfig.groupProps).forEach((propInfo) => {
556+
if (propInfo.isStringLiteral) {
557+
groupProps.push(
558+
t.jsxAttribute(
559+
t.jsxIdentifier(propInfo.key),
560+
t.stringLiteral(propInfo.value.value)
561+
)
562+
);
563+
} else {
564+
groupProps.push(
565+
t.jsxAttribute(
566+
t.jsxIdentifier(propInfo.key),
567+
t.jsxExpressionContainer(propInfo.value)
568+
)
569+
);
570+
}
571+
});
572+
573+
// Create screens for this group
574+
const groupScreenElements = [];
575+
576+
Object.entries(groupConfig.screens).forEach(
577+
([screenName, screenConfig]) => {
578+
const screenProps = [
579+
t.jsxAttribute(
580+
t.jsxIdentifier('name'),
581+
t.stringLiteral(screenName)
582+
),
583+
t.jsxAttribute(
584+
t.jsxIdentifier('component'),
585+
t.jsxExpressionContainer(t.identifier(screenConfig.component))
586+
),
587+
];
588+
589+
// Add all screen-level props
590+
Object.entries(screenConfig.screenProps).forEach(([key, value]) => {
591+
screenProps.push(
592+
t.jsxAttribute(
593+
t.jsxIdentifier(key),
594+
t.jsxExpressionContainer(value)
595+
)
596+
);
597+
});
598+
599+
const screenElement = t.jsxElement(
600+
t.jsxOpeningElement(
601+
t.jsxMemberExpression(
602+
t.jsxIdentifier(componentName),
603+
t.jsxIdentifier('Screen')
604+
),
605+
screenProps,
606+
true
607+
),
608+
null,
609+
[],
610+
true
611+
);
612+
613+
groupScreenElements.push(t.jsxText('\n '));
614+
groupScreenElements.push(screenElement);
615+
}
616+
);
617+
618+
groupScreenElements.push(t.jsxText('\n '));
619+
620+
// Create the Group element
621+
const groupElement = t.jsxElement(
622+
t.jsxOpeningElement(
623+
t.jsxMemberExpression(
624+
t.jsxIdentifier(componentName),
625+
t.jsxIdentifier('Group')
626+
),
627+
groupProps
628+
),
629+
t.jsxClosingElement(
630+
t.jsxMemberExpression(
631+
t.jsxIdentifier(componentName),
632+
t.jsxIdentifier('Group')
633+
)
634+
),
635+
groupScreenElements,
636+
false
637+
);
638+
639+
screenElements.push(t.jsxText('\n '));
640+
screenElements.push(groupElement);
641+
});
642+
}
643+
644+
// Handle standalone screens (not in groups)
458645
Object.entries(config.screens).forEach(([screenName, screenConfig]) => {
459646
const screenProps = [
460647
t.jsxAttribute(t.jsxIdentifier('name'), t.stringLiteral(screenName)),

versioned_docs/version-7.x/modal.md

Lines changed: 2 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ A modal is like a popup — it usually has a different transition animation,
1515

1616
## Creating a stack with modal screens
1717

18-
<Tabs groupId="config" queryString="config">
19-
<TabItem value="static" label="Static" default>
20-
21-
```js name="Modal" snack
18+
```js name="Modal" snack static2dynamic
2219
import * as React from 'react';
2320
import { View, Text } from 'react-native';
2421
import {
@@ -28,7 +25,6 @@ import {
2825
import { createStackNavigator } from '@react-navigation/stack';
2926
import { Button } from '@react-navigation/elements';
3027

31-
// codeblock-focus-start
3228
function HomeScreen() {
3329
const navigation = useNavigation();
3430

@@ -59,6 +55,7 @@ function DetailsScreen() {
5955
);
6056
}
6157

58+
// codeblock-focus-start
6259
const HomeStack = createStackNavigator({
6360
screens: {
6461
Home: {
@@ -107,74 +104,6 @@ export default function App() {
107104
// codeblock-focus-end
108105
```
109106

110-
</TabItem>
111-
<TabItem value="dynamic" label="Dynamic">
112-
113-
```js name="Modal" snack
114-
import * as React from 'react';
115-
import { View, Text } from 'react-native';
116-
import { NavigationContainer, useNavigation } from '@react-navigation/native';
117-
import { createStackNavigator } from '@react-navigation/stack';
118-
import { Button } from '@react-navigation/elements';
119-
120-
// codeblock-focus-start
121-
function HomeScreen() {
122-
const navigation = useNavigation();
123-
124-
return (
125-
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
126-
<Text style={{ fontSize: 30 }}>This is the home screen!</Text>
127-
<Button onPress={() => navigation.navigate('MyModal')}>Open Modal</Button>
128-
</View>
129-
);
130-
}
131-
132-
function ModalScreen() {
133-
const navigation = useNavigation();
134-
135-
return (
136-
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
137-
<Text style={{ fontSize: 30 }}>This is a modal!</Text>
138-
<Button onPress={() => navigation.goBack()}>Dismiss</Button>
139-
</View>
140-
);
141-
}
142-
143-
function DetailsScreen() {
144-
return (
145-
<View>
146-
<Text>Details</Text>
147-
</View>
148-
);
149-
}
150-
151-
const RootStack = createStackNavigator();
152-
153-
function App() {
154-
return (
155-
<NavigationContainer>
156-
<RootStack.Navigator>
157-
<RootStack.Group>
158-
<RootStack.Screen name="Home" component={HomeScreen} />
159-
<RootStack.Screen name="Details" component={DetailsScreen} />
160-
</RootStack.Group>
161-
// highlight-start
162-
<RootStack.Group screenOptions={{ presentation: 'modal' }}>
163-
<RootStack.Screen name="MyModal" component={ModalScreen} />
164-
</RootStack.Group>
165-
// highlight-end
166-
</RootStack.Navigator>
167-
</NavigationContainer>
168-
);
169-
}
170-
// codeblock-focus-end
171-
172-
export default App;
173-
```
174-
175-
</TabItem>
176-
</Tabs>
177-
178107
<video playsInline autoPlay muted loop>
179108
<source src="/assets/modal/modal.mp4" />
180109
</video>

0 commit comments

Comments
 (0)