Skip to content

Commit 4d7dd80

Browse files
Copilotanupriya13
andcommitted
Add auto-scroll on keyboard focus for left navigation drawer
Co-authored-by: anupriya13 <54227869+anupriya13@users.noreply.github.com>
1 parent b8bd934 commit 4d7dd80

File tree

2 files changed

+115
-8
lines changed

2 files changed

+115
-8
lines changed

NewArch/src/App.tsx

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
Text,
77
useColorScheme,
88
KeyboardEvent as RNKeyboardEvent,
9+
ScrollView,
910
} from 'react-native';
1011
import {NavigationContainer} from './Navigation';
1112
import {
@@ -115,26 +116,51 @@ type DrawerListItemProps = {
115116
navigation: any;
116117
currentRoute: string;
117118
onKeyDown?: (e: RNKeyboardEvent) => void;
119+
scrollViewRef?: React.RefObject<ScrollView>;
118120
ref?: React.Ref<Pressable>;
119121
};
120122
const DrawerListItem = React.forwardRef<Pressable, DrawerListItemProps>(
121123
(
122-
{ route, label, icon, navigation, currentRoute, onKeyDown },
124+
{ route, label, icon, navigation, currentRoute, onKeyDown, scrollViewRef },
123125
ref,
124126
) => {
125127
const [isHovered, setIsHovered] = React.useState(false);
126128
const [isPressed, setIsPressed] = React.useState(false);
129+
const itemRef = useRef<View>(null);
130+
131+
const handleFocus = () => {
132+
if (scrollViewRef?.current && itemRef.current) {
133+
itemRef.current.measureLayout(
134+
scrollViewRef.current as any,
135+
(_x, y, _width, _height) => {
136+
scrollViewRef.current?.scrollTo({
137+
y: y - 50,
138+
animated: true,
139+
});
140+
},
141+
() => {},
142+
);
143+
}
144+
};
127145

128146
const localStyles = createDrawerListItemStyles(isHovered, isPressed);
129147
return (
130148
<Pressable
131-
ref={ref}
149+
ref={(node) => {
150+
if (typeof ref === 'function') {
151+
ref(node);
152+
} else if (ref) {
153+
(ref as any).current = node;
154+
}
155+
(itemRef as any).current = node;
156+
}}
132157
onKeyDown={onKeyDown}
133158
onPress={() => navigation.navigate(route, { shouldFocus: true, focusTimestamp: Date.now() })}
134159
onPressIn={() => setIsPressed(true)}
135160
onPressOut={() => setIsPressed(false)}
136161
onHoverIn={() => setIsHovered(true)}
137162
onHoverOut={() => setIsHovered(false)}
163+
onFocus={handleFocus}
138164
accessibilityRole="button"
139165
accessibilityLabel={label}
140166
style={localStyles.drawerListItem}
@@ -170,6 +196,7 @@ type DrawerCollapsibleCategoryProps = {
170196
navigation: any;
171197
currentRoute: string;
172198
containsCurrentRoute: boolean;
199+
scrollViewRef?: React.RefObject<ScrollView>;
173200
};
174201
const DrawerCollapsibleCategory = ({
175202
categoryLabel,
@@ -178,6 +205,7 @@ const DrawerCollapsibleCategory = ({
178205
navigation,
179206
currentRoute,
180207
containsCurrentRoute,
208+
scrollViewRef,
181209
}: DrawerCollapsibleCategoryProps) => {
182210
const categoryRoute = `Category: ${categoryLabel}`;
183211
const isCurrentRoute = currentRoute === categoryRoute;
@@ -187,6 +215,22 @@ const DrawerCollapsibleCategory = ({
187215
const [isHovered, setIsHovered] = React.useState(false);
188216
const [isPressed, setIsPressed] = React.useState(false);
189217
const localStyles = createDrawerListItemStyles(isHovered, isPressed);
218+
const itemRef = useRef<View>(null);
219+
220+
const handleFocus = () => {
221+
if (scrollViewRef?.current && itemRef.current) {
222+
itemRef.current.measureLayout(
223+
scrollViewRef.current as any,
224+
(_x, y, _width, _height) => {
225+
scrollViewRef.current?.scrollTo({
226+
y: y - 50,
227+
animated: true,
228+
});
229+
},
230+
() => {},
231+
);
232+
}
233+
};
190234

191235
const onPress = () => {
192236
if (isExpanded && containsCurrentRoute) {
@@ -203,12 +247,14 @@ const DrawerCollapsibleCategory = ({
203247
return (
204248
<View style={styles.category}>
205249
<Pressable
250+
ref={itemRef as any}
206251
style={localStyles.drawerListItem}
207252
onPress={() => onPress()}
208253
onPressIn={() => setIsPressed(true)}
209254
onPressOut={() => setIsPressed(false)}
210255
onHoverIn={() => setIsHovered(true)}
211256
onHoverOut={() => setIsHovered(false)}
257+
onFocus={handleFocus}
212258
accessibilityRole="button"
213259
accessibilityLabel={categoryLabel}
214260
accessibilityState={{expanded: isExpanded}}
@@ -248,6 +294,7 @@ const DrawerCollapsibleCategory = ({
248294
label={item.label}
249295
navigation={navigation}
250296
currentRoute={currentRoute}
297+
scrollViewRef={scrollViewRef}
251298
/>
252299
))}
253300
</View>
@@ -257,6 +304,7 @@ const DrawerCollapsibleCategory = ({
257304
const DrawerListView = (props: {
258305
navigation: any;
259306
currentRoute: string;
307+
scrollViewRef?: React.RefObject<ScrollView>;
260308
}) => {
261309
const filterPredicate = (item: any) => item.type !== '';
262310
const filteredList = RNGalleryList.filter(filterPredicate);
@@ -288,6 +336,7 @@ const DrawerListView = (props: {
288336
navigation={props.navigation}
289337
currentRoute={props.currentRoute}
290338
containsCurrentRoute={categoryWithCurrentRoute === category.label}
339+
scrollViewRef={props.scrollViewRef}
291340
/>
292341
))}
293342
</View>
@@ -305,6 +354,7 @@ function CustomDrawerContent({ navigation }: { navigation: any }) {
305354
const hamburgerRef = useRef<View>(null);
306355
const homeRef = useRef<View>(null);
307356
const settingsRef = useRef<View>(null);
357+
const scrollViewRef = useRef<ScrollView>(null);
308358

309359
// When drawer opens, focus the Home menu item
310360
useEffect(() => {
@@ -380,7 +430,13 @@ function CustomDrawerContent({ navigation }: { navigation: any }) {
380430
focusable={true}
381431
/>
382432
<View style={styles.drawerDivider} />
383-
<DrawerListView navigation={navigation} currentRoute={currentRoute} />
433+
<ScrollView ref={scrollViewRef}>
434+
<DrawerListView
435+
navigation={navigation}
436+
currentRoute={currentRoute}
437+
scrollViewRef={scrollViewRef}
438+
/>
439+
</ScrollView>
384440
<View style={styles.drawerDivider} />
385441

386442
<DrawerListItem

src/App.tsx

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,28 +113,49 @@ type DrawerListItemProps = {
113113
icon?: string;
114114
navigation: any;
115115
currentRoute: string;
116+
scrollViewRef?: React.RefObject<ScrollView>;
116117
};
117118
const DrawerListItem = ({
118119
route,
119120
label,
120121
icon,
121122
navigation,
122123
currentRoute,
124+
scrollViewRef,
123125
}: DrawerListItemProps) => {
124126
const [isHovered, setIsHovered] = React.useState(false);
125127
const [isPressed, setIsPressed] = React.useState(false);
128+
const itemRef = React.useRef<View>(null);
129+
130+
const handleFocus = () => {
131+
if (scrollViewRef?.current && itemRef.current) {
132+
itemRef.current.measureLayout(
133+
scrollViewRef.current as any,
134+
(_x, y, _width, _height) => {
135+
scrollViewRef.current?.scrollTo({
136+
y: y - 50,
137+
animated: true,
138+
});
139+
},
140+
() => {},
141+
);
142+
}
143+
};
126144

127145
const localStyles = createDrawerListItemStyles(isHovered, isPressed);
128146
return (
129147
<Pressable
148+
ref={itemRef as any}
130149
onPress={() => navigation.navigate(route)}
131150
onPressIn={() => setIsPressed(true)}
132151
onPressOut={() => setIsPressed(false)}
133152
onHoverIn={() => setIsHovered(true)}
134153
onHoverOut={() => setIsHovered(false)}
154+
onFocus={handleFocus}
135155
accessibilityRole="button"
136156
accessibilityLabel={label}
137-
style={localStyles.drawerListItem}>
157+
style={localStyles.drawerListItem}
158+
focusable={true}>
138159
<View style={styles.indentContainer}>
139160
<SelectedNavigationItemPill
140161
currentRoute={currentRoute}
@@ -158,6 +179,7 @@ type DrawerCollapsibleCategoryProps = {
158179
navigation: any;
159180
currentRoute: string;
160181
containsCurrentRoute: boolean;
182+
scrollViewRef?: React.RefObject<ScrollView>;
161183
};
162184
const DrawerCollapsibleCategory = ({
163185
categoryLabel,
@@ -166,6 +188,7 @@ const DrawerCollapsibleCategory = ({
166188
navigation,
167189
currentRoute,
168190
containsCurrentRoute,
191+
scrollViewRef,
169192
}: DrawerCollapsibleCategoryProps) => {
170193
const categoryRoute = `Category: ${categoryLabel}`;
171194
const isCurrentRoute = currentRoute === categoryRoute;
@@ -175,6 +198,22 @@ const DrawerCollapsibleCategory = ({
175198
const [isHovered, setIsHovered] = React.useState(false);
176199
const [isPressed, setIsPressed] = React.useState(false);
177200
const localStyles = createDrawerListItemStyles(isHovered, isPressed);
201+
const itemRef = React.useRef<View>(null);
202+
203+
const handleFocus = () => {
204+
if (scrollViewRef?.current && itemRef.current) {
205+
itemRef.current.measureLayout(
206+
scrollViewRef.current as any,
207+
(_x, y, _width, _height) => {
208+
scrollViewRef.current?.scrollTo({
209+
y: y - 50,
210+
animated: true,
211+
});
212+
},
213+
() => {},
214+
);
215+
}
216+
};
178217

179218
const onPress = () => {
180219
if (isExpanded && containsCurrentRoute) {
@@ -196,13 +235,16 @@ const DrawerCollapsibleCategory = ({
196235
accessibilityLabel={categoryLabel}
197236
onAccessibilityTap={() => setIsExpanded(!isExpanded)}>
198237
<Pressable
238+
ref={itemRef as any}
199239
style={localStyles.drawerListItem}
200240
onPress={() => onPress()}
201241
onPressIn={() => setIsPressed(true)}
202242
onPressOut={() => setIsPressed(false)}
203243
onHoverIn={() => setIsHovered(true)}
204244
onHoverOut={() => setIsHovered(false)}
205-
accessible={false}>
245+
onFocus={handleFocus}
246+
accessible={false}
247+
focusable={true}>
206248
<View style={styles.indentContainer}>
207249
<SelectedNavigationItemPill
208250
currentRoute={currentRoute}
@@ -229,6 +271,7 @@ const DrawerCollapsibleCategory = ({
229271
label={item.label}
230272
navigation={navigation}
231273
currentRoute={currentRoute}
274+
scrollViewRef={scrollViewRef}
232275
/>
233276
))}
234277
</View>
@@ -262,12 +305,14 @@ const DrawerListView = (props) => {
262305
<View>
263306
{RNGalleryCategories.map((category) => (
264307
<DrawerCollapsibleCategory
308+
key={category.label}
265309
categoryLabel={category.label}
266310
categoryIcon={category.icon}
267311
items={categoryMap.get(category.label)}
268312
navigation={props.navigation}
269313
currentRoute={props.currentRoute}
270314
containsCurrentRoute={categoryWithCurrentRoute === category.label}
315+
scrollViewRef={props.scrollViewRef}
271316
/>
272317
))}
273318
</View>
@@ -280,6 +325,7 @@ function CustomDrawerContent({navigation}) {
280325

281326
const navigationState = navigation.getState();
282327
const currentRoute = navigationState.routeNames[navigationState.index];
328+
const scrollViewRef = React.useRef<ScrollView>(null);
283329

284330
if (!isDrawerOpen) {
285331
return <View />;
@@ -291,7 +337,8 @@ function CustomDrawerContent({navigation}) {
291337
accessibilityLabel="Navigation bar expanded"
292338
{...{tooltip: 'Collapse Menu'}}
293339
style={styles.menu}
294-
onPress={() => navigation.closeDrawer()}>
340+
onPress={() => navigation.closeDrawer()}
341+
focusable={true}>
295342
<Text style={styles.icon}>&#xE700;</Text>
296343
</Pressable>
297344
<DrawerListItem
@@ -309,8 +356,12 @@ function CustomDrawerContent({navigation}) {
309356
currentRoute={currentRoute}
310357
/>
311358
<View style={styles.drawerDivider} />
312-
<ScrollView>
313-
<DrawerListView navigation={navigation} currentRoute={currentRoute} />
359+
<ScrollView ref={scrollViewRef}>
360+
<DrawerListView
361+
navigation={navigation}
362+
currentRoute={currentRoute}
363+
scrollViewRef={scrollViewRef}
364+
/>
314365
</ScrollView>
315366
<View style={styles.drawerDivider} />
316367
<DrawerListItem

0 commit comments

Comments
 (0)