Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
The last step is to generate typescript interfaces from our GraphQL schema. This step is used to enhance the developer experience to strongly type our API.
Install the following dev dependencies:
yarn add @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations graphql --devAdd the following script to the script section of your package.json file:
Create a file codegen.yml in the root directory:
Replace [[API_URL]] and [[API_KEY]] with the values relevant to your project.
"graphql-codegen": "graphql-codegen",name: GraphQL API
schema:
- [[API_URL]]/graphql/v1:
headers:
apiKey: "[[API_KEY]]"
documents: ./src/**/*.graphql.ts
generates:
./src/graphql-types.ts:
plugins:
- typescript
- typescript-operationsOk, so now with the configurations out of the way, it's time to start coding our app. The first thing we need to do is to create an application skeleton by developing empty screens and configuring the navigation mechanism.
The first screen that we are going to create is going to show a list of pending tasks.
Let's start by creating our screen view component. Let's call it PendingTasksScreen. Feel free to copy the code below.
import React from 'react';
import { Text, View } from 'react-native';
export const PendingTasksScreen = () => {
return (
<View style={{ padding: 10 }}>
<Text>PendingTasksScreen</Text>
</View>
);
};
PendingTasksScreen.displayName = 'PendingTasksScreen';All we're doing right now is rendering the component name. Don't worry we will add the actual list later.
Also, create the following index file:
And also:
BlueBase provides an abstraction to create navigation plugins. This allows us to be able to use any navigation library in our app. For now, we have created & integrations.
The navigation API is loosely based on . It is very simple to create your own routes. Just add the routes property to your plugin. This is an array of objects:
Explanation of the code above:
Line 12: We changed the plugin's index route to the new one created at Line 23. By doing this, whenever we press the task icon on our launcher, we get navigated to this screen.
Line 19: Notice we added the PendingTasksScreen component to BlueBase. This is so we can reference it in our route configuration at Line 24. This is where we tell BlueBase what component to render once we have navigated to that route.
Line 23: Name of the route. We use this name to navigate to this screen. We will see an example of this later.
That's all. Run your app, and test your new screen.
Line 25: Path of the screen. This is used to create the URL for the web. The URL pattern is [[host]]/p/[[plugin-key]]/[[route-path]]. Since this is the plugin home page, we leave it empty. Please see the URL in the browser screenshot for an example.
Line 28: Route options, like title, etc.
export * from './PendingTasksScreen';
import { PendingTasksScreen } from './PendingTasksScreen';
export default PendingTasksScreen;import { PendingTasksScreen } from './PendingTasksScreen';
export const screens = {
PendingTasksScreen,
};import { createPlugin } from '@bluebase/core';
import { ToDoAppIcon } from './components/ToDoAppIcon';
import { PendingTasksScreen } from './components/PendingTasksScreen';
export default createPlugin({
key: 'tasks',
name: 'Tasks',
description: 'A todo app made with BlueBase framework.',
// ... Other plugin properties
indexRoute: 'TasksApp',
components: {
// Components
ToDoAppIcon,
// Screens
PendingTasksScreen,
},
routes: [{
name: 'TasksApp',
screen: 'PendingTasksScreen',
path: '',
exact: false,
options: {
title: 'My Tasks',
},
}]
});

BlueBase exports a ThemeConsumer component to utilise current theme. This is a render prop component, which means it takes a function as a child. The function gets an object in the param with the following interface:
interface ThemeContextData {
// Helper method to change current theme.
changeTheme: (slug: string) => void,
// Current theme
theme: Theme
}The following snippet illustrates how to use a ThemeConsumer component.
const ThemedComponent = () => (
<ThemeConsumer>
{
({ theme, changeTheme }: ThemeContextData) => {
// Use theme context here..
}
}
</ThemeConsumer>
);The following example shows how to use ThemeConsumer component to create a theme picker. This renders a picker component with a list of all installed themes. It not only uses the current theme to style itself, but also utilises the changeTheme method to switch themes.
import {
BlueBase,
BlueBaseContext,
ThemeConsumer,
ThemeContextData,
Text,
View,
} from '@bluebase/core';
import { Picker } from 'react-native';
import React from 'react';
export class ThemePicker extends React.PureComponent {
static contextType = BlueBaseContext;
render() {
const BB: BlueBase = this.context;
const themes = [...BB.Themes.entries()];
return (
<ThemeConsumer children={({ theme, changeTheme }: ThemeContextData) => (
<View style={{ backgroundColor: theme.palette.background.default }}>
<Text>Select Theme</Text>
<Picker
selectedValue={BB.Configs.getValue('theme.name')}
style={{ width: 150 }}
onValueChange={changeTheme}>
{themes.map(entry => <Picker.Item label={entry[1].name} value={entry[0]} key={entry[0]} />)}
</Picker>
</View>
)} />
);
}
}BlueBase's Logger API allows you to log messages to any number of logging services.
You can call logger for different console message modes:
BB.Logger.log('log bar');
BB.Logger.info('info bar');
BB.Logger.debug('debug bar');
BB.Logger.warn('warn bar');
BB.Logger.error('error bar');When handling an error:
try {
...
} catch(e) {
BB.Logger.error('error happened', e);
}To add support for a Logging Provider see this .
testID
string
no
-
Used to locate this view in end-to-end tests.
prop
type
required
default
description
id
string
yes
-
Plugin key
size
number
-
-
Icon size
import { PluginIcon } from '@bluebase/core';
// Then somewhere in your app:
<PluginIcon id="redux-plugin" />A more practical usage of this is as a fallback component:
If none of the components key are found in the Component Registry, and error is thrown. To avoid this, just use Noop as that last option. So in the example above, if Option1 and Option2 are not found, Noop renders null instead.
import { Noop } from '@bluebase/core';
// Then somewhere in your app:
<Noop />const CustomComponent = BB.Components.resolve('Option1', 'Option2', 'Noop');errorComponent
Component
no
-
Component to show the error state.
children
ReactNode | (() => ReactNode)
no
-
Children are rendered when there are no error.
testID
string
no
-
Used to locate this view in end-to-end tests.
prop
type
required
default
description
error
Error
no
-
If an error is passed as a prop, shows an error state.
checkError
(props) => Error
no
-
A function to check error based on props.
<ErrorObserver>
<Text>Rendered if there is no error here</Text>
</ErrorObserver>BlueBase is a framework built on top of React & React Native. It provides an opinionated application structure to create cross-platform native and web apps using the same code base.
Apart from this, BlueBase comes with a lot of features that are essential to all modern apps:
Create modular applications by breaking different features into independent plug-ins. Plug-ins are powerful enough to enhance or modify any part of the application and can be hosted in their own repositories. This also allows code reuse across applications.
Create pluggable components that can be dynamically replaced or modified through third-party plugins, without needing to change the original code. This makes it extremely easy to enhance existing views or screens in the app by writing your own plugin.
Create cross-platform routes to navigate between screens by using a simple JSON syntax.
BlueBase provides API to get or set application-level configurations. This allows developers or even end-users to change app settings.
A theme is a collection of styles that's applied to an entire app or component. When you apply a theme, every component in the app applies each of the theme's attributes that it supports. Developers can create their own themes as well as allow end-users to select one to their own liking.
Your application can use lifecycle hook methods to tap into key events in the lifecycle of an application to initialize new instances, initiate change detection when needed, respond to updates during change detection, and clean up before reboot.
Use the localization feature to translate and adapt your app into multiple languages and regions. Create multi-lingual and even right to left apps by using the simple to use API.
The Dark Mode is a first-class citizen in BlueBase and the feature comes out of the box 😎.
Add dynamic Images and assets (i.e fonts, sounds, etc), to your app that may be replaceable through plugins. Moreover use dynamic images that render different versions on mobile, desktop, light, and dark modes.
We will learn concepts of BlueBase by creating a production-ready To-Do app.
We will use Expo CLI to create a new project and add BlueBase to it. If Expo CLI is not installed, go to the Expo CLI Installation guide before proceeding.
Initialize an expo app by executing the following commands:
# Create a project named my-app.
# Select the "blank (Typescript)" template when prompted
expo init bluebase-todo-app
# Navigate to the project directory
cd bluebase-todo-appMore info here.
This will open the expo console.
You can launch the web or native version of the app from here.



yarn add @bluebase/coreimport React from 'react';
import { BlueBaseApp } from '@bluebase/core';
export default function App() {
return (
<BlueBaseApp />
);
}expo startAll functionality in BlueBase is added via plugins. Let us add some ready-made plugins to our project to take a head start on building our app.
Start by adding following devDependencies:
Some of the plugins (react-navigation) require some more dependencies to be installed:
Now in the root folder create the following file plugins.ts:
The file above exports an array of plugins, imported from their respective modules.
Create another file similar to the one above and name it plugins.native.ts. This way react-native compiler will load this version rather than plugins.ts on native platforms.
So by doing this, we can add web-specific plugins in plugins.ts and native specific code in plugins.native.ts.
In this case, the difference in the above 2 files is that plugins.ts uses:
@bluebase/plugin-react-router
@bluebase/plugin-material-ui
While plugins.native.ts uses the following instead:
@bluebase/plugin-react-navigation
@bluebase/plugin-react-native-paper
Now edit the App.tsx file and add the following content.
Here, we import the plugins array and pass them to the BlueBaseApp component as a prop.
Lastly, modify the contents of babel.config.js file to:
Now run the app again:
You should see the following screen on launch:
So as you can see, by just installing a few plugins, we were able to add a lot of functionality in a very short span of time, by writing very few lines of code.
I'll try to briefly describe what purpose each plugin serves.
yarn add @bluebase/plugin-launcher @bluebase/plugin-material-ui @bluebase/plugin-react-native-paper @bluebase/plugin-responsive-grid @bluebase/plugin-vector-icons @bluebase/plugin-settings-app @bluebase/plugin-react-router @bluebase/plugin-react-navigation @bluebase/plugin-responsive-grid @bluebase/plugin-json-schema-componentsexpo install react-native-safe-area-context react-native-gesture-handler react-native-reanimatedProvides Material UI components on native. Uses the library.
@bluebase/plugin-react-navigation
Provides Navigation capability on native. Uses the library.
@bluebase/plugin-responsive-grid
Provides Grid components (Column, Row, etc). Used by Launcher plugin.
@bluebase/plugin-vector-icons
Provides SVG vector icons used on various screens, as shown above. Uses the library.
@bluebase/plugin-launcher
Adds Android-like "launcher" home screen, that shows app icons.
@bluebase/plugin-settings-app
Adds the Settings section to the app. If you remove this plugin, the settings icon on the home screen will disappear.
@bluebase/plugin-json-schema-components
Provides components that help us build layouts by writing JSON. This is used by the settings plugin.
@bluebase/plugin-material-ui
Provides Material UI components on the web. Uses the MUI library.
@bluebase/plugin-react-router
Provides Navigation capability on the web. Uses the React Router library.




