Flexn Focus Manager
How to use it
Nevertheless Flexn Focus Manager is simple enough it has some fundamental rules which we need to comply to use it right. All primitive components must be exported from @flexn/create
since
each of them are designed for Flexn Focus Manager and helps it to understand our application layout. API for each component is described in section above so here we will go through some
real app examples and rules.
Root of your app
Root of your application must be wrapped with import { App } from '@flexn/create';
it's a single import which initialize Flexn Focus Manager events to be accessible within whole app.
import * as React from 'react';
import { App } from '@flexn/create';
const MyApp = () => {
return <App>...rest of your code</App>;
};
export default MyApp;
Screens
Screen represents collection of the children's which belongs only for particular block. Even though screen usually is wrapping separate pages of your application, but it not necessary has to be like that. With Screen you can create things like modals which overlaps the context, side navigation which typically always visible for user whatever you navigate or anything else depends on your application structure. There are few important rules working with screens:
- Don't wrap screen into another screen. That might cause side effects and break functionality;
- Screen must have states. More about those below;
State of the screen tells focus manager whenever your screen is in foreground and visible for user or it's moved to background. Example bellow shows simple implementation with react-navigation.
import * as React from 'react';
import { App, Screen, Text, Pressable, StyleSheet, ScreenStates } from '@flexn/create';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NavigationContainer, useFocusEffect } from '@react-navigation/native';
const Stack = createNativeStackNavigator();
const Screen1 = ({ navigation }) => {
const [screenState, setScreenState] = React.useState < ScreenStates > 'foreground';
useFocusEffect(
React.useCallback(() => {
setScreenState('foreground');
return () => {
setScreenState('background');
};
}, [])
);
return (
<Screen style={styles.container} focusOptions={{ screenState }}>
<Pressable style={styles.button} onPress={() => navigation.navigate('myScreen2')}>
<Text>Navigate to screen 2</Text>
</Pressable>
<Pressable style={styles.button}>
<Text>Empty action button</Text>
</Pressable>
</Screen>
);
};
const Screen2 = ({ navigation }) => {
const [screenState, setScreenState] = React.useState < ScreenStates > 'foreground';
useFocusEffect(
React.useCallback(() => {
setScreenState('foreground');
return () => {
setScreenState('background');
};
}, [])
);
return (
<Screen style={styles.container} focusOptions={{ screenState }}>
<Pressable style={styles.button} onPress={() => navigation.navigate('myScreen1')}>
<Text>Back to screen 1</Text>
</Pressable>
<Pressable style={styles.button}>
<Text>Empty action button</Text>
</Pressable>
</Screen>
);
};
const MyApp = () => {
return (
<App>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="myScreen1" component={Screen1} />
<Stack.Screen name="myScreen2" component={Screen2} />
</Stack.Navigator>
</NavigationContainer>
</App>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFFFFF',
},
button: {
margin: 20,
borderWidth: 2,
borderRadius: 25,
borderColor: '#111111',
height: 50,
width: 200,
justifyContent: 'center',
alignItems: 'center',
},
});
export default MyApp;
There are only two states you have to deal with
foreground
means screen is active in the focus finder;background
screen is removed from the focus finder;
As example above shows we're changing screen state based on your activity. If we are in Screen1 we setting screen state as foreground
and once we leave that screen background
state is applied. Same story with Screen2.
Full screen API can be found here.
Working with modals
The tricky thing with modal is that usually Modal is opened on the top of already active content. In this case focus finder somehow should recognize that we want to search for next focusable only in overflowing modal and what if there are more that one Modal on the screen?
For situation described above Screen offers property called screenOrder
which allows different screens to overlap which each other at the same time keeping focus to the right place.
import * as React from 'react';
import { App, Screen, Text, Pressable, StyleSheet } from '@flexn/create';
const MyApp = () => {
const [modalOpen, setModalOpen] = React.useState(false);
const renderModal = () => {
if (modalOpen) {
return (
<Screen style={styles.modal} focusOptions={{ screenOrder: 1 }}>
<Pressable style={styles.button}>
<Text>Hello from Flexn Create</Text>
</Pressable>
<Pressable style={styles.button}>
<Text>Hello from Flexn Create</Text>
</Pressable>
<Pressable style={styles.button} onPress={() => setModalOpen(false)}>
<Text>Close modal</Text>
</Pressable>
</Screen>
);
}
return null;
};
return (
<App style={{ flex: 1, width: '100%', height: '100%' }}>
<Screen style={styles.container}>
<Pressable style={styles.button} onPress={() => setModalOpen(true)}>
<Text>Open modal</Text>
</Pressable>
</Screen>
{renderModal()}
</App>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFFFFF',
},
modal: {
position: 'absolute',
left: 150,
top: 75,
right: 150,
bottom: 75,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'red',
},
button: {
margin: 20,
borderWidth: 2,
borderRadius: 25,
borderColor: '#111111',
height: 50,
width: 200,
justifyContent: 'center',
alignItems: 'center',
},
});
export default MyApp;
By default screenOrder
has 0 value. So in this case setting anything else bigger than 0 allows us keep multiple foreground screens in the viewport at the same time overflowing
each other.
Creating custom components
Flexn Focus Manager understand app layout by creating context
for each element in focus equation. Context holds all necessary information of element for focus manager.
All primitive components which is exported from @flexn/create
taking care of holding and passing context down to other elements by itself, but if you have created your custom component
this needs to be done by manually(which is very easy):
import * as React from 'react';
import { App, Screen, Text, Pressable, StyleSheet, FocusContext } from '@flexn/create';
const MyCustomComponent = ({ focusContext }: { focusContext?: FocusContext }) => {
return (
<Pressable focusContext={focusContext} style={styles.focusableElement}>
<Text>Hello from Flexn Create</Text>
</Pressable>
);
};
const MyApp = () => {
return (
<App>
<Screen style={styles.container}>
<Pressable style={styles.focusableElement}>
<Text>Hello from Flexn Create</Text>
</Pressable>
<Pressable style={styles.focusableElement}>
<Text>Hello from Flexn Create</Text>
</Pressable>
<MyCustomComponent />
</Screen>
</App>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
focusableElement: {
width: 250,
height: 250,
backgroundColor: '#0A74E6',
margin: 20,
},
});
export default MyApp;
As you can see in this example we are creating our custom component called MyCustomComponent
which by default inherits special property called focusContext
which needs
to be passed down to your parent component of return function. After doing that Flexn Focus Manager will take care of the rest.
IMPORTANT: do not create property for any of your custom component called focusContext
it's reserved by Flexn Focus Manager.