Skip to main content

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.