@bluebase/plugin-react-native-paper
By now it should be clear that all functionality in BlueBase is added via plugins. So in order to add views to our To-Do app, we need to create our own plugin.
Start by adding the following dependency:
This library provides typescript typings of components imported from the BlueBase context.
The first thing that we will do is to create a plugin icon component. Create the following files:
The code above uses the BlueBase theme syntax to customize styles. Read more about it in the .
Time to create the plugin itself. Create the following file:
In the above code, we use the createPlugin function from the the @bluebase/core library. This function takes attributes as input, and returns a BlueBase Plugin.
The key property servers as the plugin ID, as well as the slug in the plugin URL path. So in this case the path to access this plugin would be: http://localhost:19006/p/tasks.
The name and description properties are pretty self explanatory. The name property is rendered below the icon on the home screen.
Now we're all set to use our shiny new plugin Edit our plugin files to add our own plugin to the list.
Now run the app again:
You should see the following screen on launch:
We also need a screen to create a new task.
Similar to the previous section, now we create our third route. Notice that I have now moved the routes array dedicated file, to keep to code readable.
Import this array to your plugin file:
This should create the third route, which should be accessible via the following URL:
To improve the user experience of our app, we want to add tabs on the main page. One tab will show all pending tasks, while the other one will show the completed tasks.
It is possible to add nested routes in BlueBase. Whenever we want to do this, we will add a navigator property in the route. There are several navigators available to use (i.e. switch, stack, tab
import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
export const plugins = [
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginMaterialUI,
BlueBasePluginReactRouter,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
BlueBasePluginSettingsApp,
];import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
export const plugins = [
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginReactNativePaper,
BlueBasePluginReactNavigation,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
BlueBasePluginSettingsApp,
];import 'react-native-gesture-handler';
import React from 'react';
import { BlueBaseApp } from '@bluebase/core';
import { plugins } from './plugins';
export default function App() {
return (
<BlueBaseApp plugins={plugins} />
);
}module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};expo startyarn add @bluebase/componentsThe components property is a Map of key, value pair. Where key is a string, and value is a React component. This map is used by BlueBase to store these components in its context. These can be fetched dynamically at a later stage, and can be overwritten by plugins to change the plugin implementation. More on this here.
We save the ToDoAppIcon component in the example above.
The icon property defines the plugin icon that is rendered on the home page. In the icon.component sub-property tells which component to render.
We use 'ToDoAppIcon' value, which is the same as the key on the component object in Line 10. So, by doing this we are telling BlueBase to render a component by the key ToDoAppIcon .
Lastly, the indexRoute key tells BlueBase which screen to navigate to when the icon is pressed. We tell it to go to HomeScreen here. Since we would already be on the HomeScreen so pressing this button will not do anything.


Same as the last time, we import the apollo plugin:
Now we need to input our API URL as well as the API Token into the Apollo client. We do this by using BlueBase Configs.
Create the following file in the root folder:
Replace [[API_URL]] and [[API_KEY]] with the values relevant to your project.
Similar to how we use the plugins, we need to pass our configs object as a prop to the BlueBaseApp component.
Change the contents of the App.tsx to match below:
At the time of writing this tutorial, the latest version of Apollo Client (v3.5.4) has compatibility an issue with react-native. Add the following file in the root folder to bypass it.
This is it. Our App is configured to use the GraphQL API through the Apollo client.
import { DynamicIcon, View } from '@bluebase/components';
import { useStyles, useTheme } from '@bluebase/core';
import React from 'react';
import { TextStyle, ViewStyle } from 'react-native';
export interface ToDoAppIconStyles {
iconColor: { color: TextStyle['color'] };
root: ViewStyle;
}
export interface ToDoAppIconProps {
size: number;
styles?: Partial<ToDoAppIconStyles>;
}
export const ToDoAppIcon = (props: ToDoAppIconProps) => {
const { size } = props;
const { theme } = useTheme();
const styles: ToDoAppIconStyles = useStyles('ToDoAppIcon', props, {
iconColor: {
color: theme.palette.error.contrastText,
},
root: {
backgroundColor: theme.palette.error.main,
borderRadius: theme.shape.borderRadius * 3,
alignItems: 'center',
justifyContent: 'center',
height: size,
width: size,
},
});
return (
<View style={styles.root}>
<DynamicIcon
type="icon"
name="checkbox-multiple-marked-outline"
color={styles.iconColor.color}
size={size * 0.75}
/>
</View>
);
};
ToDoAppIcon.defaultProps = {
size: 100,
};export * from './ToDoAppIcon';
import { ToDoAppIcon } from './ToDoAppIcon';
export default ToDoAppIcon;import { ToDoAppIcon } from './ToDoAppIcon';
export const components = {
ToDoAppIcon,
};import { createPlugin } from '@bluebase/core';
import { ToDoAppIcon } from './components/ToDoAppIcon';
export default createPlugin({
key: 'tasks',
name: 'Tasks',
description: 'A todo app made with BlueBase framework.',
components: {
ToDoAppIcon,
},
icon: {
component: 'ToDoAppIcon',
type: 'component',
},
indexRoute: 'HomeScreen',
});import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
import Plugin from './src';
export const plugins = [
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginMaterialUI,
BlueBasePluginReactRouter,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
Plugin,
BlueBasePluginSettingsApp,
];import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
import Plugin from './src';
export const plugins = [
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginReactNativePaper,
BlueBasePluginReactNavigation,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
Plugin,
BlueBasePluginSettingsApp,
];expo startyarn add --dev @apollo/client @bluebase/plugin-apollo graphqlimport BlueBasePluginApollo from '@bluebase/plugin-apollo';
import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
import Plugin from './src';
export const plugins = [
BlueBasePluginApollo,
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginMaterialUI,
BlueBasePluginReactRouter,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
Plugin,
BlueBasePluginSettingsApp,
];import BlueBasePluginApollo from '@bluebase/plugin-apollo';
import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
import Plugin from './src';
export const plugins = [
BlueBasePluginApollo,
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginReactNativePaper,
BlueBasePluginReactNavigation,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
Plugin,
BlueBasePluginSettingsApp,
];
export const configs = {
// Apollo Graphql Configs
'plugin.apollo.httpLinkOptions': {
uri: [[API_URL]] + '/graphql/v1',
headers: {
apiKey: [[API_KEY]]
}
},
};import 'react-native-gesture-handler';
import { BlueBaseApp } from '@bluebase/core';
import React from 'react';
import { configs } from './configs';
import { plugins } from './plugins';
export default function App() {
return (
<BlueBaseApp configs={configs} plugins={plugins} />
);
}
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('@expo/metro-config');
const config = getDefaultConfig(__dirname);
// https://github.com/facebook/metro/issues/535
// https://github.com/apollographql/apollo-client/releases/tag/v3.5.4
config.resolver.sourceExts = process.env.RN_SRC_EXT
? [...process.env.RN_SRC_EXT.split(',').concat(config.resolver.sourceExts), 'cjs'] // <-- cjs added here
: [...config.resolver.sourceExts, 'cjs']; // <-- cjs added here
// https://docs.expo.dev/bare/installing-updates/
config.transformer.assetPlugins = [
...config.transformer.assetPlugins,
'expo-asset/tools/hashAssetFiles',
];
module.exports = config;Next, we need to create a new project. Click the "New Project" button once you login into the Supabase app, and follow the wizard to create your API project.
Now we need to create our database table to store our to-do tasks.
Start by clicking the Table Editor button on the left sidebar menu. Once you're on this page, press the "Create a new table" button.
When the form opens, add the following columns to match the screenshot below:
id (uuid)
title (varchar)
description (text)
completed (bool)
created_at (timestamptz)
Make sure "Enable Row Level Security" is NOT checked.
Since we are going to be building our app on top of GraphQL query language, we need to tell Supabase to build its schema.
On the left sidebar menu click the "SQL Editor" button. Once on this screen, press the "+ New Query" button.
Now enter the following code snippet in the editor and press "RUN"
Go to Settings => API page. Here you can find the API URL and Key as shown in the screenshot below.
That's all. We're done with creating our API for the project. All we need to do is consume it now.

But we still need a way to navigate to this route in our UI. This time we will add an icon button in the header.
Create the following component:
Also, create an index file to export the component:
To add our new TaskCreateButton to the screen, add the headerRight property in the route options like so:
That's it, run your app to test it out.
http://localhost:19006/p/tasks/createexport * from './TaskCreateButton';
import { TaskCreateButton } from './TaskCreateButton';
export default TaskCreateButton;bottom-tabsdrawerInside the navigator, we add our desired routes:
When you run the app, you should now see 2 tabs on the main page like the screenshots below:
Another thing we can do to make our UI pleasing is by adding icons to our tabs. This is done by:
Adding tabBarIcon to route.options.
Adding tabBarOptions to the navigator.
See the code below for example:
EditTaskScreen
CompletedTasksScreen
So now we should have 4 screens in total.
Add the following EditTask route to the routes array:
This should create the second route, which should be accessible via the following URL:
But we still need a way to navigate to this route in our UI. Let's modify our PendingTasksScreen component to add a button:
Notice, that we use the push method from BlueBase's useNavigation hook. The method takes route name and optional params object as its arguments.
Run your app, you should now be able to navigate between routes.
Notice that the value of path property in the route is :taskId. This means that any value that is entered in this part of the URL will be captured in the taskId param. For example the URL http://localhost:19006/p/tasks/123 will extract value 123 in the taskId variable.
Let's see how we can use this value, modify the EditTaskScreen component to match the following content:
Notice the code on Line 7, we use the getParam function to extract the URL param.
Loading State: When data is loading from the database. The modern way is to show a placeholder component.
To achieve this, we use the @bluebase/plugin-rn-placeholder plugin. This will add the components from the rn-placeholder library in BlueBase. Make sure you install and add this plugin to your project.
Data State: This will render the task data after it is loaded from the database. For this, we will use List.Item component.
See the following code for a reference implementation:
We also need to create a component that will be rendered when a list is empty. This component will show an empty message, as well as a call to action button to create a new task:
Now with these views out of the way, it's time to start creating the actual list. The first step is to write our GraphQL query.
We intend to create our list with infinite scrolling enabled. So, whenever new data is loaded, we need to merge it with the existing data, to show it as one list. To achieve this, we also create a function in this file called TasksCollectionQueryUpdateQueryFn .
There is a bug in Supabase that doesn't let us send "last" and "before" params. When it is fixed, these lines should be uncommented.
We will now generate typescript interfaces for our GraphQL API. Execute the following command:
This will update the file src/graphql-types.ts.
With all the prep work now complete, let's finally create our list. For this we will use the GraphqlList component from the @bluebase/plugin-json-graphql-components plugin. This component takes care of all the heavy lifting.
We create this component with a complete prop so that we can filter task based on this value. This will be passed as a variable to the GraphQL query.
Create the index file:
Last but not the least, lets add this list to the PendingTasksScreen and CompletedTasksScreen screens:
Refresh the app, and see it come to life. Enjoy!
-- Rebuild the GraphQL Schema Cache
select graphql.rebuild_schema();export const routes = [
{
name: 'CreateTask',
screen: 'CreateTaskScreen',
path: 'create',
exact: true,
options: {
title: 'Create Task',
},
},
{
name: 'EditTask',
screen: 'EditTaskScreen',
path: 't/:taskId',
exact: true,
options: {
title: 'Edit Task',
},
},
{
name: 'TasksApp',
screen: 'PendingTasksScreen',
path: '',
exact: false,
options: {
title: 'My Tasks',
},
}];import { createPlugin } from '@bluebase/core';
import { ToDoAppIcon } from './components/ToDoAppIcon';
import { routes } from './routes';
import { screens } from './screens';
export default createPlugin({
key: 'tasks',
name: 'Tasks',
description: 'A todo app made with BlueBase framework.',
components: {
// Components
ToDoAppIcon,
// Screens
...screens,
},
icon: {
component: 'ToDoAppIcon',
type: 'component',
},
indexRoute: 'TasksApp',
routes,
});
import { IconButton } from '@bluebase/components';
import { useNavigation, useTheme } from '@bluebase/core';
import React, { useCallback } from 'react';
export interface TaskCreateButtonProps {}
export const TaskCreateButton = (_props: TaskCreateButtonProps) => {
const { theme } = useTheme();
const { navigate } = useNavigation();
const onPress = useCallback(() => {
navigate('CreateTask');
}, []);
return (
<IconButton
name="plus"
onPress={onPress}
color={theme.palette.text.secondary}
/>
);
};{
name: 'TasksApp',
screen: 'PendingTasksScreen',
path: '',
exact: false,
options: {
title: 'My Tasks',
headerRight: () => <TaskCreateButton />
},
}{
name: 'TasksApp',
screen: Noop,
path: '',
exact: false,
options: {
title: 'My Tasks',
headerRight: () => <TaskCreateButton />
},
navigator: {
headerMode: 'none',
type: 'tab',
routes: [{
exact: true,
name: 'PendingTask',
path: 'pending',
screen: 'PendingTasksScreen',
options: {
title: 'Pending',
},
}, {
exact: true,
name: 'CompletedTasks',
path: 'completed',
screen: 'CompletedTasksScreen',
options: {
title: 'Completed',
},
}],
},
}{
name: 'TasksApp',
screen: Noop,
path: '',
exact: false,
options: {
title: 'My Tasks',
headerRight: () => <TaskCreateButton />
},
navigator: {
headerMode: 'none',
type: 'tab',
routes: [{
exact: true,
name: 'PendingTask',
path: 'pending',
screen: 'PendingTasksScreen',
options: {
title: 'Pending',
tabBarIcon: ({ color }) => <Icon name="checkbox-multiple-blank-outline" color={color} />,
},
}, {
exact: true,
name: 'CompletedTasks',
path: 'completed',
screen: 'CompletedTasksScreen',
options: {
title: 'Completed',
tabBarIcon: ({ color }) => <Icon name="checkbox-multiple-marked" color={color} />,
},
}],
tabBarOptions: {
showIcon: true,
tabStyle: {
flexDirection: 'row',
},
},
},
}routes: [{
name: 'EditTask',
screen: 'EditTaskScreen',
path: 't/:taskId',
exact: true,
options: {
title: 'Edit Task',
},
},
{
name: 'TasksApp',
screen: 'PendingTasksScreen',
path: '',
exact: false,
options: {
title: 'My Tasks',
},
}]http://localhost:19006/p/tasks/t/123import { Button, View } from '@bluebase/components';
import { useNavigation } from '@bluebase/core';
import React, { useCallback } from 'react';
import { Text } from 'react-native';
export const PendingTasksScreen = () => {
const { push } = useNavigation();
const goToEdit = useCallback(() => {
push('EditTask', { taskId: '123' });
}, []);
return (
<View>
<Text>PendingTasksScreen</Text>
<Button title="Go To Edit Screen" onPress={goToEdit} />
</View>
);
};
PendingTasksScreen.displayName = 'PendingTasksScreen';import { useNavigation } from '@bluebase/core';
import React from 'react';
import { Text, View } from 'react-native';
export const EditTaskScreen = () => {
const { getParam } = useNavigation();
const taskId = getParam('taskId', null);
return (
<View style={{ padding: 10 }}>
<Text>EditTaskScreen: {taskId}</Text>
</View>
);
};
EditTaskScreen.displayName = 'EditTaskScreen';import { Checkbox, List } from '@bluebase/components';
import { getComponent, useNavigation } from '@bluebase/core';
import { PlaceholderListItemProps } from '@bluebase/plugin-rn-placeholder';
import React, { useCallback } from 'react';
import { Tasks } from '../../graphql-types';
const PlaceholderListItem = getComponent<PlaceholderListItemProps>('PlaceholderListItem');
export interface TaskListItemProps extends Tasks {
loading?: boolean;
}
export const TaskListItem = (props: TaskListItemProps) => {
const {
id,
title,
description,
completed,
loading,
} = props;
if (loading === true) {
return <PlaceholderListItem avatar description variant="icon" />;
}
const { push } = useNavigation();
const onPress = useCallback(() => {
push('EditTask', { taskId: id });
}, [id]);
return (
<List.Item
title={title}
description={description}
left={<Checkbox checked={!!completed} disabled />}
onPress={onPress}
/>
);
};
TaskListItem.defaultProps = {};
TaskListItem.displayName = 'TaskListItem';export * from './TaskListItem';
import { TaskListItem } from './TaskListItem';
export default TaskListItem;import { ComponentState, ComponentStateProps } from '@bluebase/components';
import { useNavigation } from '@bluebase/core';
import React, { useCallback } from 'react';
export interface TaskListEmptyStateProps extends ComponentStateProps {}
export const TaskListEmptyState = (props: TaskListEmptyStateProps) => {
const { navigate } = useNavigation();
const goToCreate = useCallback(() => navigate('CreateTask'), []);
return (
<ComponentState
title="No tasks"
description="Start by creating a new task"
actionTitle="Create Task"
imageProps={{ resizeMode: 'contain' }}
actionOnPress={goToCreate}
actionProps={{ size: 'small', color: 'success', variant: 'outlined' }}
{...props}
/>
);
};
TaskListEmptyState.displayName = 'TaskListEmptyState';export * from './TaskListEmptyState';
import { TaskListEmptyState } from './TaskListEmptyState';
export default TaskListEmptyState;import { FetchMoreOptions } from '@apollo/client';
import gql from 'graphql-tag';
import { TasksCollectionQueryQuery } from '../../graphql-types';
export const TasksCollectionQuery = gql`
query TasksCollectionQuery(
$filter: tasksFilter
$first: Int
# $last: Int
# $before: Cursor
$after: Cursor
) {
tasksCollection(
filter: $filter
first: $first
# last: $last
# before: $before
after: $after
) {
edges {
cursor
node {
id
title
completed
}
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`;
export const TasksCollectionQueryUpdateQueryFn: FetchMoreOptions<TasksCollectionQueryQuery>['updateQuery'] = (
previousResult,
{ fetchMoreResult }
) => {
if (!fetchMoreResult) {
return previousResult;
}
const prevEdges = previousResult.tasksCollection?.edges || [];
const newEdges = fetchMoreResult.tasksCollection?.edges || [];
return {
// Put the new items at the end of the list and update `pageInfo`
// so we have the new `endCursor` and `hasNextPage` values
tasksCollection: {
...previousResult.tasksCollection,
edges: [...prevEdges, ...newEdges],
pageInfo: {
...previousResult.tasksCollection?.pageInfo,
endCursor: fetchMoreResult.tasksCollection?.pageInfo?.endCursor,
hasNextPage: !!fetchMoreResult.tasksCollection?.pageInfo?.hasNextPage,
hasPreviousPage: !!fetchMoreResult.tasksCollection?.pageInfo?.hasPreviousPage,
startCursor: fetchMoreResult.tasksCollection?.pageInfo?.startCursor,
},
},
};
};yarn graphql-codegenimport { QueryResult } from '@apollo/client';
import { Divider } from '@bluebase/components';
import { getComponent } from '@bluebase/core';
import { GraphqlConnection, GraphqlListProps } from '@bluebase/plugin-json-graphql-components';
import React from 'react';
import { ListRenderItemInfo } from 'react-native';
import { Tasks, TasksCollectionQueryQuery } from '../../graphql-types';
import TaskListEmptyState from '../TaskListEmptyState';
import { TaskListItem, TaskListItemProps } from '../TaskListItem';
import { TasksCollectionQuery, TasksCollectionQueryUpdateQueryFn } from './TasksCollectionQuery.graphql';
const GraphqlList = getComponent<GraphqlListProps<TaskListItemProps, TasksCollectionQueryQuery>>('GraphqlList');
function mapQueryResultToConnection(result: QueryResult<TasksCollectionQueryQuery>) {
return result.data?.tasksCollection as GraphqlConnection<Tasks>;
}
function renderItem({ item }: ListRenderItemInfo<TaskListItemProps>) {
return <TaskListItem {...item} />;
}
const renderDivider = () => <Divider inset />;
export interface TaskListProps {
completed: boolean;
}
export const TaskList = (props: TaskListProps) => {
const { completed } = props;
const itemsPerPage = 10;
return (
<GraphqlList
key="task-list"
pagination="infinite"
itemsPerPage={itemsPerPage}
query={TasksCollectionQuery}
updateQueryInfinitePagination={TasksCollectionQueryUpdateQueryFn}
mapQueryResultToConnection={mapQueryResultToConnection}
renderItem={renderItem}
ItemSeparatorComponent={renderDivider}
ListEmptyComponent={TaskListEmptyState}
queryOptions={{
variables: {
filter:{ completed: { eq: completed } }
}
}}
/>
);
};
TaskList.displayName = 'TaskList';export * from './TaskList';
import { TaskList } from './TaskList';
export default TaskList;import React from 'react';
import { TaskList } from '../../components/TaskList';
export const PendingTasksScreen = () => {
return (
<TaskList completed={false} />
);
};
PendingTasksScreen.displayName = 'PendingTasksScreen';import React from 'react';
import TaskList from '../../components/TaskList';
export const CompletedTasksScreen = () => {
return (
<TaskList completed />
);
};
CompletedTasksScreen.displayName = 'CompletedTasksScreen';
























Our users may want to edit their tasks or mark them as complete. We will have to create a form very similar to the one in Chapter 4.1. The only difference is that we need to query task data and prefill the form with it.
Let's start by creating an update mutation. This will be used when a user submits the form.
We will now generate typescript interfaces for our GraphQL API. Execute the following command:
This will update the file src/graphql-types.ts.
Create the following component:
Explanation:
Line 22: We take task ID as a prop. This will be used as a query variable.
Line 28: A function that takes the query result as input (array of tasks) and returns a single task. This task is used as the initial value of the form.
Line 33: A function that takes form data and converts it into mutation variables. This function is called when a user submits the form.
Let's add our form to the EditTaskScreen component:
When you run the app, you should results similar to the screenshot below:
Let's make our app a bit visually pleasing by adding some images. The perfect place to do this is in the TaskListEmptyState component.
Download the following image and put it at ssets/no-tasks-light.png.
Modify your plugin to add an assets property like the in the code below:
Basically, we're telling BlueBase that the said image has an ID NoTasks and where to find it. Now we can just reference this image by this ID and BlueBase will take care of loading and rendering it across formats.
Modify the ComponentState node in the TaskListEmptyState component and add a prop imageSource="NoTasks" as shown in the code below:
Now when you refresh the app, you should see an image in the empty state.
Sometimes we need a different variant of an image for dark mode. Don't worry, we have got you covered here as well.
Download the following image and put it at: assets/no-tasks-dark.png.
Now, modify the assets property in the plugin to match the following:
Try refreshing your app. You'll see different images will be rendered in light mode and dark mode.
Basically, you can define a different image with the same ID (NoTasks in this example) for all of the following scenarios:
BlueBase was built from the ground up with theming in mind. It is an extensive topic and we encourage you to read the Themes section of the docs.
Just to see a bit of a demo, we will customise the styles of our custom component from app configurations.
You remember creating the ToDoAppIcon component in Chapter 1.3:
Explanation:
Line 2: We import 2 hooks from BlueBase core: useStyles and useTheme.
Line 6: We define the interface of the stylesheet for the component.
Line 13: Add the styles property as an optional prop.
Line 18: We access the currently active theme from the useTheme hook.
Line 20: Extract the final styles from the useStyles hook. This hook takes 3 params:
Name of the component. We can use this name in our themes to override the styles of this component.
Input props of the component. These may contain the styles prop, as well as any other prop that is needed. Note that these props are also passed on to the defaultStyles
Line 35 & 39: We use the styles in our component.
Let's see how we can customize the appearance of this component from our app configs.
Create a prop in the configs by key theme.overrides. The value of this property is a theme object. Here you can override a theme's styles.
In the example below, we are basically telling BlueBase to override the styles of the ToDoAppIcon component in light mode.
Note that the value of configs['theme.overrides'].light.components.ToDoAppIcon is an object that matches the ToDoAppIconStyles interface.
Now when you run your app, you will notice the app icon color is purple in light mode, and not red.
Lastly, we want to allow our users to be able to delete tasks.
Let's write a mutation to delete our task.
import gql from 'graphql-tag';
export const DeleteTaskMutation = gql`
mutation DeleteTaskMutation($id: UUID!) {
deleteFromtasksCollection(filter: { id: { eq: $id } }) {
records {
id
}
}
}
`;We will now generate typescript interfaces for our GraphQL API. Execute the following command:
This will update the file src/graphql-types.ts.
Now lets an IconButton to delete the task.
Modify the EditTaskScreen to add the headerRight option:
This should be the result:
Internationalisation is a big part of modern apps. BlueBase has built-in support for translations and right-to-left (RTL) text support.
Go you the Settings app, and select a language. If the language needs RTL, BlueBase will change orientation automatically.
Changing content direction on native requires app to be restarted.
You can add support for any language you desire. Here are the steps on how to do this:
We support English and Urdu languages by default. If you need to add support for any other language just pass them to the locale.options object in your configs file, where the key of the object is the language code, and value is the language name.
Next, we have to create a dictionary, that the system can refer to when attempting to translate a string. The dictionary is an object where where original string is the key, and the translated version is the value.
The way to do it is, we create a function that takes a dictionary as input, and we return that dictionary by appending our own translations to it. This is because the translations feature in BlueBase is built upon its feature.
The filter key for format to add translations is: bluebase.intl.messages.${language_code}. So for french language it would be bluebase.intl.messages.fr.
When we have all our language filters setup, we just need to add them to the filters object in the plugin.
While we attempt to give built in conversion support for our official plugins, you may still need to do one additional step to convert your string to the current locale.
We'll take example of our TaskListEmptyState component. When you select a language different than English, you will note that the text is still not translated. Let's change that.
Go to your component and change to code to the following:
You will note that:
Line 2: We import useIntl hook from @bluebase/core.
Line 9: We extract the __ function from the useIntl() hook.
That's all! Since we have already provided translations to these strings in previous step, when you refresh the app you will now observe the text to be converted.
Now that we have created our plugin that adds the Tasks functionality to our app, let's see if we can make it configurable.
There are "configurable", we mean 2 things:
A developer that is using this plugin, can configure it via the configs prop.
An end-user can configure the feature through the "Settings" UI.
We will attempt to do both these things in this chapter. For this example, we will make the number of items our Task List loads per page.
Let's go back to our TaskList component from .
Change Line 32 from:
To:
Basically, rather than having a hardcoded number in the code, we extract it from config with key 'tasks.itemsPerPage'. We utilize the useConfig hook for this purpose, that is imported from the @bluebase/core package.
Note that the useConfig hook has the same API as the useState hook in the react library.
This should be your final code now:
Now that we are using the config, we also need to define its default value. Add the defaultConfigs object to your plugin as shown in the code below:
Now run your app and inspect. You will observe that BlueBase has successfully loaded the default config, which has been passed onto the GraphqlList component.
Now comes the fun part. Let's override this value from our app's configs:
Run your app again. You will observe that the value in the configs was loaded and preferred over the defaultConfigs.
Now, let's see how we can allow the end-user to customize the value too.
Let's create a form that will allow the user to input a value, and update the config when he presses the submit button.
For this purpose, we will use the JsonForm component from the @bluebase/plugin-json-schema-components.
Add the index file to export the component.
Now we want to create a screen for "Tasks" in the Settings app. Luckily the Settings app allows this to be done via bluebase.plugin.setting-app.pages filter.
This filter inputs an array of screens in the settings app, all we have to do is to append our own screen configs and return the array. Create a new file and copy the following code:
Note that in Line 22 till 27 we define the "Task Settings" panel and use the TaskSettingsForm component that we created in the previous step.
Let's add this filter to the plugin (See Line 13):
Run the app and go to the settings section. You will see the "Tasks" settings page is added.
When you use and submit the form, you will observe that it will update the config to achieve the desired result:
The first thing to do would be to create a form to insert a new task in the database.
BlueBase provides a utility to create forms quickly by just writing a simple JSON-based schema. Let's get right to it.
Add the following plugin as a dependency to the project:
Then import and use this plugin to BlueBase:
Now we're going to write a GraphQL mutation to insert a new task in the database. Create the following file.
This tutorial does not cover how GraphQL works. If you are new to it, head over to this .
We will now generate typescript interfaces for our GraphQL API. Execute the following command:
This will create a new file at: src/graphql-types.ts.
Create the follwing files:
Here's what's happening in the file above:
Line 8: We import the JsonGraphqlForm from the BlueBase context. This component was added to our app by the @bluebase/plugin-json-graphql-components plugin.
Line 15: We create a callback function, that will be called once the form is successfully submitted to the API. Here we want to navigate to the main page after success.
Line 19: If the output data of the form is different from what the API expects, we can define a function to map it to the right format. In this case, the form inputs a single task object, but the API expects an array. So we do the conversion here. This function is passed as a prop to the
Finally, we create the index file to export the component:
Now that we have created our form, it's time to add it to our layout. Change the the CreateTaskScreen component match the following code:
We're all done. Time to take our form for a test drive. Input some data into the form and submit it.
If all goes well, you should now be redirected to the task list screen. Go to Supabase admin panel, and then to the table editor to check if the data was in fact inserted into the database.
A plugin is just a JavaScript object that has the following properties:
Key
Type
Required
Description
name
string
yes
Name of the plugin.
key
string
Categories are used in plugin listing pages. Currently, we recognise the following kind of plugins, but a developer may define his own category as well.
import gql from 'graphql-tag';
export const UpdateTaskMutation = gql`
mutation UpdateTaskMutation(
$id: UUID!
$title: String
$description: String
$completed: Boolean
) {
updatetasksCollection(
filter: { id: { eq: $id } }
set: {
title: $title,
description: $description,
completed: $completed
}
) {
affectedCount
records {
id
title
description
completed
}
}
}
`;
import { DynamicIcon, View } from '@bluebase/components';
import { useStyles, useTheme } from '@bluebase/core';
import React from 'react';
import { TextStyle, ViewStyle } from 'react-native';
export interface ToDoAppIconStyles {
iconColor: { color: TextStyle['color'] };
root: ViewStyle;
}
export interface ToDoAppIconProps {
size: number;
styles?: Partial<ToDoAppIconStyles>;
}
export const ToDoAppIcon = (props: ToDoAppIconProps) => {
const { size } = props;
const { theme } = useTheme();
const styles: ToDoAppIconStyles = useStyles('ToDoAppIcon', props, {
iconColor: {
color: theme.palette.error.contrastText,
},
root: {
backgroundColor: theme.palette.error.main,
borderRadius: theme.shape.borderRadius * 3,
alignItems: 'center',
justifyContent: 'center',
height: size,
width: size,
},
});
return (
<View style={styles.root}>
<DynamicIcon
type="icon"
name="checkbox-multiple-marked-outline"
color={styles.iconColor.color}
size={size * 0.75}
/>
</View>
);
};
ToDoAppIcon.defaultProps = {
size: 100,
};
yarn add @bluebase/plugin-json-graphql-componentsimport BlueBasePluginApollo from '@bluebase/plugin-apollo';
import BlueBasePluginJsonGraphqlComponents from '@bluebase/plugin-json-graphql-components';
import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
import Plugin from './src';
export const plugins = [
BlueBasePluginApollo,
BlueBasePluginJsonGraphqlComponents,
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginMaterialUI,
BlueBasePluginReactRouter,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
Plugin,
BlueBasePluginSettingsApp,
];
import BlueBasePluginApollo from '@bluebase/plugin-apollo';
import BlueBasePluginJsonGraphqlComponents from '@bluebase/plugin-json-graphql-components';
import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
import Plugin from './src';
export const plugins = [
BlueBasePluginApollo,
BlueBasePluginJsonGraphqlComponents,
BlueBasePluginJsonSchemaComponents,
BlueBasePluginLauncher,
BlueBasePluginReactNativePaper,
BlueBasePluginReactNavigation,
BlueBasePluginResponsiveGrid,
MaterialCommunityIcons,
Plugin,
BlueBasePluginSettingsApp,
];
React as a UI library puts a lot of emphasis on components. BlueBase stores components which can be dynamically used and replaced. This plays a major role in creating a pluggable and modular system.


Desktop Screen Size version on Dark mode
NoTasks_mobile_dark
Mobile Screen Size version on Dark mode
NoTasks_desktop_light
Desktop Screen Size version on Light mode
NoTasks_mobile_light
Mobile Screen Size version on Light mode
NoTasks
Default version
NoTasks_dark
Dark mode version
NoTasks_light
Light mode verion
NoTasks_desktop
Desktop Screen Size version
NoTasks_mobile
Mobile Screen Size version







NoTasks_desktop_dark
The default styles. These are the base styles that may be overwritten by theme overrides. This param may be an object (of the interface defined at Line 6), or a function that returns this object.




__









JsonGraphqlFormLine 26: We pass our mutation to the form. This will be called when the form is submitted.
Line 31: This is our JSON schema that generates the form. Check the plugin docs for more information on its API.



A plugin that provides integration with a logging service. i.e. Google Analytics.
no
This property is used as an ID throughout the system.
description
string
no
Plugin description.
version
string
no
Plugin version.
categories
string[]
no
icon
DynamicIconProps
no
These are props of the DynamicIcon component. This value can also be a thunk, i.e. a function that should return the props object. This function receives BlueBase context as param.
enabled
boolean
no
(Default = true) Flag to check if a plugin is enabled.
filters
FilterNestedCollection
no
components
ComponentCollection
no
themes
ThemeCollection
no
defaultConfigs
ConfigCollection
no
Key
Description
app
A plugin that is a sub-app in the system. This applies that when BlueBase is used as a bigger platform, and different features are represented as individuals apps inside the platform.
store
A plugin that provides a state store functionality. i.e. Redux.
router
A plugin that provides routing mechanism. i.e. react-router or React Navigation.
logging
A plugin that provides integration with a logging service. i.e. Sentry.
theme
A plugin that provides a single or multiple themes.
analytics
You can add support for any analytics service provider by using the bluebase.analytics.track filter.
import { createPlugin } from '@bluebase/core';
export const AnalyticsPlugin = createPlugin({
key: 'analytics',
name: 'Analytics Plugin',
categories: ['analytics'],
filters: {
'bluebase.analytics.track': (data: AnalyticsTrackData) => {
// send data to analytics provider here
}
}
});yarn graphql-codegenimport { getComponent, useNavigation } from '@bluebase/core';
import { JsonGraphqlFormProps } from '@bluebase/plugin-json-graphql-components';
import React, { useCallback } from 'react';
import {
TasksCollectionQueryQuery,
TasksCollectionQueryQueryVariables,
TasksInsertInput,
UpdateTaskMutationMutationVariables
} from '../../graphql-types';
import { TasksCollectionQuery } from '../TaskList/TasksCollectionQuery.graphql';
import { UpdateTaskMutation } from './UpdateTaskMutation.graphql';
const JsonGraphqlForm = getComponent<JsonGraphqlFormProps<any>>('JsonGraphqlForm');
export interface EditTaskFormProps {
id: string;
}
export const EditTaskForm = (props: EditTaskFormProps) => {
const { navigate } = useNavigation();
const { id } = props;
const onSuccess = useCallback(() => {
navigate('TasksApp');
}, []);
const mapQueryDataToInitialValues = useCallback(
(data: TasksCollectionQueryQuery) => {
return data?.tasksCollection?.edges[0]?.node;
}, []);
const mapFormValuesToMutationVariables = useCallback(
(task: TasksInsertInput): UpdateTaskMutationMutationVariables => {
return {
id: task.id,
title: task.title,
description: task.description,
completed: task.completed
};
}, []);
const queryVariables: TasksCollectionQueryQueryVariables = {
filter: {
id: { 'eq': id }
}
};
return (
<JsonGraphqlForm
query={{
query: TasksCollectionQuery,
variables: queryVariables
}}
mutation={{
mutation: UpdateTaskMutation,
refetchQueries: [TasksCollectionQuery],
awaitRefetchQueries: true
}}
onSuccess={onSuccess}
mapFormValuesToMutationVariables={mapFormValuesToMutationVariables}
mapQueryDataToInitialValues={mapQueryDataToInitialValues}
{...props}
schema={{
validateOnBlur: false,
validateOnChange: false,
fields: [
{
autoFocus: true,
label: 'Title',
name: 'title',
required: true,
type: 'text',
},
{
label: 'Description',
name: 'description',
type: 'text',
},
{
label: 'Completed',
name: 'completed',
type: 'checkbox',
},
{
name: 'status',
type: 'status',
},
{
name: 'submit',
title: 'Update Task',
type: 'submit',
},
],
}}
/>
);
};
EditTaskForm.displayName = 'EditTaskForm';export * from './EditTaskForm';
import { EditTaskForm } from './EditTaskForm';
export default EditTaskForm;import { useNavigation } from '@bluebase/core';
import React from 'react';
import EditTaskForm from '../../components/EditTaskForm';
export const EditTaskScreen = () => {
const { getParam } = useNavigation();
const taskId = getParam('taskId', null);
return (
<EditTaskForm id={taskId} />
);
};
EditTaskScreen.displayName = 'EditTaskScreen';import { createPlugin } from '@bluebase/core';
import { ToDoAppIcon } from './components/ToDoAppIcon';
import { filters } from './filters';
import { lang } from './lang';
import { routes } from './routes';
import { screens } from './screens';
export default createPlugin({
key: 'tasks',
name: 'Tasks',
description: 'A todo app made with BlueBase framework.',
assets: {
NoTasks: require('../assets/no-tasks-light.png'),
},
// ... Other properties
});<ComponentState
imageSource="NoTasks"
title={__('No tasks')}
imageProps={{ resizeMode: 'contain' }}
description={__('Start by creating a new task')}
actionTitle={__('Create Task')}
actionOnPress={goToCreate}
actionProps={{ size: 'small', color: 'success', variant: 'outlined' }}
{...props}
/>assets: {
NoTasks_dark: require('../assets/no-tasks-dark.png'),
NoTasks_light: require('../assets/no-tasks-light.png'),
},export const configs = {
// ... Other Configs
'theme.overrides': {
light: {
components: {
ToDoAppIcon: {
root: {
backgroundColor: '#6750A4',
}
}
}
}
}
};
yarn graphql-codegenimport { useMutation } from '@apollo/client';
import { IconButton } from '@bluebase/components';
import { useNavigation, useTheme } from '@bluebase/core';
import React, { useCallback } from 'react';
import { DeleteTaskMutationMutation } from '../../graphql-types';
import { TasksCollectionQuery } from '../TaskList/TasksCollectionQuery.graphql';
import { DeleteTaskMutation } from './DeleteTaskMutation.graphql';
export interface TaskDeleteButtonProps {
id: string
}
export const TaskDeleteButton = (props: TaskDeleteButtonProps) => {
const { id } = props;
const { theme } = useTheme();
const { navigate } = useNavigation();
const onCompleted = useCallback(() => {
navigate('TasksApp');
}, []);
const [deleteTask, { loading }] = useMutation<DeleteTaskMutationMutation>(DeleteTaskMutation, {
onCompleted,
refetchQueries: [TasksCollectionQuery],
awaitRefetchQueries: true,
variables: { id }
});
return (
<IconButton
name="delete"
onPress={deleteTask}
color={theme.palette.text.secondary}
disabled={loading}
/>
);
};
TaskDeleteButton.displayName = 'TaskDeleteButton';export * from './TaskDeleteButton';
import { TaskDeleteButton } from './TaskDeleteButton';
export default TaskDeleteButton;{
name: 'EditTask',
screen: 'EditTaskScreen',
path: 't/:taskId',
exact: true,
options: ({ route }: any) => ({
title: 'Edit Task',
headerRight: () => <TaskDeleteButton id={route.params.taskId} />
}),
},export const configs = {
'locale.options': {
en: 'English',
fr: 'French',
ur: 'اُردُو',
},
// ... Other configs
};import { IntlMessages } from '@bluebase/core';
export const ur = (messages: IntlMessages) => ({
...messages,
'Title': 'عنوان',
'Description': 'تفصیل',
'Pending': 'زیر التواء',
'Completed': 'مکمل',
'Tasks': 'کام',
'My Tasks': 'میری ٹاسکس',
'No tasks': 'کوئی کام نہیں',
'Start by creating a new task': 'ایک نیا کام بنا کر شروع کریں۔',
'Create Task': 'ٹاسک بنائیں',
'Edit Task': 'ٹاسک ترمیم کریں',
'Update Task': 'ٹاسک تدوین کریں',
});
export default ur;import { ur } from './ur';
export const lang = {
'bluebase.intl.messages.ur': ur,
};import { createPlugin } from '@bluebase/core';
import { ToDoAppIcon } from './components/ToDoAppIcon';
import { lang } from './lang';
import { routes } from './routes';
import { screens } from './screens';
export default createPlugin({
key: 'tasks',
name: 'Tasks',
description: 'A todo app made with BlueBase framework.',
filters: {
...lang,
},
// ... other plugin properties
});
import { ComponentState, ComponentStateProps } from '@bluebase/components';
import { useIntl, useNavigation } from '@bluebase/core';
import React, { useCallback } from 'react';
export interface TaskListEmptyStateProps extends ComponentStateProps {}
export const TaskListEmptyState = (props: TaskListEmptyStateProps) => {
const { __ } = useIntl();
const { navigate } = useNavigation();
const goToCreate = useCallback(() => navigate('CreateTask'), []);
return (
<ComponentState
title={__('No tasks')}
description={__('Start by creating a new task')}
actionTitle={__('Create Task')}
imageProps={{ resizeMode: 'contain' }}
actionOnPress={goToCreate}
actionProps={{ size: 'small', color: 'success', variant: 'outlined' }}
{...props}
/>
);
};
TaskListEmptyState.displayName = 'TaskListEmptyState';const itemsPerPage = 10;const [itemsPerPage] = useConfig('tasks.itemsPerPage');import { QueryResult } from '@apollo/client';
import { Divider } from '@bluebase/components';
import { getComponent, useConfig } from '@bluebase/core';
import { GraphqlConnection, GraphqlListProps } from '@bluebase/plugin-json-graphql-components';
import React from 'react';
import { ListRenderItemInfo } from 'react-native';
import { Tasks, TasksCollectionQueryQuery, TasksCollectionQueryQueryVariables } from '../../graphql-types';
import TaskListEmptyState from '../TaskListEmptyState';
import { TaskListItem, TaskListItemProps } from '../TaskListItem';
import { TasksCollectionQuery, TasksCollectionQueryUpdateQueryFn } from './TasksCollectionQuery.graphql';
const GraphqlList = getComponent<GraphqlListProps<TaskListItemProps, TasksCollectionQueryQuery>>('GraphqlList');
function mapQueryResultToConnection(result: QueryResult<TasksCollectionQueryQuery>) {
return result.data?.tasksCollection as GraphqlConnection<Tasks>;
}
function renderItem({ item }: ListRenderItemInfo<TaskListItemProps>) {
return <TaskListItem {...item} />;
}
const renderDivider = () => <Divider inset />;
export interface TaskListProps {
completed: boolean;
}
export const TaskList = (props: TaskListProps) => {
const { completed } = props;
const [itemsPerPage] = useConfig('tasks.itemsPerPage');
const variables: TasksCollectionQueryQueryVariables = {
filter: {
completed: { 'eq': completed }
}
};
return (
<GraphqlList
key="task-list"
pagination="infinite"
itemsPerPage={itemsPerPage}
query={TasksCollectionQuery}
updateQueryInfinitePagination={TasksCollectionQueryUpdateQueryFn}
mapQueryResultToConnection={mapQueryResultToConnection}
renderItem={renderItem}
ItemSeparatorComponent={renderDivider}
ListEmptyComponent={TaskListEmptyState}
queryOptions={{
variables
}}
/>
);
};
TaskList.displayName = 'TaskList';import { createPlugin } from '@bluebase/core';
export default createPlugin({
key: 'tasks',
name: 'Tasks',
description: 'A todo app made with BlueBase framework.',
defaultConfigs: {
'tasks.itemsPerPage': 10,
}
// ... other properties
});export const configs = {
'tasks.itemsPerPage': 5,
// ... other configs
};import { getComponent, useConfig } from '@bluebase/core';
import { JsonFormProps } from '@bluebase/plugin-json-schema-components';
import { FormikHelpers } from 'formik';
import React from 'react';
interface TaskSettingsFormValues {
itemsPerPage: number;
}
const JsonForm = getComponent<JsonFormProps<TaskSettingsFormValues>>('JsonForm');
export interface TaskSettingsFormProps {}
export const TaskSettingsForm = (props: TaskSettingsFormProps) => {
const [itemsPerPage, setItemsPerPage] = useConfig('tasks.itemsPerPage');
return (
<JsonForm
{...props}
schema={{
validateOnBlur: false,
validateOnChange: false,
fields: [
{
autoFocus: true,
label: 'Items per page',
name: 'itemsPerPage',
required: true,
type: 'number',
},
{
schema: { component: 'Divider' },
type: 'component',
},
{
direction: 'right',
name: 'form-actions',
type: 'inline',
fields: [
{
name: 'reset',
type: 'reset',
},
{
name: 'submit',
title: 'Save',
type: 'submit',
},
],
},
],
initialValues: {
itemsPerPage,
},
onSubmit: (values: TaskSettingsFormValues, helpers: FormikHelpers<TaskSettingsFormValues>) => {
const { setSubmitting } = helpers;
setItemsPerPage(values.itemsPerPage);
// Wait one second and then setSubmitting to false
setTimeout(() => setSubmitting(false), 1000);
}
}}
/>
);
};
TaskSettingsForm.displayName = 'TaskSettingsForm';export * from './TaskSettingsForm';
import { TaskSettingsForm } from './TaskSettingsForm';
export default TaskSettingsForm;import { SettingsPageProps } from '@bluebase/plugin-settings-app';
import TaskSettingsForm from './components/TaskSettingsForm';
export const filters = {
'bluebase.plugin.setting-app.pages': [
{
key: 'bluebase-settings-todo-page',
priority: 20,
value: (pages: SettingsPageProps[]) => [
{
name: 'TaskSettings',
path: 'task',
options: {
drawerIcon: { type: 'icon', name: 'checkbox-multiple-marked' },
title: 'Tasks',
},
items: [
{
name: 'task-settings',
component: TaskSettingsForm,
title: 'Task Settings',
description: 'Configure your tasks',
},
],
},
...pages,
],
},
],
};import { createPlugin } from '@bluebase/core';
import { filters } from './filters';
import { lang } from './lang';
export default createPlugin({
key: 'tasks',
name: 'Tasks',
description: 'A todo app made with BlueBase framework.',
filters: {
...filters,
...lang,
},
// ... Other properties
});import gql from 'graphql-tag';
export const CreateTaskMutation = gql`
mutation CreateTaskMutation ($tasks: [tasksInsertInput!]!){
insertIntotasksCollection(objects: $tasks) {
records {
id
title
description
completed
}
}
}
`;yarn graphql-codegenimport { getComponent, useNavigation } from '@bluebase/core';
import { JsonGraphqlFormProps } from '@bluebase/plugin-json-graphql-components';
import React, { useCallback } from 'react';
import { CreateTaskMutationMutationVariables, TasksInsertInput } from '../../graphql-types';
import { CreateTaskMutation } from './CreateTaskMutation.graphql';
const JsonGraphqlForm = getComponent<JsonGraphqlFormProps<TasksInsertInput>>('JsonGraphqlForm');
export interface CreateTaskFormProps {}
export const CreateTaskForm = (props: CreateTaskFormProps) => {
const { navigate } = useNavigation();
const onSuccess = useCallback(() => {
navigate('TasksApp');
}, []);
const mapFormValuesToMutationVariables = useCallback(
(task: TasksInsertInput): CreateTaskMutationMutationVariables => {
return { tasks: [task] };
}, []);
return (
<JsonGraphqlForm
mutation={{
mutation: CreateTaskMutation
}}
onSuccess={onSuccess}
mapFormValuesToMutationVariables={mapFormValuesToMutationVariables}
{...props}
schema={{
validateOnBlur: false,
validateOnChange: false,
fields: [
{
autoFocus: true,
label: 'Title',
name: 'title',
required: true,
type: 'text',
},
{
label: 'Description',
name: 'description',
type: 'text',
},
{
label: 'Completed',
name: 'completed',
type: 'checkbox',
},
{
name: 'status',
type: 'status',
},
{
fullWidth: true,
name: 'submit',
title: 'Create Task',
type: 'submit',
},
],
}}
/>
);
};
CreateTaskForm.displayName = 'CreateTaskForm';export * from './CreateTaskForm';
import { CreateTaskForm } from './CreateTaskForm';
export default CreateTaskForm;import React from 'react';
import CreateTaskForm from '../../components/CreateTaskForm';
export const CreateTaskScreen = () => {
return (
<CreateTaskForm />
);
};
CreateTaskScreen.displayName = 'CreateTaskScreen';import { createPlugin } from '@bluebase/core';
export const ExamplePlugin = createPlugin({
key: 'example',
name: 'Example Plugin',
description: 'This is just an example plugin.',
version: '1.0.0',
categories: ['theme', 'app'],
icon: () => ({
type: 'icon',
name: 'rocket',
}),
defaultConfigs: {
'plugin.example.foo': 'bar'
},
// ... any components come here
components: [{
key: 'Logo',
value: import('./Logo')
}],
// ... add other configs, i.e. themes, filters, etc
});A plugin can register any number of themes. To do this simple set the themes property of your plugin class.
const Plugin = {
themes: [{
name: 'Material UI (Light)',
key: 'material-ui-light',
mode: 'light',
// .. other theme props
}, {
name: 'Material UI (Dark)',
key: 'material-ui-dark',
mode: 'dark',
// .. other theme props
}]
}The following guide needs to be added to Themes section.
Each object in the theme property can be a BlueBase Module. This mean each theme's code can be split into different bundles on web.
const Plugin = {
public themes = [
import('path/to/theme-1'),
import('path/to/theme-2'),
]
}Be aware though, that if you use this approach, these bundles will be downloaded during boot time. This is because BlueBase needs know each theme's name and key to store them in registry.
If you want to split the theme so that they are downloaded only when selected, spilt the internal theme property.
Some times developers may need to make their plugins configurable, this may even be necessary in some cases. For example, a Google Analytics plugin may need to have a configuration for tracking code, as this value is different for every project.
In BlueBase it is very each to achieve this using the Configs API. Just set or get a config value anywhere in the plugin's business logic.
There are times, when you may need to define default values for plugin configurations as well. To do this, create a defaultConfigs property in your plugin as shown below:
import { createPlugin } from '@bluebase/core';
export const ExamplePlugin = createPlugin({
key: 'example',
name: 'Example Plugin',
defaultConfigs: {
'plugin.example.foo': 'bar'
},
// ... add other configs, i.e. themes, filters, etc
});BlueBase will automatically set this value in the Config registry, if a custom value isn't provided.
In BlueBase, we use the following naming convention for configs belonging to plugins:
So, as shown in the example plugin above, a config foo for a plugin with key example becomes:
BlueBase . It is just a React Component, and can either be used as the top most component of your project or it can be embedded in your existing code base.
This component takes care of initialisation and renders the SystemApp component.
If children prop is provided, then it renders the children prop instead of the Main App Layout.
Plugins are the best way to add features to your BlueBase app.
It is recommended to keep all plugins in separate NPM packages. This helps us keep all aspects of an app modular. Adding or removing any feature becomes a matter of enabling or disabling a plugin.
A plugin can be used to do any of the following:
Subscribe to application's lifecycle events through Filters and override/modify application behaviour in a non-invasive way.
Add new or modify existing Components in the app. Moreover, plugins can wrap any component in Higher Order Components (HOCs).
Modify an existing in the app, or extend it to create a new one. It is also possible to create a theme completely from scratch.
Add new or modify existing routes to the app.
Additionally, plugins can be configurable through .
bluebase.boot Internal Filter: bluebase-boot-default (Priority = 5)
bluebase.boot.start
bluebase.components.register.internal
bluebase.configs.load
bluebase.components.register
bluebase.filters.register
bluebase.routes.register
bluebase.plugins.register
bluebase.plugins.initialize.all
bluebase.plugins.initialize
bluebase.boot.end
Each component in BlueBase has the following properties:
There are many ways to register new plugins in BlueBase:
The easiest way to add a new plugin is pass it as a prop to BlueBaseApp.
Typically, in a large project you would be using the bluebase.ts file to inject props to your main BlueBaseApp component. It is basically same thing as above.
Filters are functions that BlueBase passes data through, at certain points in execution, just before taking some action with the data (such as adding it to the database or rendering some views). Most input and output in BlueBase pass through at least one filter. BlueBase does some filtering by default, and your plugin can add its own filtering.
The basic steps to add your own filters to BlueBase (described in more detail below) are:
Create the JavaScript function that filters the data.
Hook to the filter in BlueBase, by any of the following methods:
resolve methodIn order to access a BlueBase component, just use the resolve method of Component Registry.
This will return a component wrapped in all the HOCs, and will receive a styles prop that has all the .
But in order to get access this component in your code, you will need to use a BlueBaseConsumer component to access
TODO: Move code splitting guide from this page to here.
import { createPlugin } from '@bluebase/core';
export const LoggerPlugin = createPlugin({
key: 'logger',
name: 'Logger Plugin',
categories: ['logging'],
filters: {
'bluebase.logger.log': (message: string, data: any) => {
// send data to logging provider here
}
}
});class MaterialUIPlugin extends Plugin {
public themes = [{
name: 'Material UI (Light)',
key: 'material-ui-light',
mode: 'light',
theme: import('./theme-rules-light'),
// .. other theme props
}, {
name: 'Material UI (Dark)',
key: 'material-ui-dark',
mode: 'dark',
theme: import('./theme-rules-dark'),
// .. other theme props
}]
}plugin.{key}.{config}pluign.example.foo(optional) (Default = false) Should this component be preloaded at app initialization. This is useful for components that are wrapped in promises (for code splitting).
hocs
Array
An array of React Higher Order Components. At runtime, each component is resolved with wrapping raw component in all HOCs in this array.
styles
{ [string: key]: Styles }
An object containing key value pairs of style rules. Each rule maybe a ViewStyle, TextStyle or an ImageStyle.
source
ComponentSource
This property represents the source that registered this component.
Key
Type
Description
key
string
The key is used as an ID.
value
React.ComponentType
The raw React component. This may also be a promise (for code splitting) that resolves a React component.
preload
boolean
You can add new Plugins anywhere in the code by calling the set method of PluginRegistry:
Beware though, it is important when a plugin is added to the registry in the system lifecycle. Because all plugins must be initiated, adding them to the registry is not enough.
Plugins are initialised with the bluebase.plugins.initialize.all filter, which in turn calls the bluebase.plugins.initialize filter for each plugin. So you'll either need to run this filter yourself, or add a plugin before the main filter is executed by the system.
Even so, to avoid unknown issues it is recommended to use the first method (Through BlueBaseApp Component).
import { BlueBaseApp } from '@bluebase/core';
const ExamplePlugin = {
name: 'Example Plugin',
key: 'example-plugin',
// ... other plugin properties
}
export const App = () => (
<BlueBaseApp plugins={[ExamplePlugin]} />
);Put your JavaScript function in the filters object of your plugin.
Call BB.Filters.register() function.
A filter function takes as input the unmodified data and returns modified data (or in some cases, a null value to indicate the data should be deleted or disregarded). If the data is not modified by your filter, then the original data must be returned so that subsequent plugins can continue to modify the value if necessary.
So, the first step in creating a filter in your plugin is to create a JavaScript function to do the filtering, and put it in your plugin file. For example, if you want to make sure that your posts and comments contain no profanity, you might define a variable with a list of forbidden words, and then create the following function:
After your function is defined, the next step is to register it with BlueBase. There are two ways to do this:
where:
event The name of a filter event that is provided by the developer. Defines when your filter should be applied.
key A unique ID of the function that you want to use for filtering. Keep in mind that other plugins or the BlueBase core may already be using the function key you have thought of, in that case, one filter will overwrite the other.
value The filter function. This function may or may not be an async function. The function will always have 3 params:
value: The value that is being filtered
context: An object that may have extra arguments or data, passed at the time of execution.
BB: The BlueBase instance.
priority An optional integer argument that can be used to specify the order in which the functions associated with a particular filter are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the filter.
Return Value The (optionally modified) value of the first argument is passed to the filter function.
You can also remove filters from filter hooks using the BB.Filters.delete() function. See Removing Actions and Filters.
You may define and execute your own filters by calling the following method
where
event The name of a filter event
value The value that is being filtered
context An optional object that may have extra arguments or data
In the example above, we would put the following params in the function:
Since BB.Filters.run() function returns a Promise, it gets a bit tricky to handle in a component. For this reason we provide a useFilter hook as well:
where the return value is an object with following properties:
value The (optionally modified) value of the first argument is passed to the filter function.
loading A flag that indicates if data is currently loading.
error If an error occurs, it will be set in this variable.
In some cases, you may find that you want your plugin to disable one of the actions or filters built into BlueBase, or added by another plugin. You can do that by calling BB.Filters.delete('key').
Note that in general, you shouldn't remove anything unless you know what it does and why it does it -- check the BlueBase or other plugin source code to be sure.
BBThis adds unnecessarily long and repetitive code. Not to mention the overall ugliness and unreadability. To solve this problem, we export a helper function called getComponent .
The getComponent function returns a component from BlueBase context. By using this function we can rewrite the example above as:
See how this makes the code much more developer friendly.
It is also possible to get a typed component by providing it props interface:
To make it even more convenient, we export some basic components out of box. This helps developers not only write even less code, but also port react-native apps to BlueBase by just changing the import statement.
Sometimes, you may need to have fallback components. So if they desired component is not found, a back up component can be used. In BlueBase it is very easy to achieve this. Just provide multiple arguments to the resolve function.
The first param acts as the key of the desired, with the following ones acting at fallback components in sequence.
So in the example above, first BlueBase will try to find AnimatedLogo . If it doesn't exist, BlueBase will look for SingleColorLogo . If even that isn't found, it will try to get Logo component.
This works with the getComponent function. So the code above can be written as:
As explained in the above, the resolve method returns a component wrapped in the registered HOCs, and styles. But sometimes, you may need to access the raw component that was originally registered.
For that reason, BlueRain provides a getValue method that lets you access the unwrapped “raw” component.
Note that, we used the await keyword. This is because the getValue method will return the component wrapped in a promise. This is because some components may in different bundles (because of code splitting).
In BlueBase it is possible to provide a defaultStyles static property to a component. This is very similar to the defaultProps concept of React.
In terms of priority, this method has the least priority, and these styles may be overwritten by any of the following methods.
Use this method to define the default styles of the component. These will represent a state of the component without any customisation.
It is also possible to save styles of a component in the ComponentRegistry.
In terms of priority, these styles override defaultStyles but they can be overwritten by following methods.
Us this method when you want to define global styles of a component, but you also want other plugins to able to customise them.
In BlueBase, ComponentStyles have the following structure:
This is very similar to React Native's StyleSheet.create API.
Thunks
Describe best practices: root, hover, etc.
const Logo = props => {
return (
<View>/* component code */</View>
)
}
await BB.Components.register({
key: 'Logo',
value: Logo,
preload: true,
hocs: [withRouter],
styles: {},
source: {},
});export default {
// ...other bluebase.ts properties
plugins: [{
name: 'Example Plugin',
key: 'example-plugin',
// ... other plugin properties
}]
}const ExamplePlugin = {
name: 'Example Plugin',
key: 'example-plugin',
// ... other plugin properties
}
await BB.Plugins.register(ExamplePlugin);
await BB.Filters.run('bluebase.plugins.initialize', ExamplePlugin);function filter_profanity(content: string) {
const profanities = array('badword','alsobad','...');
return content.replace(profanities, '{censored}');
}createPlugin({
// ... Other properties
filters: {
'post.comment': [{
key: 'post-comment-profanity-remover',
value: filter_profanity,
priority: 2,
}]
},
});await BB.Filters.register({
event: 'post.comment',
key: 'post-comment-profanity-remover',
value: filter_profanity,
priority: 2,
});const newValue = await BB.Filters.run(event, value, [context])await BB.Filters.run(
'post.comment',
'A comment with profanity',
{ postId: '123' }
);const { value, loading, error } = useFilter(
'post.comment',
'A comment with profanity',
{ postId: '123' }
);const Logo = BB.Components.resolve('Logo');import { BlueBase, BlueBaseConsumer } from '@bluebase/core';
const Header = () => (
<BlueBaseConsumer>
{(BB: BlueBase) => {
const Logo = BB.Components.resolve('Logo');
return <Logo />;
}}
</BlueBaseConsumer>
);import { getComponent } from '@bluebase/core';
const Logo = getComponent('Logo');
const Header = () => (<Logo />);import { getComponent } from '@bluebase/core';
interface LogoProps {
// ... props
}
const Logo = getComponent<LogoProps>('Logo');- import { Image, Text, View } from 'react-native';
+ import { Image, Text, View } from '@bluebase/components';const Logo = BB.Components.resolve('AnimatedLogo', 'SingleColorLogo', 'Logo');const Logo = getComponent('AnimatedLogo', 'SingleColorLogo', 'Logo');const rawLogoComponent = await BB.Components.getValue('Logo');import React from 'react';
import { Card } from '@bluebase/components';
export class ThemedCard extends React.Component {
static defaultStyles = {
'root': {
backgroundColor: 'blue'
},
'hover': {
backgroundColor: 'green'
}
}
render() {
const { styles, isHovering } = this.props;
return (<Card style={isHovering ? styles.hover : styles.root} />);
}
}class MaterialUIPlugin extends Plugin {
public components = {
ThemedCard: {
rawComponent: ThemedCardComponent,
styles: {
root: {
backgroundColor: 'orange'
}
}
}
}
}BB.Components.setStyles('ThemedCard', {
root: {
backgroundColor: 'orange'
}
});export const theme = {
// ...other theme props
components: {
ThemedCard: {
root: {
backgroundColor: 'yellow'
}
}
}
};const Foo = () => (
<ThemedCard styles={{
root: {
backgroundColor: 'red'
}
}} />
);interface ComponentStyles {
// rule
[key: string]: ViewStyle | TextStyle | ImageStyle | { [prop: string]: string };
}const styles = {
root: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
},
};There are many ways to register new components in BlueBase:
The easiest way to add a new component to BlueBase is to use the 'components' property of your plugin:
import { View } from '@bluebase/core';
const Logo = props => {
return (
<View>/* component code */</View>
)
}
const Plugin = {
name: 'Example Plugin',
key: 'example-plugin',
components: {
Logo
}
}Note that this may replace any existing components registered with same name.
You can add new Components anywhere in the code by calling the set method of ComponentRegistry:
A higher-order component's role is to wrap a regular component to pass it a specific prop (such as a list of posts, the current user, the Router object, etc.). You can think of HOCs as specialised assistants that each hand the component a tool it needs to do its job.
The first argument of set is the component's name, the second is the component itself, and any successive arguments will be interpreted as higher-order components and wrapped around the component.
For example, this is how you'd pass the currentUser object to the Logo component:
await BB.Components.register({
key: 'Logo',
value: LogoComponent,
hocs: [withCurrentUser]
});There are a few subtle differences between registering a component with register and the more standard export default Foo and import Foo from './Foo.jsx' ES6 syntax.
First, you can only override a component if it's been registered using register. This means that if you're building any kind of theme or plugin and would like end users to be able to replace specific components, you shouldn't use import/export.
Second, both techniques also lead to different results when it comes to higher-order components (more on this below). If you write export withCurrentUser(Foo), the withCurrentUser function will be executed immediately, which will trigger an error because the fragment it depends on isn't properly initialised yet.
On the other hand, register doesn't execute the function (note that we write withCurrentUser and not withCurrentUser()), delaying execution until app's initialisation.
But what about HoC functions that take arguments? For example if you were to write:
The withList(options) would be executed immediately, and you would have no way of overriding the options object later on (a common use case being overriding a fragment).
For that reason, to delay the execution until the start of the app, you can use the following alternative syntax:
You can add new HOCs to the already registered components any time by using the addHocs method.
There are following configuration in BlueBase:
The theme specifies the color of the components, darkness of the surfaces, level of shadow, appropriate opacity of ink elements, etc.
Themes let you apply a consistent tone to your app. It allows you to customize all design aspects of your project in order to meet the specific needs of your business or brand.
To promote greater consistency between apps, each theme has light and dark variants By default, components use the light theme type.
If you wish to customize the theme, you need to use the ThemeProvider
BlueBase supports different types of theme customisation requirements so that a developer can do a single usage customisation or globally override a theme.
There may be use cases where you may need to keep specific values same, no matter which theme is selected, i.e. primary colours, etc.
To do this, use the theme.overrides config. This change is global, and overrides all installed themes.
BlueBase supports code splitting out of the box. It not only takes care of resolving modules, but also manages showing loading and error stated, as well as takes care of server side rendering.
Please refer to the following links in the docs to learn how code splitting works in different parts of BlueBase:
Plugins
Components
Themes
BlueBase ships with 3 system level components. They play a core part in how the framework works.
Theme overrides. These are applied to all installed themes.
Key
Default
Description
theme.mode
light
This is the theme mode. Can be "light" or "dark".
theme.name
bluebase-light
This is the slug of the selected theme. Changing this value will cause a re-render to all themed components.
theme.overrides
Theme
const Logo = props => {
return (
<View>/* component code */</View>
)
}
await BB.Components.register('Logo', Logo);await BB.Components.register({
key: 'PostsList',
value: PostsList,
hocs: [withList(options)]
});ThemeProvider relies on the context feature of React to pass the theme down to the components, so you need to make sure that ThemeProvider is a parent of the components you are trying to customize. You can learn more about this in the API section.
The community has built great tools to build a theme:
mui-theme-creator: A tool to help design and customize themes for the MUI component library. Includes basic site templates to show various components and how they are affected by the theme
Material palette generator: The Material palette generator can be used to generate a palette for any color you input.
BlueBase ships with two built-in themes: BlueBase Light & BlueBase Dark. You can extend any of the two to create you own theme.
Using the ThemeProvider overrides prop. This change is only for this tree.
Nesting themes
Overriding themes
It is possible to nest multiple themes in a single project. To theme a specific portion of your app, use the ThemeProvider component.
In the example above, we pass the theme prop to the ThemeProvider component. This prop takes the key of the theme to use for children components. If this prop is not set, the globally selected theme is used.
The ThemeProvider component can also be used to override a theme for a one time usage.
The track method lets you add additional attributes to an event. For example, to record artist information with an albumVisit event:
Data can also be added to an event:
To add support for an Analytics Provider see this guide.
BB.Analytics.track({ name: 'albumVisit' });BB.Analytics.track({
name: 'albumVisit',
// Attribute values must be strings
attributes: { genre: '', artist: '' }
});await BB.Components.register({
key: 'PostsList',
value: PostsList,
hocs: [
[withList, options]
]
});BB.Components.addHocs('PostsList', withCurrentUser);import { BlueBaseApp, Theme } from '@bluebase/core';
import React from 'react';
const GreenTheme = new Theme({
key: 'green-theme',
name: 'Green Theme',
light: {
palette: {
primary: {
main: '#00a013',
dark: '#007d00',
light: '#49bb47',
},
secondary: {
main: '#a0008d',
dark: '#830082',
light: '#be5cad',
}
}
},
dark: {
palette: {
primary: {
main: '#00a013',
dark: '#007d00',
light: '#49bb47',
},
secondary: {
main: '#a0008d',
dark: '#830082',
light: '#be5cad',
}
}
},
});
export default function App() {
return (
<BlueBaseApp themes={[GreenTheme]} />
);
}import { BlueBaseApp, Theme } from '@bluebase/core';
import React from 'react';
import GreenTheme from './theme';
export default function App() {
return (
<BlueBaseApp themes={[GreenTheme]} />
);
}import { createPlugin } from '@bluebase/core';
import GreenTheme from './theme';
export default createPlugin({
key: 'green-theme-plugin',
name: 'Green Theme Plugin',
themes: [GreenTheme]
});import { buildTheme } from '@bluebase/core';
export const MyCustomTheme = ('light')({
name: 'MyCustomTheme',
key: 'my-custom-theme',
// ... theme props
});import { BlueBaseApp } from '@bluebase/core';
import { MyCustomTheme } from './MyCustomTheme.ts';
export const App = () => (
<BlueBaseApp themes={[MyCustomTheme]} />
);const bootOptions = {
configs: {
'theme.overrides': {
components: {
// component styles
}
}
},
};
export default bootOptions;<View>
<Text>Default light theme here</Text>
<ThemeProvider theme="bluebase-dark">
<View style={{ backgroundColor: theme.palette.background.default }}>
<Text>Dark theme here</Text>
</View>
</ThemeProvider>
<View><ThemeProvider theme="bluebase-dark" overrides={{ palette: { background: { default: 'red' } } }} >
{/* All components here will now have a red background color */}
</ThemeProvider>BB.Analytics.track({
name: 'albumVisit',
attributes: {},
metrics: { minutesListened: 30 }
});BlueBase uses an internal data structure to store everything.
Registry
Item
Value
Component
hocs, source, styles, isAsync, preload
BlueBaseModule<React.Component>
Config
subscriptions
BlueBaseModule<any> ⁉️
Filter
Render prop component that resolves and returns a value.
Automatically re-renders on value change.
Shows loading & error states while resolving
One of the main purpose of BlueBase is to have all functionality broken up into separate modules. So that adding or removing any feature may become as simple as press of a button.
Moving forward, from our experience with previous versions, it also became evident that code splitting was a necessity in web environments. And with the latest version, we wanted to support it out of the box.
So, BlueBase has been rewritten from the ground up to replace fixed pieces of code (i.e. functions or React components) to BlueBase Modules.
A BlueBase module is any piece of code that:
Maybe a CommonJS module or an ES Module.
Maybe the module itself, or a that resolves a module.
A challenge we faced in the earlier versions was the different module types in the Javascript ecosystem. We decided back then that BlueBase must support these out of the box.
BlueBase supports both Common JS and ES modules. We do this by checking .default property on the imported modules.
If the .default property does exist, we do module = module.default.
Code splitting is achieved through . All major bundlers (e.g. , ) already support it. An import function in dynamic import specification returns a .
So we check if a module is a , we resolve it automatically, when the module is resolved internally.
The table below list where BlueBase Modules are used internally, and their behaviour.
Filters may have individual listeners as promises too.
@bluebase/plugin-launcher
v2.1.0
v2.1.0, v.2.2.0
@bluebase/plugin-react-navigation
v2.0.0
v3.0.0
@bluebase/plugin-react-router
v2.0.0
Not Compatible. Use @bluebase/plugin-react-navigation
@bluebase/plugin-responsive-grid
0.x
1.x
@bluebase/plugin-settings-app
v5.x
v6.x
Yes
Yes
Components
Yes
Yes
No
Assets
No
Yes
No
Theme
Yes
Yes
No
Intl
Yes
Yes
No
Type
Maybe ES Module
Maybe Promise
Must be resolved at boot?
Plugin
Yes
Yes
Yes
Filter
Yes
Yes
Yes
Routes
Yes
name, priority
BlueBaseModule<Handler>
Intl ⁉️
BlueBaseModule<string>
Plugin
BlueBaseModule<Plugin>
Route ⁉️
Theme
name, slug, mode, alternate
BlueBaseModule<Theme>
Use it just like any other react component.
import { BlueBase, BlueBaseConsumer } from '@bluebase/core';
export const CustomComponent = () => (
<BlueBaseConsumer>
{(BB: BlueBase) => {
// Use Context here
}}
</BlueBaseConsumer>
);const Foo = () => (
<RegistryValue registry="Components" key="View">
{
(Component) => <Component />;
}
</RegistryValue>
);This is the main app in the BlueBase framework. It is just a React Component, and can either be used as the top most component of your project or it can be embedded in your existing code base.
This component takes care of initialisation and renders the ⛩Main App Layout. If children prop is provided, then it renders the children prop instead of the Main App Layout.
Use it just like any other react component.
Sometimes, you may want to use custom context. This is especially important when some system functionality may need to be modified before the boot process.
Just create a new object of BlueBase class. And send pass it to the BlueBaseApp component as BB prop when ready.
If children are provided to this component, then the children node is rendered instead of . The child components can use to access BlueBase context.
Display an error message. Used by UIs when an exception is caught, and an error message needs to be displayed. If in development mode, the actual error is displayed, otherwise displays a generic message in production mode.
A generic component to show different states of a screen or a view. For example, you may need to:
Show a loading state when data is loading,
Show an empty state when there is not data to show on a screen.
Show an error message when an exception occurs during execution.
These are just a few examples. This component displays a message with an image, a title, a description and a call to action button.
It is possible to customise styles of different elements of this component. To learn about how the styles prop work in BlueBase, see the section.
An enhanced Icon that can render any of the following:
BB.Components.Icon
BB.Components.Image
A custom component
prop
type
required
default
description
testID
string
no
-
Used to locate this view in end-to-end tests.
components
ComponentCollection
no
-
Collection of components to add in BlueBase's Component Registry.
configs
ConfigCollection
no
-
Collection of configs to add in BlueBase's Config Registry.
filters
FilterNestedCollection
no
-
Collection of Filter to add in BlueBase's Filter Registry.
plugins
PluginCollection
no
-
Collection of plugins to add in BlueBase's Plugin Registry.
themes
ThemeCollection
no
-
Collection of themes to add in BlueBase's Theme Registry.
testID
string
no
-
Used to locate this view in end-to-end tests.
prop
type
required
default
description
BB
BlueBase
no
-
BlueBase context. If one is not provided a new object will be created.
children
number
no
-
If this prop is provided, BlueBase's own App view will not be rendered, and nodes in this prop will be rendered instead.
testID
string
no
-
Used to locate this view in end-to-end tests.
prop
type
required
default
description
error
Error
no
-
The error to display
retry
Function
no
-
Callback function, called when retry button is pressed.
prop
type
required
default
description
testID
string
no
-
Used to locate this view in end-to-end tests.
description
string
no
-
Description Text
image
ReactNode
no
-
A ReactNode to show custom UI, if provided, imageSource will be ignored
imageSource
string
no
-
Image URL
title
string
no
-
Title text
styles
ComponentStateStyles
no
-
Style rules
testID
string
no
-
Used to locate this view in end-to-end tests.
Styles of image container view
root
ViewStyle
Main root container styles
title
TextStyle
Title styles
prop
type
required
default
description
actionTitle
string
no
-
Action title
actionOnPress
function
no
-
rule
type
description
actionRoot
ViewStyle
Action button container styles
description
TextStyle
Description text styles
image
ImageStyle
Image styles
imageRoot
Action onPress callback function
ViewStyle
import { BlueBase, BlueBaseApp } from '@bluebase/core';
export const App = () => (
<BlueBaseApp
components={{ /* Components Collection */ }}
configs={{ /* Configs Collection */ }}
filters={{ /* Filters Collection */ }}
plugins={{ /* Plugin Collection */ }}
themes={{ /* Theme Collection */ }}
/>
);import { BlueBase, BlueBaseApp } from '@bluebase/core';
const BB = new BlueBase();
// Custom business logic on BB object. i.e. Add filters, etc.
export const App = () => (<BlueBaseApp BB={BB} />);import { BlueBase, BlueBaseApp } from '@bluebase/core';
export const App = () => (
<BlueBaseApp>
{/* This child component will have access to BlueBase context */}
<CustomComponent />
</BlueBaseApp>
);import { ErrorState } from '@bluebase/core';
// Then somewhere in your app:
<ErrorState retry={retryCallback} error={Error('Bang!')} />import { EmptyState } from '@bluebase/core';
// Then somewhere in your app:
<EmptyState/>import { ComponentState } from '@bluebase/core';
// Then somewhere in your app:
<ComponentState
title="Looks like your'e new here!"
description="Start by creating your first entry."
imageSource="https://picsum.photos/200"
styles={{ image: { width: 100, height: 100 } }}
actionTitle="Tap to Create"
/>This component is shipped with BlueBase Core.
The following example shows how to use BlueBase Icon component:
The following example shows how to use an image as an icon:
The following example shows how to use a custom component as an icon:
It is also possible to use a component registered in the ComponentRegistry by using it's name:
prop
type
required
default
description
type
'component' | 'icon' | 'image'
yes
-
If value is:
component: Icon is a custom component, and looks for 'component' prop
icon: Icon is an instance of BB.Components.Icon and looks for 'icon' prop
component
React Component | string
-
-
prop
type
required
default
description
schema
JsonComponentNode | JsonComponentNode[]
yes
-
JSON Schema
filter
string
no
-
prop
type
required
default
description
timedOut
boolean
no
-
Flag if loading has timedOut.
retry
Function
no
-
import { DynamicIcon } from '@bluebase/core';
// Then somewhere in your app:
<DynamicIcon
type="icon"
size={250}
name="rocket"
/><DynamicIcon
type="image"
size={250}
source={{ uri: 'https://picsum.photos/200' }}
/>const CustomComponent = ({ size }: { size: number }) => (
<View style={{ height: size, width: size, backgroundColor: 'red' }} />
);
// Then somewhere in your component:
<DynamicIcon
type="component"
size={250}
component={CustomComponent}
/><DynamicIcon
type="component"
size={250}
component="CustomComponent" // equivalent to BB.Components.resolve('CustomComponent')
/>import { JsonSchema } from '@bluebase/core';
// Then somewhere in your app:
<JsonSchema
filter="content-filter"
args={{ style: { color: 'blue' } }}
schema={{
component: 'Text',
props: {
style: {
color: 'red'
}
},
text: 'This is the page content.',
}} />import { LoadingState } from '@bluebase/core';
// Then somewhere in your app:
<LoadingState timedOut={false} retry={retryFunction}/>image: Icon is an instance of BB.Components.Image and looks for 'source' propUsed when type is 'component'. Either a component or a component name (string). In case of string, it will be fetched from Component Registry.
name
string
-
-
Used when type is 'icon'. This is the name prop of the BB.Components.Icon component
source
Image Source Prop Type
-
-
Used when type is 'image'. This is the Image source.
size
number
-
100
Icon size.
testID
string
no
-
Used to locate this view in end-to-end tests.
Event name to filter this schema. If this is not provided, the schema is not filtered.
args
object
no
-
Arguments for the filter.
testID
string
no
-
Used to locate this view in end-to-end tests.
Callback function when Retry button is pressed.
testID
string
no
-
Used to locate this view in end-to-end tests.
BlueBase provides following typography components:
Base:
Text (same as Body1)
Styled Components:
H1
H2
H3
H4
H5
H6
Subtitle1
Subtitle2
Body1
Body2
Caption
Overline
There are 3 Icon types recognised by BlueBase


BlueBase provides observer components. They observe different aspects of application state so relevant UI state can be rendered.
There are currently four observer components:
This component is used to do the following:
WaitObserver a certain period of time before rendering a component
Show timeout state, if the component is visible for a certain time period
A use case for this can be to show a loading state after waiting a certain period of time for data to load, and if the loading takes too long, show a timeout state.
A React component that notifies its children of hover interactions.
Initial code taken from: https://github.com/ethanselzer/react-hover-observer.
loading
boolean
no
-
Loading flag.
data
any
no
-
Input data.
children
ReactNode | Function
no
-
Children, data is passed in the param object of the render prop function. This object is typed as DataObserverChildrenProps.
testID
string
no
-
Used to locate this view in end-to-end tests.
prop
type
required
default
description
isLoading
(props) => boolean
no
-
A function used to check if data is loading.
isEmpty
(props) => boolean
no
-
A function used to check if data is empty.
onTimeout
Function
no
-
A callback function executed when a timeout occurs.
onRetry
Function
no
-
A callback function executed when retry method is called from the child component.
children
ReactNode | (() => ReactNode)
no
-
testID
string
no
-
Used to locate this view in end-to-end tests.
prop
type
required
default
description
delay
number
no
-
Delay before rendering a component.
timeout
number
no
-
Timeout duration
onHoverChanged
(HoverObserverState) => void
no
-
Called with named argument isHovering when isHovering is set or unset.
onMouseEnter
Function
no
-
Defaults to set isHovering.
onMouseLeave
Function
no
-
Defaults to unsetting isHovering.
onMouseOver
Function
no
-
onMouseOut
Function
no
-
children
ReactNode | (HoverObserverState) => ReactNode
no
-
testID
string
no
-
Used to locate this view in end-to-end tests.
prop
type
required
default
description
hoverDelayInMs
number
no
0
Milliseconds to delay hover trigger.
hoverOffDelayInMs
number
no
0
Milliseconds to delay hover-off trigger.
prop
type
required
default
description
testID
string
no
-
Used to locate this view in end-to-end tests.
<DataObserver data={dataObj} >
{
({data, loading, empty}) => {
// Render UI based on params
}
}
</DataObserver><WaitObserver
delay={1000}
timeout={3000}
onTimeout={onTimeout}
onRetry={onRetry}
children={(props: WaitObserverChildrenProps) => <LoadingState {...props} />}
/><HoverObserver>
{({ isHovering }) => (
<YourChildComponent isActive={isHovering} />
)}
</HoverObserver><StatefulComponent data={data} loading={true} delay={200} timeout={10000}>
<Text>Content</Text>
</StatefulComponent>